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 => {
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                        ShowOptionsMode::Resolved,
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 => self
209                    .resolved_user_values_for_show_scope(scope)
210                    .into_iter()
211                    .find(|(entry_name, _)| entry_name == query.canonical_name())
212                    .map(|(_, value)| render_show_line(query.canonical_name(), &value, value_only))
213                    .into_iter()
214                    .collect(),
215                ShowOptionsMode::Explicit => self
216                    .node_for_show_scope(scope)
217                    .and_then(|node| node.value(query.canonical_name(), query.index()))
218                    .map(|value| render_show_line(query.canonical_name(), value, value_only))
219                    .into_iter()
220                    .collect(),
221            };
222        }
223
224        if query.is_array() && query.index().is_none() {
225            let values = match mode {
226                ShowOptionsMode::Resolved => {
227                    self.resolved_array_values_for_show_scope(scope, query)
228                }
229                ShowOptionsMode::Explicit => self
230                    .node_for_show_scope(scope)
231                    .and_then(|node| node.entry(query.canonical_name()))
232                    .map(OptionEntry::array_entries)
233                    .unwrap_or_default(),
234            };
235            if values.is_empty() && !value_only {
236                return vec![query.canonical_name().to_owned()];
237            }
238            return values
239                .into_iter()
240                .map(|(index, value)| {
241                    render_known_show_line(
242                        query,
243                        &show_option_name(query.canonical_name(), Some(index)),
244                        &value,
245                        value_only,
246                    )
247                })
248                .collect();
249        }
250
251        let value = match mode {
252            ShowOptionsMode::Resolved => self.resolved_value_for_show_scope(scope, query),
253            ShowOptionsMode::Explicit => self
254                .node_for_show_scope(scope)
255                .and_then(|node| node.value(query.canonical_name(), query.index()))
256                .map(str::to_owned),
257        };
258
259        value
260            .into_iter()
261            .map(|value| {
262                render_known_show_line(
263                    query,
264                    &show_option_name(query.canonical_name(), query.index()),
265                    &value,
266                    value_only,
267                )
268            })
269            .collect()
270    }
271
272    fn resolved_array_values_for_show_scope(
273        &self,
274        scope: &ShowScope,
275        query: &OptionQuery,
276    ) -> Vec<(u32, String)> {
277        let resolved = match scope {
278            ShowScope::Server => self.resolve_query(&ResolveContext::Server, query),
279            ShowScope::SessionGlobal => self.resolve_query(&ResolveContext::SessionGlobal, query),
280            ShowScope::WindowGlobal => self.resolve_query(&ResolveContext::WindowGlobal, query),
281            ShowScope::Session(session_name) => {
282                self.resolve_query(&ResolveContext::Session(session_name), query)
283            }
284            ShowScope::Window(target) => self.resolve_query(
285                &ResolveContext::Window(target.session_name(), target.window_index()),
286                query,
287            ),
288            ShowScope::Pane(target) => self.resolve_query(
289                &ResolveContext::Pane(
290                    target.session_name(),
291                    target.window_index(),
292                    target.pane_index(),
293                ),
294                query,
295            ),
296        };
297
298        resolved
299            .map(OptionEntry::array_entries)
300            .unwrap_or_else(|| default_array_show_values(query))
301    }
302
303    fn show_scope_for_named_query<'a>(
304        &self,
305        scope: &ShowScope<'a>,
306        query: &OptionQuery,
307    ) -> ShowScope<'a> {
308        let Some(metadata) = query.metadata() else {
309            return *scope;
310        };
311        if metadata.visible_in(scope.mask()) {
312            return *scope;
313        }
314
315        match metadata.global_root() {
316            GlobalRoot::Server => ShowScope::Server,
317            GlobalRoot::Session => ShowScope::SessionGlobal,
318            GlobalRoot::Window => ShowScope::WindowGlobal,
319        }
320    }
321
322    fn node_for_show_scope(&self, scope: &ShowScope) -> Option<&OptionNode> {
323        match scope {
324            ShowScope::Server => Some(&self.server_global),
325            ShowScope::SessionGlobal => Some(&self.session_global),
326            ShowScope::WindowGlobal => Some(&self.window_global),
327            ShowScope::Session(session_name) => self.sessions.get(session_name),
328            ShowScope::Window(target) => self.windows.get(target),
329            ShowScope::Pane(target) => self.panes.get(target),
330        }
331    }
332
333    fn user_chain_for_context<'a>(&'a self, context: &ResolveContext<'a>) -> Vec<&'a OptionNode> {
334        match context {
335            ResolveContext::Server => vec![&self.server_global],
336            ResolveContext::SessionGlobal => vec![&self.session_global, &self.server_global],
337            ResolveContext::WindowGlobal => {
338                vec![
339                    &self.window_global,
340                    &self.session_global,
341                    &self.server_global,
342                ]
343            }
344            ResolveContext::Session(session_name) => {
345                let mut chain = Vec::new();
346                if let Some(node) = self.sessions.get(*session_name) {
347                    chain.push(node);
348                }
349                chain.push(&self.session_global);
350                chain.push(&self.server_global);
351                chain
352            }
353            ResolveContext::Window(session_name, window_index) => {
354                let mut chain = Vec::new();
355                let target = WindowTarget::with_window((*session_name).clone(), *window_index);
356                if let Some(node) = self.windows.get(&target) {
357                    chain.push(node);
358                }
359                if let Some(node) = self.sessions.get(*session_name) {
360                    chain.push(node);
361                }
362                chain.push(&self.window_global);
363                chain.push(&self.session_global);
364                chain.push(&self.server_global);
365                chain
366            }
367            ResolveContext::Pane(session_name, window_index, pane_index) => {
368                let mut chain = Vec::new();
369                let target =
370                    PaneTarget::with_window((*session_name).clone(), *window_index, *pane_index);
371                if let Some(node) = self.panes.get(&target) {
372                    chain.push(node);
373                }
374                let window_target =
375                    WindowTarget::with_window((*session_name).clone(), *window_index);
376                if let Some(node) = self.windows.get(&window_target) {
377                    chain.push(node);
378                }
379                if let Some(node) = self.sessions.get(*session_name) {
380                    chain.push(node);
381                }
382                chain.push(&self.window_global);
383                chain.push(&self.session_global);
384                chain.push(&self.server_global);
385                chain
386            }
387        }
388    }
389}