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 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 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 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 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}