Skip to main content

rmux_core/options/
show.rs

1use std::collections::BTreeMap;
2
3use rmux_proto::types::OptionScopeSelector;
4use rmux_proto::{PaneTarget, RmuxError, WindowTarget};
5
6use super::registry::{registry, resolve_option_name, GlobalRoot};
7use super::render::{
8    default_array_show_values, render_entry_show_lines, render_known_show_line, render_show_line,
9    show_option_name,
10};
11use super::scope::{ResolveContext, ShowScope};
12use super::storage::{OptionEntry, OptionNode};
13use super::{OptionQuery, OptionStore, ShowOptionsMode};
14
15impl OptionStore {
16    /// Returns rendered `show-options` lines for the selected scope.
17    pub fn show_options_lines(
18        &self,
19        scope: &OptionScopeSelector,
20        value_only: bool,
21    ) -> Result<Vec<String>, RmuxError> {
22        self.show_options_lines_filtered(scope, None, value_only)
23    }
24
25    /// Returns rendered `show-options` lines for the selected scope, optionally filtered by name.
26    pub fn show_options_lines_filtered(
27        &self,
28        scope: &OptionScopeSelector,
29        name: Option<&str>,
30        value_only: bool,
31    ) -> Result<Vec<String>, RmuxError> {
32        self.show_options_lines_with_mode_filtered(
33            scope,
34            name,
35            value_only,
36            ShowOptionsMode::Resolved,
37        )
38    }
39
40    /// Returns rendered `show-options` lines for the selected scope and mode.
41    pub fn show_options_lines_with_mode(
42        &self,
43        scope: &OptionScopeSelector,
44        value_only: bool,
45        mode: ShowOptionsMode,
46    ) -> Result<Vec<String>, RmuxError> {
47        self.show_options_lines_with_mode_filtered(scope, None, value_only, mode)
48    }
49
50    /// Returns rendered `show-options` lines for the selected scope, mode, and optional name.
51    pub fn show_options_lines_with_mode_filtered(
52        &self,
53        scope: &OptionScopeSelector,
54        name: Option<&str>,
55        value_only: bool,
56        mode: ShowOptionsMode,
57    ) -> Result<Vec<String>, RmuxError> {
58        let show_scope = ShowScope::from_selector(scope);
59        if let Some(name) = name {
60            return self.show_options_lines_for_name(&show_scope, name, value_only, mode);
61        }
62
63        let mut lines = Vec::new();
64
65        match mode {
66            ShowOptionsMode::Resolved | ShowOptionsMode::ResolvedWithInheritanceMarkers => {
67                for metadata in registry()
68                    .iter()
69                    .filter(|metadata| metadata.visible_in(show_scope.mask()))
70                {
71                    let query = OptionQuery::known(metadata.option());
72                    lines.extend(self.render_show_lines_for_query(
73                        &show_scope,
74                        &query,
75                        value_only,
76                        mode,
77                    ));
78                }
79
80                for (name, value) in self.resolved_user_values_for_show_scope(&show_scope) {
81                    lines.push(render_show_line(&name, &value, value_only));
82                }
83            }
84            ShowOptionsMode::Explicit => {
85                if let Some(node) = self.node_for_show_scope(&show_scope) {
86                    for entry in node.entries.values() {
87                        if entry.known_option.is_some() {
88                            lines.extend(render_entry_show_lines(entry, value_only));
89                        }
90                    }
91                    for entry in node
92                        .entries
93                        .values()
94                        .filter(|entry| entry.known_option.is_none())
95                    {
96                        lines.extend(render_entry_show_lines(entry, value_only));
97                    }
98                }
99            }
100        }
101
102        Ok(lines)
103    }
104
105    fn resolved_value_for_show_scope(
106        &self,
107        scope: &ShowScope,
108        query: &OptionQuery,
109    ) -> Option<String> {
110        match scope {
111            ShowScope::Server => self
112                .resolve_query(&ResolveContext::Server, query)
113                .and_then(|entry| entry.value(query.index()).map(str::to_owned))
114                .or_else(|| self.default_value_as_string(query)),
115            ShowScope::SessionGlobal => self
116                .resolve_query(&ResolveContext::SessionGlobal, query)
117                .and_then(|entry| entry.value(query.index()).map(str::to_owned))
118                .or_else(|| self.default_value_as_string(query)),
119            ShowScope::WindowGlobal => self
120                .resolve_query(&ResolveContext::WindowGlobal, query)
121                .and_then(|entry| entry.value(query.index()).map(str::to_owned))
122                .or_else(|| self.default_value_as_string(query)),
123            ShowScope::Session(session_name) => self
124                .resolve_query(&ResolveContext::Session(session_name), query)
125                .and_then(|entry| entry.value(query.index()).map(str::to_owned))
126                .or_else(|| self.default_value_as_string(query)),
127            ShowScope::Window(target) => self
128                .resolve_query(
129                    &ResolveContext::Window(target.session_name(), target.window_index()),
130                    query,
131                )
132                .and_then(|entry| entry.value(query.index()).map(str::to_owned))
133                .or_else(|| self.default_value_as_string(query)),
134            ShowScope::Pane(target) => self
135                .resolve_query(
136                    &ResolveContext::Pane(
137                        target.session_name(),
138                        target.window_index(),
139                        target.pane_index(),
140                    ),
141                    query,
142                )
143                .and_then(|entry| entry.value(query.index()).map(str::to_owned))
144                .or_else(|| self.default_value_as_string(query)),
145        }
146    }
147
148    fn resolved_user_values_for_show_scope(&self, scope: &ShowScope) -> Vec<(String, String)> {
149        let context = match scope {
150            ShowScope::Server => ResolveContext::Server,
151            ShowScope::SessionGlobal => ResolveContext::SessionGlobal,
152            ShowScope::WindowGlobal => ResolveContext::WindowGlobal,
153            ShowScope::Session(session_name) => ResolveContext::Session(session_name),
154            ShowScope::Window(target) => {
155                ResolveContext::Window(target.session_name(), target.window_index())
156            }
157            ShowScope::Pane(target) => ResolveContext::Pane(
158                target.session_name(),
159                target.window_index(),
160                target.pane_index(),
161            ),
162        };
163
164        let mut values = BTreeMap::<String, String>::new();
165        for node in self.user_chain_for_context(&context) {
166            for (name, entry) in &node.entries {
167                if entry.known_option.is_none() {
168                    values
169                        .entry(name.clone())
170                        .or_insert_with(|| entry.rendered.clone());
171                }
172            }
173        }
174        values.into_iter().collect()
175    }
176
177    fn show_options_lines_for_name(
178        &self,
179        scope: &ShowScope,
180        name: &str,
181        value_only: bool,
182        mode: ShowOptionsMode,
183    ) -> Result<Vec<String>, RmuxError> {
184        let query = resolve_option_name(name)?;
185        let resolved_scope = self.show_scope_for_named_query(scope, &query);
186        let mode = if mode == ShowOptionsMode::Explicit && resolved_scope.mask() != scope.mask() {
187            ShowOptionsMode::Resolved
188        } else {
189            mode
190        };
191        let lines = self.render_show_lines_for_query(&resolved_scope, &query, value_only, mode);
192        if query.is_user() && lines.is_empty() {
193            return Err(RmuxError::Message(format!(
194                "invalid option: {}",
195                query.canonical_name()
196            )));
197        }
198        if query.is_array()
199            && query.index().is_none()
200            && lines.is_empty()
201            && !value_only
202            && empty_array_shows_name(&resolved_scope, mode, &query)
203        {
204            return Ok(vec![query.canonical_name().to_owned()]);
205        }
206        Ok(lines)
207    }
208
209    fn render_show_lines_for_query(
210        &self,
211        scope: &ShowScope,
212        query: &OptionQuery,
213        value_only: bool,
214        mode: ShowOptionsMode,
215    ) -> Vec<String> {
216        if query.is_user() {
217            return match mode {
218                ShowOptionsMode::Resolved | ShowOptionsMode::ResolvedWithInheritanceMarkers => self
219                    .resolved_user_values_for_show_scope(scope)
220                    .into_iter()
221                    .find(|(entry_name, _)| entry_name == query.canonical_name())
222                    .map(|(_, value)| {
223                        render_show_line(
224                            &self.show_name_for_mode(scope, query, mode),
225                            &value,
226                            value_only,
227                        )
228                    })
229                    .into_iter()
230                    .collect(),
231                ShowOptionsMode::Explicit => self
232                    .node_for_show_scope(scope)
233                    .and_then(|node| node.value(query.canonical_name(), query.index()))
234                    .map(|value| render_show_line(query.canonical_name(), value, value_only))
235                    .into_iter()
236                    .collect(),
237            };
238        }
239
240        if query.is_array() && query.index().is_none() {
241            let values = match mode {
242                ShowOptionsMode::Resolved | ShowOptionsMode::ResolvedWithInheritanceMarkers => {
243                    self.resolved_array_values_for_show_scope(scope, query)
244                }
245                ShowOptionsMode::Explicit => self
246                    .node_for_show_scope(scope)
247                    .and_then(|node| node.entry(query.canonical_name()))
248                    .map(OptionEntry::array_entries)
249                    .unwrap_or_default(),
250            };
251            if values.is_empty() && !value_only && empty_array_shows_name(scope, mode, query) {
252                return vec![query.canonical_name().to_owned()];
253            }
254            return values
255                .into_iter()
256                .map(|(index, value)| {
257                    let name = show_option_name(query.canonical_name(), Some(index));
258                    let name =
259                        self.show_index_name_for_mode(scope, query, Some(index), mode, &name);
260                    render_known_show_line(query, &name, &value, value_only)
261                })
262                .collect();
263        }
264
265        let value = match mode {
266            ShowOptionsMode::Resolved | ShowOptionsMode::ResolvedWithInheritanceMarkers => {
267                self.resolved_value_for_show_scope(scope, query)
268            }
269            ShowOptionsMode::Explicit => self
270                .node_for_show_scope(scope)
271                .and_then(|node| node.value(query.canonical_name(), query.index()))
272                .map(str::to_owned),
273        };
274
275        value
276            .into_iter()
277            .map(|value| {
278                let name = show_option_name(query.canonical_name(), query.index());
279                let name = self.show_index_name_for_mode(scope, query, query.index(), mode, &name);
280                render_known_show_line(query, &name, &value, value_only)
281            })
282            .collect()
283    }
284
285    fn show_name_for_mode(
286        &self,
287        scope: &ShowScope,
288        query: &OptionQuery,
289        mode: ShowOptionsMode,
290    ) -> String {
291        let name = query.canonical_name();
292        self.show_index_name_for_mode(scope, query, query.index(), mode, name)
293    }
294
295    fn show_index_name_for_mode(
296        &self,
297        scope: &ShowScope,
298        query: &OptionQuery,
299        index: Option<u32>,
300        mode: ShowOptionsMode,
301        name: &str,
302    ) -> String {
303        if mode != ShowOptionsMode::ResolvedWithInheritanceMarkers {
304            return name.to_owned();
305        }
306        if matches!(
307            scope,
308            ShowScope::Server | ShowScope::SessionGlobal | ShowScope::WindowGlobal
309        ) {
310            return name.to_owned();
311        }
312        if self
313            .node_for_show_scope(scope)
314            .is_some_and(|node| node.contains(query.canonical_name(), index))
315        {
316            name.to_owned()
317        } else {
318            format!("{name}*")
319        }
320    }
321
322    fn resolved_array_values_for_show_scope(
323        &self,
324        scope: &ShowScope,
325        query: &OptionQuery,
326    ) -> Vec<(u32, String)> {
327        let resolved = match scope {
328            ShowScope::Server => self.resolve_query(&ResolveContext::Server, query),
329            ShowScope::SessionGlobal => self.resolve_query(&ResolveContext::SessionGlobal, query),
330            ShowScope::WindowGlobal => self.resolve_query(&ResolveContext::WindowGlobal, query),
331            ShowScope::Session(session_name) => {
332                self.resolve_query(&ResolveContext::Session(session_name), query)
333            }
334            ShowScope::Window(target) => self.resolve_query(
335                &ResolveContext::Window(target.session_name(), target.window_index()),
336                query,
337            ),
338            ShowScope::Pane(target) => self.resolve_query(
339                &ResolveContext::Pane(
340                    target.session_name(),
341                    target.window_index(),
342                    target.pane_index(),
343                ),
344                query,
345            ),
346        };
347
348        resolved
349            .map(OptionEntry::array_entries)
350            .unwrap_or_else(|| default_array_show_values(query))
351    }
352
353    fn show_scope_for_named_query<'a>(
354        &self,
355        scope: &ShowScope<'a>,
356        query: &OptionQuery,
357    ) -> ShowScope<'a> {
358        let Some(metadata) = query.metadata() else {
359            return *scope;
360        };
361        if metadata.visible_in(scope.mask()) {
362            return *scope;
363        }
364
365        match metadata.global_root() {
366            GlobalRoot::Server => ShowScope::Server,
367            GlobalRoot::Session => ShowScope::SessionGlobal,
368            GlobalRoot::Window => ShowScope::WindowGlobal,
369        }
370    }
371
372    fn node_for_show_scope(&self, scope: &ShowScope) -> Option<&OptionNode> {
373        match scope {
374            ShowScope::Server => Some(&self.server_global),
375            ShowScope::SessionGlobal => Some(&self.session_global),
376            ShowScope::WindowGlobal => Some(&self.window_global),
377            ShowScope::Session(session_name) => self.sessions.get(session_name),
378            ShowScope::Window(target) => self.windows.get(target),
379            ShowScope::Pane(target) => self.panes.get(target),
380        }
381    }
382
383    fn user_chain_for_context<'a>(&'a self, context: &ResolveContext<'a>) -> Vec<&'a OptionNode> {
384        match context {
385            ResolveContext::Server => vec![&self.server_global],
386            ResolveContext::SessionGlobal => vec![&self.session_global, &self.server_global],
387            ResolveContext::WindowGlobal => {
388                vec![
389                    &self.window_global,
390                    &self.session_global,
391                    &self.server_global,
392                ]
393            }
394            ResolveContext::Session(session_name) => {
395                let mut chain = Vec::new();
396                if let Some(node) = self.sessions.get(*session_name) {
397                    chain.push(node);
398                }
399                chain.push(&self.session_global);
400                chain.push(&self.server_global);
401                chain
402            }
403            ResolveContext::Window(session_name, window_index) => {
404                let mut chain = Vec::new();
405                let target = WindowTarget::with_window((*session_name).clone(), *window_index);
406                if let Some(node) = self.windows.get(&target) {
407                    chain.push(node);
408                }
409                if let Some(node) = self.sessions.get(*session_name) {
410                    chain.push(node);
411                }
412                chain.push(&self.window_global);
413                chain.push(&self.session_global);
414                chain.push(&self.server_global);
415                chain
416            }
417            ResolveContext::Pane(session_name, window_index, pane_index) => {
418                let mut chain = Vec::new();
419                let target =
420                    PaneTarget::with_window((*session_name).clone(), *window_index, *pane_index);
421                if let Some(node) = self.panes.get(&target) {
422                    chain.push(node);
423                }
424                let window_target =
425                    WindowTarget::with_window((*session_name).clone(), *window_index);
426                if let Some(node) = self.windows.get(&window_target) {
427                    chain.push(node);
428                }
429                if let Some(node) = self.sessions.get(*session_name) {
430                    chain.push(node);
431                }
432                chain.push(&self.window_global);
433                chain.push(&self.session_global);
434                chain.push(&self.server_global);
435                chain
436            }
437        }
438    }
439}
440
441fn empty_array_shows_name(
442    scope: &ShowScope<'_>,
443    mode: ShowOptionsMode,
444    query: &OptionQuery,
445) -> bool {
446    query.is_array()
447        && matches!(
448            (scope, mode),
449            (
450                ShowScope::Server | ShowScope::SessionGlobal | ShowScope::WindowGlobal,
451                ShowOptionsMode::Resolved | ShowOptionsMode::ResolvedWithInheritanceMarkers
452            ) | (
453                ShowScope::Window(_) | ShowScope::Pane(_),
454                ShowOptionsMode::ResolvedWithInheritanceMarkers
455            )
456        )
457}