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 (name, value) in self.resolved_user_values_for_show_scope(&show_scope) {
68                    lines.push(render_show_line(&name, &value, value_only));
69                }
70
71                for metadata in registry()
72                    .iter()
73                    .filter(|metadata| metadata.visible_in(show_scope.mask()))
74                {
75                    let query = OptionQuery::known(metadata.option());
76                    lines.extend(self.render_show_lines_for_query(
77                        &show_scope,
78                        &query,
79                        value_only,
80                        mode,
81                    ));
82                }
83            }
84            ShowOptionsMode::Explicit => {
85                if let Some(node) = self.node_for_show_scope(&show_scope) {
86                    for entry in node
87                        .entries
88                        .values()
89                        .filter(|entry| entry.known_option.is_none())
90                    {
91                        lines.extend(render_entry_show_lines(entry, value_only));
92                    }
93                    for entry in node.entries.values() {
94                        if entry.known_option.is_some() {
95                            lines.extend(render_entry_show_lines(entry, value_only));
96                        }
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        if self
366            .node_for_show_scope(scope)
367            .is_some_and(|node| node.contains(query.canonical_name(), query.index()))
368        {
369            return *scope;
370        }
371
372        match metadata.global_root() {
373            GlobalRoot::Server => ShowScope::Server,
374            GlobalRoot::Session => ShowScope::SessionGlobal,
375            GlobalRoot::Window => ShowScope::WindowGlobal,
376        }
377    }
378
379    fn node_for_show_scope(&self, scope: &ShowScope) -> Option<&OptionNode> {
380        match scope {
381            ShowScope::Server => Some(&self.server_global),
382            ShowScope::SessionGlobal => Some(&self.session_global),
383            ShowScope::WindowGlobal => Some(&self.window_global),
384            ShowScope::Session(session_name) => self.sessions.get(session_name),
385            ShowScope::Window(target) => self.windows.get(target),
386            ShowScope::Pane(target) => self.panes.get(target),
387        }
388    }
389
390    fn user_chain_for_context<'a>(&'a self, context: &ResolveContext<'a>) -> Vec<&'a OptionNode> {
391        match context {
392            ResolveContext::Server => vec![&self.server_global],
393            ResolveContext::SessionGlobal => vec![&self.session_global, &self.server_global],
394            ResolveContext::WindowGlobal => {
395                vec![
396                    &self.window_global,
397                    &self.session_global,
398                    &self.server_global,
399                ]
400            }
401            ResolveContext::Session(session_name) => {
402                let mut chain = Vec::new();
403                if let Some(node) = self.sessions.get(*session_name) {
404                    chain.push(node);
405                }
406                chain.push(&self.session_global);
407                chain.push(&self.server_global);
408                chain
409            }
410            ResolveContext::Window(session_name, window_index) => {
411                let mut chain = Vec::new();
412                let target = WindowTarget::with_window((*session_name).clone(), *window_index);
413                if let Some(node) = self.windows.get(&target) {
414                    chain.push(node);
415                }
416                if let Some(node) = self.sessions.get(*session_name) {
417                    chain.push(node);
418                }
419                chain.push(&self.window_global);
420                chain.push(&self.session_global);
421                chain.push(&self.server_global);
422                chain
423            }
424            ResolveContext::Pane(session_name, window_index, pane_index) => {
425                let mut chain = Vec::new();
426                let target =
427                    PaneTarget::with_window((*session_name).clone(), *window_index, *pane_index);
428                if let Some(node) = self.panes.get(&target) {
429                    chain.push(node);
430                }
431                let window_target =
432                    WindowTarget::with_window((*session_name).clone(), *window_index);
433                if let Some(node) = self.windows.get(&window_target) {
434                    chain.push(node);
435                }
436                if let Some(node) = self.sessions.get(*session_name) {
437                    chain.push(node);
438                }
439                chain.push(&self.window_global);
440                chain.push(&self.session_global);
441                chain.push(&self.server_global);
442                chain
443            }
444        }
445    }
446}
447
448fn empty_array_shows_name(
449    scope: &ShowScope<'_>,
450    mode: ShowOptionsMode,
451    query: &OptionQuery,
452) -> bool {
453    query.is_array()
454        && matches!(
455            (scope, mode),
456            (
457                ShowScope::Server | ShowScope::SessionGlobal | ShowScope::WindowGlobal,
458                ShowOptionsMode::Resolved | ShowOptionsMode::ResolvedWithInheritanceMarkers
459            ) | (
460                ShowScope::Window(_) | ShowScope::Pane(_),
461                ShowOptionsMode::ResolvedWithInheritanceMarkers
462            )
463        )
464}