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(super) fn default_entry_for_scope(
442 &self,
443 query: &OptionQuery,
444 scope: OptionScopeSelector,
445 ) -> Option<OptionEntry> {
446 let default = query.default_value()?;
447
448 if query.is_array() {
449 let items = default_array_items(query, default).ok()?;
450 Some(OptionEntry::new_array(query, scope, items))
451 } else {
452 let value =
453 normalize_scalar_value(query, Some(default_scalar_text(default)), None).ok()?;
454 Some(OptionEntry::new_scalar(query, scope, value))
455 }
456 }
457
458 pub(super) fn default_value_as_string(&self, query: &OptionQuery) -> Option<String> {
459 let default = query.default_value()?;
460 match default {
461 DefaultValue::Scalar(value) => Some(value.to_owned()),
462 DefaultValue::Array(values) => Some(values.join(query.separator())),
463 }
464 }
465
466 fn node_for_global_root(&self, root: GlobalRoot) -> Option<&OptionNode> {
467 match root {
468 GlobalRoot::Server => Some(&self.server_global),
469 GlobalRoot::Session => Some(&self.session_global),
470 GlobalRoot::Window => Some(&self.window_global),
471 }
472 }
473
474 fn chain_for_context<'a>(
475 &'a self,
476 context: &ResolveContext<'a>,
477 query: &OptionQuery,
478 ) -> Vec<&'a OptionNode> {
479 match context {
480 ResolveContext::Server => vec![&self.server_global],
481 ResolveContext::SessionGlobal => {
484 if query.is_user() {
485 vec![&self.session_global, &self.server_global]
486 } else {
487 vec![&self.session_global]
488 }
489 }
490 ResolveContext::WindowGlobal => {
491 if query.is_user() {
492 vec![
493 &self.window_global,
494 &self.session_global,
495 &self.server_global,
496 ]
497 } else {
498 vec![&self.window_global]
499 }
500 }
501 ResolveContext::Session(session_name) => {
502 let mut chain = Vec::new();
503 if scope_allows_session(query) {
504 if let Some(node) = self.sessions.get(*session_name) {
505 chain.push(node);
506 }
507 }
508 push_known_global_roots(&mut chain, self, query);
509 chain
510 }
511 ResolveContext::Window(session_name, window_index) => {
512 let mut chain = Vec::new();
513 if scope_allows_window(query) {
514 let target = WindowTarget::with_window((*session_name).clone(), *window_index);
515 if let Some(node) = self.windows.get(&target) {
516 chain.push(node);
517 }
518 }
519 if scope_allows_session(query) {
520 if let Some(node) = self.sessions.get(*session_name) {
521 chain.push(node);
522 }
523 }
524 push_known_global_roots(&mut chain, self, query);
525 chain
526 }
527 ResolveContext::Pane(session_name, window_index, pane_index) => {
528 let mut chain = Vec::new();
529 if scope_allows_pane(query) {
530 let pane_target = PaneTarget::with_window(
531 (*session_name).clone(),
532 *window_index,
533 *pane_index,
534 );
535 if let Some(node) = self.panes.get(&pane_target) {
536 chain.push(node);
537 }
538 }
539 if scope_allows_window(query) {
540 let window_target =
541 WindowTarget::with_window((*session_name).clone(), *window_index);
542 if let Some(node) = self.windows.get(&window_target) {
543 chain.push(node);
544 }
545 }
546 if scope_allows_session(query) {
547 if let Some(node) = self.sessions.get(*session_name) {
548 chain.push(node);
549 }
550 }
551 push_known_global_roots(&mut chain, self, query);
552 chain
553 }
554 }
555 }
556
557 fn default_rendered_value(&self, query: &OptionQuery) -> Option<&'static str> {
558 if query.index().is_some() {
559 return None;
560 }
561 match query.default_value()? {
562 DefaultValue::Scalar(value) => Some(value),
563 DefaultValue::Array(_) => None,
567 }
568 }
569
570 fn resolve_name_from_nodes<'a, I>(&self, query: &OptionQuery, nodes: I) -> Option<String>
571 where
572 I: IntoIterator<Item = Option<&'a OptionNode>>,
573 {
574 for node in nodes.into_iter().flatten() {
575 if let Some(entry) = node.entry(query.canonical_name()) {
576 if let Some(value) = entry.value(query.index()) {
577 return Some(value.to_owned());
578 }
579 }
580 }
581 None
582 }
583}