1use std::collections::HashMap;
2
3use rmux_proto::types::OptionScopeSelector;
4use rmux_proto::{OptionName, PaneTarget, SessionName, WindowTarget};
5
6use super::mutation::{
7 default_array_items, default_scalar_text, normalize_scalar_value, split_array_assignment,
8};
9use super::registry::{
10 option_metadata, registry, resolve_exact_option_name, resolve_option_name, DefaultValue,
11 GlobalRoot,
12};
13use super::render::format_rendered_option_value;
14use super::scope::{
15 push_known_global_roots, scope_allows_pane, scope_allows_session, scope_allows_window,
16 ResolveContext,
17};
18use super::storage::{OptionEntry, OptionNode};
19use super::{OptionQuery, OptionStore};
20
21impl OptionStore {
22 #[must_use]
24 pub fn global_value(&self, option: OptionName) -> Option<&str> {
25 let query = OptionQuery::known(option);
26 self.node_for_global_root(option_metadata(option).global_root())
27 .and_then(|node| node.value(query.canonical_name(), None))
28 }
29
30 #[must_use]
32 pub fn session_value(&self, session_name: &SessionName, option: OptionName) -> Option<&str> {
33 let query = OptionQuery::known(option);
34 self.sessions
35 .get(session_name)
36 .and_then(|node| node.value(query.canonical_name(), None))
37 }
38
39 #[must_use]
41 pub fn window_value(&self, target: &WindowTarget, option: OptionName) -> Option<&str> {
42 let query = OptionQuery::known(option);
43 self.windows
44 .get(target)
45 .and_then(|node| node.value(query.canonical_name(), None))
46 }
47
48 #[must_use]
50 pub fn pane_value(&self, target: &PaneTarget, option: OptionName) -> Option<&str> {
51 let query = OptionQuery::known(option);
52 self.panes
53 .get(target)
54 .and_then(|node| node.value(query.canonical_name(), None))
55 }
56
57 #[must_use]
59 pub fn resolve<'a>(
60 &'a self,
61 session_name: Option<&'a SessionName>,
62 option: OptionName,
63 ) -> Option<&'a str> {
64 let query = OptionQuery::known(option);
65 let resolved = match session_name {
66 Some(session_name) => self
67 .resolve_query(&ResolveContext::Session(session_name), &query)
68 .map(OptionEntry::rendered),
69 None => self
70 .resolve_query(&ResolveContext::Server, &query)
71 .map(OptionEntry::rendered),
72 };
73 if let Some(resolved) = resolved {
74 Some(resolved)
75 } else {
76 self.default_rendered_value(&query)
77 }
78 }
79
80 #[must_use]
82 pub fn resolve_for_window<'a>(
83 &'a self,
84 session_name: &'a SessionName,
85 window_index: u32,
86 option: OptionName,
87 ) -> Option<&'a str> {
88 let query = OptionQuery::known(option);
89 let resolved = self
90 .resolve_query(&ResolveContext::Window(session_name, window_index), &query)
91 .map(OptionEntry::rendered);
92 if let Some(resolved) = resolved {
93 Some(resolved)
94 } else {
95 self.default_rendered_value(&query)
96 }
97 }
98
99 #[must_use]
101 pub fn resolve_for_pane<'a>(
102 &'a self,
103 session_name: &'a SessionName,
104 window_index: u32,
105 pane_index: u32,
106 option: OptionName,
107 ) -> Option<&'a str> {
108 let query = OptionQuery::known(option);
109 let resolved = self
110 .resolve_query(
111 &ResolveContext::Pane(session_name, window_index, pane_index),
112 &query,
113 )
114 .map(OptionEntry::rendered);
115 if let Some(resolved) = resolved {
116 Some(resolved)
117 } else {
118 self.default_rendered_value(&query)
119 }
120 }
121
122 #[must_use]
124 pub fn resolve_array_values(
125 &self,
126 session_name: Option<&SessionName>,
127 option: OptionName,
128 ) -> Vec<String> {
129 let query = OptionQuery::known(option);
130 if !query.is_array() {
131 return Vec::new();
132 }
133
134 let resolved = match session_name {
135 Some(session_name) => self
136 .resolve_query(&ResolveContext::Session(session_name), &query)
137 .map(OptionEntry::array_values),
138 None => self
139 .resolve_query(&ResolveContext::Server, &query)
140 .map(OptionEntry::array_values),
141 };
142 if let Some(resolved) = resolved {
143 return resolved;
144 }
145
146 match query.default_value() {
147 Some(DefaultValue::Scalar(value)) => split_array_assignment(value, query.separator()),
148 Some(DefaultValue::Array(values)) => {
149 values.iter().map(|value| (*value).to_owned()).collect()
150 }
151 None => Vec::new(),
152 }
153 }
154
155 #[must_use]
157 pub fn resolve_name(&self, session_name: Option<&SessionName>, name: &str) -> Option<String> {
158 let query = resolve_option_name(name).ok()?;
159 match session_name {
160 Some(session_name) => self
163 .resolve_name_from_nodes(
164 &query,
165 [
166 Some(&self.server_global),
167 self.sessions.get(session_name),
168 Some(&self.session_global),
169 ],
170 )
171 .or_else(|| self.default_value_as_string(&query)),
172 None => self
173 .resolve_name_from_nodes(&query, [Some(&self.server_global)])
174 .or_else(|| self.default_value_as_string(&query)),
175 }
176 }
177
178 #[must_use]
180 pub fn resolve_name_for_format(
181 &self,
182 session_name: Option<&SessionName>,
183 name: &str,
184 ) -> Option<String> {
185 let query = resolve_exact_option_name(name).ok()?;
186 let value = match session_name {
187 Some(session_name) => self
188 .resolve_name_from_nodes(
189 &query,
190 [
191 Some(&self.server_global),
192 self.sessions.get(session_name),
193 Some(&self.session_global),
194 ],
195 )
196 .or_else(|| self.default_value_as_string(&query)),
197 None => self
198 .resolve_name_from_nodes(
199 &query,
200 [
201 Some(&self.server_global),
202 Some(&self.session_global),
203 Some(&self.window_global),
204 ],
205 )
206 .or_else(|| self.default_value_as_string(&query)),
207 }?;
208 Some(format_rendered_option_value(&query, value))
209 }
210
211 #[must_use]
213 pub fn resolve_name_for_window(
214 &self,
215 session_name: &SessionName,
216 window_index: u32,
217 name: &str,
218 ) -> Option<String> {
219 let query = resolve_option_name(name).ok()?;
220 let target = WindowTarget::with_window(session_name.clone(), window_index);
221 self.resolve_name_from_nodes(
222 &query,
223 [
224 Some(&self.server_global),
225 self.windows.get(&target),
226 Some(&self.window_global),
227 self.sessions.get(session_name),
228 Some(&self.session_global),
229 ],
230 )
231 .or_else(|| self.default_value_as_string(&query))
232 }
233
234 #[must_use]
236 pub fn resolve_name_for_window_format(
237 &self,
238 session_name: &SessionName,
239 window_index: u32,
240 name: &str,
241 ) -> Option<String> {
242 let query = resolve_exact_option_name(name).ok()?;
243 let target = WindowTarget::with_window(session_name.clone(), window_index);
244 let value = self
245 .resolve_name_from_nodes(
246 &query,
247 [
248 Some(&self.server_global),
249 self.windows.get(&target),
250 Some(&self.window_global),
251 self.sessions.get(session_name),
252 Some(&self.session_global),
253 ],
254 )
255 .or_else(|| self.default_value_as_string(&query))?;
256 Some(format_rendered_option_value(&query, value))
257 }
258
259 #[must_use]
261 pub fn resolve_name_for_pane(
262 &self,
263 session_name: &SessionName,
264 window_index: u32,
265 pane_index: u32,
266 name: &str,
267 ) -> Option<String> {
268 let query = resolve_option_name(name).ok()?;
269 let pane_target = PaneTarget::with_window(session_name.clone(), window_index, pane_index);
270 let window_target = WindowTarget::with_window(session_name.clone(), window_index);
271 self.resolve_name_from_nodes(
272 &query,
273 [
274 Some(&self.server_global),
275 self.panes.get(&pane_target),
276 self.windows.get(&window_target),
277 Some(&self.window_global),
278 self.sessions.get(session_name),
279 Some(&self.session_global),
280 ],
281 )
282 .or_else(|| self.default_value_as_string(&query))
283 }
284
285 #[must_use]
287 pub fn resolve_name_for_pane_format(
288 &self,
289 session_name: &SessionName,
290 window_index: u32,
291 pane_index: u32,
292 name: &str,
293 ) -> Option<String> {
294 let query = resolve_exact_option_name(name).ok()?;
295 let pane_target = PaneTarget::with_window(session_name.clone(), window_index, pane_index);
296 let window_target = WindowTarget::with_window(session_name.clone(), window_index);
297 let value = self
298 .resolve_name_from_nodes(
299 &query,
300 [
301 Some(&self.server_global),
302 self.panes.get(&pane_target),
303 self.windows.get(&window_target),
304 Some(&self.window_global),
305 self.sessions.get(session_name),
306 Some(&self.session_global),
307 ],
308 )
309 .or_else(|| self.default_value_as_string(&query))?;
310 Some(format_rendered_option_value(&query, value))
311 }
312
313 #[must_use]
315 pub fn resolved(&self, session_name: &SessionName) -> HashMap<OptionName, String> {
316 registry()
317 .iter()
318 .map(|metadata| {
319 let query = OptionQuery::known(metadata.option());
320 let value = self
321 .resolve(Some(session_name), metadata.option())
322 .map(str::to_owned)
323 .or_else(|| self.default_value_as_string(&query))
324 .unwrap_or_default();
325 (metadata.option(), value)
326 })
327 .collect()
328 }
329
330 #[must_use]
332 pub fn resolved_for_pane(
333 &self,
334 session_name: &SessionName,
335 window_index: u32,
336 pane_index: u32,
337 ) -> HashMap<OptionName, String> {
338 registry()
339 .iter()
340 .map(|metadata| {
341 let query = OptionQuery::known(metadata.option());
342 let value = self
343 .resolve_for_pane(session_name, window_index, pane_index, metadata.option())
344 .map(str::to_owned)
345 .or_else(|| self.default_value_as_string(&query))
346 .unwrap_or_default();
347 (metadata.option(), value)
348 })
349 .collect()
350 }
351
352 pub(super) fn node_for_exact_scope_mut(
353 &mut self,
354 scope: &OptionScopeSelector,
355 ) -> &mut OptionNode {
356 match scope {
357 OptionScopeSelector::ServerGlobal => &mut self.server_global,
358 OptionScopeSelector::SessionGlobal => &mut self.session_global,
359 OptionScopeSelector::WindowGlobal => &mut self.window_global,
360 OptionScopeSelector::Session(session_name) => {
361 self.sessions.entry(session_name.clone()).or_default()
362 }
363 OptionScopeSelector::Window(target) => self.windows.entry(target.clone()).or_default(),
364 OptionScopeSelector::Pane(target) => self.panes.entry(target.clone()).or_default(),
365 }
366 }
367
368 pub(super) fn node_for_exact_scope(&self, scope: &OptionScopeSelector) -> Option<&OptionNode> {
369 match scope {
370 OptionScopeSelector::ServerGlobal => Some(&self.server_global),
371 OptionScopeSelector::SessionGlobal => Some(&self.session_global),
372 OptionScopeSelector::WindowGlobal => Some(&self.window_global),
373 OptionScopeSelector::Session(session_name) => self.sessions.get(session_name),
374 OptionScopeSelector::Window(target) => self.windows.get(target),
375 OptionScopeSelector::Pane(target) => self.panes.get(target),
376 }
377 }
378
379 pub(super) fn resolve_query<'a>(
380 &'a self,
381 context: &ResolveContext<'a>,
382 query: &OptionQuery,
383 ) -> Option<&'a OptionEntry> {
384 for node in self.chain_for_context(context, query) {
385 if let Some(entry) = node.entry(query.canonical_name()) {
386 if query.index().is_none() || entry.value(query.index()).is_some() {
387 return Some(entry);
388 }
389 }
390 }
391 None
392 }
393
394 pub(super) fn effective_value_for_scope(
395 &self,
396 scope: &OptionScopeSelector,
397 query: &OptionQuery,
398 ) -> Option<String> {
399 match scope {
400 OptionScopeSelector::ServerGlobal => self
401 .resolve_query(&ResolveContext::Server, query)
402 .and_then(|entry| entry.value(query.index()).map(str::to_owned)),
403 OptionScopeSelector::SessionGlobal => self
404 .resolve_query(&ResolveContext::SessionGlobal, query)
405 .and_then(|entry| entry.value(query.index()).map(str::to_owned)),
406 OptionScopeSelector::WindowGlobal => self
407 .resolve_query(&ResolveContext::WindowGlobal, query)
408 .and_then(|entry| entry.value(query.index()).map(str::to_owned)),
409 OptionScopeSelector::Session(session_name) => self
410 .resolve_query(&ResolveContext::Session(session_name), query)
411 .and_then(|entry| entry.value(query.index()).map(str::to_owned)),
412 OptionScopeSelector::Window(target) => self
413 .resolve_query(
414 &ResolveContext::Window(target.session_name(), target.window_index()),
415 query,
416 )
417 .and_then(|entry| entry.value(query.index()).map(str::to_owned)),
418 OptionScopeSelector::Pane(target) => self
419 .resolve_query(
420 &ResolveContext::Pane(
421 target.session_name(),
422 target.window_index(),
423 target.pane_index(),
424 ),
425 query,
426 )
427 .and_then(|entry| entry.value(query.index()).map(str::to_owned)),
428 }
429 }
430
431 pub(super) fn explicit_value_for_scope(
432 &self,
433 scope: &OptionScopeSelector,
434 query: &OptionQuery,
435 ) -> Option<String> {
436 self.node_for_exact_scope(scope)
437 .and_then(|node| node.value(query.canonical_name(), query.index()))
438 .map(str::to_owned)
439 }
440
441 pub fn explicit_value_by_name(
443 &self,
444 scope: &OptionScopeSelector,
445 name: &str,
446 ) -> Result<(String, Option<String>), rmux_proto::RmuxError> {
447 let query = resolve_option_name(name)?;
448 Ok((
449 query.canonical_name().to_owned(),
450 self.explicit_value_for_scope(scope, &query),
451 ))
452 }
453
454 pub fn explicit_entries_for_scope(&self, scope: &OptionScopeSelector) -> Vec<(String, String)> {
456 self.node_for_exact_scope(scope)
457 .map(|node| {
458 node.entries
459 .values()
460 .map(|entry| (entry.name.clone(), entry.rendered().to_owned()))
461 .collect()
462 })
463 .unwrap_or_default()
464 }
465
466 pub(super) fn default_entry_for_scope(
467 &self,
468 query: &OptionQuery,
469 scope: OptionScopeSelector,
470 ) -> Option<OptionEntry> {
471 let default = query.default_value()?;
472
473 if query.is_array() {
474 let items = default_array_items(query, default).ok()?;
475 Some(OptionEntry::new_array(query, scope, items))
476 } else {
477 let value =
478 normalize_scalar_value(query, Some(default_scalar_text(default)), None).ok()?;
479 Some(OptionEntry::new_scalar(query, scope, value))
480 }
481 }
482
483 pub(super) fn default_value_as_string(&self, query: &OptionQuery) -> Option<String> {
484 let default = query.default_value()?;
485 match default {
486 DefaultValue::Scalar(value) => Some(value.to_owned()),
487 DefaultValue::Array(values) => Some(values.join(query.separator())),
488 }
489 }
490
491 fn node_for_global_root(&self, root: GlobalRoot) -> Option<&OptionNode> {
492 match root {
493 GlobalRoot::Server => Some(&self.server_global),
494 GlobalRoot::Session => Some(&self.session_global),
495 GlobalRoot::Window => Some(&self.window_global),
496 }
497 }
498
499 fn chain_for_context<'a>(
500 &'a self,
501 context: &ResolveContext<'a>,
502 query: &OptionQuery,
503 ) -> Vec<&'a OptionNode> {
504 match context {
505 ResolveContext::Server => vec![&self.server_global],
506 ResolveContext::SessionGlobal => {
509 if query.is_user() {
510 vec![&self.session_global, &self.server_global]
511 } else {
512 vec![&self.session_global]
513 }
514 }
515 ResolveContext::WindowGlobal => {
516 if query.is_user() {
517 vec![
518 &self.window_global,
519 &self.session_global,
520 &self.server_global,
521 ]
522 } else {
523 vec![&self.window_global]
524 }
525 }
526 ResolveContext::Session(session_name) => {
527 let mut chain = Vec::new();
528 if let Some(node) = self.sessions.get(*session_name) {
529 if scope_allows_session(query)
530 || node.contains(query.canonical_name(), query.index())
531 {
532 chain.push(node);
533 }
534 }
535 push_known_global_roots(&mut chain, self, query);
536 chain
537 }
538 ResolveContext::Window(session_name, window_index) => {
539 let mut chain = Vec::new();
540 let target = WindowTarget::with_window((*session_name).clone(), *window_index);
541 if let Some(node) = self.windows.get(&target) {
542 if scope_allows_window(query)
543 || node.contains(query.canonical_name(), query.index())
544 {
545 chain.push(node);
546 }
547 }
548 if let Some(node) = self.sessions.get(*session_name) {
549 if scope_allows_session(query)
550 || node.contains(query.canonical_name(), query.index())
551 {
552 chain.push(node);
553 }
554 }
555 push_known_global_roots(&mut chain, self, query);
556 chain
557 }
558 ResolveContext::Pane(session_name, window_index, pane_index) => {
559 let mut chain = Vec::new();
560 let pane_target =
561 PaneTarget::with_window((*session_name).clone(), *window_index, *pane_index);
562 if let Some(node) = self.panes.get(&pane_target) {
563 if scope_allows_pane(query)
564 || node.contains(query.canonical_name(), query.index())
565 {
566 chain.push(node);
567 }
568 }
569 let window_target =
570 WindowTarget::with_window((*session_name).clone(), *window_index);
571 if let Some(node) = self.windows.get(&window_target) {
572 if scope_allows_window(query)
573 || node.contains(query.canonical_name(), query.index())
574 {
575 chain.push(node);
576 }
577 }
578 if let Some(node) = self.sessions.get(*session_name) {
579 if scope_allows_session(query)
580 || node.contains(query.canonical_name(), query.index())
581 {
582 chain.push(node);
583 }
584 }
585 push_known_global_roots(&mut chain, self, query);
586 chain
587 }
588 }
589 }
590
591 fn default_rendered_value(&self, query: &OptionQuery) -> Option<&'static str> {
592 if query.index().is_some() {
593 return None;
594 }
595 match query.default_value()? {
596 DefaultValue::Scalar(value) => Some(value),
597 DefaultValue::Array(_) => None,
601 }
602 }
603
604 fn resolve_name_from_nodes<'a, I>(&self, query: &OptionQuery, nodes: I) -> Option<String>
605 where
606 I: IntoIterator<Item = Option<&'a OptionNode>>,
607 {
608 for node in nodes.into_iter().flatten() {
609 if let Some(entry) = node.entry(query.canonical_name()) {
610 if let Some(value) = entry.value(query.index()) {
611 return Some(value.to_owned());
612 }
613 }
614 }
615 None
616 }
617}