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 scope = self.show_scope_for_named_query(scope, &query);
186        let lines = self.render_show_lines_for_query(&scope, &query, value_only, mode);
187        if query.is_user() && lines.is_empty() {
188            return Err(RmuxError::Message(format!(
189                "invalid option: {}",
190                query.canonical_name()
191            )));
192        }
193        if query.is_array() && query.index().is_none() && lines.is_empty() && !value_only {
194            return Ok(vec![query.canonical_name().to_owned()]);
195        }
196        Ok(lines)
197    }
198
199    fn render_show_lines_for_query(
200        &self,
201        scope: &ShowScope,
202        query: &OptionQuery,
203        value_only: bool,
204        mode: ShowOptionsMode,
205    ) -> Vec<String> {
206        if query.is_user() {
207            return match mode {
208                ShowOptionsMode::Resolved | ShowOptionsMode::ResolvedWithInheritanceMarkers => self
209                    .resolved_user_values_for_show_scope(scope)
210                    .into_iter()
211                    .find(|(entry_name, _)| entry_name == query.canonical_name())
212                    .map(|(_, value)| {
213                        render_show_line(
214                            &self.show_name_for_mode(scope, query, mode),
215                            &value,
216                            value_only,
217                        )
218                    })
219                    .into_iter()
220                    .collect(),
221                ShowOptionsMode::Explicit => self
222                    .node_for_show_scope(scope)
223                    .and_then(|node| node.value(query.canonical_name(), query.index()))
224                    .map(|value| render_show_line(query.canonical_name(), value, value_only))
225                    .into_iter()
226                    .collect(),
227            };
228        }
229
230        if query.is_array() && query.index().is_none() {
231            let values = match mode {
232                ShowOptionsMode::Resolved | ShowOptionsMode::ResolvedWithInheritanceMarkers => {
233                    self.resolved_array_values_for_show_scope(scope, query)
234                }
235                ShowOptionsMode::Explicit => self
236                    .node_for_show_scope(scope)
237                    .and_then(|node| node.entry(query.canonical_name()))
238                    .map(OptionEntry::array_entries)
239                    .unwrap_or_default(),
240            };
241            if values.is_empty() && !value_only {
242                return vec![query.canonical_name().to_owned()];
243            }
244            return values
245                .into_iter()
246                .map(|(index, value)| {
247                    let name = show_option_name(query.canonical_name(), Some(index));
248                    let name =
249                        self.show_index_name_for_mode(scope, query, Some(index), mode, &name);
250                    render_known_show_line(query, &name, &value, value_only)
251                })
252                .collect();
253        }
254
255        let value = match mode {
256            ShowOptionsMode::Resolved | ShowOptionsMode::ResolvedWithInheritanceMarkers => {
257                self.resolved_value_for_show_scope(scope, query)
258            }
259            ShowOptionsMode::Explicit => self
260                .node_for_show_scope(scope)
261                .and_then(|node| node.value(query.canonical_name(), query.index()))
262                .map(str::to_owned),
263        };
264
265        value
266            .into_iter()
267            .map(|value| {
268                let name = show_option_name(query.canonical_name(), query.index());
269                let name = self.show_index_name_for_mode(scope, query, query.index(), mode, &name);
270                render_known_show_line(query, &name, &value, value_only)
271            })
272            .collect()
273    }
274
275    fn show_name_for_mode(
276        &self,
277        scope: &ShowScope,
278        query: &OptionQuery,
279        mode: ShowOptionsMode,
280    ) -> String {
281        let name = query.canonical_name();
282        self.show_index_name_for_mode(scope, query, query.index(), mode, name)
283    }
284
285    fn show_index_name_for_mode(
286        &self,
287        scope: &ShowScope,
288        query: &OptionQuery,
289        index: Option<u32>,
290        mode: ShowOptionsMode,
291        name: &str,
292    ) -> String {
293        if mode != ShowOptionsMode::ResolvedWithInheritanceMarkers {
294            return name.to_owned();
295        }
296        if matches!(
297            scope,
298            ShowScope::Server | ShowScope::SessionGlobal | ShowScope::WindowGlobal
299        ) {
300            return name.to_owned();
301        }
302        if self
303            .node_for_show_scope(scope)
304            .is_some_and(|node| node.contains(query.canonical_name(), index))
305        {
306            name.to_owned()
307        } else {
308            format!("{name}*")
309        }
310    }
311
312    fn resolved_array_values_for_show_scope(
313        &self,
314        scope: &ShowScope,
315        query: &OptionQuery,
316    ) -> Vec<(u32, String)> {
317        let resolved = match scope {
318            ShowScope::Server => self.resolve_query(&ResolveContext::Server, query),
319            ShowScope::SessionGlobal => self.resolve_query(&ResolveContext::SessionGlobal, query),
320            ShowScope::WindowGlobal => self.resolve_query(&ResolveContext::WindowGlobal, query),
321            ShowScope::Session(session_name) => {
322                self.resolve_query(&ResolveContext::Session(session_name), query)
323            }
324            ShowScope::Window(target) => self.resolve_query(
325                &ResolveContext::Window(target.session_name(), target.window_index()),
326                query,
327            ),
328            ShowScope::Pane(target) => self.resolve_query(
329                &ResolveContext::Pane(
330                    target.session_name(),
331                    target.window_index(),
332                    target.pane_index(),
333                ),
334                query,
335            ),
336        };
337
338        resolved
339            .map(OptionEntry::array_entries)
340            .unwrap_or_else(|| default_array_show_values(query))
341    }
342
343    fn show_scope_for_named_query<'a>(
344        &self,
345        scope: &ShowScope<'a>,
346        query: &OptionQuery,
347    ) -> ShowScope<'a> {
348        let Some(metadata) = query.metadata() else {
349            return *scope;
350        };
351        if metadata.visible_in(scope.mask()) {
352            return *scope;
353        }
354
355        match metadata.global_root() {
356            GlobalRoot::Server => ShowScope::Server,
357            GlobalRoot::Session => ShowScope::SessionGlobal,
358            GlobalRoot::Window => ShowScope::WindowGlobal,
359        }
360    }
361
362    fn node_for_show_scope(&self, scope: &ShowScope) -> Option<&OptionNode> {
363        match scope {
364            ShowScope::Server => Some(&self.server_global),
365            ShowScope::SessionGlobal => Some(&self.session_global),
366            ShowScope::WindowGlobal => Some(&self.window_global),
367            ShowScope::Session(session_name) => self.sessions.get(session_name),
368            ShowScope::Window(target) => self.windows.get(target),
369            ShowScope::Pane(target) => self.panes.get(target),
370        }
371    }
372
373    fn user_chain_for_context<'a>(&'a self, context: &ResolveContext<'a>) -> Vec<&'a OptionNode> {
374        match context {
375            ResolveContext::Server => vec![&self.server_global],
376            ResolveContext::SessionGlobal => vec![&self.session_global, &self.server_global],
377            ResolveContext::WindowGlobal => {
378                vec![
379                    &self.window_global,
380                    &self.session_global,
381                    &self.server_global,
382                ]
383            }
384            ResolveContext::Session(session_name) => {
385                let mut chain = Vec::new();
386                if let Some(node) = self.sessions.get(*session_name) {
387                    chain.push(node);
388                }
389                chain.push(&self.session_global);
390                chain.push(&self.server_global);
391                chain
392            }
393            ResolveContext::Window(session_name, window_index) => {
394                let mut chain = Vec::new();
395                let target = WindowTarget::with_window((*session_name).clone(), *window_index);
396                if let Some(node) = self.windows.get(&target) {
397                    chain.push(node);
398                }
399                if let Some(node) = self.sessions.get(*session_name) {
400                    chain.push(node);
401                }
402                chain.push(&self.window_global);
403                chain.push(&self.session_global);
404                chain.push(&self.server_global);
405                chain
406            }
407            ResolveContext::Pane(session_name, window_index, pane_index) => {
408                let mut chain = Vec::new();
409                let target =
410                    PaneTarget::with_window((*session_name).clone(), *window_index, *pane_index);
411                if let Some(node) = self.panes.get(&target) {
412                    chain.push(node);
413                }
414                let window_target =
415                    WindowTarget::with_window((*session_name).clone(), *window_index);
416                if let Some(node) = self.windows.get(&window_target) {
417                    chain.push(node);
418                }
419                if let Some(node) = self.sessions.get(*session_name) {
420                    chain.push(node);
421                }
422                chain.push(&self.window_global);
423                chain.push(&self.session_global);
424                chain.push(&self.server_global);
425                chain
426            }
427        }
428    }
429}