1mod kdl_layout_parser;
2use crate::data::{
3 BareKey, Direction, FloatingPaneCoordinates, InputMode, KeyWithModifier, LayoutInfo,
4 LayoutMetadata, MultiplayerColors, Palette, PaletteColor, PaneId, PaneInfo, PaneManifest,
5 PermissionType, Resize, SessionInfo, StyleDeclaration, Styling, TabInfo, ThemeHue, WebSharing,
6 DEFAULT_STYLES,
7};
8use crate::envs::EnvironmentVariables;
9use crate::home::{find_default_config_dir, get_layout_dir};
10use crate::input::config::{Config, ConfigError, KdlError};
11use crate::input::keybinds::Keybinds;
12use crate::input::layout::{
13 Layout, PercentOrFixed, PluginUserConfiguration, RunPlugin, RunPluginOrAlias, TabLayoutInfo,
14};
15use crate::input::options::{
16 Clipboard, OnForceClose, Options, PaneFrameStyle, DEFAULT_WORD_SEPARATORS,
17};
18use crate::input::permission::{GrantedPermission, PermissionCache};
19use crate::input::plugins::PluginAliases;
20use crate::input::theme::{FrameConfig, Theme, Themes, UiConfig};
21use crate::input::web_client::WebClientConfig;
22use kdl_layout_parser::KdlLayoutParser;
23use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
24use std::net::{IpAddr, Ipv4Addr};
25use strum::IntoEnumIterator;
26use uuid::Uuid;
27
28use miette::NamedSource;
29
30use kdl::{KdlDocument, KdlEntry, KdlNode, KdlValue};
31
32use std::path::PathBuf;
33use std::str::FromStr;
34use std::time::Duration;
35
36use crate::input::actions::{Action, SearchDirection, SearchOption};
37use crate::input::command::RunCommandAction;
38
39#[macro_export]
40macro_rules! parse_kdl_action_arguments {
41 ( $action_name:expr, $action_arguments:expr, $action_node:expr ) => {{
42 if !$action_arguments.is_empty() {
43 Err(ConfigError::new_kdl_error(
44 format!("Action '{}' must have arguments", $action_name),
45 $action_node.span().offset(),
46 $action_node.span().len(),
47 ))
48 } else {
49 match $action_name {
50 "Quit" => Ok(Action::Quit),
51 "FocusNextPane" => Ok(Action::FocusNextPane),
52 "FocusPreviousPane" => Ok(Action::FocusPreviousPane),
53 "FocusLastPane" => Ok(Action::FocusLastPane),
54 "FocusHostSession" => Ok(Action::FocusHostSession),
55 "FocusGuestSession" => Ok(Action::FocusGuestSession),
56 "ToggleHostFullscreen" => Ok(Action::ToggleHostFullscreen),
57 "SwitchFocus" => Ok(Action::SwitchFocus),
58 "EditScrollback" => Ok(Action::EditScrollback { ansi: false }),
59 "ScrollUp" => Ok(Action::ScrollUp),
60 "ScrollDown" => Ok(Action::ScrollDown),
61 "ScrollToBottom" => Ok(Action::ScrollToBottom),
62 "ScrollToTop" => Ok(Action::ScrollToTop),
63 "ScrollToPreviousPrompt" => Ok(Action::ScrollToPreviousPrompt),
64 "ScrollToNextPrompt" => Ok(Action::ScrollToNextPrompt),
65 "SelectCommandAtScrollPosition" => Ok(Action::SelectCommandAtScrollPosition),
66 "CopyLastCommandOutput" => Ok(Action::CopyLastCommandOutput),
67 "PageScrollUp" => Ok(Action::PageScrollUp),
68 "PageScrollDown" => Ok(Action::PageScrollDown),
69 "HalfPageScrollUp" => Ok(Action::HalfPageScrollUp),
70 "HalfPageScrollDown" => Ok(Action::HalfPageScrollDown),
71 "ToggleFocusFullscreen" => Ok(Action::ToggleFocusFullscreen),
72 "ToggleFocusNoUiFullscreen" => Ok(Action::ToggleFocusNoUiFullscreen),
73 "TogglePaneFrames" => Ok(Action::TogglePaneFrames),
74 "ToggleActiveSyncTab" => Ok(Action::ToggleActiveSyncTab),
75 "TogglePaneEmbedOrFloating" => Ok(Action::TogglePaneEmbedOrFloating),
76 "ToggleFloatingPanes" => Ok(Action::ToggleFloatingPanes),
77 "ShowFloatingPanes" => Ok(Action::ShowFloatingPanes { tab_id: None }),
78 "HideFloatingPanes" => Ok(Action::HideFloatingPanes { tab_id: None }),
79 "CloseFocus" => Ok(Action::CloseFocus),
80 "UndoRenamePane" => Ok(Action::UndoRenamePane),
81 "NoOp" => Ok(Action::NoOp),
82 "GoToNextTab" => Ok(Action::GoToNextTab),
83 "GoToPreviousTab" => Ok(Action::GoToPreviousTab),
84 "CloseTab" => Ok(Action::CloseTab),
85 "ToggleTab" => Ok(Action::ToggleTab),
86 "UndoRenameTab" => Ok(Action::UndoRenameTab),
87 "Detach" => Ok(Action::Detach),
88 "SetDarkTheme" => Ok(Action::SetDarkTheme),
89 "SetLightTheme" => Ok(Action::SetLightTheme),
90 "ToggleTheme" => Ok(Action::ToggleTheme),
91 "Copy" => Ok(Action::Copy),
92 "Confirm" => Ok(Action::Confirm),
93 "Deny" => Ok(Action::Deny),
94 "ToggleMouseMode" => Ok(Action::ToggleMouseMode),
95 "PreviousSwapLayout" => Ok(Action::PreviousSwapLayout),
96 "NextSwapLayout" => Ok(Action::NextSwapLayout),
97 "Clear" => Ok(Action::ClearScreen),
98 _ => Err(ConfigError::new_kdl_error(
99 format!("Unsupported action: {:?}", $action_name),
100 $action_node.span().offset(),
101 $action_node.span().len(),
102 )),
103 }
104 }
105 }};
106}
107
108#[macro_export]
109macro_rules! parse_kdl_action_u8_arguments {
110 ( $action_name:expr, $action_arguments:expr, $action_node:expr ) => {{
111 let mut bytes = vec![];
112 for kdl_entry in $action_arguments.iter() {
113 match kdl_entry.value().as_i64() {
114 Some(int_value) => bytes.push(int_value as u8),
115 None => {
116 return Err(ConfigError::new_kdl_error(
117 format!("Arguments for '{}' must be integers", $action_name),
118 kdl_entry.span().offset(),
119 kdl_entry.span().len(),
120 ));
121 },
122 }
123 }
124 Action::new_from_bytes($action_name, bytes, $action_node)
125 }};
126}
127
128#[macro_export]
129macro_rules! kdl_parsing_error {
130 ( $message:expr, $entry:expr ) => {
131 ConfigError::new_kdl_error($message, $entry.span().offset(), $entry.span().len())
132 };
133}
134
135#[macro_export]
136macro_rules! kdl_entries_as_i64 {
137 ( $node:expr ) => {
138 $node
139 .entries()
140 .iter()
141 .map(|kdl_node| kdl_node.value().as_i64())
142 };
143}
144
145#[macro_export]
146macro_rules! kdl_first_entry_as_string {
147 ( $node:expr ) => {
148 $node
149 .entries()
150 .iter()
151 .next()
152 .and_then(|s| s.value().as_string())
153 };
154}
155
156#[macro_export]
157macro_rules! kdl_first_entry_as_i64 {
158 ( $node:expr ) => {
159 $node
160 .entries()
161 .iter()
162 .next()
163 .and_then(|i| i.value().as_i64())
164 };
165}
166
167#[macro_export]
168macro_rules! kdl_first_entry_as_bool {
169 ( $node:expr ) => {
170 $node
171 .entries()
172 .iter()
173 .next()
174 .and_then(|i| i.value().as_bool())
175 };
176}
177
178#[macro_export]
179macro_rules! entry_count {
180 ( $node:expr ) => {{
181 $node.entries().iter().len()
182 }};
183}
184
185#[macro_export]
186macro_rules! parse_kdl_action_char_or_string_arguments {
187 ( $action_name:expr, $action_arguments:expr, $action_node:expr ) => {{
188 let mut chars_to_write = String::new();
189 for kdl_entry in $action_arguments.iter() {
190 match kdl_entry.value().as_string() {
191 Some(string_value) => chars_to_write.push_str(string_value),
192 None => {
193 return Err(ConfigError::new_kdl_error(
194 format!("All entries for action '{}' must be strings", $action_name),
195 kdl_entry.span().offset(),
196 kdl_entry.span().len(),
197 ))
198 },
199 }
200 }
201 Action::new_from_string($action_name, chars_to_write, $action_node)
202 }};
203}
204
205#[macro_export]
206macro_rules! kdl_arg_is_truthy {
207 ( $kdl_node:expr, $arg_name:expr ) => {
208 match $kdl_node.get($arg_name) {
209 Some(arg) => match arg.value().as_bool() {
210 Some(value) => value,
211 None => {
212 return Err(ConfigError::new_kdl_error(
213 format!("Argument must be true or false, found: {}", arg.value()),
214 arg.span().offset(),
215 arg.span().len(),
216 ))
217 },
218 },
219 None => false,
220 }
221 };
222}
223
224#[macro_export]
225macro_rules! kdl_children_nodes_or_error {
226 ( $kdl_node:expr, $error:expr ) => {
227 $kdl_node
228 .children()
229 .ok_or(ConfigError::new_kdl_error(
230 $error.into(),
231 $kdl_node.span().offset(),
232 $kdl_node.span().len(),
233 ))?
234 .nodes()
235 };
236}
237
238#[macro_export]
239macro_rules! kdl_children_nodes {
240 ( $kdl_node:expr ) => {
241 $kdl_node.children().map(|c| c.nodes())
242 };
243}
244
245#[macro_export]
246macro_rules! kdl_property_nodes {
247 ( $kdl_node:expr ) => {{
248 $kdl_node
249 .entries()
250 .iter()
251 .filter_map(|e| e.name())
252 .map(|e| e.value())
253 }};
254}
255
256#[macro_export]
257macro_rules! kdl_children_or_error {
258 ( $kdl_node:expr, $error:expr ) => {
259 $kdl_node.children().ok_or(ConfigError::new_kdl_error(
260 $error.into(),
261 $kdl_node.span().offset(),
262 $kdl_node.span().len(),
263 ))?
264 };
265}
266
267#[macro_export]
268macro_rules! kdl_children {
269 ( $kdl_node:expr ) => {
270 $kdl_node.children().iter().copied().collect()
271 };
272}
273
274#[macro_export]
275macro_rules! kdl_get_string_property_or_child_value {
276 ( $kdl_node:expr, $name:expr ) => {
277 $kdl_node
278 .get($name)
279 .and_then(|e| e.value().as_string())
280 .or_else(|| {
281 $kdl_node
282 .children()
283 .and_then(|c| c.get($name))
284 .and_then(|c| c.get(0))
285 .and_then(|c| c.value().as_string())
286 })
287 };
288}
289
290#[macro_export]
291macro_rules! kdl_string_arguments {
292 ( $kdl_node:expr ) => {{
293 let res: Result<Vec<_>, _> = $kdl_node
294 .entries()
295 .iter()
296 .map(|e| {
297 e.value().as_string().ok_or(ConfigError::new_kdl_error(
298 "Not a string".into(),
299 e.span().offset(),
300 e.span().len(),
301 ))
302 })
303 .collect();
304 res?
305 }};
306}
307
308#[macro_export]
309macro_rules! kdl_property_names {
310 ( $kdl_node:expr ) => {{
311 $kdl_node
312 .entries()
313 .iter()
314 .filter_map(|e| e.name())
315 .map(|e| e.value())
316 }};
317}
318
319#[macro_export]
320macro_rules! kdl_argument_values {
321 ( $kdl_node:expr ) => {
322 $kdl_node.entries().iter().collect()
323 };
324}
325
326#[macro_export]
327macro_rules! kdl_name {
328 ( $kdl_node:expr ) => {
329 $kdl_node.name().value()
330 };
331}
332
333#[macro_export]
334macro_rules! kdl_document_name {
335 ( $kdl_node:expr ) => {
336 $kdl_node.node().name().value()
337 };
338}
339
340#[macro_export]
341macro_rules! keys_from_kdl {
342 ( $kdl_node:expr ) => {
343 kdl_string_arguments!($kdl_node)
344 .iter()
345 .map(|k| {
346 KeyWithModifier::from_str(k).map_err(|_| {
347 ConfigError::new_kdl_error(
348 format!("Invalid key: '{}'", k),
349 $kdl_node.span().offset(),
350 $kdl_node.span().len(),
351 )
352 })
353 })
354 .collect::<Result<_, _>>()?
355 };
356}
357
358#[macro_export]
359macro_rules! actions_from_kdl {
360 ( $kdl_node:expr, $config_options:expr ) => {
361 kdl_children_nodes_or_error!($kdl_node, "no actions found for key_block")
362 .iter()
363 .map(|kdl_action| Action::try_from((kdl_action, $config_options)))
364 .collect::<Result<_, _>>()?
365 };
366}
367
368pub fn kdl_arguments_that_are_strings<'a>(
369 arguments: impl Iterator<Item = &'a KdlEntry>,
370) -> Result<Vec<String>, ConfigError> {
371 let mut args: Vec<String> = vec![];
372 for kdl_entry in arguments {
373 match kdl_entry.value().as_string() {
374 Some(string_value) => args.push(string_value.to_string()),
375 None => {
376 return Err(ConfigError::new_kdl_error(
377 format!("Argument must be a string"),
378 kdl_entry.span().offset(),
379 kdl_entry.span().len(),
380 ));
381 },
382 }
383 }
384 Ok(args)
385}
386
387pub fn kdl_arguments_that_are_digits<'a>(
388 arguments: impl Iterator<Item = &'a KdlEntry>,
389) -> Result<Vec<i64>, ConfigError> {
390 let mut args: Vec<i64> = vec![];
391 for kdl_entry in arguments {
392 match kdl_entry.value().as_i64() {
393 Some(digit_value) => {
394 args.push(digit_value);
395 },
396 None => {
397 return Err(ConfigError::new_kdl_error(
398 format!("Argument must be a digit"),
399 kdl_entry.span().offset(),
400 kdl_entry.span().len(),
401 ));
402 },
403 }
404 }
405 Ok(args)
406}
407
408pub fn kdl_child_string_value_for_entry<'a>(
409 command_metadata: &'a KdlDocument,
410 entry_name: &'a str,
411) -> Option<&'a str> {
412 command_metadata
413 .get(entry_name)
414 .and_then(|cwd| cwd.entries().iter().next())
415 .and_then(|cwd_value| cwd_value.value().as_string())
416}
417
418pub fn kdl_child_bool_value_for_entry<'a>(
419 command_metadata: &'a KdlDocument,
420 entry_name: &'a str,
421) -> Option<bool> {
422 command_metadata
423 .get(entry_name)
424 .and_then(|cwd| cwd.entries().iter().next())
425 .and_then(|cwd_value| cwd_value.value().as_bool())
426}
427
428impl Action {
429 pub fn new_from_bytes(
430 action_name: &str,
431 bytes: Vec<u8>,
432 action_node: &KdlNode,
433 ) -> Result<Self, ConfigError> {
434 match action_name {
435 "Write" => Ok(Action::Write {
436 key_with_modifier: None,
437 bytes,
438 is_kitty_keyboard_protocol: false,
439 }),
440 "PaneNameInput" => Ok(Action::PaneNameInput { input: bytes }),
441 "TabNameInput" => Ok(Action::TabNameInput { input: bytes }),
442 "SearchInput" => Ok(Action::SearchInput { input: bytes }),
443 "GoToTab" => {
444 let tab_index = *bytes.get(0).ok_or_else(|| {
445 ConfigError::new_kdl_error(
446 format!("Missing tab index"),
447 action_node.span().offset(),
448 action_node.span().len(),
449 )
450 })? as u32;
451 Ok(Action::GoToTab { index: tab_index })
452 },
453 _ => Err(ConfigError::new_kdl_error(
454 "Failed to parse action".into(),
455 action_node.span().offset(),
456 action_node.span().len(),
457 )),
458 }
459 }
460 pub fn new_from_string(
461 action_name: &str,
462 string: String,
463 action_node: &KdlNode,
464 ) -> Result<Self, ConfigError> {
465 match action_name {
466 "WriteChars" => Ok(Action::WriteChars { chars: string }),
467 "SetPaneFrameStyle" => {
468 let style = PaneFrameStyle::from_str(string.as_str()).map_err(|e| {
469 ConfigError::new_kdl_error(
470 format!("{}", e),
471 action_node.span().offset(),
472 action_node.span().len(),
473 )
474 })?;
475 Ok(Action::SetPaneFrameStyle(style))
476 },
477 "SwitchToMode" => match InputMode::from_str(string.as_str()) {
478 Ok(input_mode) => Ok(Action::SwitchToMode { input_mode }),
479 Err(_e) => {
480 return Err(ConfigError::new_kdl_error(
481 format!("Unknown InputMode '{}'", string),
482 action_node.span().offset(),
483 action_node.span().len(),
484 ))
485 },
486 },
487 "Resize" => {
488 let mut resize: Option<Resize> = None;
489 let mut direction: Option<Direction> = None;
490 for word in string.to_ascii_lowercase().split_whitespace() {
491 match Resize::from_str(word) {
492 Ok(value) => resize = Some(value),
493 Err(_) => match Direction::from_str(word) {
494 Ok(value) => direction = Some(value),
495 Err(_) => {
496 return Err(ConfigError::new_kdl_error(
497 format!(
498 "failed to read either of resize type or direction from '{}'",
499 word
500 ),
501 action_node.span().offset(),
502 action_node.span().len(),
503 ))
504 },
505 },
506 }
507 }
508 let resize = resize.unwrap_or(Resize::Increase);
509 Ok(Action::Resize { resize, direction })
510 },
511 "MoveFocus" => {
512 let direction = Direction::from_str(string.as_str()).map_err(|_| {
513 ConfigError::new_kdl_error(
514 format!("Invalid direction: '{}'", string),
515 action_node.span().offset(),
516 action_node.span().len(),
517 )
518 })?;
519 Ok(Action::MoveFocus { direction })
520 },
521 "MoveFocusOrTab" => {
522 let direction = Direction::from_str(string.as_str()).map_err(|_| {
523 ConfigError::new_kdl_error(
524 format!("Invalid direction: '{}'", string),
525 action_node.span().offset(),
526 action_node.span().len(),
527 )
528 })?;
529 Ok(Action::MoveFocusOrTab { direction })
530 },
531 "MoveTab" => {
532 let direction = Direction::from_str(string.as_str()).map_err(|_| {
533 ConfigError::new_kdl_error(
534 format!("Invalid direction: '{}'", string),
535 action_node.span().offset(),
536 action_node.span().len(),
537 )
538 })?;
539 if direction.is_vertical() {
540 Err(ConfigError::new_kdl_error(
541 format!("Invalid horizontal direction: '{}'", string),
542 action_node.span().offset(),
543 action_node.span().len(),
544 ))
545 } else {
546 Ok(Action::MoveTab { direction })
547 }
548 },
549 "MovePane" => {
550 if string.is_empty() {
551 return Ok(Action::MovePane { direction: None });
552 } else {
553 let direction = Direction::from_str(string.as_str()).map_err(|_| {
554 ConfigError::new_kdl_error(
555 format!("Invalid direction: '{}'", string),
556 action_node.span().offset(),
557 action_node.span().len(),
558 )
559 })?;
560 Ok(Action::MovePane {
561 direction: Some(direction),
562 })
563 }
564 },
565 "MovePaneBackwards" => Ok(Action::MovePaneBackwards),
566 "DumpScreen" => Ok(Action::DumpScreen {
567 file_path: Some(string),
568 include_scrollback: false,
569 pane_id: None,
570 ansi: false,
571 }),
572 "DumpLayout" => Ok(Action::DumpLayout),
573 "NewPane" => {
574 if string.is_empty() {
575 return Ok(Action::NewPane {
576 direction: None,
577 pane_name: None,
578 start_suppressed: false,
579 });
580 } else if string == "stacked" {
581 return Ok(Action::NewStackedPane {
582 command: None,
583 pane_name: None,
584 near_current_pane: false,
585 no_focus: false,
586 tab_id: None,
587 });
588 } else {
589 let direction = Direction::from_str(string.as_str()).map_err(|_| {
590 ConfigError::new_kdl_error(
591 format!("Invalid direction: '{}'", string),
592 action_node.span().offset(),
593 action_node.span().len(),
594 )
595 })?;
596 Ok(Action::NewPane {
597 direction: Some(direction),
598 pane_name: None,
599 start_suppressed: false,
600 })
601 }
602 },
603 "SearchToggleOption" => {
604 let toggle_option = SearchOption::from_str(string.as_str()).map_err(|_| {
605 ConfigError::new_kdl_error(
606 format!("Invalid direction: '{}'", string),
607 action_node.span().offset(),
608 action_node.span().len(),
609 )
610 })?;
611 Ok(Action::SearchToggleOption {
612 option: toggle_option,
613 })
614 },
615 "Search" => {
616 let search_direction =
617 SearchDirection::from_str(string.as_str()).map_err(|_| {
618 ConfigError::new_kdl_error(
619 format!("Invalid direction: '{}'", string),
620 action_node.span().offset(),
621 action_node.span().len(),
622 )
623 })?;
624 Ok(Action::Search {
625 direction: search_direction,
626 })
627 },
628 "RenameSession" => Ok(Action::RenameSession { name: string }),
629 _ => Err(ConfigError::new_kdl_error(
630 format!("Unsupported action: {}", action_name),
631 action_node.span().offset(),
632 action_node.span().len(),
633 )),
634 }
635 }
636 pub fn to_kdl(&self) -> Option<KdlNode> {
637 match self {
638 Action::Quit => Some(KdlNode::new("Quit")),
639 Action::Write {
640 key_with_modifier: _key,
641 bytes,
642 is_kitty_keyboard_protocol: _is_kitty,
643 } => {
644 let mut node = KdlNode::new("Write");
645 for byte in bytes {
646 node.push(KdlValue::Base10(*byte as i64));
647 }
648 Some(node)
649 },
650 Action::WriteChars { chars: string } => {
651 let mut node = KdlNode::new("WriteChars");
652 node.push(string.clone());
653 Some(node)
654 },
655 Action::SwitchToMode { input_mode } => {
656 let mut node = KdlNode::new("SwitchToMode");
657 node.push(format!("{:?}", input_mode).to_lowercase());
658 Some(node)
659 },
660 Action::Resize {
661 resize,
662 direction: resize_direction,
663 } => {
664 let mut node = KdlNode::new("Resize");
665 let resize = match resize {
666 Resize::Increase => "Increase",
667 Resize::Decrease => "Decrease",
668 };
669 if let Some(resize_direction) = resize_direction {
670 let resize_direction = match resize_direction {
671 Direction::Left => "left",
672 Direction::Right => "right",
673 Direction::Up => "up",
674 Direction::Down => "down",
675 };
676 node.push(format!("{} {}", resize, resize_direction));
677 } else {
678 node.push(format!("{}", resize));
679 }
680 Some(node)
681 },
682 Action::FocusNextPane => Some(KdlNode::new("FocusNextPane")),
683 Action::FocusPreviousPane => Some(KdlNode::new("FocusPreviousPane")),
684 Action::FocusLastPane => Some(KdlNode::new("FocusLastPane")),
685 Action::SwitchFocus => Some(KdlNode::new("SwitchFocus")),
686 Action::MoveFocus { direction } => {
687 let mut node = KdlNode::new("MoveFocus");
688 let direction = match direction {
689 Direction::Left => "left",
690 Direction::Right => "right",
691 Direction::Up => "up",
692 Direction::Down => "down",
693 };
694 node.push(direction);
695 Some(node)
696 },
697 Action::MoveFocusOrTab { direction } => {
698 let mut node = KdlNode::new("MoveFocusOrTab");
699 let direction = match direction {
700 Direction::Left => "left",
701 Direction::Right => "right",
702 Direction::Up => "up",
703 Direction::Down => "down",
704 };
705 node.push(direction);
706 Some(node)
707 },
708 Action::MovePane { direction } => {
709 let mut node = KdlNode::new("MovePane");
710 if let Some(direction) = direction {
711 let direction = match direction {
712 Direction::Left => "left",
713 Direction::Right => "right",
714 Direction::Up => "up",
715 Direction::Down => "down",
716 };
717 node.push(direction);
718 }
719 Some(node)
720 },
721 Action::MovePaneBackwards => Some(KdlNode::new("MovePaneBackwards")),
722 Action::DumpScreen {
723 file_path: Some(file),
724 include_scrollback: _,
725 pane_id: _,
726 ansi: _,
727 } => {
728 let mut node = KdlNode::new("DumpScreen");
729 node.push(file.clone());
730 Some(node)
731 },
732 Action::DumpScreen {
733 file_path: None, ..
734 } => None,
735 Action::DumpLayout => Some(KdlNode::new("DumpLayout")),
736 Action::EditScrollback { ansi } => {
737 let mut node = KdlNode::new("EditScrollback");
738 if *ansi {
739 let mut children = KdlDocument::new();
740 let mut ansi_node = KdlNode::new("ansi");
741 ansi_node.push(KdlValue::Bool(true));
742 children.nodes_mut().push(ansi_node);
743 node.set_children(children);
744 }
745 Some(node)
746 },
747 Action::ScrollUp => Some(KdlNode::new("ScrollUp")),
748 Action::ScrollDown => Some(KdlNode::new("ScrollDown")),
749 Action::ScrollToBottom => Some(KdlNode::new("ScrollToBottom")),
750 Action::ScrollToTop => Some(KdlNode::new("ScrollToTop")),
751 Action::ScrollToPreviousPrompt => Some(KdlNode::new("ScrollToPreviousPrompt")),
752 Action::ScrollToNextPrompt => Some(KdlNode::new("ScrollToNextPrompt")),
753 Action::SelectCommandAtScrollPosition => {
754 Some(KdlNode::new("SelectCommandAtScrollPosition"))
755 },
756 Action::CopyLastCommandOutput => Some(KdlNode::new("CopyLastCommandOutput")),
757 Action::PageScrollUp => Some(KdlNode::new("PageScrollUp")),
758 Action::PageScrollDown => Some(KdlNode::new("PageScrollDown")),
759 Action::HalfPageScrollUp => Some(KdlNode::new("HalfPageScrollUp")),
760 Action::HalfPageScrollDown => Some(KdlNode::new("HalfPageScrollDown")),
761 Action::ToggleFocusFullscreen => Some(KdlNode::new("ToggleFocusFullscreen")),
762 Action::ToggleFocusNoUiFullscreen => Some(KdlNode::new("ToggleFocusNoUiFullscreen")),
763 Action::TogglePaneFrames => Some(KdlNode::new("TogglePaneFrames")),
764 Action::SetPaneFrameStyle(style) => {
765 let mut node = KdlNode::new("SetPaneFrameStyle");
766 let style = match style {
767 PaneFrameStyle::Full => "full",
768 PaneFrameStyle::Titles => "titles",
769 PaneFrameStyle::None => "none",
770 };
771 node.push(style);
772 Some(node)
773 },
774 Action::ToggleActiveSyncTab => Some(KdlNode::new("ToggleActiveSyncTab")),
775 Action::NewPane {
776 direction,
777 pane_name: _,
778 start_suppressed: _,
779 } => {
780 let mut node = KdlNode::new("NewPane");
781 if let Some(direction) = direction {
782 let direction = match direction {
783 Direction::Left => "left",
784 Direction::Right => "right",
785 Direction::Up => "up",
786 Direction::Down => "down",
787 };
788 node.push(direction);
789 }
790 Some(node)
791 },
792 Action::TogglePaneEmbedOrFloating => Some(KdlNode::new("TogglePaneEmbedOrFloating")),
793 Action::ToggleFloatingPanes => Some(KdlNode::new("ToggleFloatingPanes")),
794 Action::ShowFloatingPanes { tab_id } => {
795 let mut node = KdlNode::new("ShowFloatingPanes");
796 if let Some(id) = tab_id {
797 node.push(KdlValue::Base10(*id as i64));
798 }
799 Some(node)
800 },
801 Action::HideFloatingPanes { tab_id } => {
802 let mut node = KdlNode::new("HideFloatingPanes");
803 if let Some(id) = tab_id {
804 node.push(KdlValue::Base10(*id as i64));
805 }
806 Some(node)
807 },
808 Action::CloseFocus => Some(KdlNode::new("CloseFocus")),
809 Action::PaneNameInput { input: bytes } => {
810 let mut node = KdlNode::new("PaneNameInput");
811 for byte in bytes {
812 node.push(KdlValue::Base10(*byte as i64));
813 }
814 Some(node)
815 },
816 Action::UndoRenamePane => Some(KdlNode::new("UndoRenamePane")),
817 Action::NewTab {
818 tiled_layout: _,
819 floating_layouts: _,
820 swap_tiled_layouts: _,
821 swap_floating_layouts: _,
822 tab_name: name,
823 should_change_focus_to_new_tab,
824 cwd,
825 initial_panes: _,
826 first_pane_unblock_condition: _,
827 } => {
828 let mut node = KdlNode::new("NewTab");
829 let mut children = KdlDocument::new();
830 if let Some(name) = name {
831 let mut name_node = KdlNode::new("name");
832 if !should_change_focus_to_new_tab {
833 let mut should_change_focus_to_new_tab_node =
834 KdlNode::new("should_change_focus_to_new_tab");
835 should_change_focus_to_new_tab_node.push(KdlValue::Bool(false));
836 children
837 .nodes_mut()
838 .push(should_change_focus_to_new_tab_node);
839 }
840 name_node.push(name.clone());
841 children.nodes_mut().push(name_node);
842 }
843 if let Some(cwd) = cwd {
844 let mut cwd_node = KdlNode::new("cwd");
845 cwd_node.push(cwd.display().to_string());
846 children.nodes_mut().push(cwd_node);
847 }
848 if name.is_some() || cwd.is_some() {
849 node.set_children(children);
850 }
851 Some(node)
852 },
853 Action::GoToNextTab => Some(KdlNode::new("GoToNextTab")),
854 Action::GoToPreviousTab => Some(KdlNode::new("GoToPreviousTab")),
855 Action::CloseTab => Some(KdlNode::new("CloseTab")),
856 Action::GoToTab { index } => {
857 let mut node = KdlNode::new("GoToTab");
858 node.push(KdlValue::Base10(*index as i64));
859 Some(node)
860 },
861 Action::ToggleTab => Some(KdlNode::new("ToggleTab")),
862 Action::TabNameInput { input: bytes } => {
863 let mut node = KdlNode::new("TabNameInput");
864 for byte in bytes {
865 node.push(KdlValue::Base10(*byte as i64));
866 }
867 Some(node)
868 },
869 Action::UndoRenameTab => Some(KdlNode::new("UndoRenameTab")),
870 Action::MoveTab { direction } => {
871 let mut node = KdlNode::new("MoveTab");
872 let direction = match direction {
873 Direction::Left => "left",
874 Direction::Right => "right",
875 Direction::Up => "up",
876 Direction::Down => "down",
877 };
878 node.push(direction);
879 Some(node)
880 },
881 Action::NewTiledPane {
882 direction,
883 command: run_command_action,
884 pane_name: name,
885 near_current_pane: false,
886 borderless: _,
887 ..
888 } => {
889 let mut node = KdlNode::new("Run");
890 let mut node_children = KdlDocument::new();
891 if let Some(run_command_action) = run_command_action {
892 node.push(run_command_action.command.display().to_string());
893 for arg in &run_command_action.args {
894 node.push(arg.clone());
895 }
896 if let Some(cwd) = &run_command_action.cwd {
897 let mut cwd_node = KdlNode::new("cwd");
898 cwd_node.push(cwd.display().to_string());
899 node_children.nodes_mut().push(cwd_node);
900 }
901 if run_command_action.hold_on_start {
902 let mut hos_node = KdlNode::new("hold_on_start");
903 hos_node.push(KdlValue::Bool(true));
904 node_children.nodes_mut().push(hos_node);
905 }
906 if !run_command_action.hold_on_close {
907 let mut hoc_node = KdlNode::new("hold_on_close");
908 hoc_node.push(KdlValue::Bool(false));
909 node_children.nodes_mut().push(hoc_node);
910 }
911 }
912 if let Some(name) = name {
913 let mut name_node = KdlNode::new("name");
914 name_node.push(name.clone());
915 node_children.nodes_mut().push(name_node);
916 }
917 if let Some(direction) = direction {
918 let mut direction_node = KdlNode::new("direction");
919 let direction = match direction {
920 Direction::Left => "left",
921 Direction::Right => "right",
922 Direction::Up => "up",
923 Direction::Down => "down",
924 };
925 direction_node.push(direction);
926 node_children.nodes_mut().push(direction_node);
927 }
928 if !node_children.nodes().is_empty() {
929 node.set_children(node_children);
930 }
931 Some(node)
932 },
933 Action::NewFloatingPane {
934 command: run_command_action,
935 pane_name: name,
936 coordinates: floating_pane_coordinates,
937 near_current_pane: false,
938 ..
939 } => {
940 let mut node = KdlNode::new("Run");
941 let mut node_children = KdlDocument::new();
942 let mut floating_pane = KdlNode::new("floating");
943 floating_pane.push(KdlValue::Bool(true));
944 node_children.nodes_mut().push(floating_pane);
945 if let Some(run_command_action) = run_command_action {
946 node.push(run_command_action.command.display().to_string());
947 for arg in &run_command_action.args {
948 node.push(arg.clone());
949 }
950 if let Some(cwd) = &run_command_action.cwd {
951 let mut cwd_node = KdlNode::new("cwd");
952 cwd_node.push(cwd.display().to_string());
953 node_children.nodes_mut().push(cwd_node);
954 }
955 if run_command_action.hold_on_start {
956 let mut hos_node = KdlNode::new("hold_on_start");
957 hos_node.push(KdlValue::Bool(true));
958 node_children.nodes_mut().push(hos_node);
959 }
960 if !run_command_action.hold_on_close {
961 let mut hoc_node = KdlNode::new("hold_on_close");
962 hoc_node.push(KdlValue::Bool(false));
963 node_children.nodes_mut().push(hoc_node);
964 }
965 }
966 if let Some(floating_pane_coordinates) = floating_pane_coordinates {
967 if let Some(x) = floating_pane_coordinates.x {
968 let mut x_node = KdlNode::new("x");
969 match x {
970 PercentOrFixed::Percent(x) => {
971 x_node.push(format!("{}%", x));
972 },
973 PercentOrFixed::Fixed(x) => {
974 x_node.push(KdlValue::Base10(x as i64));
975 },
976 };
977 node_children.nodes_mut().push(x_node);
978 }
979 if let Some(y) = floating_pane_coordinates.y {
980 let mut y_node = KdlNode::new("y");
981 match y {
982 PercentOrFixed::Percent(y) => {
983 y_node.push(format!("{}%", y));
984 },
985 PercentOrFixed::Fixed(y) => {
986 y_node.push(KdlValue::Base10(y as i64));
987 },
988 };
989 node_children.nodes_mut().push(y_node);
990 }
991 if let Some(width) = floating_pane_coordinates.width {
992 let mut width_node = KdlNode::new("width");
993 match width {
994 PercentOrFixed::Percent(width) => {
995 width_node.push(format!("{}%", width));
996 },
997 PercentOrFixed::Fixed(width) => {
998 width_node.push(KdlValue::Base10(width as i64));
999 },
1000 };
1001 node_children.nodes_mut().push(width_node);
1002 }
1003 if let Some(height) = floating_pane_coordinates.height {
1004 let mut height_node = KdlNode::new("height");
1005 match height {
1006 PercentOrFixed::Percent(height) => {
1007 height_node.push(format!("{}%", height));
1008 },
1009 PercentOrFixed::Fixed(height) => {
1010 height_node.push(KdlValue::Base10(height as i64));
1011 },
1012 };
1013 node_children.nodes_mut().push(height_node);
1014 }
1015 }
1016 if let Some(name) = name {
1017 let mut name_node = KdlNode::new("name");
1018 name_node.push(name.clone());
1019 node_children.nodes_mut().push(name_node);
1020 }
1021 if !node_children.nodes().is_empty() {
1022 node.set_children(node_children);
1023 }
1024 Some(node)
1025 },
1026 Action::NewInPlacePane {
1027 command: run_command_action,
1028 pane_name: name,
1029 near_current_pane: false,
1030 pane_id_to_replace: None,
1031 close_replaced_pane,
1032 ..
1033 } => {
1034 let mut node = KdlNode::new("Run");
1035 let mut node_children = KdlDocument::new();
1036 if let Some(run_command_action) = run_command_action {
1037 node.push(run_command_action.command.display().to_string());
1038 for arg in &run_command_action.args {
1039 node.push(arg.clone());
1040 }
1041 let mut in_place_node = KdlNode::new("in_place");
1042 in_place_node.push(KdlValue::Bool(true));
1043 node_children.nodes_mut().push(in_place_node);
1044 if let Some(cwd) = &run_command_action.cwd {
1045 let mut cwd_node = KdlNode::new("cwd");
1046 cwd_node.push(cwd.display().to_string());
1047 node_children.nodes_mut().push(cwd_node);
1048 }
1049 if run_command_action.hold_on_start {
1050 let mut hos_node = KdlNode::new("hold_on_start");
1051 hos_node.push(KdlValue::Bool(true));
1052 node_children.nodes_mut().push(hos_node);
1053 }
1054 if !run_command_action.hold_on_close {
1055 let mut hoc_node = KdlNode::new("hold_on_close");
1056 hoc_node.push(KdlValue::Bool(false));
1057 node_children.nodes_mut().push(hoc_node);
1058 }
1059 }
1060 if *close_replaced_pane {
1061 let mut crp_node = KdlNode::new("close_replaced_pane");
1062 crp_node.push(KdlValue::Bool(true));
1063 node_children.nodes_mut().push(crp_node);
1064 }
1065 if let Some(name) = name {
1066 let mut name_node = KdlNode::new("name");
1067 name_node.push(name.clone());
1068 node_children.nodes_mut().push(name_node);
1069 }
1070 if !node_children.nodes().is_empty() {
1071 node.set_children(node_children);
1072 }
1073 Some(node)
1074 },
1075 Action::NewStackedPane {
1076 command: run_command_action,
1077 pane_name: name,
1078 near_current_pane: _,
1079 ..
1080 } => match run_command_action {
1081 Some(run_command_action) => {
1082 let mut node = KdlNode::new("Run");
1083 let mut node_children = KdlDocument::new();
1084 node.push(run_command_action.command.display().to_string());
1085 for arg in &run_command_action.args {
1086 node.push(arg.clone());
1087 }
1088 let mut stacked_node = KdlNode::new("stacked");
1089 stacked_node.push(KdlValue::Bool(true));
1090 node_children.nodes_mut().push(stacked_node);
1091 if let Some(cwd) = &run_command_action.cwd {
1092 let mut cwd_node = KdlNode::new("cwd");
1093 cwd_node.push(cwd.display().to_string());
1094 node_children.nodes_mut().push(cwd_node);
1095 }
1096 if run_command_action.hold_on_start {
1097 let mut hos_node = KdlNode::new("hold_on_start");
1098 hos_node.push(KdlValue::Bool(true));
1099 node_children.nodes_mut().push(hos_node);
1100 }
1101 if !run_command_action.hold_on_close {
1102 let mut hoc_node = KdlNode::new("hold_on_close");
1103 hoc_node.push(KdlValue::Bool(false));
1104 node_children.nodes_mut().push(hoc_node);
1105 }
1106 if let Some(name) = name {
1107 let mut name_node = KdlNode::new("name");
1108 name_node.push(name.clone());
1109 node_children.nodes_mut().push(name_node);
1110 }
1111 if !node_children.nodes().is_empty() {
1112 node.set_children(node_children);
1113 }
1114 Some(node)
1115 },
1116 None => {
1117 let mut node = KdlNode::new("NewPane");
1118 node.push("stacked");
1119 Some(node)
1120 },
1121 },
1122 Action::Detach => Some(KdlNode::new("Detach")),
1123 Action::SwitchSession {
1124 name,
1125 tab_position,
1126 pane_id,
1127 layout,
1128 cwd,
1129 } => {
1130 let mut node = KdlNode::new("SwitchSession");
1131 node.push(KdlEntry::new_prop("name", name.clone()));
1132 if let Some(pos) = tab_position {
1133 node.push(KdlEntry::new_prop("tab_position", *pos as i64));
1134 }
1135 if let Some((id, is_plugin)) = pane_id {
1136 node.push(KdlEntry::new_prop("pane_id", *id as i64));
1137 if *is_plugin {
1138 node.push(KdlEntry::new_prop("is_plugin", true));
1139 }
1140 }
1141 if let Some(layout_info) = layout {
1142 node.push(KdlEntry::new_prop("layout", layout_info.name()));
1143 }
1144 if let Some(cwd_path) = cwd {
1145 node.push(KdlEntry::new_prop(
1146 "cwd",
1147 cwd_path.to_string_lossy().to_string(),
1148 ));
1149 }
1150 Some(node)
1151 },
1152 Action::LaunchOrFocusPlugin {
1153 plugin: run_plugin_or_alias,
1154 should_float,
1155 move_to_focused_tab,
1156 should_open_in_place,
1157 close_replaced_pane,
1158 skip_cache: skip_plugin_cache,
1159 ..
1160 } => {
1161 let mut node = KdlNode::new("LaunchOrFocusPlugin");
1162 let mut node_children = KdlDocument::new();
1163 let location = run_plugin_or_alias.location_string();
1164 node.push(location);
1165 if *should_float {
1166 let mut should_float_node = KdlNode::new("floating");
1167 should_float_node.push(KdlValue::Bool(true));
1168 node_children.nodes_mut().push(should_float_node);
1169 }
1170 if *move_to_focused_tab {
1171 let mut move_to_focused_tab_node = KdlNode::new("move_to_focused_tab");
1172 move_to_focused_tab_node.push(KdlValue::Bool(true));
1173 node_children.nodes_mut().push(move_to_focused_tab_node);
1174 }
1175 if *should_open_in_place {
1176 let mut should_open_in_place_node = KdlNode::new("in_place");
1177 should_open_in_place_node.push(KdlValue::Bool(true));
1178 node_children.nodes_mut().push(should_open_in_place_node);
1179 }
1180 if *close_replaced_pane {
1181 let mut crp_node = KdlNode::new("close_replaced_pane");
1182 crp_node.push(KdlValue::Bool(true));
1183 node_children.nodes_mut().push(crp_node);
1184 }
1185 if *skip_plugin_cache {
1186 let mut skip_plugin_cache_node = KdlNode::new("skip_plugin_cache");
1187 skip_plugin_cache_node.push(KdlValue::Bool(true));
1188 node_children.nodes_mut().push(skip_plugin_cache_node);
1189 }
1190 if let Some(configuration) = run_plugin_or_alias.get_configuration() {
1191 for (config_key, config_value) in configuration.inner().iter() {
1192 let mut node = KdlNode::new(config_key.clone());
1193 node.push(config_value.clone());
1194 node_children.nodes_mut().push(node);
1195 }
1196 }
1197 if !node_children.nodes().is_empty() {
1198 node.set_children(node_children);
1199 }
1200 Some(node)
1201 },
1202 Action::LaunchPlugin {
1203 plugin: run_plugin_or_alias,
1204 should_float,
1205 should_open_in_place,
1206 close_replaced_pane,
1207 skip_cache: skip_plugin_cache,
1208 cwd,
1209 ..
1210 } => {
1211 let mut node = KdlNode::new("LaunchPlugin");
1212 let mut node_children = KdlDocument::new();
1213 let location = run_plugin_or_alias.location_string();
1214 node.push(location);
1215 if *should_float {
1216 let mut should_float_node = KdlNode::new("floating");
1217 should_float_node.push(KdlValue::Bool(true));
1218 node_children.nodes_mut().push(should_float_node);
1219 }
1220 if *should_open_in_place {
1221 let mut should_open_in_place_node = KdlNode::new("in_place");
1222 should_open_in_place_node.push(KdlValue::Bool(true));
1223 node_children.nodes_mut().push(should_open_in_place_node);
1224 }
1225 if *close_replaced_pane {
1226 let mut crp_node = KdlNode::new("close_replaced_pane");
1227 crp_node.push(KdlValue::Bool(true));
1228 node_children.nodes_mut().push(crp_node);
1229 }
1230 if *skip_plugin_cache {
1231 let mut skip_plugin_cache_node = KdlNode::new("skip_plugin_cache");
1232 skip_plugin_cache_node.push(KdlValue::Bool(true));
1233 node_children.nodes_mut().push(skip_plugin_cache_node);
1234 }
1235 if let Some(cwd) = &cwd {
1236 let mut cwd_node = KdlNode::new("cwd");
1237 cwd_node.push(cwd.display().to_string());
1238 node_children.nodes_mut().push(cwd_node);
1239 } else if let Some(cwd) = run_plugin_or_alias.get_initial_cwd() {
1240 let mut cwd_node = KdlNode::new("cwd");
1241 cwd_node.push(cwd.display().to_string());
1242 node_children.nodes_mut().push(cwd_node);
1243 }
1244 if let Some(configuration) = run_plugin_or_alias.get_configuration() {
1245 for (config_key, config_value) in configuration.inner().iter() {
1246 let mut node = KdlNode::new(config_key.clone());
1247 node.push(config_value.clone());
1248 node_children.nodes_mut().push(node);
1249 }
1250 }
1251 if !node_children.nodes().is_empty() {
1252 node.set_children(node_children);
1253 }
1254 Some(node)
1255 },
1256 Action::Copy => Some(KdlNode::new("Copy")),
1257 Action::SearchInput { input: bytes } => {
1258 let mut node = KdlNode::new("SearchInput");
1259 for byte in bytes {
1260 node.push(KdlValue::Base10(*byte as i64));
1261 }
1262 Some(node)
1263 },
1264 Action::Search {
1265 direction: search_direction,
1266 } => {
1267 let mut node = KdlNode::new("Search");
1268 let direction = match search_direction {
1269 SearchDirection::Down => "down",
1270 SearchDirection::Up => "up",
1271 };
1272 node.push(direction);
1273 Some(node)
1274 },
1275 Action::SearchToggleOption {
1276 option: search_toggle_option,
1277 } => {
1278 let mut node = KdlNode::new("SearchToggleOption");
1279 node.push(format!("{:?}", search_toggle_option));
1280 Some(node)
1281 },
1282 Action::ToggleMouseMode => Some(KdlNode::new("ToggleMouseMode")),
1283 Action::PreviousSwapLayout => Some(KdlNode::new("PreviousSwapLayout")),
1284 Action::NextSwapLayout => Some(KdlNode::new("NextSwapLayout")),
1285 Action::BreakPane => Some(KdlNode::new("BreakPane")),
1286 Action::BreakPaneRight => Some(KdlNode::new("BreakPaneRight")),
1287 Action::BreakPaneLeft => Some(KdlNode::new("BreakPaneLeft")),
1288 Action::KeybindPipe {
1289 name,
1290 payload,
1291 args: _, plugin,
1293 configuration,
1294 launch_new,
1295 skip_cache,
1296 floating,
1297 in_place: _, cwd,
1299 pane_title,
1300 plugin_id,
1301 } => {
1302 if plugin_id.is_some() {
1303 log::warn!("Not serializing temporary keybinding MessagePluginId");
1304 return None;
1305 }
1306 let mut node = KdlNode::new("MessagePlugin");
1307 let mut node_children = KdlDocument::new();
1308 if let Some(plugin) = plugin {
1309 node.push(plugin.clone());
1310 }
1311 if let Some(name) = name {
1312 let mut name_node = KdlNode::new("name");
1313 name_node.push(name.clone());
1314 node_children.nodes_mut().push(name_node);
1315 }
1316 if let Some(cwd) = cwd {
1317 let mut cwd_node = KdlNode::new("cwd");
1318 cwd_node.push(cwd.display().to_string());
1319 node_children.nodes_mut().push(cwd_node);
1320 }
1321 if let Some(payload) = payload {
1322 let mut payload_node = KdlNode::new("payload");
1323 payload_node.push(payload.clone());
1324 node_children.nodes_mut().push(payload_node);
1325 }
1326 if *launch_new {
1327 let mut launch_new_node = KdlNode::new("launch_new");
1328 launch_new_node.push(KdlValue::Bool(true));
1329 node_children.nodes_mut().push(launch_new_node);
1330 }
1331 if *skip_cache {
1332 let mut skip_cache_node = KdlNode::new("skip_cache");
1333 skip_cache_node.push(KdlValue::Bool(true));
1334 node_children.nodes_mut().push(skip_cache_node);
1335 }
1336 if let Some(floating) = floating {
1337 let mut floating_node = KdlNode::new("floating");
1338 floating_node.push(KdlValue::Bool(*floating));
1339 node_children.nodes_mut().push(floating_node);
1340 }
1341 if let Some(title) = pane_title {
1342 let mut title_node = KdlNode::new("title");
1343 title_node.push(title.clone());
1344 node_children.nodes_mut().push(title_node);
1345 }
1346 if let Some(configuration) = configuration {
1347 let configuration = PluginUserConfiguration::new(configuration.clone());
1350 let configuration = configuration.inner();
1351 for (config_key, config_value) in configuration.iter() {
1352 let mut node = KdlNode::new(config_key.clone());
1353 node.push(config_value.clone());
1354 node_children.nodes_mut().push(node);
1355 }
1356 }
1357 if !node_children.nodes().is_empty() {
1358 node.set_children(node_children);
1359 }
1360 Some(node)
1361 },
1362 Action::TogglePanePinned => Some(KdlNode::new("TogglePanePinned")),
1363 Action::TogglePaneInGroup => Some(KdlNode::new("TogglePaneInGroup")),
1364 Action::ToggleGroupMarking => Some(KdlNode::new("ToggleGroupMarking")),
1365 Action::SetDarkTheme => Some(KdlNode::new("SetDarkTheme")),
1366 Action::SetLightTheme => Some(KdlNode::new("SetLightTheme")),
1367 Action::ToggleTheme => Some(KdlNode::new("ToggleTheme")),
1368 Action::FocusHostSession => Some(KdlNode::new("FocusHostSession")),
1369 Action::FocusGuestSession => Some(KdlNode::new("FocusGuestSession")),
1370 Action::ToggleHostFullscreen => Some(KdlNode::new("ToggleHostFullscreen")),
1371 _ => None,
1372 }
1373 }
1374}
1375
1376impl TryFrom<(&str, &KdlDocument)> for PaletteColor {
1377 type Error = ConfigError;
1378
1379 fn try_from(
1380 (color_name, theme_colors): (&str, &KdlDocument),
1381 ) -> Result<PaletteColor, Self::Error> {
1382 let color = theme_colors
1383 .get(color_name)
1384 .ok_or(ConfigError::new_kdl_error(
1385 format!("Missing theme color: {}", color_name),
1386 theme_colors.span().offset(),
1387 theme_colors.span().len(),
1388 ))?;
1389 let entry_count = entry_count!(color);
1390 let is_rgb = || entry_count == 3;
1391 let is_three_digit_hex = || {
1392 match kdl_first_entry_as_string!(color) {
1393 Some(s) => entry_count == 1 && s.starts_with('#') && s.len() == 4,
1395 None => false,
1396 }
1397 };
1398 let is_six_digit_hex = || {
1399 match kdl_first_entry_as_string!(color) {
1400 Some(s) => entry_count == 1 && s.starts_with('#') && s.len() == 7,
1402 None => false,
1403 }
1404 };
1405 let is_eight_bit = || kdl_first_entry_as_i64!(color).is_some() && entry_count == 1;
1406 if is_rgb() {
1407 let mut channels = kdl_entries_as_i64!(color);
1408 let r = channels.next().unwrap().ok_or(ConfigError::new_kdl_error(
1409 format!("invalid rgb color"),
1410 color.span().offset(),
1411 color.span().len(),
1412 ))? as u8;
1413 let g = channels.next().unwrap().ok_or(ConfigError::new_kdl_error(
1414 format!("invalid rgb color"),
1415 color.span().offset(),
1416 color.span().len(),
1417 ))? as u8;
1418 let b = channels.next().unwrap().ok_or(ConfigError::new_kdl_error(
1419 format!("invalid rgb color"),
1420 color.span().offset(),
1421 color.span().len(),
1422 ))? as u8;
1423 Ok(PaletteColor::Rgb((r, g, b)))
1424 } else if is_three_digit_hex() {
1425 let mut s = String::from(kdl_first_entry_as_string!(color).unwrap());
1427 s.remove(0);
1428 let r = u8::from_str_radix(&s[0..1], 16).map_err(|_| {
1429 ConfigError::new_kdl_error(
1430 "Failed to parse hex color".into(),
1431 color.span().offset(),
1432 color.span().len(),
1433 )
1434 })? * 0x11;
1435 let g = u8::from_str_radix(&s[1..2], 16).map_err(|_| {
1436 ConfigError::new_kdl_error(
1437 "Failed to parse hex color".into(),
1438 color.span().offset(),
1439 color.span().len(),
1440 )
1441 })? * 0x11;
1442 let b = u8::from_str_radix(&s[2..3], 16).map_err(|_| {
1443 ConfigError::new_kdl_error(
1444 "Failed to parse hex color".into(),
1445 color.span().offset(),
1446 color.span().len(),
1447 )
1448 })? * 0x11;
1449 Ok(PaletteColor::Rgb((r, g, b)))
1450 } else if is_six_digit_hex() {
1451 let mut s = String::from(kdl_first_entry_as_string!(color).unwrap());
1453 s.remove(0);
1454 let r = u8::from_str_radix(&s[0..2], 16).map_err(|_| {
1455 ConfigError::new_kdl_error(
1456 "Failed to parse hex color".into(),
1457 color.span().offset(),
1458 color.span().len(),
1459 )
1460 })?;
1461 let g = u8::from_str_radix(&s[2..4], 16).map_err(|_| {
1462 ConfigError::new_kdl_error(
1463 "Failed to parse hex color".into(),
1464 color.span().offset(),
1465 color.span().len(),
1466 )
1467 })?;
1468 let b = u8::from_str_radix(&s[4..6], 16).map_err(|_| {
1469 ConfigError::new_kdl_error(
1470 "Failed to parse hex color".into(),
1471 color.span().offset(),
1472 color.span().len(),
1473 )
1474 })?;
1475 Ok(PaletteColor::Rgb((r, g, b)))
1476 } else if is_eight_bit() {
1477 let n = kdl_first_entry_as_i64!(color).ok_or(ConfigError::new_kdl_error(
1478 "Failed to parse color".into(),
1479 color.span().offset(),
1480 color.span().len(),
1481 ))?;
1482 Ok(PaletteColor::EightBit(n as u8))
1483 } else {
1484 Err(ConfigError::new_kdl_error(
1485 "Failed to parse color".into(),
1486 color.span().offset(),
1487 color.span().len(),
1488 ))
1489 }
1490 }
1491}
1492
1493impl PaletteColor {
1494 pub fn to_kdl(&self, color_name: &str) -> KdlNode {
1495 let mut node = KdlNode::new(color_name);
1496 match self {
1497 PaletteColor::Rgb((r, g, b)) => {
1498 node.push(KdlValue::Base10(*r as i64));
1499 node.push(KdlValue::Base10(*g as i64));
1500 node.push(KdlValue::Base10(*b as i64));
1501 },
1502 PaletteColor::EightBit(color_index) => {
1503 node.push(KdlValue::Base10(*color_index as i64));
1504 },
1505 }
1506 node
1507 }
1508}
1509
1510impl StyleDeclaration {
1511 pub fn to_kdl(&self, declaration_name: &str) -> KdlNode {
1512 let mut node = KdlNode::new(declaration_name);
1513 let mut doc = KdlDocument::new();
1514
1515 doc.nodes_mut().push(self.base.to_kdl("base"));
1516 doc.nodes_mut().push(self.background.to_kdl("background"));
1517 doc.nodes_mut().push(self.emphasis_0.to_kdl("emphasis_0"));
1518 doc.nodes_mut().push(self.emphasis_1.to_kdl("emphasis_1"));
1519 doc.nodes_mut().push(self.emphasis_2.to_kdl("emphasis_2"));
1520 doc.nodes_mut().push(self.emphasis_3.to_kdl("emphasis_3"));
1521 node.set_children(doc);
1522 node
1523 }
1524}
1525
1526impl MultiplayerColors {
1527 pub fn to_kdl(&self) -> KdlNode {
1528 let mut node = KdlNode::new("multiplayer_user_colors");
1529 let mut doc = KdlDocument::new();
1530 doc.nodes_mut().push(self.player_1.to_kdl("player_1"));
1531 doc.nodes_mut().push(self.player_2.to_kdl("player_2"));
1532 doc.nodes_mut().push(self.player_3.to_kdl("player_3"));
1533 doc.nodes_mut().push(self.player_4.to_kdl("player_4"));
1534 doc.nodes_mut().push(self.player_5.to_kdl("player_5"));
1535 doc.nodes_mut().push(self.player_6.to_kdl("player_6"));
1536 doc.nodes_mut().push(self.player_7.to_kdl("player_7"));
1537 doc.nodes_mut().push(self.player_8.to_kdl("player_8"));
1538 doc.nodes_mut().push(self.player_9.to_kdl("player_9"));
1539 doc.nodes_mut().push(self.player_10.to_kdl("player_10"));
1540 node.set_children(doc);
1541 node
1542 }
1543}
1544
1545impl TryFrom<(&KdlNode, &Options)> for Action {
1546 type Error = ConfigError;
1547 fn try_from((kdl_action, config_options): (&KdlNode, &Options)) -> Result<Self, Self::Error> {
1548 let action_name = kdl_name!(kdl_action);
1549 let action_arguments: Vec<&KdlEntry> = kdl_argument_values!(kdl_action);
1550 let action_children: Vec<&KdlDocument> = kdl_children!(kdl_action);
1551 match action_name {
1552 "Quit" => parse_kdl_action_arguments!(action_name, action_arguments, kdl_action),
1553 "FocusNextPane" => {
1554 parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
1555 },
1556 "FocusPreviousPane" => {
1557 parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
1558 },
1559 "FocusLastPane" => {
1560 parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
1561 },
1562 "FocusHostSession" => {
1563 parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
1564 },
1565 "FocusGuestSession" => {
1566 parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
1567 },
1568 "ToggleHostFullscreen" => {
1569 parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
1570 },
1571 "SwitchFocus" => parse_kdl_action_arguments!(action_name, action_arguments, kdl_action),
1572 "EditScrollback" => {
1573 let ansi = crate::kdl_get_bool_property_or_child_value!(kdl_action, "ansi")
1574 .unwrap_or(false);
1575 Ok(Action::EditScrollback { ansi })
1576 },
1577 "ScrollUp" => parse_kdl_action_arguments!(action_name, action_arguments, kdl_action),
1578 "ScrollDown" => parse_kdl_action_arguments!(action_name, action_arguments, kdl_action),
1579 "ScrollToBottom" => {
1580 parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
1581 },
1582 "ScrollToTop" => {
1583 parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
1584 },
1585 "ScrollToPreviousPrompt" => {
1586 parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
1587 },
1588 "ScrollToNextPrompt" => {
1589 parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
1590 },
1591 "SelectCommandAtScrollPosition" => {
1592 parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
1593 },
1594 "CopyLastCommandOutput" => {
1595 parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
1596 },
1597 "PageScrollUp" => {
1598 parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
1599 },
1600 "PageScrollDown" => {
1601 parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
1602 },
1603 "HalfPageScrollUp" => {
1604 parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
1605 },
1606 "HalfPageScrollDown" => {
1607 parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
1608 },
1609 "ToggleFocusFullscreen" => {
1610 parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
1611 },
1612 "ToggleFocusNoUiFullscreen" => {
1613 parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
1614 },
1615 "TogglePaneFrames" => {
1616 parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
1617 },
1618 "ToggleActiveSyncTab" => {
1619 parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
1620 },
1621 "TogglePaneEmbedOrFloating" => {
1622 parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
1623 },
1624 "ToggleFloatingPanes" => {
1625 parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
1626 },
1627 "ShowFloatingPanes" => {
1628 let tab_id = action_arguments
1629 .first()
1630 .and_then(|v| v.value().as_i64())
1631 .map(|n| n as usize);
1632 Ok(Action::ShowFloatingPanes { tab_id })
1633 },
1634 "HideFloatingPanes" => {
1635 let tab_id = action_arguments
1636 .first()
1637 .and_then(|v| v.value().as_i64())
1638 .map(|n| n as usize);
1639 Ok(Action::HideFloatingPanes { tab_id })
1640 },
1641 "CloseFocus" => parse_kdl_action_arguments!(action_name, action_arguments, kdl_action),
1642 "UndoRenamePane" => {
1643 parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
1644 },
1645 "NoOp" => parse_kdl_action_arguments!(action_name, action_arguments, kdl_action),
1646 "GoToNextTab" => parse_kdl_action_arguments!(action_name, action_arguments, kdl_action),
1647 "GoToPreviousTab" => {
1648 parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
1649 },
1650 "CloseTab" => parse_kdl_action_arguments!(action_name, action_arguments, kdl_action),
1651 "ToggleTab" => parse_kdl_action_arguments!(action_name, action_arguments, kdl_action),
1652 "UndoRenameTab" => {
1653 parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
1654 },
1655 "ToggleMouseMode" => {
1656 parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
1657 },
1658 "Detach" => parse_kdl_action_arguments!(action_name, action_arguments, kdl_action),
1659 "SetDarkTheme" => {
1660 parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
1661 },
1662 "SetLightTheme" => {
1663 parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
1664 },
1665 "ToggleTheme" => {
1666 parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
1667 },
1668 "SwitchSession" => {
1669 let name = kdl_get_string_property_or_child_value!(kdl_action, "name")
1670 .map(|s| s.to_string())
1671 .ok_or(ConfigError::new_kdl_error(
1672 "SwitchSession action requires a 'name' property".into(),
1673 kdl_action.span().offset(),
1674 kdl_action.span().len(),
1675 ))?;
1676 let tab_position =
1677 crate::kdl_get_int_property_or_child_value!(kdl_action, "tab_position")
1678 .map(|i| i as usize);
1679 let pane_id = crate::kdl_get_int_property_or_child_value!(kdl_action, "pane_id")
1680 .map(|i| i as u32);
1681 let is_plugin =
1682 crate::kdl_get_bool_property_or_child_value!(kdl_action, "is_plugin")
1683 .unwrap_or(false);
1684 let pane_id_tuple = pane_id.map(|id| (id, is_plugin));
1685
1686 let layout = if let Some(layout_str) =
1688 kdl_get_string_property_or_child_value!(kdl_action, "layout")
1689 {
1690 let layout_path = PathBuf::from(layout_str);
1691 let layout_dir = config_options
1692 .layout_dir
1693 .clone()
1694 .or_else(|| get_layout_dir(find_default_config_dir()));
1695 LayoutInfo::from_config(&layout_dir, &Some(layout_path))
1696 } else {
1697 None
1698 };
1699
1700 let cwd =
1702 kdl_get_string_property_or_child_value!(kdl_action, "cwd").map(PathBuf::from);
1703
1704 Ok(Action::SwitchSession {
1705 name,
1706 tab_position,
1707 pane_id: pane_id_tuple,
1708 layout,
1709 cwd,
1710 })
1711 },
1712 "Copy" => parse_kdl_action_arguments!(action_name, action_arguments, kdl_action),
1713 "Clear" => parse_kdl_action_arguments!(action_name, action_arguments, kdl_action),
1714 "Confirm" => parse_kdl_action_arguments!(action_name, action_arguments, kdl_action),
1715 "Deny" => parse_kdl_action_arguments!(action_name, action_arguments, kdl_action),
1716 "Write" => parse_kdl_action_u8_arguments!(action_name, action_arguments, kdl_action),
1717 "WriteChars" => parse_kdl_action_char_or_string_arguments!(
1718 action_name,
1719 action_arguments,
1720 kdl_action
1721 ),
1722 "SwitchToMode" => parse_kdl_action_char_or_string_arguments!(
1723 action_name,
1724 action_arguments,
1725 kdl_action
1726 ),
1727 "SetPaneFrameStyle" => parse_kdl_action_char_or_string_arguments!(
1728 action_name,
1729 action_arguments,
1730 kdl_action
1731 ),
1732 "Search" => parse_kdl_action_char_or_string_arguments!(
1733 action_name,
1734 action_arguments,
1735 kdl_action
1736 ),
1737 "Resize" => parse_kdl_action_char_or_string_arguments!(
1738 action_name,
1739 action_arguments,
1740 kdl_action
1741 ),
1742 "ResizeNew" => parse_kdl_action_char_or_string_arguments!(
1743 action_name,
1744 action_arguments,
1745 kdl_action
1746 ),
1747 "MoveFocus" => parse_kdl_action_char_or_string_arguments!(
1748 action_name,
1749 action_arguments,
1750 kdl_action
1751 ),
1752 "MoveTab" => parse_kdl_action_char_or_string_arguments!(
1753 action_name,
1754 action_arguments,
1755 kdl_action
1756 ),
1757 "MoveFocusOrTab" => parse_kdl_action_char_or_string_arguments!(
1758 action_name,
1759 action_arguments,
1760 kdl_action
1761 ),
1762 "MovePane" => parse_kdl_action_char_or_string_arguments!(
1763 action_name,
1764 action_arguments,
1765 kdl_action
1766 ),
1767 "MovePaneBackwards" => parse_kdl_action_char_or_string_arguments!(
1768 action_name,
1769 action_arguments,
1770 kdl_action
1771 ),
1772 "DumpScreen" => parse_kdl_action_char_or_string_arguments!(
1773 action_name,
1774 action_arguments,
1775 kdl_action
1776 ),
1777 "DumpLayout" => parse_kdl_action_char_or_string_arguments!(
1778 action_name,
1779 action_arguments,
1780 kdl_action
1781 ),
1782 "NewPane" => parse_kdl_action_char_or_string_arguments!(
1783 action_name,
1784 action_arguments,
1785 kdl_action
1786 ),
1787 "PaneNameInput" => {
1788 parse_kdl_action_u8_arguments!(action_name, action_arguments, kdl_action)
1789 },
1790 "NewTab" => {
1791 let command_metadata = action_children.iter().next();
1792 if command_metadata.is_none() {
1793 return Ok(Action::NewTab {
1794 tiled_layout: None,
1795 floating_layouts: vec![],
1796 swap_tiled_layouts: None,
1797 swap_floating_layouts: None,
1798 tab_name: None,
1799 should_change_focus_to_new_tab: true,
1800 cwd: None,
1801 initial_panes: None,
1802 first_pane_unblock_condition: None,
1803 });
1804 }
1805
1806 let current_dir = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
1807
1808 let layout = command_metadata
1809 .and_then(|c_m| kdl_child_string_value_for_entry(c_m, "layout"))
1810 .map(|layout_string| PathBuf::from(layout_string))
1811 .or_else(|| config_options.default_layout.clone());
1812 let cwd = command_metadata
1813 .and_then(|c_m| kdl_child_string_value_for_entry(c_m, "cwd"))
1814 .map(|cwd_string| PathBuf::from(cwd_string))
1815 .map(|cwd| current_dir.join(cwd));
1816 let name = command_metadata
1817 .and_then(|c_m| kdl_child_string_value_for_entry(c_m, "name"))
1818 .map(|name_string| name_string.to_string());
1819
1820 let layout_dir = config_options
1821 .layout_dir
1822 .clone()
1823 .or_else(|| get_layout_dir(find_default_config_dir()));
1824 let (path_to_raw_layout, raw_layout, swap_layouts) =
1825 Layout::stringified_from_path_or_default(layout.as_ref(), layout_dir).map_err(
1826 |e| {
1827 ConfigError::new_kdl_error(
1828 format!("Failed to load layout: {}", e),
1829 kdl_action.span().offset(),
1830 kdl_action.span().len(),
1831 )
1832 },
1833 )?;
1834
1835 let layout = Layout::from_str(
1836 &raw_layout,
1837 path_to_raw_layout,
1838 swap_layouts.as_ref().map(|(f, p)| (f.as_str(), p.as_str())),
1839 cwd.clone(),
1840 )
1841 .map_err(|e| {
1842 ConfigError::new_kdl_error(
1843 format!("Failed to load layout: {}", e),
1844 kdl_action.span().offset(),
1845 kdl_action.span().len(),
1846 )
1847 })?;
1848
1849 let swap_tiled_layouts = Some(layout.swap_tiled_layouts.clone());
1850 let swap_floating_layouts = Some(layout.swap_floating_layouts.clone());
1851
1852 let mut tabs = layout.tabs();
1853 if tabs.len() > 1 {
1854 return Err(ConfigError::new_kdl_error(
1855 "Tab layout cannot itself have tabs".to_string(),
1856 kdl_action.span().offset(),
1857 kdl_action.span().len(),
1858 ));
1859 } else if !tabs.is_empty() {
1860 let (tab_name, layout, floating_panes_layout) = tabs.drain(..).next().unwrap();
1861 let name = tab_name.or(name);
1862 let should_change_focus_to_new_tab = layout.focus.unwrap_or(true);
1863
1864 Ok(Action::NewTab {
1865 tiled_layout: Some(layout),
1866 floating_layouts: floating_panes_layout,
1867 swap_tiled_layouts,
1868 swap_floating_layouts,
1869 tab_name: name,
1870 should_change_focus_to_new_tab,
1871 cwd,
1872 initial_panes: None,
1873 first_pane_unblock_condition: None,
1874 })
1875 } else {
1876 let (layout, floating_panes_layout) = layout.new_tab();
1877 let should_change_focus_to_new_tab = layout.focus.unwrap_or(true);
1878
1879 Ok(Action::NewTab {
1880 tiled_layout: Some(layout),
1881 floating_layouts: floating_panes_layout,
1882 swap_tiled_layouts,
1883 swap_floating_layouts,
1884 tab_name: name,
1885 should_change_focus_to_new_tab,
1886 cwd,
1887 initial_panes: None,
1888 first_pane_unblock_condition: None,
1889 })
1890 }
1891 },
1892 "OverrideLayout" => {
1893 let command_metadata = action_children.iter().next();
1894 if command_metadata.is_none() {
1895 return Ok(Action::OverrideLayout {
1896 tabs: vec![],
1897 retain_existing_terminal_panes: false,
1898 retain_existing_plugin_panes: false,
1899 apply_only_to_active_tab: false,
1900 });
1901 }
1902
1903 let current_dir = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
1904
1905 let layout = command_metadata
1906 .and_then(|c_m| kdl_child_string_value_for_entry(c_m, "layout"))
1907 .map(|layout_string| PathBuf::from(layout_string))
1908 .or_else(|| config_options.default_layout.clone());
1909 let cwd = command_metadata
1910 .and_then(|c_m| kdl_child_string_value_for_entry(c_m, "cwd"))
1911 .map(|cwd_string| PathBuf::from(cwd_string))
1912 .map(|cwd| current_dir.join(cwd));
1913 let name = command_metadata
1914 .and_then(|c_m| kdl_child_string_value_for_entry(c_m, "name"))
1915 .map(|name_string| name_string.to_string());
1916 let retain_existing_terminal_panes = command_metadata
1917 .and_then(|c_m| {
1918 kdl_child_bool_value_for_entry(c_m, "retain_existing_terminal_panes")
1919 })
1920 .unwrap_or(false);
1921 let retain_existing_plugin_panes = command_metadata
1922 .and_then(|c_m| {
1923 kdl_child_bool_value_for_entry(c_m, "retain_existing_plugin_panes")
1924 })
1925 .unwrap_or(false);
1926 let apply_only_to_active_tab = command_metadata
1927 .and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "apply_only_to_active_tab"))
1928 .unwrap_or(false);
1929
1930 let layout_dir = config_options
1931 .layout_dir
1932 .clone()
1933 .or_else(|| get_layout_dir(find_default_config_dir()));
1934 let (path_to_raw_layout, raw_layout, swap_layouts) =
1935 Layout::stringified_from_path_or_default(layout.as_ref(), layout_dir).map_err(
1936 |e| {
1937 ConfigError::new_kdl_error(
1938 format!("Failed to load layout: {}", e),
1939 kdl_action.span().offset(),
1940 kdl_action.span().len(),
1941 )
1942 },
1943 )?;
1944
1945 let layout = Layout::from_str(
1946 &raw_layout,
1947 path_to_raw_layout,
1948 swap_layouts.as_ref().map(|(f, p)| (f.as_str(), p.as_str())),
1949 cwd.clone(),
1950 )
1951 .map_err(|e| {
1952 ConfigError::new_kdl_error(
1953 format!("Failed to load layout: {}", e),
1954 kdl_action.span().offset(),
1955 kdl_action.span().len(),
1956 )
1957 })?;
1958
1959 let swap_tiled_layouts = Some(layout.swap_tiled_layouts.clone());
1960 let swap_floating_layouts = Some(layout.swap_floating_layouts.clone());
1961
1962 let mut tabs = layout.tabs();
1963 if tabs.len() > 1 {
1964 return Err(ConfigError::new_kdl_error(
1965 "Tab layout cannot itself have tabs".to_string(),
1966 kdl_action.span().offset(),
1967 kdl_action.span().len(),
1968 ));
1969 } else if !tabs.is_empty() {
1970 let (tab_name, layout, floating_panes_layout) = tabs.drain(..).next().unwrap();
1971 let name = tab_name.or(name);
1972
1973 let tab_layout_info = TabLayoutInfo {
1974 tab_index: 0,
1975 tab_name: name,
1976 tiled_layout: layout,
1977 floating_layouts: floating_panes_layout,
1978 swap_tiled_layouts,
1979 swap_floating_layouts,
1980 };
1981
1982 Ok(Action::OverrideLayout {
1983 tabs: vec![tab_layout_info],
1984 retain_existing_terminal_panes,
1985 retain_existing_plugin_panes,
1986 apply_only_to_active_tab,
1987 })
1988 } else {
1989 let (layout, floating_panes_layout) = layout.new_tab();
1990
1991 let tab_layout_info = TabLayoutInfo {
1992 tab_index: 0,
1993 tab_name: name,
1994 tiled_layout: layout,
1995 floating_layouts: floating_panes_layout,
1996 swap_tiled_layouts,
1997 swap_floating_layouts,
1998 };
1999
2000 Ok(Action::OverrideLayout {
2001 tabs: vec![tab_layout_info],
2002 retain_existing_terminal_panes,
2003 retain_existing_plugin_panes,
2004 apply_only_to_active_tab,
2005 })
2006 }
2007 },
2008 "GoToTab" => parse_kdl_action_u8_arguments!(action_name, action_arguments, kdl_action),
2009 "TabNameInput" => {
2010 parse_kdl_action_u8_arguments!(action_name, action_arguments, kdl_action)
2011 },
2012 "SearchInput" => {
2013 parse_kdl_action_u8_arguments!(action_name, action_arguments, kdl_action)
2014 },
2015 "SearchToggleOption" => parse_kdl_action_char_or_string_arguments!(
2016 action_name,
2017 action_arguments,
2018 kdl_action
2019 ),
2020 "Run" => {
2021 let arguments = action_arguments.iter().copied();
2022 let mut args = kdl_arguments_that_are_strings(arguments)?;
2023 if args.is_empty() {
2024 return Err(ConfigError::new_kdl_error(
2025 "No command found in Run action".into(),
2026 kdl_action.span().offset(),
2027 kdl_action.span().len(),
2028 ));
2029 }
2030 let command = args.remove(0);
2031 let command_metadata = action_children.iter().next();
2032 let cwd = command_metadata
2033 .and_then(|c_m| kdl_child_string_value_for_entry(c_m, "cwd"))
2034 .map(|cwd_string| PathBuf::from(cwd_string));
2035 let name = command_metadata
2036 .and_then(|c_m| kdl_child_string_value_for_entry(c_m, "name"))
2037 .map(|name_string| name_string.to_string());
2038 let direction = command_metadata
2039 .and_then(|c_m| kdl_child_string_value_for_entry(c_m, "direction"))
2040 .and_then(|direction_string| Direction::from_str(direction_string).ok());
2041 let hold_on_close = command_metadata
2042 .and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "close_on_exit"))
2043 .and_then(|close_on_exit| Some(!close_on_exit))
2044 .unwrap_or(true);
2045 let hold_on_start = command_metadata
2046 .and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "start_suspended"))
2047 .unwrap_or(false);
2048 let floating = command_metadata
2049 .and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "floating"))
2050 .unwrap_or(false);
2051 let in_place = command_metadata
2052 .and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "in_place"))
2053 .unwrap_or(false);
2054 let close_replaced_pane = command_metadata
2055 .and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "close_replaced_pane"))
2056 .unwrap_or(false);
2057 let stacked = command_metadata
2058 .and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "stacked"))
2059 .unwrap_or(false);
2060 let run_command_action = RunCommandAction {
2061 command: PathBuf::from(command),
2062 args,
2063 cwd,
2064 direction,
2065 hold_on_close,
2066 hold_on_start,
2067 ..Default::default()
2068 };
2069 let x = command_metadata
2070 .and_then(|c_m| kdl_child_string_value_for_entry(c_m, "x"))
2071 .map(|s| s.to_owned());
2072 let y = command_metadata
2073 .and_then(|c_m| kdl_child_string_value_for_entry(c_m, "y"))
2074 .map(|s| s.to_owned());
2075 let width = command_metadata
2076 .and_then(|c_m| kdl_child_string_value_for_entry(c_m, "width"))
2077 .map(|s| s.to_owned());
2078 let height = command_metadata
2079 .and_then(|c_m| kdl_child_string_value_for_entry(c_m, "height"))
2080 .map(|s| s.to_owned());
2081 let pinned =
2082 command_metadata.and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "pinned"));
2083 let borderless = command_metadata
2084 .and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "borderless"));
2085 if floating {
2086 Ok(Action::NewFloatingPane {
2087 command: Some(run_command_action),
2088 pane_name: name,
2089 coordinates: FloatingPaneCoordinates::new(
2090 x, y, width, height, pinned, borderless,
2091 ),
2092 near_current_pane: false,
2093 no_focus: false,
2094 tab_id: None,
2095 })
2096 } else if in_place {
2097 Ok(Action::NewInPlacePane {
2098 command: Some(run_command_action),
2099 pane_name: name,
2100 near_current_pane: false,
2101 no_focus: false,
2102 pane_id_to_replace: None,
2103 close_replaced_pane,
2104 tab_id: None,
2105 })
2106 } else if stacked {
2107 Ok(Action::NewStackedPane {
2108 command: Some(run_command_action),
2109 pane_name: name,
2110 near_current_pane: false,
2111 no_focus: false,
2112 tab_id: None,
2113 })
2114 } else {
2115 Ok(Action::NewTiledPane {
2116 direction,
2117 command: Some(run_command_action),
2118 pane_name: name,
2119 near_current_pane: false,
2120 no_focus: false,
2121 borderless: None,
2122 tab_id: None,
2123 })
2124 }
2125 },
2126 "LaunchOrFocusPlugin" => {
2127 let arguments = action_arguments.iter().copied();
2128 let mut args = kdl_arguments_that_are_strings(arguments)?;
2129 if args.is_empty() {
2130 return Err(ConfigError::new_kdl_error(
2131 "No plugin found to launch in LaunchOrFocusPlugin".into(),
2132 kdl_action.span().offset(),
2133 kdl_action.span().len(),
2134 ));
2135 }
2136 let plugin_path = args.remove(0);
2137
2138 let command_metadata = action_children.iter().next();
2139 let should_float = command_metadata
2140 .and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "floating"))
2141 .unwrap_or(false);
2142 let move_to_focused_tab = command_metadata
2143 .and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "move_to_focused_tab"))
2144 .unwrap_or(false);
2145 let should_open_in_place = command_metadata
2146 .and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "in_place"))
2147 .unwrap_or(false);
2148 let close_replaced_pane = command_metadata
2149 .and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "close_replaced_pane"))
2150 .unwrap_or(false);
2151 let skip_plugin_cache = command_metadata
2152 .and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "skip_plugin_cache"))
2153 .unwrap_or(false);
2154 let current_dir = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
2155 let configuration = KdlLayoutParser::parse_plugin_user_configuration(&kdl_action)?;
2156 let initial_cwd = kdl_get_string_property_or_child_value!(kdl_action, "cwd")
2157 .map(|s| PathBuf::from(s));
2158 let run_plugin_or_alias = RunPluginOrAlias::from_url(
2159 &plugin_path,
2160 &Some(configuration.inner().clone()),
2161 None,
2162 Some(current_dir),
2163 )
2164 .map_err(|e| {
2165 ConfigError::new_kdl_error(
2166 format!("Failed to parse plugin: {}", e),
2167 kdl_action.span().offset(),
2168 kdl_action.span().len(),
2169 )
2170 })?
2171 .with_initial_cwd(initial_cwd);
2172 Ok(Action::LaunchOrFocusPlugin {
2173 plugin: run_plugin_or_alias,
2174 should_float,
2175 move_to_focused_tab,
2176 should_open_in_place,
2177 close_replaced_pane,
2178 skip_cache: skip_plugin_cache,
2179 tab_id: None,
2180 })
2181 },
2182 "LaunchPlugin" => {
2183 let arguments = action_arguments.iter().copied();
2184 let mut args = kdl_arguments_that_are_strings(arguments)?;
2185 if args.is_empty() {
2186 return Err(ConfigError::new_kdl_error(
2187 "No plugin found to launch in LaunchPlugin".into(),
2188 kdl_action.span().offset(),
2189 kdl_action.span().len(),
2190 ));
2191 }
2192 let plugin_path = args.remove(0);
2193
2194 let command_metadata = action_children.iter().next();
2195 let should_float = command_metadata
2196 .and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "floating"))
2197 .unwrap_or(false);
2198 let should_open_in_place = command_metadata
2199 .and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "in_place"))
2200 .unwrap_or(false);
2201 let close_replaced_pane = command_metadata
2202 .and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "close_replaced_pane"))
2203 .unwrap_or(false);
2204 let skip_plugin_cache = command_metadata
2205 .and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "skip_plugin_cache"))
2206 .unwrap_or(false);
2207 let current_dir = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
2208 let configuration = KdlLayoutParser::parse_plugin_user_configuration(&kdl_action)?;
2209 let run_plugin_or_alias = RunPluginOrAlias::from_url(
2210 &plugin_path,
2211 &Some(configuration.inner().clone()),
2212 None,
2213 Some(current_dir),
2214 )
2215 .map_err(|e| {
2216 ConfigError::new_kdl_error(
2217 format!("Failed to parse plugin: {}", e),
2218 kdl_action.span().offset(),
2219 kdl_action.span().len(),
2220 )
2221 })?;
2222 Ok(Action::LaunchPlugin {
2223 plugin: run_plugin_or_alias,
2224 should_float,
2225 should_open_in_place,
2226 close_replaced_pane,
2227 skip_cache: skip_plugin_cache,
2228 cwd: None, no_focus: false,
2231 tab_id: None,
2232 })
2233 },
2234 "PreviousSwapLayout" => Ok(Action::PreviousSwapLayout),
2235 "NextSwapLayout" => Ok(Action::NextSwapLayout),
2236 "BreakPane" => Ok(Action::BreakPane),
2237 "BreakPaneRight" => Ok(Action::BreakPaneRight),
2238 "BreakPaneLeft" => Ok(Action::BreakPaneLeft),
2239 "RenameSession" => parse_kdl_action_char_or_string_arguments!(
2240 action_name,
2241 action_arguments,
2242 kdl_action
2243 ),
2244 "MessagePlugin" => {
2245 let arguments = action_arguments.iter().copied();
2246 let mut args = kdl_arguments_that_are_strings(arguments)?;
2247 let plugin_path = if args.is_empty() {
2248 None
2249 } else {
2250 Some(args.remove(0))
2251 };
2252
2253 let command_metadata = action_children.iter().next();
2254 let launch_new = command_metadata
2255 .and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "launch_new"))
2256 .unwrap_or(false);
2257 let skip_cache = command_metadata
2258 .and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "skip_cache"))
2259 .unwrap_or(false);
2260 let should_float = command_metadata
2261 .and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "floating"))
2262 .unwrap_or(false);
2263 let name = command_metadata
2264 .and_then(|c_m| kdl_child_string_value_for_entry(c_m, "name"))
2265 .map(|n| n.to_owned());
2266 let payload = command_metadata
2267 .and_then(|c_m| kdl_child_string_value_for_entry(c_m, "payload"))
2268 .map(|p| p.to_owned());
2269 let title = command_metadata
2270 .and_then(|c_m| kdl_child_string_value_for_entry(c_m, "title"))
2271 .map(|t| t.to_owned());
2272 let configuration = KdlLayoutParser::parse_plugin_user_configuration(&kdl_action)?;
2273 let configuration = if configuration.inner().is_empty() {
2274 None
2275 } else {
2276 Some(configuration.inner().clone())
2277 };
2278 let cwd = kdl_get_string_property_or_child_value!(kdl_action, "cwd")
2279 .map(|s| PathBuf::from(s));
2280
2281 let name = name
2282 .or_else(|| plugin_path.clone())
2285 .or_else(|| Some(Uuid::new_v4().to_string()));
2287
2288 Ok(Action::KeybindPipe {
2289 name,
2290 payload,
2291 args: None, plugin: plugin_path,
2293 configuration,
2294 launch_new,
2295 skip_cache,
2296 floating: Some(should_float),
2297 in_place: None, cwd,
2299 pane_title: title,
2300 plugin_id: None,
2301 })
2302 },
2303 "MessagePluginId" => {
2304 let arguments = action_arguments.iter().copied();
2305 let mut args = kdl_arguments_that_are_digits(arguments)?;
2306 let plugin_id = if args.is_empty() {
2307 None
2308 } else {
2309 Some(args.remove(0) as u32)
2310 };
2311
2312 let command_metadata = action_children.iter().next();
2313 let launch_new = false;
2314 let skip_cache = false;
2315 let name = command_metadata
2316 .and_then(|c_m| kdl_child_string_value_for_entry(c_m, "name"))
2317 .map(|n| n.to_owned());
2318 let payload = command_metadata
2319 .and_then(|c_m| kdl_child_string_value_for_entry(c_m, "payload"))
2320 .map(|p| p.to_owned());
2321 let configuration = None;
2322
2323 let name = name
2324 .or_else(|| Some(Uuid::new_v4().to_string()));
2326
2327 Ok(Action::KeybindPipe {
2328 name,
2329 payload,
2330 args: None, plugin: None,
2332 configuration,
2333 launch_new,
2334 skip_cache,
2335 floating: None,
2336 in_place: None, cwd: None,
2338 pane_title: None,
2339 plugin_id,
2340 })
2341 },
2342 "TogglePanePinned" => Ok(Action::TogglePanePinned),
2343 "TogglePaneInGroup" => Ok(Action::TogglePaneInGroup),
2344 "ToggleGroupMarking" => Ok(Action::ToggleGroupMarking),
2345 _ => Err(ConfigError::new_kdl_error(
2346 format!("Unsupported action: {}", action_name).into(),
2347 kdl_action.span().offset(),
2348 kdl_action.span().len(),
2349 )),
2350 }
2351 }
2352}
2353
2354#[macro_export]
2355macro_rules! kdl_property_first_arg_as_string {
2356 ( $kdl_node:expr, $property_name:expr ) => {
2357 $kdl_node
2358 .get($property_name)
2359 .and_then(|p| p.entries().iter().next())
2360 .and_then(|p| p.value().as_string())
2361 };
2362}
2363
2364#[macro_export]
2365macro_rules! kdl_property_first_arg_as_string_or_error {
2366 ( $kdl_node:expr, $property_name:expr ) => {{
2367 match $kdl_node.get($property_name) {
2368 Some(property) => match property.entries().iter().next() {
2369 Some(first_entry) => match first_entry.value().as_string() {
2370 Some(string_entry) => Some((string_entry, first_entry)),
2371 None => {
2372 return Err(ConfigError::new_kdl_error(
2373 format!(
2374 "Property {} must be a string, found: {}",
2375 $property_name,
2376 first_entry.value()
2377 ),
2378 property.span().offset(),
2379 property.span().len(),
2380 ));
2381 },
2382 },
2383 None => {
2384 return Err(ConfigError::new_kdl_error(
2385 format!("Property {} must have a value", $property_name),
2386 property.span().offset(),
2387 property.span().len(),
2388 ));
2389 },
2390 },
2391 None => None,
2392 }
2393 }};
2394}
2395
2396#[macro_export]
2397macro_rules! kdl_property_first_arg_as_bool_or_error {
2398 ( $kdl_node:expr, $property_name:expr ) => {{
2399 match $kdl_node.get($property_name) {
2400 Some(property) => match property.entries().iter().next() {
2401 Some(first_entry) => match first_entry.value().as_bool() {
2402 Some(bool_entry) => Some((bool_entry, first_entry)),
2403 None => {
2404 return Err(ConfigError::new_kdl_error(
2405 format!(
2406 "Property {} must be true or false, found {}",
2407 $property_name,
2408 first_entry.value()
2409 ),
2410 property.span().offset(),
2411 property.span().len(),
2412 ));
2413 },
2414 },
2415 None => {
2416 return Err(ConfigError::new_kdl_error(
2417 format!("Property {} must have a value", $property_name),
2418 property.span().offset(),
2419 property.span().len(),
2420 ));
2421 },
2422 },
2423 None => None,
2424 }
2425 }};
2426}
2427
2428#[macro_export]
2429macro_rules! kdl_property_first_arg_as_i64_or_error {
2430 ( $kdl_node:expr, $property_name:expr ) => {{
2431 match $kdl_node.get($property_name) {
2432 Some(property) => match property.entries().iter().next() {
2433 Some(first_entry) => match first_entry.value().as_i64() {
2434 Some(int_entry) => Some((int_entry, first_entry)),
2435 None => {
2436 return Err(ConfigError::new_kdl_error(
2437 format!(
2438 "Property {} must be numeric, found {}",
2439 $property_name,
2440 first_entry.value()
2441 ),
2442 property.span().offset(),
2443 property.span().len(),
2444 ));
2445 },
2446 },
2447 None => {
2448 return Err(ConfigError::new_kdl_error(
2449 format!("Property {} must have a value", $property_name),
2450 property.span().offset(),
2451 property.span().len(),
2452 ));
2453 },
2454 },
2455 None => None,
2456 }
2457 }};
2458}
2459
2460#[macro_export]
2461macro_rules! kdl_has_string_argument {
2462 ( $kdl_node:expr, $string_argument:expr ) => {
2463 $kdl_node
2464 .entries()
2465 .iter()
2466 .find(|e| e.value().as_string() == Some($string_argument))
2467 .is_some()
2468 };
2469}
2470
2471#[macro_export]
2472macro_rules! kdl_children_property_first_arg_as_string {
2473 ( $kdl_node:expr, $property_name:expr ) => {
2474 $kdl_node
2475 .children()
2476 .and_then(|c| c.get($property_name))
2477 .and_then(|p| p.entries().iter().next())
2478 .and_then(|p| p.value().as_string())
2479 };
2480}
2481
2482#[macro_export]
2483macro_rules! kdl_property_first_arg_as_bool {
2484 ( $kdl_node:expr, $property_name:expr ) => {
2485 $kdl_node
2486 .get($property_name)
2487 .and_then(|p| p.entries().iter().next())
2488 .and_then(|p| p.value().as_bool())
2489 };
2490}
2491
2492#[macro_export]
2493macro_rules! kdl_children_property_first_arg_as_bool {
2494 ( $kdl_node:expr, $property_name:expr ) => {
2495 $kdl_node
2496 .children()
2497 .and_then(|c| c.get($property_name))
2498 .and_then(|p| p.entries().iter().next())
2499 .and_then(|p| p.value().as_bool())
2500 };
2501}
2502
2503#[macro_export]
2504macro_rules! kdl_property_first_arg_as_i64 {
2505 ( $kdl_node:expr, $property_name:expr ) => {
2506 $kdl_node
2507 .get($property_name)
2508 .and_then(|p| p.entries().iter().next())
2509 .and_then(|p| p.value().as_i64())
2510 };
2511}
2512
2513#[macro_export]
2514macro_rules! kdl_get_child {
2515 ( $kdl_node:expr, $child_name:expr ) => {
2516 $kdl_node.children().and_then(|c| c.get($child_name))
2517 };
2518}
2519
2520#[macro_export]
2521macro_rules! kdl_get_child_entry_bool_value {
2522 ( $kdl_node:expr, $child_name:expr ) => {
2523 $kdl_node
2524 .children()
2525 .and_then(|c| c.get($child_name))
2526 .and_then(|c| c.get(0))
2527 .and_then(|c| c.value().as_bool())
2528 };
2529}
2530
2531#[macro_export]
2532macro_rules! kdl_get_child_entry_string_value {
2533 ( $kdl_node:expr, $child_name:expr ) => {
2534 $kdl_node
2535 .children()
2536 .and_then(|c| c.get($child_name))
2537 .and_then(|c| c.get(0))
2538 .and_then(|c| c.value().as_string())
2539 };
2540}
2541
2542#[macro_export]
2543macro_rules! kdl_get_bool_property_or_child_value {
2544 ( $kdl_node:expr, $name:expr ) => {
2545 $kdl_node
2546 .get($name)
2547 .and_then(|e| e.value().as_bool())
2548 .or_else(|| {
2549 $kdl_node
2550 .children()
2551 .and_then(|c| c.get($name))
2552 .and_then(|c| c.get(0))
2553 .and_then(|c| c.value().as_bool())
2554 })
2555 };
2556}
2557
2558#[macro_export]
2559macro_rules! kdl_get_bool_property_or_child_value_with_error {
2560 ( $kdl_node:expr, $name:expr ) => {
2561 match $kdl_node.get($name) {
2562 Some(e) => match e.value().as_bool() {
2563 Some(bool_value) => Some(bool_value),
2564 None => {
2565 return Err(kdl_parsing_error!(
2566 format!(
2567 "{} should be either true or false, found {}",
2568 $name,
2569 e.value()
2570 ),
2571 e
2572 ))
2573 },
2574 },
2575 None => {
2576 let child_value = $kdl_node
2577 .children()
2578 .and_then(|c| c.get($name))
2579 .and_then(|c| c.get(0));
2580 match child_value {
2581 Some(e) => match e.value().as_bool() {
2582 Some(bool_value) => Some(bool_value),
2583 None => {
2584 return Err(kdl_parsing_error!(
2585 format!(
2586 "{} should be either true or false, found {}",
2587 $name,
2588 e.value()
2589 ),
2590 e
2591 ))
2592 },
2593 },
2594 None => {
2595 if let Some(child_node) = kdl_child_with_name!($kdl_node, $name) {
2596 return Err(kdl_parsing_error!(
2597 format!(
2598 "{} must have a value, eg. '{} true'",
2599 child_node.name().value(),
2600 child_node.name().value()
2601 ),
2602 child_node
2603 ));
2604 }
2605 None
2606 },
2607 }
2608 },
2609 }
2610 };
2611}
2612
2613#[macro_export]
2614macro_rules! kdl_property_or_child_value_node {
2615 ( $kdl_node:expr, $name:expr ) => {
2616 $kdl_node.get($name).or_else(|| {
2617 $kdl_node
2618 .children()
2619 .and_then(|c| c.get($name))
2620 .and_then(|c| c.get(0))
2621 })
2622 };
2623}
2624
2625#[macro_export]
2626macro_rules! kdl_child_with_name {
2627 ( $kdl_node:expr, $name:expr ) => {{
2628 $kdl_node
2629 .children()
2630 .and_then(|children| children.nodes().iter().find(|c| c.name().value() == $name))
2631 }};
2632}
2633
2634#[macro_export]
2635macro_rules! kdl_child_with_name_or_error {
2636 ( $kdl_node:expr, $name:expr) => {{
2637 $kdl_node
2638 .children()
2639 .and_then(|children| children.nodes().iter().find(|c| c.name().value() == $name))
2640 .ok_or(ConfigError::new_kdl_error(
2641 format!("Missing node {}", $name).into(),
2642 $kdl_node.span().offset(),
2643 $kdl_node.span().len(),
2644 ))
2645 }};
2646}
2647
2648#[macro_export]
2649macro_rules! kdl_get_string_property_or_child_value_with_error {
2650 ( $kdl_node:expr, $name:expr ) => {
2651 match $kdl_node.get($name) {
2652 Some(e) => match e.value().as_string() {
2653 Some(string_value) => Some(string_value),
2654 None => {
2655 return Err(kdl_parsing_error!(
2656 format!(
2657 "{} should be a string, found {} - not a string",
2658 $name,
2659 e.value()
2660 ),
2661 e
2662 ))
2663 },
2664 },
2665 None => {
2666 let child_value = $kdl_node
2667 .children()
2668 .and_then(|c| c.get($name))
2669 .and_then(|c| c.get(0));
2670 match child_value {
2671 Some(e) => match e.value().as_string() {
2672 Some(string_value) => Some(string_value),
2673 None => {
2674 return Err(kdl_parsing_error!(
2675 format!(
2676 "{} should be a string, found {} - not a string",
2677 $name,
2678 e.value()
2679 ),
2680 e
2681 ))
2682 },
2683 },
2684 None => {
2685 if let Some(child_node) = kdl_child_with_name!($kdl_node, $name) {
2686 return Err(kdl_parsing_error!(
2687 format!(
2688 "{} must have a value, eg. '{} \"foo\"'",
2689 child_node.name().value(),
2690 child_node.name().value()
2691 ),
2692 child_node
2693 ));
2694 }
2695 None
2696 },
2697 }
2698 },
2699 }
2700 };
2701}
2702
2703#[macro_export]
2704macro_rules! kdl_get_property_or_child {
2705 ( $kdl_node:expr, $name:expr ) => {
2706 $kdl_node.get($name).or_else(|| {
2707 $kdl_node
2708 .children()
2709 .and_then(|c| c.get($name))
2710 .and_then(|c| c.get(0))
2711 })
2712 };
2713}
2714
2715#[macro_export]
2716macro_rules! kdl_get_int_property_or_child_value {
2717 ( $kdl_node:expr, $name:expr ) => {
2718 $kdl_node
2719 .get($name)
2720 .and_then(|e| e.value().as_i64())
2721 .or_else(|| {
2722 $kdl_node
2723 .children()
2724 .and_then(|c| c.get($name))
2725 .and_then(|c| c.get(0))
2726 .and_then(|c| c.value().as_i64())
2727 })
2728 };
2729}
2730
2731#[macro_export]
2732macro_rules! kdl_get_string_entry {
2733 ( $kdl_node:expr, $entry_name:expr ) => {
2734 $kdl_node
2735 .get($entry_name)
2736 .and_then(|e| e.value().as_string())
2737 };
2738}
2739
2740#[macro_export]
2741macro_rules! kdl_get_int_entry {
2742 ( $kdl_node:expr, $entry_name:expr ) => {
2743 $kdl_node.get($entry_name).and_then(|e| e.value().as_i64())
2744 };
2745}
2746
2747impl Options {
2748 pub fn from_kdl(kdl_options: &KdlDocument) -> Result<Self, ConfigError> {
2749 let on_force_close =
2750 match kdl_property_first_arg_as_string_or_error!(kdl_options, "on_force_close") {
2751 Some((string, entry)) => Some(OnForceClose::from_str(string).map_err(|_| {
2752 kdl_parsing_error!(
2753 format!("Invalid value for on_force_close: '{}'", string),
2754 entry
2755 )
2756 })?),
2757 None => None,
2758 };
2759 let simplified_ui =
2760 kdl_property_first_arg_as_bool_or_error!(kdl_options, "simplified_ui").map(|(v, _)| v);
2761 let default_shell =
2762 kdl_property_first_arg_as_string_or_error!(kdl_options, "default_shell")
2763 .map(|(string, _entry)| PathBuf::from(string));
2764 let default_cwd = kdl_property_first_arg_as_string_or_error!(kdl_options, "default_cwd")
2765 .map(|(string, _entry)| PathBuf::from(string));
2766 let pane_frames =
2767 kdl_property_first_arg_as_bool_or_error!(kdl_options, "pane_frames").map(|(v, _)| v);
2768 let pane_frame_style =
2769 match kdl_property_first_arg_as_string_or_error!(kdl_options, "pane_frame_style") {
2770 Some((string, entry)) => Some(PaneFrameStyle::from_str(string).map_err(|_| {
2771 kdl_parsing_error!(
2772 format!("Invalid value for pane_frame_style: '{}'", string),
2773 entry
2774 )
2775 })?),
2776 None => None,
2777 };
2778 let auto_layout =
2779 kdl_property_first_arg_as_bool_or_error!(kdl_options, "auto_layout").map(|(v, _)| v);
2780 let theme = kdl_property_first_arg_as_string_or_error!(kdl_options, "theme")
2781 .map(|(theme, _entry)| theme.to_string());
2782 let theme_dark = kdl_property_first_arg_as_string_or_error!(kdl_options, "theme_dark")
2783 .map(|(theme, _entry)| theme.to_string());
2784 let theme_light = kdl_property_first_arg_as_string_or_error!(kdl_options, "theme_light")
2785 .map(|(theme, _entry)| theme.to_string());
2786 let explicit_theme_hue =
2787 match kdl_property_first_arg_as_string_or_error!(kdl_options, "explicit_theme_hue") {
2788 Some((string, entry)) => Some(ThemeHue::from_str(string).map_err(|_| {
2789 kdl_parsing_error!(
2790 format!("Invalid value for explicit_theme_hue: '{}'", string),
2791 entry
2792 )
2793 })?),
2794 None => None,
2795 };
2796 let default_mode =
2797 match kdl_property_first_arg_as_string_or_error!(kdl_options, "default_mode") {
2798 Some((string, entry)) => Some(InputMode::from_str(string).map_err(|_| {
2799 kdl_parsing_error!(format!("Invalid input mode: '{}'", string), entry)
2800 })?),
2801 None => None,
2802 };
2803 let default_layout =
2804 kdl_property_first_arg_as_string_or_error!(kdl_options, "default_layout")
2805 .map(|(string, _entry)| PathBuf::from(string));
2806 let layout_dir = kdl_property_first_arg_as_string_or_error!(kdl_options, "layout_dir")
2807 .map(|(string, _entry)| PathBuf::from(string));
2808 let theme_dir = kdl_property_first_arg_as_string_or_error!(kdl_options, "theme_dir")
2809 .map(|(string, _entry)| PathBuf::from(string));
2810 let mouse_mode =
2811 kdl_property_first_arg_as_bool_or_error!(kdl_options, "mouse_mode").map(|(v, _)| v);
2812 let scroll_buffer_size =
2813 kdl_property_first_arg_as_i64_or_error!(kdl_options, "scroll_buffer_size")
2814 .map(|(scroll_buffer_size, _entry)| scroll_buffer_size as usize);
2815 let copy_command = kdl_property_first_arg_as_string_or_error!(kdl_options, "copy_command")
2816 .map(|(copy_command, _entry)| copy_command.to_string());
2817 let copy_clipboard =
2818 match kdl_property_first_arg_as_string_or_error!(kdl_options, "copy_clipboard") {
2819 Some((string, entry)) => Some(Clipboard::from_str(string).map_err(|_| {
2820 kdl_parsing_error!(
2821 format!("Invalid value for copy_clipboard: '{}'", string),
2822 entry
2823 )
2824 })?),
2825 None => None,
2826 };
2827 let copy_on_select =
2828 kdl_property_first_arg_as_bool_or_error!(kdl_options, "copy_on_select").map(|(v, _)| v);
2829 let osc8_hyperlinks =
2830 kdl_property_first_arg_as_bool_or_error!(kdl_options, "osc8_hyperlinks")
2831 .map(|(v, _)| v);
2832 let scrollback_editor =
2833 kdl_property_first_arg_as_string_or_error!(kdl_options, "scrollback_editor")
2834 .map(|(string, _entry)| PathBuf::from(string));
2835 let mirror_session =
2836 kdl_property_first_arg_as_bool_or_error!(kdl_options, "mirror_session").map(|(v, _)| v);
2837 let session_name = kdl_property_first_arg_as_string_or_error!(kdl_options, "session_name")
2838 .map(|(session_name, _entry)| session_name.to_string());
2839 let attach_to_session =
2840 kdl_property_first_arg_as_bool_or_error!(kdl_options, "attach_to_session")
2841 .map(|(v, _)| v);
2842 let session_serialization =
2843 kdl_property_first_arg_as_bool_or_error!(kdl_options, "session_serialization")
2844 .map(|(v, _)| v);
2845 let serialize_pane_viewport =
2846 kdl_property_first_arg_as_bool_or_error!(kdl_options, "serialize_pane_viewport")
2847 .map(|(v, _)| v);
2848 let scrollback_lines_to_serialize =
2849 kdl_property_first_arg_as_i64_or_error!(kdl_options, "scrollback_lines_to_serialize")
2850 .map(|(v, _)| v as usize);
2851 let styled_underlines =
2852 kdl_property_first_arg_as_bool_or_error!(kdl_options, "styled_underlines")
2853 .map(|(v, _)| v);
2854 let serialization_interval =
2855 kdl_property_first_arg_as_i64_or_error!(kdl_options, "serialization_interval")
2856 .map(|(scroll_buffer_size, _entry)| scroll_buffer_size as u64);
2857 let disable_session_metadata =
2858 kdl_property_first_arg_as_bool_or_error!(kdl_options, "disable_session_metadata")
2859 .map(|(v, _)| v);
2860 let support_kitty_keyboard_protocol = kdl_property_first_arg_as_bool_or_error!(
2861 kdl_options,
2862 "support_kitty_keyboard_protocol"
2863 )
2864 .map(|(v, _)| v);
2865 let support_kitty_graphics_protocol = kdl_property_first_arg_as_bool_or_error!(
2866 kdl_options,
2867 "support_kitty_graphics_protocol"
2868 )
2869 .map(|(v, _)| v);
2870 let web_server =
2871 kdl_property_first_arg_as_bool_or_error!(kdl_options, "web_server").map(|(v, _)| v);
2872 let web_sharing =
2873 match kdl_property_first_arg_as_string_or_error!(kdl_options, "web_sharing") {
2874 Some((string, entry)) => Some(WebSharing::from_str(string).map_err(|_| {
2875 kdl_parsing_error!(
2876 format!("Invalid value for web_sharing: '{}'", string),
2877 entry
2878 )
2879 })?),
2880 None => None,
2881 };
2882 let stacked_resize =
2883 kdl_property_first_arg_as_bool_or_error!(kdl_options, "stacked_resize").map(|(v, _)| v);
2884 let stacked_pane_list =
2885 kdl_property_first_arg_as_bool_or_error!(kdl_options, "stacked_pane_list")
2886 .map(|(v, _)| v);
2887 let show_startup_tips =
2888 kdl_property_first_arg_as_bool_or_error!(kdl_options, "show_startup_tips")
2889 .map(|(v, _)| v);
2890 let show_release_notes =
2891 kdl_property_first_arg_as_bool_or_error!(kdl_options, "show_release_notes")
2892 .map(|(v, _)| v);
2893 let advanced_mouse_actions =
2894 kdl_property_first_arg_as_bool_or_error!(kdl_options, "advanced_mouse_actions")
2895 .map(|(v, _)| v);
2896 let mouse_scroll_resize =
2897 kdl_property_first_arg_as_bool_or_error!(kdl_options, "mouse_scroll_resize")
2898 .map(|(v, _)| v);
2899 let scroll_mode_sync =
2900 kdl_property_first_arg_as_bool_or_error!(kdl_options, "scroll_mode_sync")
2901 .map(|(v, _)| v);
2902 let mouse_hover_effects =
2903 kdl_property_first_arg_as_bool_or_error!(kdl_options, "mouse_hover_effects")
2904 .map(|(v, _)| v);
2905 let mouse_hover_tips =
2906 kdl_property_first_arg_as_bool_or_error!(kdl_options, "mouse_hover_tips")
2907 .map(|(v, _)| v);
2908 let web_server_ip =
2909 match kdl_property_first_arg_as_string_or_error!(kdl_options, "web_server_ip") {
2910 Some((string, entry)) => Some(IpAddr::from_str(string).map_err(|_| {
2911 kdl_parsing_error!(
2912 format!("Invalid value for web_server_ip: '{}'", string),
2913 entry
2914 )
2915 })?),
2916 None => None,
2917 };
2918 let web_server_port =
2919 kdl_property_first_arg_as_i64_or_error!(kdl_options, "web_server_port")
2920 .map(|(web_server_port, _entry)| web_server_port as u16);
2921 let web_server_cert =
2922 kdl_property_first_arg_as_string_or_error!(kdl_options, "web_server_cert")
2923 .map(|(string, _entry)| PathBuf::from(string));
2924 let web_server_key =
2925 kdl_property_first_arg_as_string_or_error!(kdl_options, "web_server_key")
2926 .map(|(string, _entry)| PathBuf::from(string));
2927 let enforce_https_for_localhost =
2928 kdl_property_first_arg_as_bool_or_error!(kdl_options, "enforce_https_for_localhost")
2929 .map(|(v, _)| v);
2930 let post_command_discovery_hook =
2931 kdl_property_first_arg_as_string_or_error!(kdl_options, "post_command_discovery_hook")
2932 .map(|(hook, _entry)| hook.to_string());
2933 let client_async_worker_tasks =
2934 match kdl_property_first_arg_as_i64_or_error!(kdl_options, "client_async_worker_tasks")
2935 {
2936 Some((value, _)) if value >= 0 => Some(value as usize),
2937 Some((value, entry)) => {
2938 return Err(kdl_parsing_error!(
2939 format!(
2940 "Number of client async worker tasks must be greater than 0, found '{}'",
2941 value
2942 ),
2943 entry
2944 ));
2945 },
2946 None => None,
2947 };
2948 let visual_bell =
2949 kdl_property_first_arg_as_bool_or_error!(kdl_options, "visual_bell").map(|(v, _)| v);
2950 let focus_follows_mouse =
2951 kdl_property_first_arg_as_bool_or_error!(kdl_options, "focus_follows_mouse")
2952 .map(|(v, _)| v);
2953 let mouse_click_through =
2954 kdl_property_first_arg_as_bool_or_error!(kdl_options, "mouse_click_through")
2955 .map(|(v, _)| v);
2956 let osc133_command_selection =
2957 kdl_property_first_arg_as_bool_or_error!(kdl_options, "osc133_command_selection")
2958 .map(|(v, _)| v);
2959 let word_separators =
2960 kdl_property_first_arg_as_string_or_error!(kdl_options, "word_separators")
2961 .map(|(separators, _entry)| separators.to_string());
2962 let nested_session_handling = match kdl_property_first_arg_as_string_or_error!(
2963 kdl_options,
2964 "nested_session_handling"
2965 ) {
2966 Some((value, entry)) => {
2967 use crate::input::options::NestedSessionHandling;
2968 match value.parse::<NestedSessionHandling>() {
2969 Ok(v) => Some(v),
2970 Err(e) => return Err(kdl_parsing_error!(e, entry)),
2971 }
2972 },
2973 None => None,
2974 };
2975 let host_notification_protocol = match kdl_property_first_arg_as_string_or_error!(
2976 kdl_options,
2977 "host_notification_protocol"
2978 ) {
2979 Some((value, entry)) => {
2980 use crate::input::options::HostNotificationProtocol;
2981 match value.parse::<HostNotificationProtocol>() {
2982 Ok(v) => Some(v),
2983 Err(e) => return Err(kdl_parsing_error!(e, entry)),
2984 }
2985 },
2986 None => None,
2987 };
2988 let dangerously_enable_paste_buffer_read = kdl_property_first_arg_as_bool_or_error!(
2989 kdl_options,
2990 "dangerously_enable_paste_buffer_read"
2991 )
2992 .map(|(v, _)| v);
2993
2994 Ok(Options {
2995 simplified_ui,
2996 theme,
2997 theme_dark,
2998 theme_light,
2999 explicit_theme_hue,
3000 default_mode,
3001 default_shell,
3002 default_cwd,
3003 default_layout,
3004 layout_dir,
3005 theme_dir,
3006 mouse_mode,
3007 pane_frames,
3008 pane_frame_style,
3009 mirror_session,
3010 on_force_close,
3011 scroll_buffer_size,
3012 copy_command,
3013 copy_clipboard,
3014 copy_on_select,
3015 osc8_hyperlinks,
3016 scrollback_editor,
3017 session_name,
3018 attach_to_session,
3019 auto_layout,
3020 session_serialization,
3021 serialize_pane_viewport,
3022 scrollback_lines_to_serialize,
3023 styled_underlines,
3024 serialization_interval,
3025 disable_session_metadata,
3026 support_kitty_keyboard_protocol,
3027 support_kitty_graphics_protocol,
3028 web_server,
3029 web_sharing,
3030 stacked_resize,
3031 stacked_pane_list,
3032 show_startup_tips,
3033 show_release_notes,
3034 advanced_mouse_actions,
3035 mouse_scroll_resize,
3036 scroll_mode_sync,
3037 mouse_hover_effects,
3038 mouse_hover_tips,
3039 visual_bell,
3040 focus_follows_mouse,
3041 mouse_click_through,
3042 osc133_command_selection,
3043 word_separators,
3044 host_notification_protocol,
3045 web_server_ip,
3046 web_server_port,
3047 web_server_cert,
3048 web_server_key,
3049 enforce_https_for_localhost,
3050 post_command_discovery_hook,
3051 client_async_worker_tasks,
3052 nested_session_handling,
3053 dangerously_enable_paste_buffer_read,
3054 })
3055 }
3056 pub fn from_string(stringified_keybindings: &String) -> Result<Self, ConfigError> {
3057 let document: KdlDocument = stringified_keybindings.parse()?;
3058 Options::from_kdl(&document)
3059 }
3060 fn simplified_ui_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3061 let comment_text = format!(
3062 "{}\n{}\n{}\n{}\n{}\n{}",
3063 " ",
3064 "// Use a simplified UI without special fonts (arrow glyphs)",
3065 "// Options:",
3066 "// - true",
3067 "// - false (Default)",
3068 "// ",
3069 );
3070
3071 let create_node = |node_value: bool| -> KdlNode {
3072 let mut node = KdlNode::new("simplified_ui");
3073 node.push(KdlValue::Bool(node_value));
3074 node
3075 };
3076 if let Some(simplified_ui) = self.simplified_ui {
3077 let mut node = create_node(simplified_ui);
3078 if add_comments {
3079 node.set_leading(format!("{}\n", comment_text));
3080 }
3081 Some(node)
3082 } else if add_comments {
3083 let mut node = create_node(true);
3084 node.set_leading(format!("{}\n// ", comment_text));
3085 Some(node)
3086 } else {
3087 None
3088 }
3089 }
3090 fn osc8_hyperlinks_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3091 let comment_text = format!(
3092 "{}\n{}\n{}\n{}\n{}\n{}",
3093 " ",
3094 "// Enable OSC8 hyperlink output",
3095 "// Options:",
3096 "// - true (Default)",
3097 "// - false",
3098 "// ",
3099 );
3100
3101 let create_node = |node_value: bool| -> KdlNode {
3102 let mut node = KdlNode::new("osc8_hyperlinks");
3103 node.push(KdlValue::Bool(node_value));
3104 node
3105 };
3106 if let Some(osc8_hyperlinks) = self.osc8_hyperlinks {
3107 let mut node = create_node(osc8_hyperlinks);
3108 if add_comments {
3109 node.set_leading(format!("{}\n", comment_text));
3110 }
3111 Some(node)
3112 } else if add_comments {
3113 let mut node = create_node(true);
3114 node.set_leading(format!("{}\n// ", comment_text));
3115 Some(node)
3116 } else {
3117 None
3118 }
3119 }
3120 fn theme_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3121 let comment_text = format!(
3122 "{}\n{}\n{}\n{}",
3123 " ",
3124 "// Choose the theme that is specified in the themes section.",
3125 "// Default: default",
3126 "// ",
3127 );
3128
3129 let create_node = |node_value: &str| -> KdlNode {
3130 let mut node = KdlNode::new("theme");
3131 node.push(node_value.to_owned());
3132 node
3133 };
3134 if let Some(theme) = &self.theme {
3135 let mut node = create_node(theme);
3136 if add_comments {
3137 node.set_leading(format!("{}\n", comment_text));
3138 }
3139 Some(node)
3140 } else if add_comments {
3141 let mut node = create_node("dracula");
3142 node.set_leading(format!("{}\n// ", comment_text));
3143 Some(node)
3144 } else {
3145 None
3146 }
3147 }
3148 fn theme_dark_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3149 let comment_text = format!(
3150 "{}\n{}\n{}\n{}",
3151 " ",
3152 "// Theme to use when the host terminal reports a dark color palette.",
3153 "// Requires `theme_light` to also be set; otherwise `theme` is used.",
3154 "// ",
3155 );
3156
3157 let create_node = |node_value: &str| -> KdlNode {
3158 let mut node = KdlNode::new("theme_dark");
3159 node.push(node_value.to_owned());
3160 node
3161 };
3162 if let Some(theme) = &self.theme_dark {
3163 let mut node = create_node(theme);
3164 if add_comments {
3165 node.set_leading(format!("{}\n", comment_text));
3166 }
3167 Some(node)
3168 } else if add_comments {
3169 let mut node = create_node("dracula");
3170 node.set_leading(format!("{}\n// ", comment_text));
3171 Some(node)
3172 } else {
3173 None
3174 }
3175 }
3176 fn theme_light_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3177 let comment_text = format!(
3178 "{}\n{}\n{}\n{}",
3179 " ",
3180 "// Theme to use when the host terminal reports a light color palette.",
3181 "// Requires `theme_dark` to also be set; otherwise `theme` is used.",
3182 "// ",
3183 );
3184
3185 let create_node = |node_value: &str| -> KdlNode {
3186 let mut node = KdlNode::new("theme_light");
3187 node.push(node_value.to_owned());
3188 node
3189 };
3190 if let Some(theme) = &self.theme_light {
3191 let mut node = create_node(theme);
3192 if add_comments {
3193 node.set_leading(format!("{}\n", comment_text));
3194 }
3195 Some(node)
3196 } else if add_comments {
3197 let mut node = create_node("solarized-light");
3198 node.set_leading(format!("{}\n// ", comment_text));
3199 Some(node)
3200 } else {
3201 None
3202 }
3203 }
3204 fn explicit_theme_hue_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3205 let comment_text = format!(
3206 "{}\n{}\n{}\n{}\n{}",
3207 " ",
3208 "// Pin the session to a dark or light appearance, ignoring what the",
3209 "// host terminal reports. When unset, the host terminal decides.",
3210 "// Options: dark, light",
3211 "// ",
3212 );
3213
3214 let create_node = |hue: &ThemeHue| -> KdlNode {
3215 let mut node = KdlNode::new("explicit_theme_hue");
3216 node.push(format!("{}", hue));
3217 node
3218 };
3219 if let Some(explicit_theme_hue) = &self.explicit_theme_hue {
3220 let mut node = create_node(explicit_theme_hue);
3221 if add_comments {
3222 node.set_leading(format!("{}\n", comment_text));
3223 }
3224 Some(node)
3225 } else if add_comments {
3226 let mut node = create_node(&ThemeHue::Dark);
3227 node.set_leading(format!("{}\n// ", comment_text));
3228 Some(node)
3229 } else {
3230 None
3231 }
3232 }
3233 fn default_mode_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3234 let comment_text = format!(
3235 "{}\n{}\n{}\n{}",
3236 " ", "// Choose the base input mode of zellij.", "// Default: normal", "// "
3237 );
3238
3239 let create_node = |default_mode: &InputMode| -> KdlNode {
3240 let mut node = KdlNode::new("default_mode");
3241 node.push(format!("{:?}", default_mode).to_lowercase());
3242 node
3243 };
3244 if let Some(default_mode) = &self.default_mode {
3245 let mut node = create_node(default_mode);
3246 if add_comments {
3247 node.set_leading(format!("{}\n", comment_text));
3248 }
3249 Some(node)
3250 } else if add_comments {
3251 let mut node = create_node(&InputMode::Locked);
3252 node.set_leading(format!("{}\n// ", comment_text));
3253 Some(node)
3254 } else {
3255 None
3256 }
3257 }
3258 fn default_shell_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3259 let comment_text =
3260 format!("{}\n{}\n{}\n{}",
3261 " ",
3262 "// Choose the path to the default shell that zellij will use for opening new panes",
3263 "// Default: $SHELL",
3264 "// ",
3265 );
3266
3267 let create_node = |node_value: &str| -> KdlNode {
3268 let mut node = KdlNode::new("default_shell");
3269 node.push(node_value.to_owned());
3270 node
3271 };
3272 if let Some(default_shell) = &self.default_shell {
3273 let mut node = create_node(&default_shell.display().to_string());
3274 if add_comments {
3275 node.set_leading(format!("{}\n", comment_text));
3276 }
3277 Some(node)
3278 } else if add_comments {
3279 let mut node = create_node("fish");
3280 node.set_leading(format!("{}\n// ", comment_text));
3281 Some(node)
3282 } else {
3283 None
3284 }
3285 }
3286 fn default_cwd_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3287 let comment_text = format!(
3288 "{}\n{}\n{}",
3289 " ",
3290 "// Choose the path to override cwd that zellij will use for opening new panes",
3291 "// ",
3292 );
3293
3294 let create_node = |node_value: &str| -> KdlNode {
3295 let mut node = KdlNode::new("default_cwd");
3296 node.push(node_value.to_owned());
3297 node
3298 };
3299 if let Some(default_cwd) = &self.default_cwd {
3300 let mut node = create_node(&default_cwd.display().to_string());
3301 if add_comments {
3302 node.set_leading(format!("{}\n", comment_text));
3303 }
3304 Some(node)
3305 } else if add_comments {
3306 let mut node = create_node("/tmp");
3307 node.set_leading(format!("{}\n// ", comment_text));
3308 Some(node)
3309 } else {
3310 None
3311 }
3312 }
3313 fn default_layout_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3314 let comment_text = format!(
3315 "{}\n{}\n{}\n{}",
3316 " ",
3317 "// The name of the default layout to load on startup",
3318 "// Default: \"default\"",
3319 "// ",
3320 );
3321
3322 let create_node = |node_value: &str| -> KdlNode {
3323 let mut node = KdlNode::new("default_layout");
3324 node.push(node_value.to_owned());
3325 node
3326 };
3327 if let Some(default_layout) = &self.default_layout {
3328 let mut node = create_node(&default_layout.display().to_string());
3329 if add_comments {
3330 node.set_leading(format!("{}\n", comment_text));
3331 }
3332 Some(node)
3333 } else if add_comments {
3334 let mut node = create_node("compact");
3335 node.set_leading(format!("{}\n// ", comment_text));
3336 Some(node)
3337 } else {
3338 None
3339 }
3340 }
3341 fn layout_dir_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3342 let comment_text = format!(
3343 "{}\n{}\n{}\n{}",
3344 " ",
3345 "// The folder in which Zellij will look for layouts",
3346 "// (Requires restart)",
3347 "// ",
3348 );
3349
3350 let create_node = |node_value: &str| -> KdlNode {
3351 let mut node = KdlNode::new("layout_dir");
3352 node.push(node_value.to_owned());
3353 node
3354 };
3355 if let Some(layout_dir) = &self.layout_dir {
3356 let mut node = create_node(&layout_dir.display().to_string());
3357 if add_comments {
3358 node.set_leading(format!("{}\n", comment_text));
3359 }
3360 Some(node)
3361 } else if add_comments {
3362 let mut node = create_node("/tmp");
3363 node.set_leading(format!("{}\n// ", comment_text));
3364 Some(node)
3365 } else {
3366 None
3367 }
3368 }
3369 fn theme_dir_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3370 let comment_text = format!(
3371 "{}\n{}\n{}\n{}",
3372 " ",
3373 "// The folder in which Zellij will look for themes",
3374 "// (Requires restart)",
3375 "// ",
3376 );
3377
3378 let create_node = |node_value: &str| -> KdlNode {
3379 let mut node = KdlNode::new("theme_dir");
3380 node.push(node_value.to_owned());
3381 node
3382 };
3383 if let Some(theme_dir) = &self.theme_dir {
3384 let mut node = create_node(&theme_dir.display().to_string());
3385 if add_comments {
3386 node.set_leading(format!("{}\n", comment_text));
3387 }
3388 Some(node)
3389 } else if add_comments {
3390 let mut node = create_node("/tmp");
3391 node.set_leading(format!("{}\n// ", comment_text));
3392 Some(node)
3393 } else {
3394 None
3395 }
3396 }
3397 fn mouse_mode_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3398 let comment_text = format!(
3399 "{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}",
3400 " ",
3401 "// Toggle enabling the mouse mode.",
3402 "// On certain configurations, or terminals this could",
3403 "// potentially interfere with copying text.",
3404 "// Options:",
3405 "// - true (default)",
3406 "// - false",
3407 "// ",
3408 );
3409
3410 let create_node = |node_value: bool| -> KdlNode {
3411 let mut node = KdlNode::new("mouse_mode");
3412 node.push(KdlValue::Bool(node_value));
3413 node
3414 };
3415 if let Some(mouse_mode) = self.mouse_mode {
3416 let mut node = create_node(mouse_mode);
3417 if add_comments {
3418 node.set_leading(format!("{}\n", comment_text));
3419 }
3420 Some(node)
3421 } else if add_comments {
3422 let mut node = create_node(false);
3423 node.set_leading(format!("{}\n// ", comment_text));
3424 Some(node)
3425 } else {
3426 None
3427 }
3428 }
3429 fn pane_frames_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3430 let comment_text = format!(
3431 "{}\n{}\n{}\n{}\n{}\n{}",
3432 " ",
3433 "// Toggle having pane frames around the panes",
3434 "// Options:",
3435 "// - true (default, enabled)",
3436 "// - false",
3437 "// ",
3438 );
3439
3440 let create_node = |node_value: bool| -> KdlNode {
3441 let mut node = KdlNode::new("pane_frames");
3442 node.push(KdlValue::Bool(node_value));
3443 node
3444 };
3445 if let Some(pane_frames) = self.pane_frames {
3446 let mut node = create_node(pane_frames);
3447 if add_comments {
3448 node.set_leading(format!("{}\n", comment_text));
3449 }
3450 Some(node)
3451 } else if add_comments {
3452 let mut node = create_node(false);
3453 node.set_leading(format!("{}\n// ", comment_text));
3454 Some(node)
3455 } else {
3456 None
3457 }
3458 }
3459 fn pane_frame_style_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3460 let comment_text = format!(
3461 "{}\n{}\n{}\n{}\n{}\n{}",
3462 " ",
3463 "// Set the pane frame style when pane_frames is enabled",
3464 "// Options:",
3465 "// - full",
3466 "// - titles (default)",
3467 "// ",
3468 );
3469
3470 let style_as_str = |style: &PaneFrameStyle| -> &'static str {
3471 match style {
3472 PaneFrameStyle::Full => "full",
3473 PaneFrameStyle::Titles => "titles",
3474 PaneFrameStyle::None => "none",
3475 }
3476 };
3477
3478 let create_node = |node_value: &str| -> KdlNode {
3479 let mut node = KdlNode::new("pane_frame_style");
3480 node.push(node_value.to_owned());
3481 node
3482 };
3483 if let Some(pane_frame_style) = &self.pane_frame_style {
3484 let mut node = create_node(style_as_str(pane_frame_style));
3485 if add_comments {
3486 node.set_leading(format!("{}\n", comment_text));
3487 }
3488 Some(node)
3489 } else if add_comments {
3490 let mut node = create_node("titles");
3491 node.set_leading(format!("{}\n// ", comment_text));
3492 Some(node)
3493 } else {
3494 None
3495 }
3496 }
3497 fn mirror_session_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3498 let comment_text = format!(
3499 "{}\n{}\n{}\n{}\n{}\n{}\n{}",
3500 " ",
3501 "// When attaching to an existing session with other users,",
3502 "// should the session be mirrored (true)",
3503 "// or should each user have their own cursor (false)",
3504 "// (Requires restart)",
3505 "// Default: false",
3506 "// ",
3507 );
3508
3509 let create_node = |node_value: bool| -> KdlNode {
3510 let mut node = KdlNode::new("mirror_session");
3511 node.push(KdlValue::Bool(node_value));
3512 node
3513 };
3514 if let Some(mirror_session) = self.mirror_session {
3515 let mut node = create_node(mirror_session);
3516 if add_comments {
3517 node.set_leading(format!("{}\n", comment_text));
3518 }
3519 Some(node)
3520 } else if add_comments {
3521 let mut node = create_node(true);
3522 node.set_leading(format!("{}\n// ", comment_text));
3523 Some(node)
3524 } else {
3525 None
3526 }
3527 }
3528 fn on_force_close_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3529 let comment_text = format!(
3530 "{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}",
3531 " ",
3532 "// Choose what to do when zellij receives SIGTERM, SIGINT, SIGQUIT or SIGHUP",
3533 "// eg. when terminal window with an active zellij session is closed",
3534 "// (Requires restart)",
3535 "// Options:",
3536 "// - detach (Default)",
3537 "// - quit",
3538 "// ",
3539 );
3540
3541 let create_node = |node_value: &str| -> KdlNode {
3542 let mut node = KdlNode::new("on_force_close");
3543 node.push(node_value.to_owned());
3544 node
3545 };
3546 if let Some(on_force_close) = &self.on_force_close {
3547 let mut node = match on_force_close {
3548 OnForceClose::Detach => create_node("detach"),
3549 OnForceClose::Quit => create_node("quit"),
3550 };
3551 if add_comments {
3552 node.set_leading(format!("{}\n", comment_text));
3553 }
3554 Some(node)
3555 } else if add_comments {
3556 let mut node = create_node("quit");
3557 node.set_leading(format!("{}\n// ", comment_text));
3558 Some(node)
3559 } else {
3560 None
3561 }
3562 }
3563 fn scroll_buffer_size_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3564 let comment_text = format!(
3565 "{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}",
3566 " ",
3567 "// Configure the scroll back buffer size",
3568 "// This is the number of lines zellij stores for each pane in the scroll back",
3569 "// buffer. Excess number of lines are discarded in a FIFO fashion.",
3570 "// (Requires restart)",
3571 "// Valid values: positive integers",
3572 "// Default value: 10000",
3573 "// ",
3574 );
3575
3576 let create_node = |node_value: usize| -> KdlNode {
3577 let mut node = KdlNode::new("scroll_buffer_size");
3578 node.push(KdlValue::Base10(node_value as i64));
3579 node
3580 };
3581 if let Some(scroll_buffer_size) = self.scroll_buffer_size {
3582 let mut node = create_node(scroll_buffer_size);
3583 if add_comments {
3584 node.set_leading(format!("{}\n", comment_text));
3585 }
3586 Some(node)
3587 } else if add_comments {
3588 let mut node = create_node(10000);
3589 node.set_leading(format!("{}\n// ", comment_text));
3590 Some(node)
3591 } else {
3592 None
3593 }
3594 }
3595 fn copy_command_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3596 let comment_text = format!(
3597 "{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}",
3598 " ",
3599 "// Provide a command to execute when copying text. The text will be piped to",
3600 "// the stdin of the program to perform the copy. This can be used with",
3601 "// terminal emulators which do not support the OSC 52 ANSI control sequence",
3602 "// that will be used by default if this option is not set.",
3603 "// Examples:",
3604 "//",
3605 "// copy_command \"xclip -selection clipboard\" // x11",
3606 "// copy_command \"wl-copy\" // wayland",
3607 "// copy_command \"pbcopy\" // osx",
3608 "// ",
3609 );
3610
3611 let create_node = |node_value: &str| -> KdlNode {
3612 let mut node = KdlNode::new("copy_command");
3613 node.push(node_value.to_owned());
3614 node
3615 };
3616 if let Some(copy_command) = &self.copy_command {
3617 let mut node = create_node(copy_command);
3618 if add_comments {
3619 node.set_leading(format!("{}\n", comment_text));
3620 }
3621 Some(node)
3622 } else if add_comments {
3623 let mut node = create_node("pbcopy");
3624 node.set_leading(format!("{}\n// ", comment_text));
3625 Some(node)
3626 } else {
3627 None
3628 }
3629 }
3630 fn copy_clipboard_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3631 let comment_text = format!("{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}",
3632 " ",
3633 "// Choose the destination for copied text",
3634 "// Allows using the primary selection buffer (on x11/wayland) instead of the system clipboard.",
3635 "// Does not apply when using copy_command.",
3636 "// Options:",
3637 "// - system (default)",
3638 "// - primary",
3639 "// ",
3640 );
3641
3642 let create_node = |node_value: &str| -> KdlNode {
3643 let mut node = KdlNode::new("copy_clipboard");
3644 node.push(node_value.to_owned());
3645 node
3646 };
3647 if let Some(copy_clipboard) = &self.copy_clipboard {
3648 let mut node = match copy_clipboard {
3649 Clipboard::Primary => create_node("primary"),
3650 Clipboard::System => create_node("system"),
3651 };
3652 if add_comments {
3653 node.set_leading(format!("{}\n", comment_text));
3654 }
3655 Some(node)
3656 } else if add_comments {
3657 let mut node = create_node("primary");
3658 node.set_leading(format!("{}\n// ", comment_text));
3659 Some(node)
3660 } else {
3661 None
3662 }
3663 }
3664 fn copy_on_select_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3665 let comment_text = format!(
3666 "{}\n{}\n{}\n{}",
3667 " ",
3668 "// Enable automatic copying (and clearing) of selection when releasing mouse",
3669 "// Default: true",
3670 "// ",
3671 );
3672
3673 let create_node = |node_value: bool| -> KdlNode {
3674 let mut node = KdlNode::new("copy_on_select");
3675 node.push(KdlValue::Bool(node_value));
3676 node
3677 };
3678 if let Some(copy_on_select) = self.copy_on_select {
3679 let mut node = create_node(copy_on_select);
3680 if add_comments {
3681 node.set_leading(format!("{}\n", comment_text));
3682 }
3683 Some(node)
3684 } else if add_comments {
3685 let mut node = create_node(true);
3686 node.set_leading(format!("{}\n// ", comment_text));
3687 Some(node)
3688 } else {
3689 None
3690 }
3691 }
3692 fn scrollback_editor_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3693 let comment_text = format!(
3694 "{}\n{}\n{}",
3695 " ",
3696 "// Path to the default editor to use to edit pane scrollbuffer",
3697 "// Default: $EDITOR or $VISUAL",
3698 );
3699
3700 let create_node = |node_value: &str| -> KdlNode {
3701 let mut node = KdlNode::new("scrollback_editor");
3702 node.push(node_value.to_owned());
3703 node
3704 };
3705 if let Some(scrollback_editor) = &self.scrollback_editor {
3706 let mut node = create_node(&scrollback_editor.display().to_string());
3707 if add_comments {
3708 node.set_leading(format!("{}\n", comment_text));
3709 }
3710 Some(node)
3711 } else if add_comments {
3712 let mut node = create_node("/usr/bin/vim");
3713 node.set_leading(format!("{}\n// ", comment_text));
3714 Some(node)
3715 } else {
3716 None
3717 }
3718 }
3719 fn session_name_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3720 let comment_text = format!(
3721 "{}\n{}\n{}\n{}\n{}\n{}",
3722 " ",
3723 "// A fixed name to always give the Zellij session.",
3724 "// Consider also setting `attach_to_session true,`",
3725 "// otherwise this will error if such a session exists.",
3726 "// Default: <RANDOM>",
3727 "// ",
3728 );
3729
3730 let create_node = |node_value: &str| -> KdlNode {
3731 let mut node = KdlNode::new("session_name");
3732 node.push(node_value.to_owned());
3733 node
3734 };
3735 if let Some(session_name) = &self.session_name {
3736 let mut node = create_node(&session_name);
3737 if add_comments {
3738 node.set_leading(format!("{}\n", comment_text));
3739 }
3740 Some(node)
3741 } else if add_comments {
3742 let mut node = create_node("My singleton session");
3743 node.set_leading(format!("{}\n// ", comment_text));
3744 Some(node)
3745 } else {
3746 None
3747 }
3748 }
3749 fn attach_to_session_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3750 let comment_text = format!(
3751 "{}\n{}\n{}\n{}\n{}",
3752 " ",
3753 "// When `session_name` is provided, attaches to that session",
3754 "// if it is already running or creates it otherwise.",
3755 "// Default: false",
3756 "// ",
3757 );
3758
3759 let create_node = |node_value: bool| -> KdlNode {
3760 let mut node = KdlNode::new("attach_to_session");
3761 node.push(KdlValue::Bool(node_value));
3762 node
3763 };
3764 if let Some(attach_to_session) = self.attach_to_session {
3765 let mut node = create_node(attach_to_session);
3766 if add_comments {
3767 node.set_leading(format!("{}\n", comment_text));
3768 }
3769 Some(node)
3770 } else if add_comments {
3771 let mut node = create_node(true);
3772 node.set_leading(format!("{}\n// ", comment_text));
3773 Some(node)
3774 } else {
3775 None
3776 }
3777 }
3778 fn auto_layout_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3779 let comment_text = format!("{}\n{}\n{}\n{}\n{}\n{}",
3780 " ",
3781 "// Toggle between having Zellij lay out panes according to a predefined set of layouts whenever possible",
3782 "// Options:",
3783 "// - true (default)",
3784 "// - false",
3785 "// ",
3786 );
3787
3788 let create_node = |node_value: bool| -> KdlNode {
3789 let mut node = KdlNode::new("auto_layout");
3790 node.push(KdlValue::Bool(node_value));
3791 node
3792 };
3793 if let Some(auto_layout) = self.auto_layout {
3794 let mut node = create_node(auto_layout);
3795 if add_comments {
3796 node.set_leading(format!("{}\n", comment_text));
3797 }
3798 Some(node)
3799 } else if add_comments {
3800 let mut node = create_node(false);
3801 node.set_leading(format!("{}\n// ", comment_text));
3802 Some(node)
3803 } else {
3804 None
3805 }
3806 }
3807 fn session_serialization_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3808 let comment_text = format!("{}\n{}\n{}\n{}\n{}\n{}",
3809 " ",
3810 "// Whether sessions should be serialized to the cache folder (including their tabs/panes, cwds and running commands) so that they can later be resurrected",
3811 "// Options:",
3812 "// - true (default)",
3813 "// - false",
3814 "// ",
3815 );
3816
3817 let create_node = |node_value: bool| -> KdlNode {
3818 let mut node = KdlNode::new("session_serialization");
3819 node.push(KdlValue::Bool(node_value));
3820 node
3821 };
3822 if let Some(session_serialization) = self.session_serialization {
3823 let mut node = create_node(session_serialization);
3824 if add_comments {
3825 node.set_leading(format!("{}\n", comment_text));
3826 }
3827 Some(node)
3828 } else if add_comments {
3829 let mut node = create_node(false);
3830 node.set_leading(format!("{}\n// ", comment_text));
3831 Some(node)
3832 } else {
3833 None
3834 }
3835 }
3836 fn serialize_pane_viewport_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3837 let comment_text = format!(
3838 "{}\n{}\n{}\n{}\n{}\n{}",
3839 " ",
3840 "// Whether pane viewports are serialized along with the session, default is false",
3841 "// Options:",
3842 "// - true",
3843 "// - false (default)",
3844 "// ",
3845 );
3846
3847 let create_node = |node_value: bool| -> KdlNode {
3848 let mut node = KdlNode::new("serialize_pane_viewport");
3849 node.push(KdlValue::Bool(node_value));
3850 node
3851 };
3852 if let Some(serialize_pane_viewport) = self.serialize_pane_viewport {
3853 let mut node = create_node(serialize_pane_viewport);
3854 if add_comments {
3855 node.set_leading(format!("{}\n", comment_text));
3856 }
3857 Some(node)
3858 } else if add_comments {
3859 let mut node = create_node(false);
3860 node.set_leading(format!("{}\n// ", comment_text));
3861 Some(node)
3862 } else {
3863 None
3864 }
3865 }
3866 fn scrollback_lines_to_serialize_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3867 let comment_text = format!("{}\n{}\n{}\n{}\n{}",
3868 " ",
3869 "// Scrollback lines to serialize along with the pane viewport when serializing sessions, 0",
3870 "// defaults to the scrollback size. If this number is higher than the scrollback size, it will",
3871 "// also default to the scrollback size. This does nothing if `serialize_pane_viewport` is not true.",
3872 "// ",
3873 );
3874
3875 let create_node = |node_value: usize| -> KdlNode {
3876 let mut node = KdlNode::new("scrollback_lines_to_serialize");
3877 node.push(KdlValue::Base10(node_value as i64));
3878 node
3879 };
3880 if let Some(scrollback_lines_to_serialize) = self.scrollback_lines_to_serialize {
3881 let mut node = create_node(scrollback_lines_to_serialize);
3882 if add_comments {
3883 node.set_leading(format!("{}\n", comment_text));
3884 }
3885 Some(node)
3886 } else if add_comments {
3887 let mut node = create_node(10000);
3888 node.set_leading(format!("{}\n// ", comment_text));
3889 Some(node)
3890 } else {
3891 None
3892 }
3893 }
3894 fn styled_underlines_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3895 let comment_text = format!(
3896 "{}\n{}\n{}\n{}\n{}\n{}",
3897 " ",
3898 "// Enable or disable the rendering of styled and colored underlines (undercurl).",
3899 "// May need to be disabled for certain unsupported terminals",
3900 "// (Requires restart)",
3901 "// Default: true",
3902 "// ",
3903 );
3904
3905 let create_node = |node_value: bool| -> KdlNode {
3906 let mut node = KdlNode::new("styled_underlines");
3907 node.push(KdlValue::Bool(node_value));
3908 node
3909 };
3910 if let Some(styled_underlines) = self.styled_underlines {
3911 let mut node = create_node(styled_underlines);
3912 if add_comments {
3913 node.set_leading(format!("{}\n", comment_text));
3914 }
3915 Some(node)
3916 } else if add_comments {
3917 let mut node = create_node(false);
3918 node.set_leading(format!("{}\n// ", comment_text));
3919 Some(node)
3920 } else {
3921 None
3922 }
3923 }
3924 fn serialization_interval_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3925 let comment_text = format!(
3926 "{}\n{}\n{}",
3927 " ", "// How often in seconds sessions are serialized", "// ",
3928 );
3929
3930 let create_node = |node_value: u64| -> KdlNode {
3931 let mut node = KdlNode::new("serialization_interval");
3932 node.push(KdlValue::Base10(node_value as i64));
3933 node
3934 };
3935 if let Some(serialization_interval) = self.serialization_interval {
3936 let mut node = create_node(serialization_interval);
3937 if add_comments {
3938 node.set_leading(format!("{}\n", comment_text));
3939 }
3940 Some(node)
3941 } else if add_comments {
3942 let mut node = create_node(10000);
3943 node.set_leading(format!("{}\n// ", comment_text));
3944 Some(node)
3945 } else {
3946 None
3947 }
3948 }
3949 fn disable_session_metadata_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3950 let comment_text = format!("{}\n{}\n{}\n{}\n{}\n{}",
3951 " ",
3952 "// Enable or disable writing of session metadata to disk (if disabled, other sessions might not know",
3953 "// metadata info on this session)",
3954 "// (Requires restart)",
3955 "// Default: false",
3956 "// ",
3957 );
3958
3959 let create_node = |node_value: bool| -> KdlNode {
3960 let mut node = KdlNode::new("disable_session_metadata");
3961 node.push(KdlValue::Bool(node_value));
3962 node
3963 };
3964 if let Some(disable_session_metadata) = self.disable_session_metadata {
3965 let mut node = create_node(disable_session_metadata);
3966 if add_comments {
3967 node.set_leading(format!("{}\n", comment_text));
3968 }
3969 Some(node)
3970 } else if add_comments {
3971 let mut node = create_node(false);
3972 node.set_leading(format!("{}\n// ", comment_text));
3973 Some(node)
3974 } else {
3975 None
3976 }
3977 }
3978 fn support_kitty_keyboard_protocol_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3979 let comment_text = format!("{}\n{}\n{}\n{}\n{}",
3980 " ",
3981 "// Enable or disable support for the enhanced Kitty Keyboard Protocol (the host terminal must also support it)",
3982 "// (Requires restart)",
3983 "// Default: true (if the host terminal supports it)",
3984 "// ",
3985 );
3986
3987 let create_node = |node_value: bool| -> KdlNode {
3988 let mut node = KdlNode::new("support_kitty_keyboard_protocol");
3989 node.push(KdlValue::Bool(node_value));
3990 node
3991 };
3992 if let Some(support_kitty_keyboard_protocol) = self.support_kitty_keyboard_protocol {
3993 let mut node = create_node(support_kitty_keyboard_protocol);
3994 if add_comments {
3995 node.set_leading(format!("{}\n", comment_text));
3996 }
3997 Some(node)
3998 } else if add_comments {
3999 let mut node = create_node(false);
4000 node.set_leading(format!("{}\n// ", comment_text));
4001 Some(node)
4002 } else {
4003 None
4004 }
4005 }
4006 fn support_kitty_graphics_protocol_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4007 let comment_text = format!("{}\n{}\n{}\n{}\n{}",
4008 " ",
4009 "// Enable or disable support for the Kitty Graphics Protocol, used to display images (the host terminal must also support it)",
4010 "// (Requires restart)",
4011 "// Default: true (if the host terminal supports it)",
4012 "// ",
4013 );
4014
4015 let create_node = |node_value: bool| -> KdlNode {
4016 let mut node = KdlNode::new("support_kitty_graphics_protocol");
4017 node.push(KdlValue::Bool(node_value));
4018 node
4019 };
4020 if let Some(support_kitty_graphics_protocol) = self.support_kitty_graphics_protocol {
4021 let mut node = create_node(support_kitty_graphics_protocol);
4022 if add_comments {
4023 node.set_leading(format!("{}\n", comment_text));
4024 }
4025 Some(node)
4026 } else if add_comments {
4027 let mut node = create_node(false);
4028 node.set_leading(format!("{}\n// ", comment_text));
4029 Some(node)
4030 } else {
4031 None
4032 }
4033 }
4034 fn web_server_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4035 let comment_text = format!(
4036 "{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}",
4037 "// Whether to make sure a local web server is running when a new Zellij session starts.",
4038 "// This web server will allow creating new sessions and attaching to existing ones that have",
4039 "// opted in to being shared in the browser.",
4040 "// When enabled, navigate to http://127.0.0.1:8082",
4041 "// (Requires restart)",
4042 "// ",
4043 "// Note: a local web server can still be manually started from within a Zellij session or from the CLI.",
4044 "// If this is not desired, one can use a version of Zellij compiled without",
4045 "// `web_server_capability`",
4046 "// ",
4047 "// Possible values:",
4048 "// - true",
4049 "// - false",
4050 "// Default: false",
4051 "// ",
4052 );
4053
4054 let create_node = |node_value: bool| -> KdlNode {
4055 let mut node = KdlNode::new("web_server");
4056 node.push(KdlValue::Bool(node_value));
4057 node
4058 };
4059 if let Some(web_server) = self.web_server {
4060 let mut node = create_node(web_server);
4061 if add_comments {
4062 node.set_leading(format!("{}\n", comment_text));
4063 }
4064 Some(node)
4065 } else if add_comments {
4066 let mut node = create_node(false);
4067 node.set_leading(format!("{}\n// ", comment_text));
4068 Some(node)
4069 } else {
4070 None
4071 }
4072 }
4073 fn web_sharing_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4074 let comment_text = format!(
4075 "{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}",
4076 "// Whether to allow sessions started in the terminal to be shared through a local web server, assuming one is",
4077 "// running (see the `web_server` option for more details).",
4078 "// (Requires restart)",
4079 "// ",
4080 "// Note: This is an administrative separation and not intended as a security measure.",
4081 "// ",
4082 "// Possible values:",
4083 "// - \"on\" (allow web sharing through the local web server if it",
4084 "// is online)",
4085 "// - \"off\" (do not allow web sharing unless sessions explicitly opt-in to it)",
4086 "// - \"disabled\" (do not allow web sharing and do not permit sessions started in the terminal to opt-in to it)",
4087 "// Default: \"off\"",
4088 "// ",
4089 );
4090
4091 let create_node = |node_value: &str| -> KdlNode {
4092 let mut node = KdlNode::new("web_sharing");
4093 node.push(node_value.to_owned());
4094 node
4095 };
4096 if let Some(web_sharing) = &self.web_sharing {
4097 let mut node = match web_sharing {
4098 WebSharing::On => create_node("on"),
4099 WebSharing::Off => create_node("off"),
4100 WebSharing::Disabled => create_node("disabled"),
4101 };
4102 if add_comments {
4103 node.set_leading(format!("{}\n", comment_text));
4104 }
4105 Some(node)
4106 } else if add_comments {
4107 let mut node = create_node("off");
4108 node.set_leading(format!("{}\n// ", comment_text));
4109 Some(node)
4110 } else {
4111 None
4112 }
4113 }
4114 fn web_server_cert_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4115 let comment_text = format!(
4116 "{}\n{}\n{}",
4117 "// A path to a certificate file to be used when setting up the web client to serve the",
4118 "// connection over HTTPs",
4119 "// ",
4120 );
4121 let create_node = |node_value: &str| -> KdlNode {
4122 let mut node = KdlNode::new("web_server_cert");
4123 node.push(node_value.to_owned());
4124 node
4125 };
4126 if let Some(web_server_cert) = &self.web_server_cert {
4127 let mut node = create_node(&web_server_cert.display().to_string());
4128 if add_comments {
4129 node.set_leading(format!("{}\n", comment_text));
4130 }
4131 Some(node)
4132 } else if add_comments {
4133 let mut node = create_node("/path/to/cert.pem");
4134 node.set_leading(format!("{}\n// ", comment_text));
4135 Some(node)
4136 } else {
4137 None
4138 }
4139 }
4140 fn web_server_key_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4141 let comment_text = format!(
4142 "{}\n{}\n{}",
4143 "// A path to a key file to be used when setting up the web client to serve the",
4144 "// connection over HTTPs",
4145 "// ",
4146 );
4147 let create_node = |node_value: &str| -> KdlNode {
4148 let mut node = KdlNode::new("web_server_key");
4149 node.push(node_value.to_owned());
4150 node
4151 };
4152 if let Some(web_server_key) = &self.web_server_key {
4153 let mut node = create_node(&web_server_key.display().to_string());
4154 if add_comments {
4155 node.set_leading(format!("{}\n", comment_text));
4156 }
4157 Some(node)
4158 } else if add_comments {
4159 let mut node = create_node("/path/to/key.pem");
4160 node.set_leading(format!("{}\n// ", comment_text));
4161 Some(node)
4162 } else {
4163 None
4164 }
4165 }
4166 fn enforce_https_for_localhost_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4167 let comment_text = format!(
4168 "{}\n{}\n{}\n{}\n{}\n{}\n{}",
4169 "/// Whether to enforce https connections to the web server when it is bound to localhost",
4170 "/// (127.0.0.0/8)",
4171 "///",
4172 "/// Note: https is ALWAYS enforced when bound to non-local interfaces",
4173 "///",
4174 "/// Default: false",
4175 "// ",
4176 );
4177
4178 let create_node = |node_value: bool| -> KdlNode {
4179 let mut node = KdlNode::new("enforce_https_for_localhost");
4180 node.push(KdlValue::Bool(node_value));
4181 node
4182 };
4183 if let Some(enforce_https_for_localhost) = self.enforce_https_for_localhost {
4184 let mut node = create_node(enforce_https_for_localhost);
4185 if add_comments {
4186 node.set_leading(format!("{}\n", comment_text));
4187 }
4188 Some(node)
4189 } else if add_comments {
4190 let mut node = create_node(false);
4191 node.set_leading(format!("{}\n// ", comment_text));
4192 Some(node)
4193 } else {
4194 None
4195 }
4196 }
4197 fn stacked_resize_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4198 let comment_text = format!(
4199 "{}\n{}\n{}\n{}",
4200 " ",
4201 "// Whether to stack panes when resizing beyond a certain size",
4202 "// Default: true",
4203 "// ",
4204 );
4205
4206 let create_node = |node_value: bool| -> KdlNode {
4207 let mut node = KdlNode::new("stacked_resize");
4208 node.push(KdlValue::Bool(node_value));
4209 node
4210 };
4211 if let Some(stacked_resize) = self.stacked_resize {
4212 let mut node = create_node(stacked_resize);
4213 if add_comments {
4214 node.set_leading(format!("{}\n", comment_text));
4215 }
4216 Some(node)
4217 } else if add_comments {
4218 let mut node = create_node(false);
4219 node.set_leading(format!("{}\n// ", comment_text));
4220 Some(node)
4221 } else {
4222 None
4223 }
4224 }
4225 fn stacked_pane_list_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4226 let comment_text = format!(
4227 "{}\n{}\n{}\n{}",
4228 " ",
4229 "// Whether stacked panes display as a list with the expanded pane pinned to the bottom",
4230 "// Default: true",
4231 "// ",
4232 );
4233
4234 let create_node = |node_value: bool| -> KdlNode {
4235 let mut node = KdlNode::new("stacked_pane_list");
4236 node.push(KdlValue::Bool(node_value));
4237 node
4238 };
4239 if let Some(stacked_pane_list) = self.stacked_pane_list {
4240 let mut node = create_node(stacked_pane_list);
4241 if add_comments {
4242 node.set_leading(format!("{}\n", comment_text));
4243 }
4244 Some(node)
4245 } else if add_comments {
4246 let mut node = create_node(false);
4247 node.set_leading(format!("{}\n// ", comment_text));
4248 Some(node)
4249 } else {
4250 None
4251 }
4252 }
4253 fn show_startup_tips_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4254 let comment_text = format!(
4255 "{}\n{}\n{}\n{}",
4256 " ", "// Whether to show tips on startup", "// Default: true", "// ",
4257 );
4258
4259 let create_node = |node_value: bool| -> KdlNode {
4260 let mut node = KdlNode::new("show_startup_tips");
4261 node.push(KdlValue::Bool(node_value));
4262 node
4263 };
4264 if let Some(show_startup_tips) = self.show_startup_tips {
4265 let mut node = create_node(show_startup_tips);
4266 if add_comments {
4267 node.set_leading(format!("{}\n", comment_text));
4268 }
4269 Some(node)
4270 } else if add_comments {
4271 let mut node = create_node(false);
4272 node.set_leading(format!("{}\n// ", comment_text));
4273 Some(node)
4274 } else {
4275 None
4276 }
4277 }
4278 fn show_release_notes_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4279 let comment_text = format!(
4280 "{}\n{}\n{}\n{}",
4281 " ", "// Whether to show release notes on first version run", "// Default: true", "// ",
4282 );
4283
4284 let create_node = |node_value: bool| -> KdlNode {
4285 let mut node = KdlNode::new("show_release_notes");
4286 node.push(KdlValue::Bool(node_value));
4287 node
4288 };
4289 if let Some(show_release_notes) = self.show_release_notes {
4290 let mut node = create_node(show_release_notes);
4291 if add_comments {
4292 node.set_leading(format!("{}\n", comment_text));
4293 }
4294 Some(node)
4295 } else if add_comments {
4296 let mut node = create_node(false);
4297 node.set_leading(format!("{}\n// ", comment_text));
4298 Some(node)
4299 } else {
4300 None
4301 }
4302 }
4303 fn advanced_mouse_actions_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4304 let comment_text = format!(
4305 "{}\n{}\n{}",
4306 " ",
4307 "// Whether to enable mouse hover effects and pane grouping functionality",
4308 "// default is true",
4309 );
4310
4311 let create_node = |node_value: bool| -> KdlNode {
4312 let mut node = KdlNode::new("advanced_mouse_actions");
4313 node.push(KdlValue::Bool(node_value));
4314 node
4315 };
4316 if let Some(advanced_mouse_actions) = self.advanced_mouse_actions {
4317 let mut node = create_node(advanced_mouse_actions);
4318 if add_comments {
4319 node.set_leading(format!("{}\n", comment_text));
4320 }
4321 Some(node)
4322 } else if add_comments {
4323 let mut node = create_node(false);
4324 node.set_leading(format!("{}\n// ", comment_text));
4325 Some(node)
4326 } else {
4327 None
4328 }
4329 }
4330 fn mouse_scroll_resize_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4331 let comment_text = format!(
4332 "{}\n{}\n{}",
4333 " ", "// Whether Ctrl+ScrollWheel resizes panes", "// default is true",
4334 );
4335
4336 let create_node = |node_value: bool| -> KdlNode {
4337 let mut node = KdlNode::new("mouse_scroll_resize");
4338 node.push(KdlValue::Bool(node_value));
4339 node
4340 };
4341 if let Some(mouse_scroll_resize) = self.mouse_scroll_resize {
4342 let mut node = create_node(mouse_scroll_resize);
4343 if add_comments {
4344 node.set_leading(format!("{}\n", comment_text));
4345 }
4346 Some(node)
4347 } else if add_comments {
4348 let mut node = create_node(false);
4349 node.set_leading(format!("{}\n// ", comment_text));
4350 Some(node)
4351 } else {
4352 None
4353 }
4354 }
4355 fn scroll_mode_sync_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4356 let comment_text = format!(
4357 "{}\n{}\n{}",
4358 " ",
4359 "// Whether scrolling a pane implicitly enters and exits Scroll mode",
4360 "// default is true",
4361 );
4362
4363 let create_node = |node_value: bool| -> KdlNode {
4364 let mut node = KdlNode::new("scroll_mode_sync");
4365 node.push(KdlValue::Bool(node_value));
4366 node
4367 };
4368 if let Some(scroll_mode_sync) = self.scroll_mode_sync {
4369 let mut node = create_node(scroll_mode_sync);
4370 if add_comments {
4371 node.set_leading(format!("{}\n", comment_text));
4372 }
4373 Some(node)
4374 } else if add_comments {
4375 let mut node = create_node(false);
4376 node.set_leading(format!("{}\n// ", comment_text));
4377 Some(node)
4378 } else {
4379 None
4380 }
4381 }
4382 fn mouse_hover_tips_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4383 let comment_text = format!(
4384 "{}\n{}\n{}",
4385 " ",
4386 "// Whether to show mouse hover help-text tips (resize help and group shortcuts)",
4387 "// default is true",
4388 );
4389
4390 let create_node = |node_value: bool| -> KdlNode {
4391 let mut node = KdlNode::new("mouse_hover_tips");
4392 node.push(KdlValue::Bool(node_value));
4393 node
4394 };
4395 if let Some(mouse_hover_tips) = self.mouse_hover_tips {
4396 let mut node = create_node(mouse_hover_tips);
4397 if add_comments {
4398 node.set_leading(format!("{}\n", comment_text));
4399 }
4400 Some(node)
4401 } else if add_comments {
4402 let mut node = create_node(false);
4403 node.set_leading(format!("{}\n// ", comment_text));
4404 Some(node)
4405 } else {
4406 None
4407 }
4408 }
4409 fn mouse_hover_effects_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4410 let comment_text = format!(
4411 "{}\n{}\n{}",
4412 " ",
4413 "// Whether to enable mouse hover visual effects (frame highlight and help text)",
4414 "// default is true",
4415 );
4416
4417 let create_node = |node_value: bool| -> KdlNode {
4418 let mut node = KdlNode::new("mouse_hover_effects");
4419 node.push(KdlValue::Bool(node_value));
4420 node
4421 };
4422 if let Some(mouse_hover_effects) = self.mouse_hover_effects {
4423 let mut node = create_node(mouse_hover_effects);
4424 if add_comments {
4425 node.set_leading(format!("{}\n", comment_text));
4426 }
4427 Some(node)
4428 } else if add_comments {
4429 let mut node = create_node(false);
4430 node.set_leading(format!("{}\n// ", comment_text));
4431 Some(node)
4432 } else {
4433 None
4434 }
4435 }
4436 fn visual_bell_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4437 let comment_text = format!(
4438 "{}\n{}\n{}",
4439 " ",
4440 "// Whether to show visual bell indicators (pane/tab frame flash and [!] suffix)",
4441 "// default is true",
4442 );
4443
4444 let create_node = |node_value: bool| -> KdlNode {
4445 let mut node = KdlNode::new("visual_bell");
4446 node.push(KdlValue::Bool(node_value));
4447 node
4448 };
4449 if let Some(visual_bell) = self.visual_bell {
4450 let mut node = create_node(visual_bell);
4451 if add_comments {
4452 node.set_leading(format!("{}\n", comment_text));
4453 }
4454 Some(node)
4455 } else if add_comments {
4456 let mut node = create_node(true);
4457 node.set_leading(format!("{}\n// ", comment_text));
4458 Some(node)
4459 } else {
4460 None
4461 }
4462 }
4463 fn focus_follows_mouse_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4464 let comment_text = format!(
4465 "{}\n{}\n{}",
4466 " ", "// Whether to focus panes on mouse hover", "// default is false",
4467 );
4468
4469 let create_node = |node_value: bool| -> KdlNode {
4470 let mut node = KdlNode::new("focus_follows_mouse");
4471 node.push(KdlValue::Bool(node_value));
4472 node
4473 };
4474 if let Some(focus_follows_mouse) = self.focus_follows_mouse {
4475 let mut node = create_node(focus_follows_mouse);
4476 if add_comments {
4477 node.set_leading(format!("{}\n", comment_text));
4478 }
4479 Some(node)
4480 } else if add_comments {
4481 let mut node = create_node(false);
4482 node.set_leading(format!("{}\n// ", comment_text));
4483 Some(node)
4484 } else {
4485 None
4486 }
4487 }
4488 fn mouse_click_through_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4489 let comment_text = format!(
4490 "{}\n{}\n{}",
4491 " ",
4492 "// Whether clicking a pane to focus it also sends the click into the pane",
4493 "// default is false",
4494 );
4495
4496 let create_node = |node_value: bool| -> KdlNode {
4497 let mut node = KdlNode::new("mouse_click_through");
4498 node.push(KdlValue::Bool(node_value));
4499 node
4500 };
4501 if let Some(mouse_click_through) = self.mouse_click_through {
4502 let mut node = create_node(mouse_click_through);
4503 if add_comments {
4504 node.set_leading(format!("{}\n", comment_text));
4505 }
4506 Some(node)
4507 } else if add_comments {
4508 let mut node = create_node(false);
4509 node.set_leading(format!("{}\n// ", comment_text));
4510 Some(node)
4511 } else {
4512 None
4513 }
4514 }
4515 fn osc133_command_selection_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4516 let comment_text = format!(
4517 "{}\n{}\n{}\n{}",
4518 " ",
4519 "// Whether triple-clicking inside command output marked by the shell (OSC 133) selects",
4520 "// the command and its output instead of the logical line",
4521 "// default is true",
4522 );
4523
4524 let create_node = |node_value: bool| -> KdlNode {
4525 let mut node = KdlNode::new("osc133_command_selection");
4526 node.push(KdlValue::Bool(node_value));
4527 node
4528 };
4529 if let Some(osc133_command_selection) = self.osc133_command_selection {
4530 let mut node = create_node(osc133_command_selection);
4531 if add_comments {
4532 node.set_leading(format!("{}\n", comment_text));
4533 }
4534 Some(node)
4535 } else if add_comments {
4536 let mut node = create_node(false);
4537 node.set_leading(format!("{}\n// ", comment_text));
4538 Some(node)
4539 } else {
4540 None
4541 }
4542 }
4543 fn word_separators_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4544 let comment_text = format!(
4545 "{}\n{}\n{}\n{}",
4546 " ",
4547 "// Characters that terminate a word when double-clicking to select it",
4548 "// whitespace is always a separator and need not be listed here",
4549 "// default is \"[]{}<>()\"",
4550 );
4551
4552 let create_node = |node_value: &str| -> KdlNode {
4553 let mut node = KdlNode::new("word_separators");
4554 node.push(node_value.to_owned());
4555 node
4556 };
4557 if let Some(word_separators) = &self.word_separators {
4558 let mut node = create_node(word_separators);
4559 if add_comments {
4560 node.set_leading(format!("{}\n", comment_text));
4561 }
4562 Some(node)
4563 } else if add_comments {
4564 let mut node = create_node(DEFAULT_WORD_SEPARATORS);
4565 node.set_leading(format!("{}\n// ", comment_text));
4566 Some(node)
4567 } else {
4568 None
4569 }
4570 }
4571 fn web_server_ip_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4572 let comment_text = format!(
4573 "{}\n{}\n{}\n{}",
4574 " ",
4575 "// The ip address the web server should listen on when it starts",
4576 "// Default: \"127.0.0.1\"",
4577 "// (Requires restart)",
4578 );
4579
4580 let create_node = |node_value: IpAddr| -> KdlNode {
4581 let mut node = KdlNode::new("web_server_ip");
4582 node.push(KdlValue::String(node_value.to_string()));
4583 node
4584 };
4585 if let Some(web_server_ip) = self.web_server_ip {
4586 let mut node = create_node(web_server_ip);
4587 if add_comments {
4588 node.set_leading(format!("{}\n", comment_text));
4589 }
4590 Some(node)
4591 } else if add_comments {
4592 let mut node = create_node(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
4593 node.set_leading(format!("{}\n// ", comment_text));
4594 Some(node)
4595 } else {
4596 None
4597 }
4598 }
4599 fn web_server_port_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4600 let comment_text = format!(
4601 "{}\n{}\n{}\n{}",
4602 " ",
4603 "// The port the web server should listen on when it starts",
4604 "// Default: 8082",
4605 "// (Requires restart)",
4606 );
4607
4608 let create_node = |node_value: u16| -> KdlNode {
4609 let mut node = KdlNode::new("web_server_port");
4610 node.push(KdlValue::Base10(node_value as i64));
4611 node
4612 };
4613 if let Some(web_server_port) = self.web_server_port {
4614 let mut node = create_node(web_server_port);
4615 if add_comments {
4616 node.set_leading(format!("{}\n", comment_text));
4617 }
4618 Some(node)
4619 } else if add_comments {
4620 let mut node = create_node(8082);
4621 node.set_leading(format!("{}\n// ", comment_text));
4622 Some(node)
4623 } else {
4624 None
4625 }
4626 }
4627 fn post_command_discovery_hook_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4628 let comment_text = format!(
4629 "{}\n{}\n{}\n{}\n{}\n{}",
4630 " ",
4631 "// A command to run (will be wrapped with sh -c and provided the RESURRECT_COMMAND env variable) ",
4632 "// after Zellij attempts to discover a command inside a pane when resurrecting sessions, the STDOUT",
4633 "// of this command will be used instead of the discovered RESURRECT_COMMAND",
4634 "// can be useful for removing wrappers around commands",
4635 "// Note: be sure to escape backslashes and similar characters properly",
4636 );
4637
4638 let create_node = |node_value: &str| -> KdlNode {
4639 let mut node = KdlNode::new("post_command_discovery_hook");
4640 node.push(node_value.to_owned());
4641 node
4642 };
4643 if let Some(post_command_discovery_hook) = &self.post_command_discovery_hook {
4644 let mut node = create_node(&post_command_discovery_hook);
4645 if add_comments {
4646 node.set_leading(format!("{}\n", comment_text));
4647 }
4648 Some(node)
4649 } else if add_comments {
4650 let mut node = create_node("echo $RESURRECT_COMMAND | sed <your_regex_here>");
4651 node.set_leading(format!("{}\n// ", comment_text));
4652 Some(node)
4653 } else {
4654 None
4655 }
4656 }
4657 fn nested_session_handling_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4658 use crate::input::options::NestedSessionHandling;
4659 let comment_text = format!(
4660 "{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}",
4661 " ",
4662 "// How to handle a nested Zellij session detected inside a pane.",
4663 "// Options:",
4664 "// - \"ask\" (Default — prompt with a modal)",
4665 "// - \"fullscreen\" (always zoom into the nested session)",
4666 "// - \"descend\" (always control the nested session on focus)",
4667 "// - \"never\" (never prompt or descend; do it manually)",
4668 "// ",
4669 );
4670 let create_node = |value: NestedSessionHandling| -> KdlNode {
4671 let mut node = KdlNode::new("nested_session_handling");
4672 let s = match value {
4673 NestedSessionHandling::Ask => "ask",
4674 NestedSessionHandling::Fullscreen => "fullscreen",
4675 NestedSessionHandling::Descend => "descend",
4676 NestedSessionHandling::Never => "never",
4677 };
4678 node.push(KdlValue::String(s.to_string()));
4679 node
4680 };
4681 if let Some(value) = self.nested_session_handling {
4682 let mut node = create_node(value);
4683 if add_comments {
4684 node.set_leading(format!("{}\n", comment_text));
4685 }
4686 Some(node)
4687 } else if add_comments {
4688 let mut node = create_node(NestedSessionHandling::Ask);
4689 node.set_leading(format!("{}\n// ", comment_text));
4690 Some(node)
4691 } else {
4692 None
4693 }
4694 }
4695 fn host_notification_protocol_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4696 use crate::input::options::HostNotificationProtocol;
4697 let comment_text = format!(
4698 "{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}",
4699 " ",
4700 "// Which escape sequence desktop notifications coming from panes are",
4701 "// forwarded to the host terminal with.",
4702 "// Options:",
4703 "// - \"auto\" (Default — detect from the host terminal environment)",
4704 "// - \"osc9\" (the legacy iTerm2 protocol, understood by most terminals)",
4705 "// - \"osc99\" (kitty's notification protocol)",
4706 "// - \"bell\" (ring the terminal bell instead)",
4707 "// - \"off\" (do not forward notifications to the host terminal)",
4708 "// ",
4709 );
4710 let create_node = |value: HostNotificationProtocol| -> KdlNode {
4711 let mut node = KdlNode::new("host_notification_protocol");
4712 node.push(KdlValue::String(value.as_str().to_string()));
4713 node
4714 };
4715 if let Some(value) = self.host_notification_protocol {
4716 let mut node = create_node(value);
4717 if add_comments {
4718 node.set_leading(format!("{}\n", comment_text));
4719 }
4720 Some(node)
4721 } else if add_comments {
4722 let mut node = create_node(HostNotificationProtocol::Auto);
4723 node.set_leading(format!("{}\n// ", comment_text));
4724 Some(node)
4725 } else {
4726 None
4727 }
4728 }
4729 fn dangerously_enable_paste_buffer_read_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4730 let comment_text = format!(
4731 "{}\n{}\n{}\n{}\n{}\n{}",
4732 " ",
4733 "// Whether to let programs running inside panes read the paste buffer",
4734 "// (clipboard) with the OSC 52 escape sequence. When enabled, any program",
4735 "// in any pane - including one running on a remote machine over SSH - can",
4736 "// read the clipboard without the user being asked.",
4737 "// Default: false",
4738 );
4739 let create_node = |node_value: bool| -> KdlNode {
4740 let mut node = KdlNode::new("dangerously_enable_paste_buffer_read");
4741 node.push(KdlValue::Bool(node_value));
4742 node
4743 };
4744 if let Some(value) = self.dangerously_enable_paste_buffer_read {
4745 let mut node = create_node(value);
4746 if add_comments {
4747 node.set_leading(format!("{}\n", comment_text));
4748 }
4749 Some(node)
4750 } else if add_comments {
4751 let mut node = create_node(false);
4752 node.set_leading(format!("{}\n// ", comment_text));
4753 Some(node)
4754 } else {
4755 None
4756 }
4757 }
4758 fn client_async_worker_tasks_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4759 let comment_text = r#"
4760// Number of async worker tasks to spawn per active client.
4761//
4762// Allocating few tasks may result in resource contention and lags. Small values (around 4) should
4763// typically work best. Set to 0 to use the number of (physical) CPU cores.
4764// Note: This only applies to web clients at the moment."#;
4765 let create_node = |node_value: usize| -> KdlNode {
4766 let mut node = KdlNode::new("client_async_worker_tasks");
4767 node.push(KdlValue::Base10(node_value as i64));
4768 node
4769 };
4770 if let Some(client_async_worker_tasks) = self.client_async_worker_tasks {
4771 let mut node = create_node(client_async_worker_tasks);
4772 if add_comments {
4773 node.set_leading(format!("{}\n", comment_text));
4774 }
4775 Some(node)
4776 } else if add_comments {
4777 let mut node = create_node(4usize);
4778 node.set_leading(format!("{}\n// ", comment_text));
4779 Some(node)
4780 } else {
4781 None
4782 }
4783 }
4784 pub fn to_kdl(&self, add_comments: bool) -> Vec<KdlNode> {
4785 let mut nodes = vec![];
4786 if let Some(simplified_ui_node) = self.simplified_ui_to_kdl(add_comments) {
4787 nodes.push(simplified_ui_node);
4788 }
4789 if let Some(osc8_hyperlinks_node) = self.osc8_hyperlinks_to_kdl(add_comments) {
4790 nodes.push(osc8_hyperlinks_node);
4791 }
4792 if let Some(theme_node) = self.theme_to_kdl(add_comments) {
4793 nodes.push(theme_node);
4794 }
4795 if let Some(theme_dark_node) = self.theme_dark_to_kdl(add_comments) {
4796 nodes.push(theme_dark_node);
4797 }
4798 if let Some(theme_light_node) = self.theme_light_to_kdl(add_comments) {
4799 nodes.push(theme_light_node);
4800 }
4801 if let Some(explicit_theme_hue_node) = self.explicit_theme_hue_to_kdl(add_comments) {
4802 nodes.push(explicit_theme_hue_node);
4803 }
4804 if let Some(default_mode) = self.default_mode_to_kdl(add_comments) {
4805 nodes.push(default_mode);
4806 }
4807 if let Some(default_shell) = self.default_shell_to_kdl(add_comments) {
4808 nodes.push(default_shell);
4809 }
4810 if let Some(default_cwd) = self.default_cwd_to_kdl(add_comments) {
4811 nodes.push(default_cwd);
4812 }
4813 if let Some(default_layout) = self.default_layout_to_kdl(add_comments) {
4814 nodes.push(default_layout);
4815 }
4816 if let Some(layout_dir) = self.layout_dir_to_kdl(add_comments) {
4817 nodes.push(layout_dir);
4818 }
4819 if let Some(theme_dir) = self.theme_dir_to_kdl(add_comments) {
4820 nodes.push(theme_dir);
4821 }
4822 if let Some(mouse_mode) = self.mouse_mode_to_kdl(add_comments) {
4823 nodes.push(mouse_mode);
4824 }
4825 if let Some(pane_frames) = self.pane_frames_to_kdl(add_comments) {
4826 nodes.push(pane_frames);
4827 }
4828 if let Some(pane_frame_style) = self.pane_frame_style_to_kdl(add_comments) {
4829 nodes.push(pane_frame_style);
4830 }
4831 if let Some(mirror_session) = self.mirror_session_to_kdl(add_comments) {
4832 nodes.push(mirror_session);
4833 }
4834 if let Some(on_force_close) = self.on_force_close_to_kdl(add_comments) {
4835 nodes.push(on_force_close);
4836 }
4837 if let Some(scroll_buffer_size) = self.scroll_buffer_size_to_kdl(add_comments) {
4838 nodes.push(scroll_buffer_size);
4839 }
4840 if let Some(copy_command) = self.copy_command_to_kdl(add_comments) {
4841 nodes.push(copy_command);
4842 }
4843 if let Some(copy_clipboard) = self.copy_clipboard_to_kdl(add_comments) {
4844 nodes.push(copy_clipboard);
4845 }
4846 if let Some(copy_on_select) = self.copy_on_select_to_kdl(add_comments) {
4847 nodes.push(copy_on_select);
4848 }
4849 if let Some(scrollback_editor) = self.scrollback_editor_to_kdl(add_comments) {
4850 nodes.push(scrollback_editor);
4851 }
4852 if let Some(session_name) = self.session_name_to_kdl(add_comments) {
4853 nodes.push(session_name);
4854 }
4855 if let Some(attach_to_session) = self.attach_to_session_to_kdl(add_comments) {
4856 nodes.push(attach_to_session);
4857 }
4858 if let Some(auto_layout) = self.auto_layout_to_kdl(add_comments) {
4859 nodes.push(auto_layout);
4860 }
4861 if let Some(session_serialization) = self.session_serialization_to_kdl(add_comments) {
4862 nodes.push(session_serialization);
4863 }
4864 if let Some(serialize_pane_viewport) = self.serialize_pane_viewport_to_kdl(add_comments) {
4865 nodes.push(serialize_pane_viewport);
4866 }
4867 if let Some(scrollback_lines_to_serialize) =
4868 self.scrollback_lines_to_serialize_to_kdl(add_comments)
4869 {
4870 nodes.push(scrollback_lines_to_serialize);
4871 }
4872 if let Some(styled_underlines) = self.styled_underlines_to_kdl(add_comments) {
4873 nodes.push(styled_underlines);
4874 }
4875 if let Some(serialization_interval) = self.serialization_interval_to_kdl(add_comments) {
4876 nodes.push(serialization_interval);
4877 }
4878 if let Some(disable_session_metadata) = self.disable_session_metadata_to_kdl(add_comments) {
4879 nodes.push(disable_session_metadata);
4880 }
4881 if let Some(support_kitty_keyboard_protocol) =
4882 self.support_kitty_keyboard_protocol_to_kdl(add_comments)
4883 {
4884 nodes.push(support_kitty_keyboard_protocol);
4885 }
4886 if let Some(support_kitty_graphics_protocol) =
4887 self.support_kitty_graphics_protocol_to_kdl(add_comments)
4888 {
4889 nodes.push(support_kitty_graphics_protocol);
4890 }
4891 if let Some(web_server) = self.web_server_to_kdl(add_comments) {
4892 nodes.push(web_server);
4893 }
4894 if let Some(web_sharing) = self.web_sharing_to_kdl(add_comments) {
4895 nodes.push(web_sharing);
4896 }
4897 if let Some(web_server_cert) = self.web_server_cert_to_kdl(add_comments) {
4898 nodes.push(web_server_cert);
4899 }
4900 if let Some(web_server_key) = self.web_server_key_to_kdl(add_comments) {
4901 nodes.push(web_server_key);
4902 }
4903 if let Some(enforce_https_for_localhost) =
4904 self.enforce_https_for_localhost_to_kdl(add_comments)
4905 {
4906 nodes.push(enforce_https_for_localhost);
4907 }
4908 if let Some(stacked_resize) = self.stacked_resize_to_kdl(add_comments) {
4909 nodes.push(stacked_resize);
4910 }
4911 if let Some(stacked_pane_list) = self.stacked_pane_list_to_kdl(add_comments) {
4912 nodes.push(stacked_pane_list);
4913 }
4914 if let Some(show_startup_tips) = self.show_startup_tips_to_kdl(add_comments) {
4915 nodes.push(show_startup_tips);
4916 }
4917 if let Some(show_release_notes) = self.show_release_notes_to_kdl(add_comments) {
4918 nodes.push(show_release_notes);
4919 }
4920 if let Some(advanced_mouse_actions) = self.advanced_mouse_actions_to_kdl(add_comments) {
4921 nodes.push(advanced_mouse_actions);
4922 }
4923 if let Some(mouse_scroll_resize) = self.mouse_scroll_resize_to_kdl(add_comments) {
4924 nodes.push(mouse_scroll_resize);
4925 }
4926 if let Some(scroll_mode_sync) = self.scroll_mode_sync_to_kdl(add_comments) {
4927 nodes.push(scroll_mode_sync);
4928 }
4929 if let Some(mouse_hover_effects) = self.mouse_hover_effects_to_kdl(add_comments) {
4930 nodes.push(mouse_hover_effects);
4931 }
4932 if let Some(mouse_hover_tips) = self.mouse_hover_tips_to_kdl(add_comments) {
4933 nodes.push(mouse_hover_tips);
4934 }
4935 if let Some(visual_bell) = self.visual_bell_to_kdl(add_comments) {
4936 nodes.push(visual_bell);
4937 }
4938 if let Some(focus_follows_mouse) = self.focus_follows_mouse_to_kdl(add_comments) {
4939 nodes.push(focus_follows_mouse);
4940 }
4941 if let Some(mouse_click_through) = self.mouse_click_through_to_kdl(add_comments) {
4942 nodes.push(mouse_click_through);
4943 }
4944 if let Some(osc133_command_selection) = self.osc133_command_selection_to_kdl(add_comments) {
4945 nodes.push(osc133_command_selection);
4946 }
4947 if let Some(word_separators) = self.word_separators_to_kdl(add_comments) {
4948 nodes.push(word_separators);
4949 }
4950 if let Some(web_server_ip) = self.web_server_ip_to_kdl(add_comments) {
4951 nodes.push(web_server_ip);
4952 }
4953 if let Some(web_server_port) = self.web_server_port_to_kdl(add_comments) {
4954 nodes.push(web_server_port);
4955 }
4956 if let Some(post_command_discovery_hook) =
4957 self.post_command_discovery_hook_to_kdl(add_comments)
4958 {
4959 nodes.push(post_command_discovery_hook);
4960 }
4961 if let Some(client_async_worker_tasks) = self.client_async_worker_tasks_to_kdl(add_comments)
4962 {
4963 nodes.push(client_async_worker_tasks);
4964 }
4965 if let Some(dangerously_enable_paste_buffer_read) =
4966 self.dangerously_enable_paste_buffer_read_to_kdl(add_comments)
4967 {
4968 nodes.push(dangerously_enable_paste_buffer_read);
4969 }
4970 if let Some(nested_session_handling) = self.nested_session_handling_to_kdl(add_comments) {
4971 nodes.push(nested_session_handling);
4972 }
4973 if let Some(host_notification_protocol) =
4974 self.host_notification_protocol_to_kdl(add_comments)
4975 {
4976 nodes.push(host_notification_protocol);
4977 }
4978 nodes
4979 }
4980}
4981
4982impl Layout {
4983 pub fn from_kdl(
4984 raw_layout: &str,
4985 file_name: Option<String>,
4986 raw_swap_layouts: Option<(&str, &str)>, cwd: Option<PathBuf>,
4988 ) -> Result<Self, ConfigError> {
4989 let mut kdl_layout_parser = KdlLayoutParser::new(raw_layout, cwd, file_name.clone());
4990 let layout = kdl_layout_parser.parse().map_err(|e| match e {
4991 ConfigError::KdlError(kdl_error) => ConfigError::KdlError(kdl_error.add_src(
4992 file_name.unwrap_or_else(|| "N/A".to_owned()),
4993 String::from(raw_layout),
4994 )),
4995 ConfigError::KdlDeserializationError(kdl_error) => kdl_layout_error(
4996 kdl_error,
4997 file_name.unwrap_or_else(|| "N/A".to_owned()),
4998 raw_layout,
4999 ),
5000 e => e,
5001 })?;
5002 match raw_swap_layouts {
5003 Some((raw_swap_layout_filename, raw_swap_layout)) => {
5004 kdl_layout_parser
5007 .parse_external_swap_layouts(raw_swap_layout, layout)
5008 .map_err(|e| match e {
5009 ConfigError::KdlError(kdl_error) => {
5010 ConfigError::KdlError(kdl_error.add_src(
5011 String::from(raw_swap_layout_filename),
5012 String::from(raw_swap_layout),
5013 ))
5014 },
5015 ConfigError::KdlDeserializationError(kdl_error) => kdl_layout_error(
5016 kdl_error,
5017 raw_swap_layout_filename.into(),
5018 raw_swap_layout,
5019 ),
5020 e => e,
5021 })
5022 },
5023 None => Ok(layout),
5024 }
5025 }
5026}
5027
5028fn kdl_layout_error(kdl_error: kdl::KdlError, file_name: String, raw_layout: &str) -> ConfigError {
5029 let error_message = match kdl_error.kind {
5030 kdl::KdlErrorKind::Context("valid node terminator") => {
5031 format!("Failed to deserialize KDL node. \nPossible reasons:\n{}\n{}\n{}\n{}",
5032 "- Missing `;` after a node name, eg. { node; another_node; }",
5033 "- Missing quotations (\") around an argument node eg. { first_node \"argument_node\"; }",
5034 "- Missing an equal sign (=) between node arguments on a title line. eg. argument=\"value\"",
5035 "- Found an extraneous equal sign (=) between node child arguments and their values. eg. { argument=\"value\" }")
5036 },
5037 _ => String::from(kdl_error.help.unwrap_or("Kdl Deserialization Error")),
5038 };
5039 let kdl_error = KdlError {
5040 error_message,
5041 src: Some(NamedSource::new(file_name, String::from(raw_layout))),
5042 offset: Some(kdl_error.span.offset()),
5043 len: Some(kdl_error.span.len()),
5044 help_message: None,
5045 };
5046 ConfigError::KdlError(kdl_error)
5047}
5048
5049impl EnvironmentVariables {
5050 pub fn from_kdl(kdl_env_variables: &KdlNode) -> Result<Self, ConfigError> {
5051 let mut env: HashMap<String, String> = HashMap::new();
5052 for env_var in kdl_children_nodes_or_error!(kdl_env_variables, "empty env variable block") {
5053 let env_var_name = kdl_name!(env_var);
5054 let env_var_str_value =
5055 kdl_first_entry_as_string!(env_var).map(|s| format!("{}", s.to_string()));
5056 let env_var_int_value =
5057 kdl_first_entry_as_i64!(env_var).map(|s| format!("{}", s.to_string()));
5058 let env_var_value =
5059 env_var_str_value
5060 .or(env_var_int_value)
5061 .ok_or(ConfigError::new_kdl_error(
5062 format!("Failed to parse env var: {:?}", env_var_name),
5063 env_var.span().offset(),
5064 env_var.span().len(),
5065 ))?;
5066 env.insert(env_var_name.into(), env_var_value);
5067 }
5068 Ok(EnvironmentVariables::from_data(env))
5069 }
5070 pub fn to_kdl(&self) -> Option<KdlNode> {
5071 let mut has_env_vars = false;
5072 let mut env = KdlNode::new("env");
5073 let mut env_vars = KdlDocument::new();
5074
5075 let mut stable_sorted = BTreeMap::new();
5076 for (env_var_name, env_var_value) in self.inner() {
5077 stable_sorted.insert(env_var_name, env_var_value);
5078 }
5079 for (env_key, env_value) in stable_sorted {
5080 has_env_vars = true;
5081 let mut variable_key = KdlNode::new(env_key.to_owned());
5082 variable_key.push(env_value.to_owned());
5083 env_vars.nodes_mut().push(variable_key);
5084 }
5085
5086 if has_env_vars {
5087 env.set_children(env_vars);
5088 Some(env)
5089 } else {
5090 None
5091 }
5092 }
5093}
5094
5095impl Keybinds {
5096 fn bind_keys_in_block(
5097 block: &KdlNode,
5098 input_mode_keybinds: &mut HashMap<KeyWithModifier, Vec<Action>>,
5099 config_options: &Options,
5100 ) -> Result<(), ConfigError> {
5101 let all_nodes = kdl_children_nodes_or_error!(block, "no keybinding block for mode");
5102 let bind_nodes = all_nodes.iter().filter(|n| kdl_name!(n) == "bind");
5103 let unbind_nodes = all_nodes.iter().filter(|n| kdl_name!(n) == "unbind");
5104 for key_block in bind_nodes {
5105 Keybinds::bind_actions_for_each_key(key_block, input_mode_keybinds, config_options)?;
5106 }
5107 for key_block in unbind_nodes {
5109 Keybinds::unbind_keys(key_block, input_mode_keybinds)?;
5110 }
5111 for key_block in all_nodes {
5112 if kdl_name!(key_block) != "bind" && kdl_name!(key_block) != "unbind" {
5113 return Err(ConfigError::new_kdl_error(
5114 format!("Unknown keybind instruction: '{}'", kdl_name!(key_block)),
5115 key_block.span().offset(),
5116 key_block.span().len(),
5117 ));
5118 }
5119 }
5120 Ok(())
5121 }
5122 pub fn from_kdl(
5123 kdl_keybinds: &KdlNode,
5124 base_keybinds: Keybinds,
5125 config_options: &Options,
5126 ) -> Result<Self, ConfigError> {
5127 let clear_defaults = kdl_arg_is_truthy!(kdl_keybinds, "clear-defaults");
5128 let mut keybinds_from_config = if clear_defaults {
5129 Keybinds::default()
5130 } else {
5131 base_keybinds
5132 };
5133 for block in kdl_children_nodes_or_error!(kdl_keybinds, "keybindings with no children") {
5134 if kdl_name!(block) == "shared_except" || kdl_name!(block) == "shared" {
5135 let mut modes_to_exclude = vec![];
5136 for mode_name in kdl_string_arguments!(block) {
5137 modes_to_exclude.push(InputMode::from_str(mode_name).map_err(|_| {
5138 ConfigError::new_kdl_error(
5139 format!("Invalid mode: '{}'", mode_name),
5140 block.name().span().offset(),
5141 block.name().span().len(),
5142 )
5143 })?);
5144 }
5145 for mode in InputMode::iter() {
5146 if modes_to_exclude.contains(&mode) {
5147 continue;
5148 }
5149 let mut input_mode_keybinds = keybinds_from_config.get_input_mode_mut(&mode);
5150 Keybinds::bind_keys_in_block(block, &mut input_mode_keybinds, config_options)?;
5151 }
5152 }
5153 if kdl_name!(block) == "shared_among" {
5154 let mut modes_to_include = vec![];
5155 for mode_name in kdl_string_arguments!(block) {
5156 modes_to_include.push(InputMode::from_str(mode_name)?);
5157 }
5158 for mode in InputMode::iter() {
5159 if !modes_to_include.contains(&mode) {
5160 continue;
5161 }
5162 let mut input_mode_keybinds = keybinds_from_config.get_input_mode_mut(&mode);
5163 Keybinds::bind_keys_in_block(block, &mut input_mode_keybinds, config_options)?;
5164 }
5165 }
5166 }
5167 for mode in kdl_children_nodes_or_error!(kdl_keybinds, "keybindings with no children") {
5168 if kdl_name!(mode) == "unbind"
5169 || kdl_name!(mode) == "shared_except"
5170 || kdl_name!(mode) == "shared_among"
5171 || kdl_name!(mode) == "shared"
5172 {
5173 continue;
5174 }
5175 let mut input_mode_keybinds =
5176 Keybinds::input_mode_keybindings(mode, &mut keybinds_from_config)?;
5177 Keybinds::bind_keys_in_block(mode, &mut input_mode_keybinds, config_options)?;
5178 }
5179 if let Some(global_unbind) = kdl_keybinds.children().and_then(|c| c.get("unbind")) {
5180 Keybinds::unbind_keys_in_all_modes(global_unbind, &mut keybinds_from_config)?;
5181 };
5182 Ok(keybinds_from_config)
5183 }
5184 fn bind_actions_for_each_key(
5185 key_block: &KdlNode,
5186 input_mode_keybinds: &mut HashMap<KeyWithModifier, Vec<Action>>,
5187 config_options: &Options,
5188 ) -> Result<(), ConfigError> {
5189 let keys: Vec<KeyWithModifier> = keys_from_kdl!(key_block);
5190 let actions: Vec<Action> = actions_from_kdl!(key_block, config_options);
5191 for key in keys {
5192 input_mode_keybinds.insert(key, actions.clone());
5193 }
5194 Ok(())
5195 }
5196 fn unbind_keys(
5197 key_block: &KdlNode,
5198 input_mode_keybinds: &mut HashMap<KeyWithModifier, Vec<Action>>,
5199 ) -> Result<(), ConfigError> {
5200 let keys: Vec<KeyWithModifier> = keys_from_kdl!(key_block);
5201 for key in keys {
5202 input_mode_keybinds.remove(&key);
5203 }
5204 Ok(())
5205 }
5206 fn unbind_keys_in_all_modes(
5207 global_unbind: &KdlNode,
5208 keybinds_from_config: &mut Keybinds,
5209 ) -> Result<(), ConfigError> {
5210 let keys: Vec<KeyWithModifier> = keys_from_kdl!(global_unbind);
5211 for mode in keybinds_from_config.0.values_mut() {
5212 for key in &keys {
5213 mode.remove(&key);
5214 }
5215 }
5216 Ok(())
5217 }
5218 fn input_mode_keybindings<'a>(
5219 mode: &KdlNode,
5220 keybinds_from_config: &'a mut Keybinds,
5221 ) -> Result<&'a mut HashMap<KeyWithModifier, Vec<Action>>, ConfigError> {
5222 let mode_name = kdl_name!(mode);
5223 let input_mode = InputMode::from_str(mode_name).map_err(|_| {
5224 ConfigError::new_kdl_error(
5225 format!("Invalid mode: '{}'", mode_name),
5226 mode.name().span().offset(),
5227 mode.name().span().len(),
5228 )
5229 })?;
5230 let input_mode_keybinds = keybinds_from_config.get_input_mode_mut(&input_mode);
5231 let clear_defaults_for_mode = kdl_arg_is_truthy!(mode, "clear-defaults");
5232 if clear_defaults_for_mode {
5233 input_mode_keybinds.clear();
5234 }
5235 Ok(input_mode_keybinds)
5236 }
5237 pub fn from_string(
5238 stringified_keybindings: String,
5239 base_keybinds: Keybinds,
5240 config_options: &Options,
5241 ) -> Result<Self, ConfigError> {
5242 let document: KdlDocument = stringified_keybindings.parse()?;
5243 if let Some(kdl_keybinds) = document.get("keybinds") {
5244 Keybinds::from_kdl(&kdl_keybinds, base_keybinds, config_options)
5245 } else {
5246 Err(ConfigError::new_kdl_error(
5247 format!("Could not find keybinds node"),
5248 document.span().offset(),
5249 document.span().len(),
5250 ))
5251 }
5252 }
5253 fn minimize_entries(
5256 &self,
5257 ) -> BTreeMap<BTreeSet<InputMode>, BTreeMap<KeyWithModifier, Vec<Action>>> {
5258 let mut minimized: BTreeMap<BTreeSet<InputMode>, BTreeMap<KeyWithModifier, Vec<Action>>> =
5259 BTreeMap::new();
5260 let mut flattened: Vec<BTreeMap<KeyWithModifier, Vec<Action>>> = self
5261 .0
5262 .iter()
5263 .map(|(_input_mode, keybind)| keybind.clone().into_iter().collect())
5264 .collect();
5265 for keybind in flattened.drain(..) {
5266 for (key, actions) in keybind.into_iter() {
5267 let mut appears_in_modes: BTreeSet<InputMode> = BTreeSet::new();
5268 for (input_mode, keybinds) in self.0.iter() {
5269 if keybinds.get(&key) == Some(&actions) {
5270 appears_in_modes.insert(*input_mode);
5271 }
5272 }
5273 minimized
5274 .entry(appears_in_modes)
5275 .or_insert_with(Default::default)
5276 .insert(key, actions);
5277 }
5278 }
5279 minimized
5280 }
5281 fn serialize_mode_title_node(&self, input_modes: &BTreeSet<InputMode>) -> KdlNode {
5282 let all_modes: Vec<InputMode> = InputMode::iter().collect();
5283 let total_input_mode_count = all_modes.len();
5284 if input_modes.len() == 1 {
5285 let input_mode_name =
5286 format!("{:?}", input_modes.iter().next().unwrap()).to_lowercase();
5287 KdlNode::new(input_mode_name)
5288 } else if input_modes.len() == total_input_mode_count {
5289 KdlNode::new("shared")
5290 } else if input_modes.len() < total_input_mode_count / 2 {
5291 let mut node = KdlNode::new("shared_among");
5292 for input_mode in input_modes {
5293 node.push(format!("{:?}", input_mode).to_lowercase());
5294 }
5295 node
5296 } else {
5297 let mut node = KdlNode::new("shared_except");
5298 let mut modes = all_modes.clone();
5299 for input_mode in input_modes {
5300 modes.retain(|m| m != input_mode)
5301 }
5302 for mode in modes {
5303 node.push(format!("{:?}", mode).to_lowercase());
5304 }
5305 node
5306 }
5307 }
5308 fn serialize_mode_keybinds(
5309 &self,
5310 keybinds: &BTreeMap<KeyWithModifier, Vec<Action>>,
5311 ) -> KdlDocument {
5312 let mut mode_keybinds = KdlDocument::new();
5313 for keybind in keybinds {
5314 let mut keybind_node = KdlNode::new("bind");
5315 keybind_node.push(keybind.0.to_kdl());
5316 let mut actions = KdlDocument::new();
5317 let mut actions_have_children = false;
5318 for action in keybind.1 {
5319 if let Some(kdl_action) = action.to_kdl() {
5320 if kdl_action.children().is_some() {
5321 actions_have_children = true;
5322 }
5323 actions.nodes_mut().push(kdl_action);
5324 }
5325 }
5326 if !actions_have_children {
5327 for action in actions.nodes_mut() {
5328 action.set_leading("");
5329 action.set_trailing("; ");
5330 }
5331 actions.set_leading(" ");
5332 actions.set_trailing("");
5333 }
5334 keybind_node.set_children(actions);
5335 mode_keybinds.nodes_mut().push(keybind_node);
5336 }
5337 mode_keybinds
5338 }
5339 pub fn to_kdl(&self, should_clear_defaults: bool) -> KdlNode {
5340 let mut keybinds_node = KdlNode::new("keybinds");
5341 if should_clear_defaults {
5342 keybinds_node.insert("clear-defaults", true);
5343 }
5344 let mut minimized = self.minimize_entries();
5345 let mut keybinds_children = KdlDocument::new();
5346
5347 macro_rules! encode_single_input_mode {
5348 ($mode_name:ident) => {{
5349 if let Some(keybinds) = minimized.remove(&BTreeSet::from([InputMode::$mode_name])) {
5350 let mut mode_node =
5351 KdlNode::new(format!("{:?}", InputMode::$mode_name).to_lowercase());
5352 let mode_keybinds = self.serialize_mode_keybinds(&keybinds);
5353 mode_node.set_children(mode_keybinds);
5354 keybinds_children.nodes_mut().push(mode_node);
5355 }
5356 }};
5357 }
5358 encode_single_input_mode!(Normal);
5361 encode_single_input_mode!(Locked);
5362 encode_single_input_mode!(Pane);
5363 encode_single_input_mode!(Tab);
5364 encode_single_input_mode!(Resize);
5365 encode_single_input_mode!(Move);
5366 encode_single_input_mode!(Scroll);
5367 encode_single_input_mode!(Search);
5368 encode_single_input_mode!(Session);
5369
5370 for (input_modes, keybinds) in minimized {
5371 if input_modes.is_empty() {
5372 log::error!("invalid input mode for keybinds: {:#?}", keybinds);
5373 continue;
5374 }
5375 let mut mode_node = self.serialize_mode_title_node(&input_modes);
5376 let mode_keybinds = self.serialize_mode_keybinds(&keybinds);
5377 mode_node.set_children(mode_keybinds);
5378 keybinds_children.nodes_mut().push(mode_node);
5379 }
5380 keybinds_node.set_children(keybinds_children);
5381 keybinds_node
5382 }
5383}
5384
5385impl KeyWithModifier {
5386 pub fn to_kdl(&self) -> String {
5387 if self.key_modifiers.is_empty() {
5388 self.bare_key.to_kdl()
5389 } else {
5390 format!(
5391 "{} {}",
5392 self.key_modifiers
5393 .iter()
5394 .map(|m| m.to_string())
5395 .collect::<Vec<_>>()
5396 .join(" "),
5397 self.bare_key.to_kdl()
5398 )
5399 }
5400 }
5401}
5402
5403impl BareKey {
5404 pub fn to_kdl(&self) -> String {
5405 match self {
5406 BareKey::PageDown => format!("PageDown"),
5407 BareKey::PageUp => format!("PageUp"),
5408 BareKey::Left => format!("left"),
5409 BareKey::Down => format!("down"),
5410 BareKey::Up => format!("up"),
5411 BareKey::Right => format!("right"),
5412 BareKey::Home => format!("home"),
5413 BareKey::End => format!("end"),
5414 BareKey::Backspace => format!("backspace"),
5415 BareKey::Delete => format!("del"),
5416 BareKey::Insert => format!("insert"),
5417 BareKey::F(index) => format!("F{}", index),
5418 BareKey::Char(' ') => format!("space"),
5419 BareKey::Char(character) => format!("{}", character),
5420 BareKey::Tab => format!("tab"),
5421 BareKey::Esc => format!("esc"),
5422 BareKey::Enter => format!("enter"),
5423 BareKey::CapsLock => format!("capslock"),
5424 BareKey::ScrollLock => format!("scrolllock"),
5425 BareKey::NumLock => format!("numlock"),
5426 BareKey::PrintScreen => format!("printscreen"),
5427 BareKey::Pause => format!("pause"),
5428 BareKey::Menu => format!("menu"),
5429 }
5430 }
5431}
5432
5433impl Config {
5434 pub fn from_kdl(kdl_config: &str, base_config: Option<Config>) -> Result<Config, ConfigError> {
5435 let mut config = base_config.unwrap_or_else(|| Config::default());
5436 let kdl_config: KdlDocument = kdl_config.parse()?;
5437
5438 let config_options = Options::from_kdl(&kdl_config)?;
5439 config.options = config.options.merge(config_options);
5440
5441 if let Some(kdl_keybinds) = kdl_config.get("keybinds") {
5444 config.keybinds = Keybinds::from_kdl(&kdl_keybinds, config.keybinds, &config.options)?;
5445 }
5446 if let Some(kdl_themes) = kdl_config.get("themes") {
5447 let sourced_from_external_file = false;
5448 let config_themes = Themes::from_kdl(kdl_themes, sourced_from_external_file)?;
5449 config.themes = config.themes.merge(config_themes);
5450 }
5451 if let Some(kdl_plugin_aliases) = kdl_config.get("plugins") {
5452 let config_plugins = PluginAliases::from_kdl(kdl_plugin_aliases)?;
5453 config.plugins.merge(config_plugins);
5454 }
5455 if let Some(kdl_load_plugins) = kdl_config.get("load_plugins") {
5456 let load_plugins = load_plugins_from_kdl(kdl_load_plugins)?;
5457 config.background_plugins = load_plugins;
5458 }
5459 if let Some(kdl_ui_config) = kdl_config.get("ui") {
5460 let config_ui = UiConfig::from_kdl(&kdl_ui_config)?;
5461 config.ui = config.ui.merge(config_ui);
5462 }
5463 if let Some(env_config) = kdl_config.get("env") {
5464 let config_env = EnvironmentVariables::from_kdl(&env_config)?;
5465 config.env = config.env.merge(config_env);
5466 }
5467 if let Some(web_client_config) = kdl_config.get("web_client") {
5468 let config_web_client = WebClientConfig::from_kdl(&web_client_config)?;
5469 config.web_client = config.web_client.merge(config_web_client);
5470 }
5471 Ok(config)
5472 }
5473 pub fn to_string(&self, add_comments: bool) -> String {
5474 let mut document = KdlDocument::new();
5475
5476 let clear_defaults = true;
5477 let keybinds = self.keybinds.to_kdl(clear_defaults);
5478 document.nodes_mut().push(keybinds);
5479
5480 if let Some(themes) = self.themes.to_kdl() {
5481 document.nodes_mut().push(themes);
5482 }
5483
5484 let plugins = self.plugins.to_kdl(add_comments);
5485 document.nodes_mut().push(plugins);
5486
5487 let load_plugins = load_plugins_to_kdl(&self.background_plugins, add_comments);
5488 document.nodes_mut().push(load_plugins);
5489
5490 if let Some(ui_config) = self.ui.to_kdl() {
5491 document.nodes_mut().push(ui_config);
5492 }
5493
5494 if let Some(env) = self.env.to_kdl() {
5495 document.nodes_mut().push(env);
5496 }
5497
5498 document.nodes_mut().push(self.web_client.to_kdl());
5499
5500 document
5501 .nodes_mut()
5502 .append(&mut self.options.to_kdl(add_comments));
5503
5504 document.to_string()
5505 }
5506}
5507
5508impl PluginAliases {
5509 pub fn from_kdl(kdl_plugin_aliases: &KdlNode) -> Result<PluginAliases, ConfigError> {
5510 let mut aliases: BTreeMap<String, RunPlugin> = BTreeMap::new();
5511 if let Some(kdl_plugin_aliases) = kdl_children_nodes!(kdl_plugin_aliases) {
5512 for alias_definition in kdl_plugin_aliases {
5513 let alias_name = kdl_name!(alias_definition);
5514 if let Some(string_url) =
5515 kdl_get_string_property_or_child_value!(alias_definition, "location")
5516 {
5517 let configuration =
5518 KdlLayoutParser::parse_plugin_user_configuration(&alias_definition)?;
5519 let initial_cwd =
5520 kdl_get_string_property_or_child_value!(alias_definition, "cwd")
5521 .map(|s| PathBuf::from(s));
5522 let run_plugin = RunPlugin::from_url(string_url)?
5523 .with_configuration(configuration.inner().clone())
5524 .with_initial_cwd(initial_cwd);
5525 aliases.insert(alias_name.to_owned(), run_plugin);
5526 }
5527 }
5528 }
5529 Ok(PluginAliases { aliases })
5530 }
5531 pub fn to_kdl(&self, add_comments: bool) -> KdlNode {
5532 let mut plugins = KdlNode::new("plugins");
5533 let mut plugins_children = KdlDocument::new();
5534 for (alias_name, plugin_alias) in self.aliases.iter() {
5535 let mut plugin_alias_node = KdlNode::new(alias_name.clone());
5536 let mut plugin_alias_children = KdlDocument::new();
5537 let location_string = plugin_alias.location.display();
5538
5539 plugin_alias_node.insert("location", location_string);
5540 let cwd = plugin_alias.initial_cwd.as_ref();
5541 let mut has_children = false;
5542 if let Some(cwd) = cwd {
5543 has_children = true;
5544 let mut cwd_node = KdlNode::new("cwd");
5545 cwd_node.push(cwd.display().to_string());
5546 plugin_alias_children.nodes_mut().push(cwd_node);
5547 }
5548 let configuration = plugin_alias.configuration.inner();
5549 if !configuration.is_empty() {
5550 has_children = true;
5551 for (config_key, config_value) in configuration {
5552 let mut node = KdlNode::new(config_key.to_owned());
5553 if config_value == "true" {
5554 node.push(KdlValue::Bool(true));
5555 } else if config_value == "false" {
5556 node.push(KdlValue::Bool(false));
5557 } else {
5558 node.push(config_value.to_string());
5559 }
5560 plugin_alias_children.nodes_mut().push(node);
5561 }
5562 }
5563 if has_children {
5564 plugin_alias_node.set_children(plugin_alias_children);
5565 }
5566 plugins_children.nodes_mut().push(plugin_alias_node);
5567 }
5568 plugins.set_children(plugins_children);
5569
5570 if add_comments {
5571 plugins.set_leading(format!(
5572 "\n{}\n{}\n",
5573 "// Plugin aliases - can be used to change the implementation of Zellij",
5574 "// changing these requires a restart to take effect",
5575 ));
5576 }
5577 plugins
5578 }
5579}
5580
5581pub fn load_plugins_to_kdl(
5582 background_plugins: &HashSet<RunPluginOrAlias>,
5583 add_comments: bool,
5584) -> KdlNode {
5585 let mut load_plugins = KdlNode::new("load_plugins");
5586 let mut load_plugins_children = KdlDocument::new();
5587 for run_plugin_or_alias in background_plugins.iter() {
5588 let mut background_plugin_node = KdlNode::new(run_plugin_or_alias.location_string());
5589 let mut background_plugin_children = KdlDocument::new();
5590
5591 let cwd = match run_plugin_or_alias {
5592 RunPluginOrAlias::RunPlugin(run_plugin) => run_plugin.initial_cwd.clone(),
5593 RunPluginOrAlias::Alias(plugin_alias) => plugin_alias.initial_cwd.clone(),
5594 };
5595 let mut has_children = false;
5596 if let Some(cwd) = cwd.as_ref() {
5597 has_children = true;
5598 let mut cwd_node = KdlNode::new("cwd");
5599 cwd_node.push(cwd.display().to_string());
5600 background_plugin_children.nodes_mut().push(cwd_node);
5601 }
5602 let configuration = match run_plugin_or_alias {
5603 RunPluginOrAlias::RunPlugin(run_plugin) => {
5604 Some(run_plugin.configuration.inner().clone())
5605 },
5606 RunPluginOrAlias::Alias(plugin_alias) => plugin_alias
5607 .configuration
5608 .as_ref()
5609 .map(|c| c.inner().clone()),
5610 };
5611 if let Some(configuration) = configuration {
5612 if !configuration.is_empty() {
5613 has_children = true;
5614 for (config_key, config_value) in configuration {
5615 let mut node = KdlNode::new(config_key.to_owned());
5616 if config_value == "true" {
5617 node.push(KdlValue::Bool(true));
5618 } else if config_value == "false" {
5619 node.push(KdlValue::Bool(false));
5620 } else {
5621 node.push(config_value.to_string());
5622 }
5623 background_plugin_children.nodes_mut().push(node);
5624 }
5625 }
5626 }
5627 if has_children {
5628 background_plugin_node.set_children(background_plugin_children);
5629 }
5630 load_plugins_children
5631 .nodes_mut()
5632 .push(background_plugin_node);
5633 }
5634 load_plugins.set_children(load_plugins_children);
5635
5636 if add_comments {
5637 load_plugins.set_leading(format!(
5638 "\n{}\n{}\n{}\n",
5639 "// Plugins to load in the background when a new session starts",
5640 "// eg. \"file:/path/to/my-plugin.wasm\"",
5641 "// eg. \"https://example.com/my-plugin.wasm\"",
5642 ));
5643 }
5644 load_plugins
5645}
5646
5647fn load_plugins_from_kdl(
5648 kdl_load_plugins: &KdlNode,
5649) -> Result<HashSet<RunPluginOrAlias>, ConfigError> {
5650 let mut load_plugins: HashSet<RunPluginOrAlias> = HashSet::new();
5651 if let Some(kdl_load_plugins) = kdl_children_nodes!(kdl_load_plugins) {
5652 for plugin_block in kdl_load_plugins {
5653 let url_node = plugin_block.name();
5654 let string_url = url_node.value();
5655 let configuration = KdlLayoutParser::parse_plugin_user_configuration(&plugin_block)?;
5656 let cwd = kdl_get_string_property_or_child_value!(&plugin_block, "cwd")
5657 .map(|s| PathBuf::from(s));
5658 let run_plugin_or_alias = RunPluginOrAlias::from_url(
5659 &string_url,
5660 &Some(configuration.inner().clone()),
5661 None,
5662 cwd.clone(),
5663 )
5664 .map_err(|e| {
5665 ConfigError::new_kdl_error(
5666 format!("Failed to parse plugin: {}", e),
5667 url_node.span().offset(),
5668 url_node.span().len(),
5669 )
5670 })?
5671 .with_initial_cwd(cwd);
5672 load_plugins.insert(run_plugin_or_alias);
5673 }
5674 }
5675 Ok(load_plugins)
5676}
5677
5678impl UiConfig {
5679 pub fn from_kdl(kdl_ui_config: &KdlNode) -> Result<UiConfig, ConfigError> {
5680 let mut ui_config = UiConfig::default();
5681 if let Some(pane_frames) = kdl_get_child!(kdl_ui_config, "pane_frames") {
5682 let rounded_corners =
5683 kdl_children_property_first_arg_as_bool!(pane_frames, "rounded_corners")
5684 .unwrap_or(false);
5685 let hide_session_name =
5686 kdl_get_child_entry_bool_value!(pane_frames, "hide_session_name").unwrap_or(false);
5687 let frame_config = FrameConfig {
5688 rounded_corners,
5689 hide_session_name,
5690 };
5691 ui_config.pane_frames = frame_config;
5692 }
5693 Ok(ui_config)
5694 }
5695 pub fn to_kdl(&self) -> Option<KdlNode> {
5696 let mut ui_config = KdlNode::new("ui");
5697 let mut ui_config_children = KdlDocument::new();
5698 let mut frame_config = KdlNode::new("pane_frames");
5699 let mut frame_config_children = KdlDocument::new();
5700 let mut has_ui_config = false;
5701 if self.pane_frames.rounded_corners {
5702 has_ui_config = true;
5703 let mut rounded_corners = KdlNode::new("rounded_corners");
5704 rounded_corners.push(KdlValue::Bool(true));
5705 frame_config_children.nodes_mut().push(rounded_corners);
5706 }
5707 if self.pane_frames.hide_session_name {
5708 has_ui_config = true;
5709 let mut hide_session_name = KdlNode::new("hide_session_name");
5710 hide_session_name.push(KdlValue::Bool(true));
5711 frame_config_children.nodes_mut().push(hide_session_name);
5712 }
5713 if has_ui_config {
5714 frame_config.set_children(frame_config_children);
5715 ui_config_children.nodes_mut().push(frame_config);
5716 ui_config.set_children(ui_config_children);
5717 Some(ui_config)
5718 } else {
5719 None
5720 }
5721 }
5722}
5723
5724impl Themes {
5725 fn style_declaration_from_node(
5726 style_node: &KdlNode,
5727 style_descriptor: &str,
5728 ) -> Result<Option<StyleDeclaration>, ConfigError> {
5729 let descriptor_node = kdl_child_with_name!(style_node, style_descriptor);
5730
5731 match descriptor_node {
5732 Some(descriptor) => {
5733 let colors = kdl_children_or_error!(
5734 descriptor,
5735 format!("Missing colors for {}", style_descriptor)
5736 );
5737 Ok(Some(StyleDeclaration {
5738 base: PaletteColor::try_from(("base", colors))?,
5739 background: PaletteColor::try_from(("background", colors)).unwrap_or_default(),
5740 emphasis_0: PaletteColor::try_from(("emphasis_0", colors))?,
5741 emphasis_1: PaletteColor::try_from(("emphasis_1", colors))?,
5742 emphasis_2: PaletteColor::try_from(("emphasis_2", colors))?,
5743 emphasis_3: PaletteColor::try_from(("emphasis_3", colors))?,
5744 }))
5745 },
5746 None => Ok(None),
5747 }
5748 }
5749
5750 fn multiplayer_colors(style_node: &KdlNode) -> Result<MultiplayerColors, ConfigError> {
5751 let descriptor_node = kdl_child_with_name!(style_node, "multiplayer_user_colors");
5752 match descriptor_node {
5753 Some(descriptor) => {
5754 let colors = kdl_children_or_error!(
5755 descriptor,
5756 format!("Missing colors for {}", "multiplayer_user_colors")
5757 );
5758 Ok(MultiplayerColors {
5759 player_1: PaletteColor::try_from(("player_1", colors))
5760 .unwrap_or(DEFAULT_STYLES.multiplayer_user_colors.player_1),
5761 player_2: PaletteColor::try_from(("player_2", colors))
5762 .unwrap_or(DEFAULT_STYLES.multiplayer_user_colors.player_2),
5763 player_3: PaletteColor::try_from(("player_3", colors))
5764 .unwrap_or(DEFAULT_STYLES.multiplayer_user_colors.player_3),
5765 player_4: PaletteColor::try_from(("player_4", colors))
5766 .unwrap_or(DEFAULT_STYLES.multiplayer_user_colors.player_4),
5767 player_5: PaletteColor::try_from(("player_5", colors))
5768 .unwrap_or(DEFAULT_STYLES.multiplayer_user_colors.player_5),
5769 player_6: PaletteColor::try_from(("player_6", colors))
5770 .unwrap_or(DEFAULT_STYLES.multiplayer_user_colors.player_6),
5771 player_7: PaletteColor::try_from(("player_7", colors))
5772 .unwrap_or(DEFAULT_STYLES.multiplayer_user_colors.player_7),
5773 player_8: PaletteColor::try_from(("player_8", colors))
5774 .unwrap_or(DEFAULT_STYLES.multiplayer_user_colors.player_8),
5775 player_9: PaletteColor::try_from(("player_9", colors))
5776 .unwrap_or(DEFAULT_STYLES.multiplayer_user_colors.player_9),
5777 player_10: PaletteColor::try_from(("player_10", colors))
5778 .unwrap_or(DEFAULT_STYLES.multiplayer_user_colors.player_10),
5779 })
5780 },
5781 None => Ok(DEFAULT_STYLES.multiplayer_user_colors),
5782 }
5783 }
5784
5785 pub fn from_kdl(
5786 themes_from_kdl: &KdlNode,
5787 sourced_from_external_file: bool,
5788 ) -> Result<Self, ConfigError> {
5789 let mut themes: HashMap<String, Theme> = HashMap::new();
5790 for theme_config in kdl_children_nodes_or_error!(themes_from_kdl, "no themes found") {
5791 let theme_name = kdl_name!(theme_config);
5792 let theme_colors = kdl_children_or_error!(theme_config, "empty theme");
5793 let palette_color_names = HashSet::from([
5794 "fg", "bg", "red", "green", "blue", "yellow", "magenta", "orange", "cyan", "black",
5795 "white",
5796 ]);
5797 let theme = if theme_colors
5798 .nodes()
5799 .iter()
5800 .all(|n| palette_color_names.contains(n.name().value()))
5801 {
5802 let palette = Palette {
5804 fg: PaletteColor::try_from(("fg", theme_colors))?,
5805 bg: PaletteColor::try_from(("bg", theme_colors))?,
5806 red: PaletteColor::try_from(("red", theme_colors))?,
5807 green: PaletteColor::try_from(("green", theme_colors))?,
5808 yellow: PaletteColor::try_from(("yellow", theme_colors))?,
5809 blue: PaletteColor::try_from(("blue", theme_colors))?,
5810 magenta: PaletteColor::try_from(("magenta", theme_colors))?,
5811 orange: PaletteColor::try_from(("orange", theme_colors))?,
5812 cyan: PaletteColor::try_from(("cyan", theme_colors))?,
5813 black: PaletteColor::try_from(("black", theme_colors))?,
5814 white: PaletteColor::try_from(("white", theme_colors))?,
5815 ..Default::default()
5816 };
5817 Theme {
5818 palette: palette.into(),
5819 sourced_from_external_file,
5820 }
5821 } else {
5822 let s = Styling {
5824 text_unselected: Themes::style_declaration_from_node(
5825 theme_config,
5826 "text_unselected",
5827 )
5828 .map(|maybe_style| maybe_style.unwrap_or(DEFAULT_STYLES.text_unselected))?,
5829 text_selected: Themes::style_declaration_from_node(
5830 theme_config,
5831 "text_selected",
5832 )
5833 .map(|maybe_style| maybe_style.unwrap_or(DEFAULT_STYLES.text_selected))?,
5834 ribbon_unselected: Themes::style_declaration_from_node(
5835 theme_config,
5836 "ribbon_unselected",
5837 )
5838 .map(|maybe_style| maybe_style.unwrap_or(DEFAULT_STYLES.ribbon_unselected))?,
5839 ribbon_selected: Themes::style_declaration_from_node(
5840 theme_config,
5841 "ribbon_selected",
5842 )
5843 .map(|maybe_style| maybe_style.unwrap_or(DEFAULT_STYLES.ribbon_selected))?,
5844 table_title: Themes::style_declaration_from_node(theme_config, "table_title")
5845 .map(|maybe_style| {
5846 maybe_style.unwrap_or(DEFAULT_STYLES.table_title)
5847 })?,
5848 table_cell_unselected: Themes::style_declaration_from_node(
5849 theme_config,
5850 "table_cell_unselected",
5851 )
5852 .map(|maybe_style| {
5853 maybe_style.unwrap_or(DEFAULT_STYLES.table_cell_unselected)
5854 })?,
5855 table_cell_selected: Themes::style_declaration_from_node(
5856 theme_config,
5857 "table_cell_selected",
5858 )
5859 .map(|maybe_style| maybe_style.unwrap_or(DEFAULT_STYLES.table_cell_selected))?,
5860 list_unselected: Themes::style_declaration_from_node(
5861 theme_config,
5862 "list_unselected",
5863 )
5864 .map(|maybe_style| maybe_style.unwrap_or(DEFAULT_STYLES.list_unselected))?,
5865 list_selected: Themes::style_declaration_from_node(
5866 theme_config,
5867 "list_selected",
5868 )
5869 .map(|maybe_style| maybe_style.unwrap_or(DEFAULT_STYLES.list_selected))?,
5870 frame_unselected: Themes::style_declaration_from_node(
5871 theme_config,
5872 "frame_unselected",
5873 )?,
5874 frame_selected: Themes::style_declaration_from_node(
5875 theme_config,
5876 "frame_selected",
5877 )
5878 .map(|maybe_style| maybe_style.unwrap_or(DEFAULT_STYLES.frame_selected))?,
5879 frame_highlight: Themes::style_declaration_from_node(
5880 theme_config,
5881 "frame_highlight",
5882 )
5883 .map(|maybe_style| maybe_style.unwrap_or(DEFAULT_STYLES.frame_highlight))?,
5884 exit_code_success: Themes::style_declaration_from_node(
5885 theme_config,
5886 "exit_code_success",
5887 )
5888 .map(|maybe_style| maybe_style.unwrap_or(DEFAULT_STYLES.exit_code_success))?,
5889 exit_code_error: Themes::style_declaration_from_node(
5890 theme_config,
5891 "exit_code_error",
5892 )
5893 .map(|maybe_style| maybe_style.unwrap_or(DEFAULT_STYLES.exit_code_error))?,
5894 multiplayer_user_colors: Themes::multiplayer_colors(theme_config)
5895 .unwrap_or_default(),
5896 };
5897
5898 Theme {
5899 palette: s,
5900 sourced_from_external_file,
5901 }
5902 };
5903 themes.insert(theme_name.into(), theme);
5904 }
5905 let themes = Themes::from_data(themes);
5906 Ok(themes)
5907 }
5908
5909 pub fn from_string(
5910 raw_string: &String,
5911 sourced_from_external_file: bool,
5912 ) -> Result<Self, ConfigError> {
5913 let kdl_config: KdlDocument = raw_string.parse()?;
5914 let kdl_themes = kdl_config.get("themes").ok_or(ConfigError::new_kdl_error(
5915 "No theme node found in file".into(),
5916 kdl_config.span().offset(),
5917 kdl_config.span().len(),
5918 ))?;
5919 let all_themes_in_file = Themes::from_kdl(kdl_themes, sourced_from_external_file)?;
5920 Ok(all_themes_in_file)
5921 }
5922
5923 pub fn from_path(path_to_theme_file: PathBuf) -> Result<Self, ConfigError> {
5924 let kdl_config = std::fs::read_to_string(&path_to_theme_file)
5926 .map_err(|e| ConfigError::IoPath(e, path_to_theme_file.clone()))?;
5927 let sourced_from_external_file = true;
5928 Themes::from_string(&kdl_config, sourced_from_external_file).map_err(|e| match e {
5929 ConfigError::KdlError(kdl_error) => ConfigError::KdlError(
5930 kdl_error.add_src(path_to_theme_file.display().to_string(), kdl_config),
5931 ),
5932 e => e,
5933 })
5934 }
5935
5936 pub fn from_dir(path_to_theme_dir: PathBuf) -> Result<Self, ConfigError> {
5937 let mut themes = Themes::default();
5938 for entry in std::fs::read_dir(&path_to_theme_dir)
5939 .map_err(|e| ConfigError::IoPath(e, path_to_theme_dir.clone()))?
5940 {
5941 let entry = entry.map_err(|e| ConfigError::IoPath(e, path_to_theme_dir.clone()))?;
5942 let path = entry.path();
5943 if let Some(extension) = path.extension() {
5944 if extension == "kdl" {
5945 themes = themes.merge(Themes::from_path(path)?);
5946 }
5947 }
5948 }
5949 Ok(themes)
5950 }
5951 pub fn to_kdl(&self) -> Option<KdlNode> {
5952 let mut theme_node = KdlNode::new("themes");
5953 let mut themes = KdlDocument::new();
5954 let mut has_themes = false;
5955 let sorted_themes: BTreeMap<String, Theme> = self.inner().clone().into_iter().collect();
5956 for (theme_name, theme) in sorted_themes {
5957 if theme.sourced_from_external_file {
5958 continue;
5961 }
5962 has_themes = true;
5963 let mut current_theme_node = KdlNode::new(theme_name.clone());
5964 let mut current_theme_node_children = KdlDocument::new();
5965
5966 current_theme_node_children
5967 .nodes_mut()
5968 .push(theme.palette.text_unselected.to_kdl("text_unselected"));
5969 current_theme_node_children
5970 .nodes_mut()
5971 .push(theme.palette.text_selected.to_kdl("text_selected"));
5972 current_theme_node_children
5973 .nodes_mut()
5974 .push(theme.palette.ribbon_selected.to_kdl("ribbon_selected"));
5975 current_theme_node_children
5976 .nodes_mut()
5977 .push(theme.palette.ribbon_unselected.to_kdl("ribbon_unselected"));
5978 current_theme_node_children
5979 .nodes_mut()
5980 .push(theme.palette.table_title.to_kdl("table_title"));
5981 current_theme_node_children.nodes_mut().push(
5982 theme
5983 .palette
5984 .table_cell_selected
5985 .to_kdl("table_cell_selected"),
5986 );
5987 current_theme_node_children.nodes_mut().push(
5988 theme
5989 .palette
5990 .table_cell_unselected
5991 .to_kdl("table_cell_unselected"),
5992 );
5993 current_theme_node_children
5994 .nodes_mut()
5995 .push(theme.palette.list_selected.to_kdl("list_selected"));
5996 current_theme_node_children
5997 .nodes_mut()
5998 .push(theme.palette.list_unselected.to_kdl("list_unselected"));
5999 current_theme_node_children
6000 .nodes_mut()
6001 .push(theme.palette.frame_selected.to_kdl("frame_selected"));
6002
6003 match theme.palette.frame_unselected {
6004 None => {},
6005 Some(frame_unselected_style) => {
6006 current_theme_node_children
6007 .nodes_mut()
6008 .push(frame_unselected_style.to_kdl("frame_unselected"));
6009 },
6010 }
6011 current_theme_node_children
6012 .nodes_mut()
6013 .push(theme.palette.frame_highlight.to_kdl("frame_highlight"));
6014 current_theme_node_children
6015 .nodes_mut()
6016 .push(theme.palette.exit_code_success.to_kdl("exit_code_success"));
6017 current_theme_node_children
6018 .nodes_mut()
6019 .push(theme.palette.exit_code_error.to_kdl("exit_code_error"));
6020 current_theme_node_children
6021 .nodes_mut()
6022 .push(theme.palette.multiplayer_user_colors.to_kdl());
6023 current_theme_node.set_children(current_theme_node_children);
6024 themes.nodes_mut().push(current_theme_node);
6025 }
6026 if has_themes {
6027 theme_node.set_children(themes);
6028 Some(theme_node)
6029 } else {
6030 None
6031 }
6032 }
6033}
6034
6035impl PermissionCache {
6036 pub fn from_string(raw_string: String) -> Result<GrantedPermission, ConfigError> {
6037 let kdl_document: KdlDocument = raw_string.parse()?;
6038
6039 let mut granted_permission = GrantedPermission::default();
6040
6041 for node in kdl_document.nodes() {
6042 if let Some(children) = node.children() {
6043 let key = kdl_name!(node);
6044 let permissions: Vec<PermissionType> = children
6045 .nodes()
6046 .iter()
6047 .filter_map(|p| {
6048 let v = kdl_name!(p);
6049 PermissionType::from_str(v).ok()
6050 })
6051 .collect();
6052
6053 granted_permission.insert(key.into(), permissions);
6054 }
6055 }
6056
6057 Ok(granted_permission)
6058 }
6059
6060 pub fn to_string(granted: &GrantedPermission) -> String {
6061 let mut kdl_doucment = KdlDocument::new();
6062
6063 granted.iter().for_each(|(k, v)| {
6064 let mut node = KdlNode::new(k.as_str());
6065 let mut children = KdlDocument::new();
6066
6067 let permissions: HashSet<PermissionType> = v.clone().into_iter().collect();
6068 permissions.iter().for_each(|f| {
6069 let n = KdlNode::new(f.to_string().as_str());
6070 children.nodes_mut().push(n);
6071 });
6072
6073 node.set_children(children);
6074 kdl_doucment.nodes_mut().push(node);
6075 });
6076
6077 kdl_doucment.fmt();
6078 kdl_doucment.to_string()
6079 }
6080}
6081
6082impl SessionInfo {
6083 pub fn from_string(raw_session_info: &str, current_session_name: &str) -> Result<Self, String> {
6084 let kdl_document: KdlDocument = raw_session_info
6085 .parse()
6086 .map_err(|e| format!("Failed to parse kdl document: {}", e))?;
6087 let name = kdl_document
6088 .get("name")
6089 .and_then(|n| n.entries().iter().next())
6090 .and_then(|e| e.value().as_string())
6091 .map(|s| s.to_owned())
6092 .ok_or("Failed to parse session name")?;
6093 let connected_clients = kdl_document
6094 .get("connected_clients")
6095 .and_then(|n| n.entries().iter().next())
6096 .and_then(|e| e.value().as_i64())
6097 .map(|c| c as usize)
6098 .ok_or("Failed to parse connected_clients")?;
6099 let tabs: Vec<TabInfo> = kdl_document
6100 .get("tabs")
6101 .and_then(|t| t.children())
6102 .and_then(|c| {
6103 let mut tab_nodes = vec![];
6104 for tab_node in c.nodes() {
6105 if let Some(tab) = tab_node.children() {
6106 tab_nodes.push(TabInfo::decode_from_kdl(tab).ok()?);
6107 }
6108 }
6109 Some(tab_nodes)
6110 })
6111 .ok_or("Failed to parse tabs")?;
6112 let panes: PaneManifest = kdl_document
6113 .get("panes")
6114 .and_then(|p| p.children())
6115 .map(|p| PaneManifest::decode_from_kdl(p))
6116 .ok_or("Failed to parse panes")?;
6117 let available_layouts: Vec<LayoutInfo> = kdl_document
6118 .get("available_layouts")
6119 .and_then(|p| p.children())
6120 .map(|e| {
6121 e.nodes()
6122 .iter()
6123 .filter_map(|n| {
6124 let layout_name = n.name().value().to_owned();
6125 let layout_source = n
6126 .entries()
6127 .iter()
6128 .find(|e| e.name().map(|n| n.value()) == Some("source"))
6129 .and_then(|e| e.value().as_string());
6130 match layout_source {
6131 Some(layout_source) => match layout_source {
6132 "built-in" => Some(LayoutInfo::BuiltIn(layout_name)),
6133 "file" => {
6134 Some(LayoutInfo::File(layout_name, LayoutMetadata::default()))
6135 },
6136 _ => None,
6137 },
6138 None => None,
6139 }
6140 })
6141 .collect()
6142 })
6143 .ok_or("Failed to parse available_layouts")?;
6144 let web_client_count = kdl_document
6145 .get("web_client_count")
6146 .and_then(|n| n.entries().iter().next())
6147 .and_then(|e| e.value().as_i64())
6148 .map(|c| c as usize)
6149 .unwrap_or(0);
6150 let web_clients_allowed = kdl_document
6151 .get("web_clients_allowed")
6152 .and_then(|n| n.entries().iter().next())
6153 .and_then(|e| e.value().as_bool())
6154 .unwrap_or(false);
6155 let is_current_session = name == current_session_name;
6156 let mut tab_history = BTreeMap::new();
6157 if let Some(kdl_tab_history) = kdl_document.get("tab_history").and_then(|p| p.children()) {
6158 for client_node in kdl_tab_history.nodes() {
6159 if let Some(client_id) = client_node.children().and_then(|c| {
6160 c.get("id")
6161 .and_then(|c| c.entries().iter().next().and_then(|e| e.value().as_i64()))
6162 }) {
6163 let mut history = vec![];
6164 if let Some(history_entries) = client_node
6165 .children()
6166 .and_then(|c| c.get("history"))
6167 .map(|h| h.entries())
6168 {
6169 for entry in history_entries {
6170 if let Some(entry) = entry.value().as_i64() {
6171 history.push(entry as usize);
6172 }
6173 }
6174 }
6175 tab_history.insert(client_id as u16, history);
6176 }
6177 }
6178 }
6179 let mut pane_history = BTreeMap::new();
6180 if let Some(kdl_pane_history) = kdl_document.get("pane_history").and_then(|p| p.children())
6181 {
6182 for client_node in kdl_pane_history.nodes() {
6183 if let Some(client_id) = client_node.children().and_then(|c| {
6184 c.get("id")
6185 .and_then(|c| c.entries().iter().next().and_then(|e| e.value().as_i64()))
6186 }) {
6187 let mut history = vec![];
6188 if let Some(history_node) =
6189 client_node.children().and_then(|c| c.get("history"))
6190 {
6191 if let Some(history_children) = history_node.children() {
6192 for pane_id_node in history_children.nodes() {
6193 if pane_id_node.name().value() == "pane_id" {
6194 let pane_type = pane_id_node
6195 .entries()
6196 .iter()
6197 .find(|e| e.name().map(|n| n.value()) == Some("type"))
6198 .and_then(|e| e.value().as_string());
6199 let id = pane_id_node
6200 .entries()
6201 .iter()
6202 .find(|e| e.name().is_none())
6203 .and_then(|e| e.value().as_i64())
6204 .map(|i| i as u32);
6205 if let (Some(pane_type), Some(id)) = (pane_type, id) {
6206 let pane_id = match pane_type {
6207 "terminal" => Some(PaneId::Terminal(id)),
6208 "plugin" => Some(PaneId::Plugin(id)),
6209 _ => None,
6210 };
6211 if let Some(pane_id) = pane_id {
6212 history.push(pane_id);
6213 }
6214 }
6215 }
6216 }
6217 }
6218 }
6219 pane_history.insert(client_id as u16, history);
6220 }
6221 }
6222 }
6223 let creation_time = kdl_document
6224 .get("creation_time")
6225 .and_then(|n| n.entries().iter().next())
6226 .and_then(|e| e.value().as_i64())
6227 .map(|c| Duration::from_secs(c as u64))
6228 .unwrap_or_default();
6229 Ok(SessionInfo {
6230 name,
6231 tabs,
6232 panes,
6233 connected_clients,
6234 is_current_session,
6235 available_layouts,
6236 web_client_count,
6237 web_clients_allowed,
6238 plugins: Default::default(), tab_history,
6240 pane_history,
6241 creation_time,
6242 })
6243 }
6244 pub fn to_string(&self) -> String {
6245 let mut kdl_document = KdlDocument::new();
6246
6247 let mut name = KdlNode::new("name");
6248 name.push(self.name.clone());
6249
6250 let mut connected_clients = KdlNode::new("connected_clients");
6251 connected_clients.push(self.connected_clients as i64);
6252
6253 let mut tabs = KdlNode::new("tabs");
6254 let mut tab_children = KdlDocument::new();
6255 for tab_info in &self.tabs {
6256 let mut tab = KdlNode::new("tab");
6257 let kdl_tab_info = tab_info.encode_to_kdl();
6258 tab.set_children(kdl_tab_info);
6259 tab_children.nodes_mut().push(tab);
6260 }
6261 tabs.set_children(tab_children);
6262
6263 let mut panes = KdlNode::new("panes");
6264 panes.set_children(self.panes.encode_to_kdl());
6265
6266 let mut web_client_count = KdlNode::new("web_client_count");
6267 web_client_count.push(self.web_client_count as i64);
6268
6269 let mut web_clients_allowed = KdlNode::new("web_clients_allowed");
6270 web_clients_allowed.push(self.web_clients_allowed);
6271
6272 let mut available_layouts = KdlNode::new("available_layouts");
6273 let mut available_layouts_children = KdlDocument::new();
6274 for layout_info in &self.available_layouts {
6275 let (layout_name, layout_source) = match layout_info {
6276 LayoutInfo::File(name, _layout_metadata) => (name.clone(), "file"),
6277 LayoutInfo::BuiltIn(name) => (name.clone(), "built-in"),
6278 LayoutInfo::Url(url) => (url.clone(), "url"),
6279 LayoutInfo::Stringified(_stringified) => ("stringified-layout".to_owned(), "N/A"),
6280 };
6281 let mut layout_node = KdlNode::new(format!("{}", layout_name));
6282 let layout_source = KdlEntry::new_prop("source", layout_source);
6283 layout_node.entries_mut().push(layout_source);
6284 available_layouts_children.nodes_mut().push(layout_node);
6285 }
6286 available_layouts.set_children(available_layouts_children);
6287
6288 let mut tab_history = KdlNode::new("tab_history");
6289 let mut tab_history_children = KdlDocument::new();
6290 for (client_id, client_tab_history) in &self.tab_history {
6291 let mut client_document = KdlDocument::new();
6292 let mut client_node = KdlNode::new("client");
6293 let mut id = KdlNode::new("id");
6294 id.push(*client_id as i64);
6295 client_document.nodes_mut().push(id);
6296 let mut history = KdlNode::new("history");
6297 for entry in client_tab_history {
6298 history.push(*entry as i64);
6299 }
6300 client_document.nodes_mut().push(history);
6301 client_node.set_children(client_document);
6302 tab_history_children.nodes_mut().push(client_node);
6303 }
6304 tab_history.set_children(tab_history_children);
6305
6306 let mut pane_history = KdlNode::new("pane_history");
6307 let mut pane_history_children = KdlDocument::new();
6308 for (client_id, client_pane_history) in &self.pane_history {
6309 let mut client_document = KdlDocument::new();
6310 let mut client_node = KdlNode::new("client");
6311 let mut id = KdlNode::new("id");
6312 id.push(*client_id as i64);
6313 client_document.nodes_mut().push(id);
6314 let mut history = KdlNode::new("history");
6315 for pane_id in client_pane_history {
6316 let mut pane_id_node = KdlNode::new("pane_id");
6317 match pane_id {
6318 PaneId::Terminal(id) => {
6319 pane_id_node.push(KdlEntry::new_prop("type", "terminal"));
6320 pane_id_node.push(*id as i64);
6321 },
6322 PaneId::Plugin(id) => {
6323 pane_id_node.push(KdlEntry::new_prop("type", "plugin"));
6324 pane_id_node.push(*id as i64);
6325 },
6326 }
6327 history.ensure_children().nodes_mut().push(pane_id_node);
6328 }
6329 client_document.nodes_mut().push(history);
6330 client_node.set_children(client_document);
6331 pane_history_children.nodes_mut().push(client_node);
6332 }
6333 pane_history.set_children(pane_history_children);
6334
6335 kdl_document.nodes_mut().push(name);
6336 kdl_document.nodes_mut().push(tabs);
6337 kdl_document.nodes_mut().push(panes);
6338 kdl_document.nodes_mut().push(connected_clients);
6339 kdl_document.nodes_mut().push(web_clients_allowed);
6340 kdl_document.nodes_mut().push(web_client_count);
6341 kdl_document.nodes_mut().push(available_layouts);
6342 kdl_document.nodes_mut().push(tab_history);
6343 kdl_document.nodes_mut().push(pane_history);
6344
6345 let mut creation_time_node = KdlNode::new("creation_time");
6346 creation_time_node.push(self.creation_time.as_secs() as i64);
6347 kdl_document.nodes_mut().push(creation_time_node);
6348
6349 kdl_document.fmt();
6350 kdl_document.to_string()
6351 }
6352}
6353
6354impl TabInfo {
6355 pub fn decode_from_kdl(kdl_document: &KdlDocument) -> Result<Self, String> {
6356 macro_rules! int_node {
6357 ($name:expr, $type:ident) => {{
6358 kdl_document
6359 .get($name)
6360 .and_then(|n| n.entries().iter().next())
6361 .and_then(|e| e.value().as_i64())
6362 .map(|e| e as $type)
6363 .ok_or(format!("Failed to parse tab {}", $name))?
6364 }};
6365 }
6366 macro_rules! string_node {
6367 ($name:expr) => {{
6368 kdl_document
6369 .get($name)
6370 .and_then(|n| n.entries().iter().next())
6371 .and_then(|e| e.value().as_string())
6372 .map(|s| s.to_owned())
6373 .ok_or(format!("Failed to parse tab {}", $name))?
6374 }};
6375 }
6376 macro_rules! optional_string_node {
6377 ($name:expr) => {{
6378 kdl_document
6379 .get($name)
6380 .and_then(|n| n.entries().iter().next())
6381 .and_then(|e| e.value().as_string())
6382 .map(|s| s.to_owned())
6383 }};
6384 }
6385 macro_rules! optional_int_node {
6386 ($name:expr, $type:ident) => {{
6387 kdl_document
6388 .get($name)
6389 .and_then(|n| n.entries().iter().next())
6390 .and_then(|e| e.value().as_i64())
6391 .map(|e| e as $type)
6392 }};
6393 }
6394 macro_rules! bool_node {
6395 ($name:expr) => {{
6396 kdl_document
6397 .get($name)
6398 .and_then(|n| n.entries().iter().next())
6399 .and_then(|e| e.value().as_bool())
6400 .ok_or(format!("Failed to parse tab {}", $name))?
6401 }};
6402 }
6403
6404 let position = int_node!("position", usize);
6405 let name = string_node!("name");
6406 let active = bool_node!("active");
6407 let panes_to_hide = int_node!("panes_to_hide", usize);
6408 let is_fullscreen_active = bool_node!("is_fullscreen_active");
6409 let is_sync_panes_active = bool_node!("is_sync_panes_active");
6410 let are_floating_panes_visible = bool_node!("are_floating_panes_visible");
6411 let mut other_focused_clients = vec![];
6412 if let Some(tab_other_focused_clients) = kdl_document
6413 .get("other_focused_clients")
6414 .map(|n| n.entries())
6415 {
6416 for entry in tab_other_focused_clients {
6417 if let Some(entry_parsed) = entry.value().as_i64() {
6418 other_focused_clients.push(entry_parsed as u16);
6419 }
6420 }
6421 }
6422 let active_swap_layout_name = optional_string_node!("active_swap_layout_name");
6423 let viewport_rows = optional_int_node!("viewport_rows", usize).unwrap_or(0);
6424 let viewport_columns = optional_int_node!("viewport_columns", usize).unwrap_or(0);
6425 let display_area_rows = optional_int_node!("display_area_rows", usize).unwrap_or(0);
6426 let display_area_columns = optional_int_node!("display_area_columns", usize).unwrap_or(0);
6427 let is_swap_layout_dirty = bool_node!("is_swap_layout_dirty");
6428 let selectable_tiled_panes_count =
6429 optional_int_node!("selectable_tiled_panes_count", usize).unwrap_or(0);
6430 let selectable_floating_panes_count =
6431 optional_int_node!("selectable_floating_panes_count", usize).unwrap_or(0);
6432 let tab_id = optional_int_node!("tab_id", usize).unwrap_or(0);
6433 Ok(TabInfo {
6434 position,
6435 name,
6436 active,
6437 panes_to_hide,
6438 is_fullscreen_active,
6439 is_sync_panes_active,
6440 are_floating_panes_visible,
6441 other_focused_clients,
6442 active_swap_layout_name,
6443 is_swap_layout_dirty,
6444 viewport_rows,
6445 viewport_columns,
6446 display_area_rows,
6447 display_area_columns,
6448 selectable_tiled_panes_count,
6449 selectable_floating_panes_count,
6450 tab_id,
6451 has_bell_notification: false,
6452 is_flashing_bell: false,
6453 })
6454 }
6455 pub fn encode_to_kdl(&self) -> KdlDocument {
6456 let mut kdl_doucment = KdlDocument::new();
6457
6458 let mut position = KdlNode::new("position");
6459 position.push(self.position as i64);
6460 kdl_doucment.nodes_mut().push(position);
6461
6462 let mut name = KdlNode::new("name");
6463 name.push(self.name.clone());
6464 kdl_doucment.nodes_mut().push(name);
6465
6466 let mut active = KdlNode::new("active");
6467 active.push(self.active);
6468 kdl_doucment.nodes_mut().push(active);
6469
6470 let mut panes_to_hide = KdlNode::new("panes_to_hide");
6471 panes_to_hide.push(self.panes_to_hide as i64);
6472 kdl_doucment.nodes_mut().push(panes_to_hide);
6473
6474 let mut is_fullscreen_active = KdlNode::new("is_fullscreen_active");
6475 is_fullscreen_active.push(self.is_fullscreen_active);
6476 kdl_doucment.nodes_mut().push(is_fullscreen_active);
6477
6478 let mut is_sync_panes_active = KdlNode::new("is_sync_panes_active");
6479 is_sync_panes_active.push(self.is_sync_panes_active);
6480 kdl_doucment.nodes_mut().push(is_sync_panes_active);
6481
6482 let mut are_floating_panes_visible = KdlNode::new("are_floating_panes_visible");
6483 are_floating_panes_visible.push(self.are_floating_panes_visible);
6484 kdl_doucment.nodes_mut().push(are_floating_panes_visible);
6485
6486 if !self.other_focused_clients.is_empty() {
6487 let mut other_focused_clients = KdlNode::new("other_focused_clients");
6488 for client_id in &self.other_focused_clients {
6489 other_focused_clients.push(*client_id as i64);
6490 }
6491 kdl_doucment.nodes_mut().push(other_focused_clients);
6492 }
6493
6494 if let Some(active_swap_layout_name) = self.active_swap_layout_name.as_ref() {
6495 let mut active_swap_layout = KdlNode::new("active_swap_layout_name");
6496 active_swap_layout.push(active_swap_layout_name.to_string());
6497 kdl_doucment.nodes_mut().push(active_swap_layout);
6498 }
6499
6500 let mut viewport_rows = KdlNode::new("viewport_rows");
6501 viewport_rows.push(self.viewport_rows as i64);
6502 kdl_doucment.nodes_mut().push(viewport_rows);
6503
6504 let mut viewport_columns = KdlNode::new("viewport_columns");
6505 viewport_columns.push(self.viewport_columns as i64);
6506 kdl_doucment.nodes_mut().push(viewport_columns);
6507
6508 let mut display_area_columns = KdlNode::new("display_area_columns");
6509 display_area_columns.push(self.display_area_columns as i64);
6510 kdl_doucment.nodes_mut().push(display_area_columns);
6511
6512 let mut display_area_rows = KdlNode::new("display_area_rows");
6513 display_area_rows.push(self.display_area_rows as i64);
6514 kdl_doucment.nodes_mut().push(display_area_rows);
6515
6516 let mut is_swap_layout_dirty = KdlNode::new("is_swap_layout_dirty");
6517 is_swap_layout_dirty.push(self.is_swap_layout_dirty);
6518 kdl_doucment.nodes_mut().push(is_swap_layout_dirty);
6519
6520 let mut selectable_tiled_panes_count = KdlNode::new("selectable_tiled_panes_count");
6521 selectable_tiled_panes_count.push(self.selectable_tiled_panes_count as i64);
6522 kdl_doucment.nodes_mut().push(selectable_tiled_panes_count);
6523
6524 let mut selectable_floating_panes_count = KdlNode::new("selectable_floating_panes_count");
6525 selectable_floating_panes_count.push(self.selectable_floating_panes_count as i64);
6526 kdl_doucment
6527 .nodes_mut()
6528 .push(selectable_floating_panes_count);
6529
6530 let mut tab_id = KdlNode::new("tab_id");
6531 tab_id.push(self.tab_id as i64);
6532 kdl_doucment.nodes_mut().push(tab_id);
6533
6534 kdl_doucment
6535 }
6536}
6537
6538impl PaneManifest {
6539 pub fn decode_from_kdl(kdl_doucment: &KdlDocument) -> Self {
6540 let mut panes: HashMap<usize, Vec<PaneInfo>> = HashMap::new();
6541 for node in kdl_doucment.nodes() {
6542 if node.name().to_string() == "pane" {
6543 if let Some(pane_document) = node.children() {
6544 if let Ok((tab_position, pane_info)) = PaneInfo::decode_from_kdl(pane_document)
6545 {
6546 let panes_in_tab_position =
6547 panes.entry(tab_position).or_insert_with(Vec::new);
6548 panes_in_tab_position.push(pane_info);
6549 }
6550 }
6551 }
6552 }
6553 PaneManifest { panes }
6554 }
6555 pub fn encode_to_kdl(&self) -> KdlDocument {
6556 let mut kdl_doucment = KdlDocument::new();
6557 for (tab_position, panes) in &self.panes {
6558 for pane in panes {
6559 let mut pane_node = KdlNode::new("pane");
6560 let mut pane = pane.encode_to_kdl();
6561
6562 let mut position_node = KdlNode::new("tab_position");
6563 position_node.push(*tab_position as i64);
6564 pane.nodes_mut().push(position_node);
6565
6566 pane_node.set_children(pane);
6567 kdl_doucment.nodes_mut().push(pane_node);
6568 }
6569 }
6570 kdl_doucment
6571 }
6572}
6573
6574impl PaneInfo {
6575 pub fn decode_from_kdl(kdl_document: &KdlDocument) -> Result<(usize, Self), String> {
6576 macro_rules! int_node {
6578 ($name:expr, $type:ident) => {{
6579 kdl_document
6580 .get($name)
6581 .and_then(|n| n.entries().iter().next())
6582 .and_then(|e| e.value().as_i64())
6583 .map(|e| e as $type)
6584 .ok_or(format!("Failed to parse pane {}", $name))?
6585 }};
6586 }
6587 macro_rules! optional_int_node {
6588 ($name:expr, $type:ident) => {{
6589 kdl_document
6590 .get($name)
6591 .and_then(|n| n.entries().iter().next())
6592 .and_then(|e| e.value().as_i64())
6593 .map(|e| e as $type)
6594 }};
6595 }
6596 macro_rules! bool_node {
6597 ($name:expr) => {{
6598 kdl_document
6599 .get($name)
6600 .and_then(|n| n.entries().iter().next())
6601 .and_then(|e| e.value().as_bool())
6602 .ok_or(format!("Failed to parse pane {}", $name))?
6603 }};
6604 }
6605 macro_rules! string_node {
6606 ($name:expr) => {{
6607 kdl_document
6608 .get($name)
6609 .and_then(|n| n.entries().iter().next())
6610 .and_then(|e| e.value().as_string())
6611 .map(|s| s.to_owned())
6612 .ok_or(format!("Failed to parse pane {}", $name))?
6613 }};
6614 }
6615 macro_rules! optional_string_node {
6616 ($name:expr) => {{
6617 kdl_document
6618 .get($name)
6619 .and_then(|n| n.entries().iter().next())
6620 .and_then(|e| e.value().as_string())
6621 .map(|s| s.to_owned())
6622 }};
6623 }
6624 let tab_position = int_node!("tab_position", usize);
6625 let id = int_node!("id", u32);
6626
6627 let is_plugin = bool_node!("is_plugin");
6628 let is_focused = bool_node!("is_focused");
6629 let is_fullscreen = bool_node!("is_fullscreen");
6630 let is_floating = bool_node!("is_floating");
6631 let is_suppressed = bool_node!("is_suppressed");
6632 let title = string_node!("title");
6633 let exited = bool_node!("exited");
6634 let exit_status = optional_int_node!("exit_status", i32);
6635 let is_held = bool_node!("is_held");
6636 let pane_x = int_node!("pane_x", usize);
6637 let pane_content_x = int_node!("pane_content_x", usize);
6638 let pane_y = int_node!("pane_y", usize);
6639 let pane_content_y = int_node!("pane_content_y", usize);
6640 let pane_rows = int_node!("pane_rows", usize);
6641 let pane_content_rows = int_node!("pane_content_rows", usize);
6642 let pane_columns = int_node!("pane_columns", usize);
6643 let pane_content_columns = int_node!("pane_content_columns", usize);
6644 let cursor_coordinates_in_pane = kdl_document
6645 .get("cursor_coordinates_in_pane")
6646 .map(|n| {
6647 let mut entries = n.entries().iter();
6648 (entries.next(), entries.next())
6649 })
6650 .and_then(|(x, y)| {
6651 let x = x.and_then(|x| x.value().as_i64()).map(|x| x as usize);
6652 let y = y.and_then(|y| y.value().as_i64()).map(|y| y as usize);
6653 match (x, y) {
6654 (Some(x), Some(y)) => Some((x, y)),
6655 _ => None,
6656 }
6657 });
6658 let terminal_command = optional_string_node!("terminal_command");
6659 let plugin_url = optional_string_node!("plugin_url");
6660 let is_selectable = bool_node!("is_selectable");
6661
6662 let pane_info = PaneInfo {
6663 id,
6664 is_plugin,
6665 is_focused,
6666 is_fullscreen,
6667 is_floating,
6668 is_suppressed,
6669 title,
6670 exited,
6671 exit_status,
6672 is_held,
6673 pane_x,
6674 pane_content_x,
6675 pane_y,
6676 pane_content_y,
6677 pane_rows,
6678 pane_content_rows,
6679 pane_columns,
6680 pane_content_columns,
6681 cursor_coordinates_in_pane,
6682 terminal_command,
6683 plugin_url,
6684 is_selectable,
6685 index_in_pane_group: Default::default(), default_fg: None,
6687 default_bg: None,
6688 };
6689 Ok((tab_position, pane_info))
6690 }
6691 pub fn encode_to_kdl(&self) -> KdlDocument {
6692 let mut kdl_doucment = KdlDocument::new();
6693 macro_rules! int_node {
6694 ($name:expr, $val:expr) => {{
6695 let mut att = KdlNode::new($name);
6696 att.push($val as i64);
6697 kdl_doucment.nodes_mut().push(att);
6698 }};
6699 }
6700 macro_rules! bool_node {
6701 ($name:expr, $val:expr) => {{
6702 let mut att = KdlNode::new($name);
6703 att.push($val);
6704 kdl_doucment.nodes_mut().push(att);
6705 }};
6706 }
6707 macro_rules! string_node {
6708 ($name:expr, $val:expr) => {{
6709 let mut att = KdlNode::new($name);
6710 att.push($val);
6711 kdl_doucment.nodes_mut().push(att);
6712 }};
6713 }
6714
6715 int_node!("id", self.id);
6716 bool_node!("is_plugin", self.is_plugin);
6717 bool_node!("is_focused", self.is_focused);
6718 bool_node!("is_fullscreen", self.is_fullscreen);
6719 bool_node!("is_floating", self.is_floating);
6720 bool_node!("is_suppressed", self.is_suppressed);
6721 string_node!("title", self.title.to_string());
6722 bool_node!("exited", self.exited);
6723 if let Some(exit_status) = self.exit_status {
6724 int_node!("exit_status", exit_status);
6725 }
6726 bool_node!("is_held", self.is_held);
6727 int_node!("pane_x", self.pane_x);
6728 int_node!("pane_content_x", self.pane_content_x);
6729 int_node!("pane_y", self.pane_y);
6730 int_node!("pane_content_y", self.pane_content_y);
6731 int_node!("pane_rows", self.pane_rows);
6732 int_node!("pane_content_rows", self.pane_content_rows);
6733 int_node!("pane_columns", self.pane_columns);
6734 int_node!("pane_content_columns", self.pane_content_columns);
6735 if let Some((cursor_x, cursor_y)) = self.cursor_coordinates_in_pane {
6736 let mut cursor_coordinates_in_pane = KdlNode::new("cursor_coordinates_in_pane");
6737 cursor_coordinates_in_pane.push(cursor_x as i64);
6738 cursor_coordinates_in_pane.push(cursor_y as i64);
6739 kdl_doucment.nodes_mut().push(cursor_coordinates_in_pane);
6740 }
6741 if let Some(terminal_command) = &self.terminal_command {
6742 string_node!("terminal_command", terminal_command.to_string());
6743 }
6744 if let Some(plugin_url) = &self.plugin_url {
6745 string_node!("plugin_url", plugin_url.to_string());
6746 }
6747 bool_node!("is_selectable", self.is_selectable);
6748 kdl_doucment
6749 }
6750}
6751
6752pub fn parse_plugin_user_configuration(
6753 plugin_block: &KdlNode,
6754) -> Result<BTreeMap<String, String>, ConfigError> {
6755 let mut configuration = BTreeMap::new();
6756 for user_configuration_entry in plugin_block.entries() {
6757 let name = user_configuration_entry.name();
6758 let value = user_configuration_entry.value();
6759 if let Some(name) = name {
6760 let name = name.to_string();
6761 if KdlLayoutParser::is_a_reserved_plugin_property(&name) {
6762 continue;
6763 }
6764 configuration.insert(name, value.to_string());
6765 }
6766 }
6767 if let Some(user_config) = kdl_children_nodes!(plugin_block) {
6768 for user_configuration_entry in user_config {
6769 let config_entry_name = kdl_name!(user_configuration_entry);
6770 if KdlLayoutParser::is_a_reserved_plugin_property(&config_entry_name) {
6771 continue;
6772 }
6773 let config_entry_str_value = kdl_first_entry_as_string!(user_configuration_entry)
6774 .map(|s| format!("{}", s.to_string()));
6775 let config_entry_int_value = kdl_first_entry_as_i64!(user_configuration_entry)
6776 .map(|s| format!("{}", s.to_string()));
6777 let config_entry_bool_value = kdl_first_entry_as_bool!(user_configuration_entry)
6778 .map(|s| format!("{}", s.to_string()));
6779 let config_entry_children = user_configuration_entry
6780 .children()
6781 .map(|s| format!("{}", s.to_string().trim()));
6782 let config_entry_value = config_entry_str_value
6783 .or(config_entry_int_value)
6784 .or(config_entry_bool_value)
6785 .or(config_entry_children)
6786 .ok_or(ConfigError::new_kdl_error(
6787 format!(
6788 "Failed to parse plugin block configuration: {:?}",
6789 user_configuration_entry
6790 ),
6791 plugin_block.span().offset(),
6792 plugin_block.span().len(),
6793 ))?;
6794 configuration.insert(config_entry_name.into(), config_entry_value);
6795 }
6796 }
6797 Ok(configuration)
6798}
6799
6800#[test]
6801fn serialize_and_deserialize_session_info() {
6802 let session_info = SessionInfo::default();
6803 let serialized = session_info.to_string();
6804 let deserealized = SessionInfo::from_string(&serialized, "not this session").unwrap();
6805 assert_eq!(session_info, deserealized);
6806 insta::assert_snapshot!(serialized);
6807}
6808
6809#[test]
6810fn serialize_and_deserialize_session_info_with_data() {
6811 let panes_list = vec![
6812 PaneInfo {
6813 id: 1,
6814 is_plugin: false,
6815 is_focused: true,
6816 is_fullscreen: true,
6817 is_floating: false,
6818 is_suppressed: false,
6819 title: "pane 1".to_owned(),
6820 exited: false,
6821 exit_status: None,
6822 is_held: false,
6823 pane_x: 0,
6824 pane_content_x: 1,
6825 pane_y: 0,
6826 pane_content_y: 1,
6827 pane_rows: 5,
6828 pane_content_rows: 4,
6829 pane_columns: 22,
6830 pane_content_columns: 21,
6831 cursor_coordinates_in_pane: Some((0, 0)),
6832 terminal_command: Some("foo".to_owned()),
6833 plugin_url: None,
6834 is_selectable: true,
6835 index_in_pane_group: Default::default(), default_fg: None,
6837 default_bg: None,
6838 },
6839 PaneInfo {
6840 id: 1,
6841 is_plugin: true,
6842 is_focused: true,
6843 is_fullscreen: true,
6844 is_floating: false,
6845 is_suppressed: false,
6846 title: "pane 1".to_owned(),
6847 exited: false,
6848 exit_status: None,
6849 is_held: false,
6850 pane_x: 0,
6851 pane_content_x: 1,
6852 pane_y: 0,
6853 pane_content_y: 1,
6854 pane_rows: 5,
6855 pane_content_rows: 4,
6856 pane_columns: 22,
6857 pane_content_columns: 21,
6858 cursor_coordinates_in_pane: Some((0, 0)),
6859 terminal_command: None,
6860 plugin_url: Some("i_am_a_fake_plugin".to_owned()),
6861 is_selectable: true,
6862 index_in_pane_group: Default::default(), default_fg: None,
6864 default_bg: None,
6865 },
6866 ];
6867 let mut panes = HashMap::new();
6868 panes.insert(0, panes_list);
6869 let session_info = SessionInfo {
6870 name: "my session name".to_owned(),
6871 tabs: vec![
6872 TabInfo {
6873 position: 0,
6874 name: "tab 1".to_owned(),
6875 active: true,
6876 panes_to_hide: 1,
6877 is_fullscreen_active: true,
6878 is_sync_panes_active: false,
6879 are_floating_panes_visible: true,
6880 other_focused_clients: vec![2, 3],
6881 active_swap_layout_name: Some("BASE".to_owned()),
6882 is_swap_layout_dirty: true,
6883 viewport_rows: 10,
6884 viewport_columns: 10,
6885 display_area_rows: 10,
6886 display_area_columns: 10,
6887 selectable_tiled_panes_count: 10,
6888 selectable_floating_panes_count: 10,
6889 tab_id: 0,
6890 is_flashing_bell: false,
6891 has_bell_notification: false,
6892 },
6893 TabInfo {
6894 position: 1,
6895 name: "tab 2".to_owned(),
6896 active: true,
6897 panes_to_hide: 0,
6898 is_fullscreen_active: false,
6899 is_sync_panes_active: true,
6900 are_floating_panes_visible: true,
6901 other_focused_clients: vec![2, 3],
6902 active_swap_layout_name: None,
6903 is_swap_layout_dirty: false,
6904 viewport_rows: 10,
6905 viewport_columns: 10,
6906 display_area_rows: 10,
6907 display_area_columns: 10,
6908 selectable_tiled_panes_count: 10,
6909 selectable_floating_panes_count: 10,
6910 tab_id: 1,
6911 is_flashing_bell: false,
6912 has_bell_notification: false,
6913 },
6914 ],
6915 panes: PaneManifest { panes },
6916 connected_clients: 2,
6917 is_current_session: false,
6918 available_layouts: vec![
6919 LayoutInfo::File("layout1".to_owned(), LayoutMetadata::default()),
6920 LayoutInfo::BuiltIn("layout2".to_owned()),
6921 LayoutInfo::File("layout3".to_owned(), LayoutMetadata::default()),
6922 ],
6923 plugins: Default::default(),
6924 web_client_count: 2,
6925 web_clients_allowed: true,
6926 tab_history: Default::default(),
6927 pane_history: Default::default(),
6928 creation_time: Duration::from_secs(300),
6929 };
6930 let serialized = session_info.to_string();
6931 let deserealized = SessionInfo::from_string(&serialized, "not this session").unwrap();
6932 assert_eq!(session_info, deserealized);
6933 insta::assert_snapshot!(serialized);
6934}
6935
6936#[test]
6937fn keybinds_to_string() {
6938 let fake_config = r#"
6939 keybinds {
6940 normal {
6941 bind "Ctrl g" { SwitchToMode "Locked"; }
6942 }
6943 }"#;
6944 let document: KdlDocument = fake_config.parse().unwrap();
6945 let deserialized = Keybinds::from_kdl(
6946 document.get("keybinds").unwrap(),
6947 Default::default(),
6948 &Default::default(),
6949 )
6950 .unwrap();
6951 let clear_defaults = true;
6952 let serialized = Keybinds::to_kdl(&deserialized, clear_defaults);
6953 let deserialized_from_serialized = Keybinds::from_kdl(
6954 serialized
6955 .to_string()
6956 .parse::<KdlDocument>()
6957 .unwrap()
6958 .get("keybinds")
6959 .unwrap(),
6960 Default::default(),
6961 &Default::default(),
6962 )
6963 .unwrap();
6964 insta::assert_snapshot!(serialized.to_string());
6965 assert_eq!(
6966 deserialized, deserialized_from_serialized,
6967 "Deserialized serialized config equals original config"
6968 );
6969}
6970
6971#[test]
6972fn keybinds_to_string_without_clearing_defaults() {
6973 let fake_config = r#"
6974 keybinds {
6975 normal {
6976 bind "Ctrl g" { SwitchToMode "Locked"; }
6977 }
6978 }"#;
6979 let document: KdlDocument = fake_config.parse().unwrap();
6980 let deserialized = Keybinds::from_kdl(
6981 document.get("keybinds").unwrap(),
6982 Default::default(),
6983 &Default::default(),
6984 )
6985 .unwrap();
6986 let clear_defaults = false;
6987 let serialized = Keybinds::to_kdl(&deserialized, clear_defaults);
6988 let deserialized_from_serialized = Keybinds::from_kdl(
6989 serialized
6990 .to_string()
6991 .parse::<KdlDocument>()
6992 .unwrap()
6993 .get("keybinds")
6994 .unwrap(),
6995 Default::default(),
6996 &Default::default(),
6997 )
6998 .unwrap();
6999 insta::assert_snapshot!(serialized.to_string());
7000 assert_eq!(
7001 deserialized, deserialized_from_serialized,
7002 "Deserialized serialized config equals original config"
7003 );
7004}
7005
7006#[test]
7007fn keybinds_to_string_with_multiple_actions() {
7008 let fake_config = r#"
7009 keybinds {
7010 normal {
7011 bind "Ctrl n" { NewPane; SwitchToMode "Locked"; }
7012 }
7013 }"#;
7014 let document: KdlDocument = fake_config.parse().unwrap();
7015 let deserialized = Keybinds::from_kdl(
7016 document.get("keybinds").unwrap(),
7017 Default::default(),
7018 &Default::default(),
7019 )
7020 .unwrap();
7021 let clear_defaults = true;
7022 let serialized = Keybinds::to_kdl(&deserialized, clear_defaults);
7023 let deserialized_from_serialized = Keybinds::from_kdl(
7024 serialized
7025 .to_string()
7026 .parse::<KdlDocument>()
7027 .unwrap()
7028 .get("keybinds")
7029 .unwrap(),
7030 Default::default(),
7031 &Default::default(),
7032 )
7033 .unwrap();
7034 assert_eq!(
7035 deserialized, deserialized_from_serialized,
7036 "Deserialized serialized config equals original config"
7037 );
7038 insta::assert_snapshot!(serialized.to_string());
7039}
7040
7041#[test]
7042fn can_bind_theme_actions() {
7043 let fake_config = r#"
7047 keybinds {
7048 normal {
7049 bind "Ctrl t" { ToggleTheme; }
7050 bind "Ctrl d" { SetDarkTheme; }
7051 bind "Ctrl l" { SetLightTheme; }
7052 }
7053 }"#;
7054 let document: KdlDocument = fake_config.parse().unwrap();
7055 let deserialized = Keybinds::from_kdl(
7056 document.get("keybinds").unwrap(),
7057 Default::default(),
7058 &Default::default(),
7059 )
7060 .unwrap();
7061 let ctrl_t = KeyWithModifier::new(BareKey::Char('t')).with_ctrl_modifier();
7062 assert_eq!(
7063 deserialized.get_actions_for_key_in_mode(&InputMode::Normal, &ctrl_t),
7064 Some(&vec![Action::ToggleTheme])
7065 );
7066 let ctrl_d = KeyWithModifier::new(BareKey::Char('d')).with_ctrl_modifier();
7067 assert_eq!(
7068 deserialized.get_actions_for_key_in_mode(&InputMode::Normal, &ctrl_d),
7069 Some(&vec![Action::SetDarkTheme])
7070 );
7071 let ctrl_l = KeyWithModifier::new(BareKey::Char('l')).with_ctrl_modifier();
7072 assert_eq!(
7073 deserialized.get_actions_for_key_in_mode(&InputMode::Normal, &ctrl_l),
7074 Some(&vec![Action::SetLightTheme])
7075 );
7076 let serialized = Keybinds::to_kdl(&deserialized, true);
7078 let deserialized_from_serialized = Keybinds::from_kdl(
7079 serialized
7080 .to_string()
7081 .parse::<KdlDocument>()
7082 .unwrap()
7083 .get("keybinds")
7084 .unwrap(),
7085 Default::default(),
7086 &Default::default(),
7087 )
7088 .unwrap();
7089 assert_eq!(deserialized, deserialized_from_serialized);
7090}
7091
7092#[test]
7093fn keybinds_to_string_with_all_actions() {
7094 let fake_config = r#"
7095 keybinds {
7096 normal {
7097 bind "Ctrl a" { Quit; }
7098 bind "Ctrl b" { Write 102 111 111; }
7099 bind "Ctrl c" { WriteChars "hi there!"; }
7100 bind "Ctrl d" { SwitchToMode "Locked"; }
7101 bind "Ctrl e" { Resize "Increase"; }
7102 bind "Ctrl f" { FocusNextPane; }
7103 bind "Ctrl g" { FocusPreviousPane; }
7104 bind "Ctrl h" { SwitchFocus; }
7105 bind "Ctrl i" { MoveFocus "Right"; }
7106 bind "Ctrl j" { MoveFocusOrTab "Right"; }
7107 bind "Ctrl k" { MovePane "Right"; }
7108 bind "Ctrl l" { MovePaneBackwards; }
7109 bind "Ctrl m" { Resize "Decrease Down"; }
7110 bind "Ctrl n" { DumpScreen "/tmp/dumped"; }
7111 bind "Ctrl o" { DumpLayout "/tmp/dumped-layout"; }
7112 bind "Ctrl p" { EditScrollback; }
7113 bind "Ctrl q" { ScrollUp; }
7114 bind "Ctrl r" { ScrollDown; }
7115 bind "Ctrl s" { ScrollToBottom; }
7116 bind "Ctrl t" { ScrollToTop; }
7117 bind "Ctrl u" { PageScrollUp; }
7118 bind "Ctrl v" { PageScrollDown; }
7119 bind "Ctrl w" { HalfPageScrollUp; }
7120 bind "Ctrl x" { HalfPageScrollDown; }
7121 bind "Ctrl y" { ToggleFocusFullscreen; }
7122 bind "Ctrl z" { TogglePaneFrames; }
7123 bind "Alt a" { ToggleActiveSyncTab; }
7124 bind "Alt b" { NewPane "Right"; }
7125 bind "Alt c" { TogglePaneEmbedOrFloating; }
7126 bind "Alt d" { ToggleFloatingPanes; }
7127 bind "Alt e" { CloseFocus; }
7128 bind "Alt f" { PaneNameInput 0; }
7129 bind "Alt g" { UndoRenamePane; }
7130 bind "Alt h" { NewTab; }
7131 bind "Alt i" { GoToNextTab; }
7132 bind "Alt j" { GoToPreviousTab; }
7133 bind "Alt k" { CloseTab; }
7134 bind "Alt l" { GoToTab 1; }
7135 bind "Alt m" { ToggleTab; }
7136 bind "Alt n" { TabNameInput 0; }
7137 bind "Alt o" { UndoRenameTab; }
7138 bind "Alt p" { MoveTab "Right"; }
7139 bind "Alt q" {
7140 Run "ls" "-l" {
7141 hold_on_start true;
7142 hold_on_close false;
7143 cwd "/tmp";
7144 name "my cool pane";
7145 };
7146 }
7147 bind "Alt r" {
7148 Run "ls" "-l" {
7149 hold_on_start true;
7150 hold_on_close false;
7151 cwd "/tmp";
7152 name "my cool pane";
7153 floating true;
7154 };
7155 }
7156 bind "Alt s" {
7157 Run "ls" "-l" {
7158 hold_on_start true;
7159 hold_on_close false;
7160 cwd "/tmp";
7161 name "my cool pane";
7162 in_place true;
7163 };
7164 }
7165 bind "Alt t" { Detach; }
7166 bind "Alt u" {
7167 LaunchOrFocusPlugin "zellij:session-manager"{
7168 floating true;
7169 move_to_focused_tab true;
7170 skip_plugin_cache true;
7171 config_key_1 "config_value_1";
7172 config_key_2 "config_value_2";
7173 };
7174 }
7175 bind "Alt v" {
7176 LaunchOrFocusPlugin "zellij:session-manager"{
7177 in_place true;
7178 move_to_focused_tab true;
7179 skip_plugin_cache true;
7180 config_key_1 "config_value_1";
7181 config_key_2 "config_value_2";
7182 };
7183 }
7184 bind "Alt w" {
7185 LaunchPlugin "zellij:session-manager" {
7186 floating true;
7187 skip_plugin_cache true;
7188 config_key_1 "config_value_1";
7189 config_key_2 "config_value_2";
7190 };
7191 }
7192 bind "Alt x" {
7193 LaunchPlugin "zellij:session-manager"{
7194 in_place true;
7195 skip_plugin_cache true;
7196 config_key_1 "config_value_1";
7197 config_key_2 "config_value_2";
7198 };
7199 }
7200 bind "Alt y" { Copy; }
7201 bind "Alt z" { SearchInput 0; }
7202 bind "Ctrl Alt a" { Search "Up"; }
7203 bind "Ctrl Alt b" { SearchToggleOption "CaseSensitivity"; }
7204 bind "Ctrl Alt c" { ToggleMouseMode; }
7205 bind "Ctrl Alt d" { PreviousSwapLayout; }
7206 bind "Ctrl Alt e" { NextSwapLayout; }
7207 bind "Ctrl Alt g" { BreakPane; }
7208 bind "Ctrl Alt h" { BreakPaneRight; }
7209 bind "Ctrl Alt i" { BreakPaneLeft; }
7210 bind "Ctrl Alt i" { BreakPaneLeft; }
7211 bind "Ctrl Alt j" {
7212 MessagePlugin "zellij:session-manager"{
7213 name "message_name";
7214 payload "message_payload";
7215 cwd "/tmp";
7216 launch_new true;
7217 skip_cache true;
7218 floating true;
7219 title "plugin_title";
7220 config_key_1 "config_value_1";
7221 config_key_2 "config_value_2";
7222 };
7223 }
7224 bind "Ctrl Alt k" { FocusLastPane; }
7225 }
7226 }"#;
7227 let document: KdlDocument = fake_config.parse().unwrap();
7228 let deserialized = Keybinds::from_kdl(
7229 document.get("keybinds").unwrap(),
7230 Default::default(),
7231 &Default::default(),
7232 )
7233 .unwrap();
7234 let clear_defaults = true;
7235 let serialized = Keybinds::to_kdl(&deserialized, clear_defaults);
7236 let deserialized_from_serialized = Keybinds::from_kdl(
7237 serialized
7238 .to_string()
7239 .parse::<KdlDocument>()
7240 .unwrap()
7241 .get("keybinds")
7242 .unwrap(),
7243 Default::default(),
7244 &Default::default(),
7245 )
7246 .unwrap();
7247 assert_eq!(
7260 deserialized, deserialized_from_serialized,
7261 "Deserialized serialized config equals original config"
7262 );
7263 insta::assert_snapshot!(serialized.to_string());
7264}
7265
7266#[test]
7267fn keybinds_to_string_with_shared_modes() {
7268 let fake_config = r#"
7269 keybinds {
7270 normal {
7271 bind "Ctrl n" { NewPane; SwitchToMode "Locked"; }
7272 }
7273 locked {
7274 bind "Ctrl n" { NewPane; SwitchToMode "Locked"; }
7275 }
7276 shared_except "locked" "pane" {
7277 bind "Ctrl f" { TogglePaneEmbedOrFloating; }
7278 }
7279 shared_among "locked" "pane" {
7280 bind "Ctrl p" { WriteChars "foo"; }
7281 }
7282 }"#;
7283 let document: KdlDocument = fake_config.parse().unwrap();
7284 let deserialized = Keybinds::from_kdl(
7285 document.get("keybinds").unwrap(),
7286 Default::default(),
7287 &Default::default(),
7288 )
7289 .unwrap();
7290 let clear_defaults = true;
7291 let serialized = Keybinds::to_kdl(&deserialized, clear_defaults);
7292 let deserialized_from_serialized = Keybinds::from_kdl(
7293 serialized
7294 .to_string()
7295 .parse::<KdlDocument>()
7296 .unwrap()
7297 .get("keybinds")
7298 .unwrap(),
7299 Default::default(),
7300 &Default::default(),
7301 )
7302 .unwrap();
7303 assert_eq!(
7304 deserialized, deserialized_from_serialized,
7305 "Deserialized serialized config equals original config"
7306 );
7307 insta::assert_snapshot!(serialized.to_string());
7308}
7309
7310#[test]
7311fn keybinds_to_string_with_multiple_multiline_actions() {
7312 let fake_config = r#"
7313 keybinds {
7314 shared {
7315 bind "Ctrl n" {
7316 NewPane
7317 SwitchToMode "Locked"
7318 MessagePlugin "zellij:session-manager"{
7319 name "message_name";
7320 payload "message_payload";
7321 cwd "/tmp";
7322 launch_new true;
7323 skip_cache true;
7324 floating true;
7325 title "plugin_title";
7326 config_key_1 "config_value_1";
7327 config_key_2 "config_value_2";
7328 };
7329 }
7330 }
7331 }"#;
7332 let document: KdlDocument = fake_config.parse().unwrap();
7333 let deserialized = Keybinds::from_kdl(
7334 document.get("keybinds").unwrap(),
7335 Default::default(),
7336 &Default::default(),
7337 )
7338 .unwrap();
7339 let clear_defaults = true;
7340 let serialized = Keybinds::to_kdl(&deserialized, clear_defaults);
7341 let deserialized_from_serialized = Keybinds::from_kdl(
7342 serialized
7343 .to_string()
7344 .parse::<KdlDocument>()
7345 .unwrap()
7346 .get("keybinds")
7347 .unwrap(),
7348 Default::default(),
7349 &Default::default(),
7350 )
7351 .unwrap();
7352 assert_eq!(
7353 deserialized, deserialized_from_serialized,
7354 "Deserialized serialized config equals original config"
7355 );
7356 insta::assert_snapshot!(serialized.to_string());
7357}
7358
7359#[test]
7360fn themes_to_string() {
7361 let fake_config = r#"
7362 themes {
7363 dracula {
7364 fg 248 248 242
7365 bg 40 42 54
7366 black 0 0 0
7367 red 255 85 85
7368 green 80 250 123
7369 yellow 241 250 140
7370 blue 98 114 164
7371 magenta 255 121 198
7372 cyan 139 233 253
7373 white 255 255 255
7374 orange 255 184 108
7375 }
7376 }"#;
7377 let document: KdlDocument = fake_config.parse().unwrap();
7378 let sourced_from_external_file = false;
7379 let deserialized =
7380 Themes::from_kdl(document.get("themes").unwrap(), sourced_from_external_file).unwrap();
7381 let serialized = Themes::to_kdl(&deserialized).unwrap();
7382 let deserialized_from_serialized = Themes::from_kdl(
7383 serialized
7384 .to_string()
7385 .parse::<KdlDocument>()
7386 .unwrap()
7387 .get("themes")
7388 .unwrap(),
7389 sourced_from_external_file,
7390 )
7391 .unwrap();
7392 assert_eq!(
7393 deserialized, deserialized_from_serialized,
7394 "Deserialized serialized config equals original config",
7395 );
7396 insta::assert_snapshot!(serialized.to_string());
7397}
7398
7399#[test]
7400fn themes_to_string_with_hex_definitions() {
7401 let fake_config = r##"
7402 themes {
7403 nord {
7404 fg "#D8DEE9"
7405 bg "#2E3440"
7406 black "#3B4252"
7407 red "#BF616A"
7408 green "#A3BE8C"
7409 yellow "#EBCB8B"
7410 blue "#81A1C1"
7411 magenta "#B48EAD"
7412 cyan "#88C0D0"
7413 white "#E5E9F0"
7414 orange "#D08770"
7415 }
7416 }"##;
7417 let document: KdlDocument = fake_config.parse().unwrap();
7418 let sourced_from_external_file = false;
7419 let deserialized =
7420 Themes::from_kdl(document.get("themes").unwrap(), sourced_from_external_file).unwrap();
7421 let serialized = Themes::to_kdl(&deserialized).unwrap();
7422 let deserialized_from_serialized = Themes::from_kdl(
7423 serialized
7424 .to_string()
7425 .parse::<KdlDocument>()
7426 .unwrap()
7427 .get("themes")
7428 .unwrap(),
7429 sourced_from_external_file,
7430 )
7431 .unwrap();
7432 assert_eq!(
7433 deserialized, deserialized_from_serialized,
7434 "Deserialized serialized config equals original config"
7435 );
7436 insta::assert_snapshot!(serialized.to_string());
7437}
7438
7439#[test]
7440fn themes_to_string_with_eight_bit_definitions() {
7441 let fake_config = r##"
7442 themes {
7443 default {
7444 fg 1
7445 bg 10
7446 black 20
7447 red 30
7448 green 40
7449 yellow 50
7450 blue 60
7451 magenta 70
7452 cyan 80
7453 white 90
7454 orange 254
7455 }
7456 }"##;
7457 let document: KdlDocument = fake_config.parse().unwrap();
7458 let sourced_from_external_file = false;
7459 let deserialized =
7460 Themes::from_kdl(document.get("themes").unwrap(), sourced_from_external_file).unwrap();
7461 let serialized = Themes::to_kdl(&deserialized).unwrap();
7462 let deserialized_from_serialized = Themes::from_kdl(
7463 serialized
7464 .to_string()
7465 .parse::<KdlDocument>()
7466 .unwrap()
7467 .get("themes")
7468 .unwrap(),
7469 sourced_from_external_file,
7470 )
7471 .unwrap();
7472 assert_eq!(
7473 deserialized, deserialized_from_serialized,
7474 "Deserialized serialized config equals original config"
7475 );
7476 insta::assert_snapshot!(serialized.to_string());
7477}
7478
7479#[test]
7480fn themes_to_string_with_combined_definitions() {
7481 let fake_config = r##"
7482 themes {
7483 default {
7484 fg 1
7485 bg 10
7486 black 20
7487 red 30
7488 green 40
7489 yellow 50
7490 blue 60
7491 magenta 70
7492 cyan 80
7493 white 255 255 255
7494 orange "#D08770"
7495 }
7496 }"##;
7497 let document: KdlDocument = fake_config.parse().unwrap();
7498 let sourced_from_external_file = false;
7499 let deserialized =
7500 Themes::from_kdl(document.get("themes").unwrap(), sourced_from_external_file).unwrap();
7501 let serialized = Themes::to_kdl(&deserialized).unwrap();
7502 let deserialized_from_serialized = Themes::from_kdl(
7503 serialized
7504 .to_string()
7505 .parse::<KdlDocument>()
7506 .unwrap()
7507 .get("themes")
7508 .unwrap(),
7509 sourced_from_external_file,
7510 )
7511 .unwrap();
7512 assert_eq!(
7513 deserialized, deserialized_from_serialized,
7514 "Deserialized serialized config equals original config"
7515 );
7516 insta::assert_snapshot!(serialized.to_string());
7517}
7518
7519#[test]
7520fn themes_to_string_with_multiple_theme_definitions() {
7521 let fake_config = r##"
7522 themes {
7523 nord {
7524 fg "#D8DEE9"
7525 bg "#2E3440"
7526 black "#3B4252"
7527 red "#BF616A"
7528 green "#A3BE8C"
7529 yellow "#EBCB8B"
7530 blue "#81A1C1"
7531 magenta "#B48EAD"
7532 cyan "#88C0D0"
7533 white "#E5E9F0"
7534 orange "#D08770"
7535 }
7536 dracula {
7537 fg 248 248 242
7538 bg 40 42 54
7539 black 0 0 0
7540 red 255 85 85
7541 green 80 250 123
7542 yellow 241 250 140
7543 blue 98 114 164
7544 magenta 255 121 198
7545 cyan 139 233 253
7546 white 255 255 255
7547 orange 255 184 108
7548 }
7549 }"##;
7550 let document: KdlDocument = fake_config.parse().unwrap();
7551 let sourced_from_external_file = false;
7552 let deserialized =
7553 Themes::from_kdl(document.get("themes").unwrap(), sourced_from_external_file).unwrap();
7554 let serialized = Themes::to_kdl(&deserialized).unwrap();
7555 let deserialized_from_serialized = Themes::from_kdl(
7556 serialized
7557 .to_string()
7558 .parse::<KdlDocument>()
7559 .unwrap()
7560 .get("themes")
7561 .unwrap(),
7562 sourced_from_external_file,
7563 )
7564 .unwrap();
7565 assert_eq!(
7566 deserialized, deserialized_from_serialized,
7567 "Deserialized serialized config equals original config"
7568 );
7569 insta::assert_snapshot!(serialized.to_string());
7570}
7571
7572#[test]
7573fn plugins_to_string() {
7574 let fake_config = r##"
7575 plugins {
7576 tab-bar location="zellij:tab-bar"
7577 status-bar location="zellij:status-bar"
7578 strider location="zellij:strider"
7579 compact-bar location="zellij:compact-bar"
7580 session-manager location="zellij:session-manager"
7581 welcome-screen location="zellij:session-manager" {
7582 welcome_screen true
7583 }
7584 filepicker location="zellij:strider" {
7585 cwd "/"
7586 }
7587 }"##;
7588 let document: KdlDocument = fake_config.parse().unwrap();
7589 let deserialized = PluginAliases::from_kdl(document.get("plugins").unwrap()).unwrap();
7590 let serialized = PluginAliases::to_kdl(&deserialized, true);
7591 let deserialized_from_serialized = PluginAliases::from_kdl(
7592 serialized
7593 .to_string()
7594 .parse::<KdlDocument>()
7595 .unwrap()
7596 .get("plugins")
7597 .unwrap(),
7598 )
7599 .unwrap();
7600 assert_eq!(
7601 deserialized, deserialized_from_serialized,
7602 "Deserialized serialized config equals original config"
7603 );
7604 insta::assert_snapshot!(serialized.to_string());
7605}
7606
7607#[test]
7608fn plugins_to_string_with_file_and_web() {
7609 let fake_config = r##"
7610 plugins {
7611 tab-bar location="https://foo.com/plugin.wasm"
7612 filepicker location="file:/path/to/my/plugin.wasm" {
7613 cwd "/"
7614 }
7615 }"##;
7616 let document: KdlDocument = fake_config.parse().unwrap();
7617 let deserialized = PluginAliases::from_kdl(document.get("plugins").unwrap()).unwrap();
7618 let serialized = PluginAliases::to_kdl(&deserialized, true);
7619 let deserialized_from_serialized = PluginAliases::from_kdl(
7620 serialized
7621 .to_string()
7622 .parse::<KdlDocument>()
7623 .unwrap()
7624 .get("plugins")
7625 .unwrap(),
7626 )
7627 .unwrap();
7628 assert_eq!(
7629 deserialized, deserialized_from_serialized,
7630 "Deserialized serialized config equals original config"
7631 );
7632 insta::assert_snapshot!(serialized.to_string());
7633}
7634
7635#[test]
7636fn ui_config_to_string() {
7637 let fake_config = r##"
7638 ui {
7639 pane_frames {
7640 rounded_corners true
7641 hide_session_name true
7642 }
7643 }"##;
7644 let document: KdlDocument = fake_config.parse().unwrap();
7645 let deserialized = UiConfig::from_kdl(document.get("ui").unwrap()).unwrap();
7646 let serialized = UiConfig::to_kdl(&deserialized).unwrap();
7647 let deserialized_from_serialized = UiConfig::from_kdl(
7648 serialized
7649 .to_string()
7650 .parse::<KdlDocument>()
7651 .unwrap()
7652 .get("ui")
7653 .unwrap(),
7654 )
7655 .unwrap();
7656 assert_eq!(
7657 deserialized, deserialized_from_serialized,
7658 "Deserialized serialized config equals original config"
7659 );
7660 insta::assert_snapshot!(serialized.to_string());
7661}
7662
7663#[test]
7664fn ui_config_to_string_with_no_ui_config() {
7665 let fake_config = r##"
7666 ui {
7667 pane_frames {
7668 }
7669 }"##;
7670 let document: KdlDocument = fake_config.parse().unwrap();
7671 let deserialized = UiConfig::from_kdl(document.get("ui").unwrap()).unwrap();
7672 assert_eq!(UiConfig::to_kdl(&deserialized), None);
7673}
7674
7675#[test]
7676fn env_vars_to_string() {
7677 let fake_config = r##"
7678 env {
7679 foo "bar"
7680 bar "foo"
7681 thing 1
7682 baz "true"
7683 }"##;
7684 let document: KdlDocument = fake_config.parse().unwrap();
7685 let deserialized = EnvironmentVariables::from_kdl(document.get("env").unwrap()).unwrap();
7686 let serialized = EnvironmentVariables::to_kdl(&deserialized).unwrap();
7687 let deserialized_from_serialized = EnvironmentVariables::from_kdl(
7688 serialized
7689 .to_string()
7690 .parse::<KdlDocument>()
7691 .unwrap()
7692 .get("env")
7693 .unwrap(),
7694 )
7695 .unwrap();
7696 assert_eq!(
7697 deserialized, deserialized_from_serialized,
7698 "Deserialized serialized config equals original config"
7699 );
7700 insta::assert_snapshot!(serialized.to_string());
7701}
7702
7703#[test]
7704fn env_vars_to_string_with_no_env_vars() {
7705 let fake_config = r##"
7706 env {
7707 }"##;
7708 let document: KdlDocument = fake_config.parse().unwrap();
7709 let deserialized = EnvironmentVariables::from_kdl(document.get("env").unwrap()).unwrap();
7710 assert_eq!(EnvironmentVariables::to_kdl(&deserialized), None);
7711}
7712
7713#[test]
7714fn selection_options_from_kdl() {
7715 let fake_config = r##"
7716 osc133_command_selection false
7717 word_separators "[]{}<>():,"
7718 "##;
7719 let document: KdlDocument = fake_config.parse().unwrap();
7720 let deserialized = Options::from_kdl(&document).unwrap();
7721 assert_eq!(deserialized.osc133_command_selection, Some(false));
7722 assert_eq!(
7723 deserialized.word_separators,
7724 Some("[]{}<>():,".to_owned()),
7725 "word separators are parsed verbatim"
7726 );
7727}
7728
7729#[test]
7730fn selection_options_default_to_none_when_unspecified() {
7731 let document: KdlDocument = "".parse().unwrap();
7732 let deserialized = Options::from_kdl(&document).unwrap();
7733 assert_eq!(deserialized.osc133_command_selection, None);
7734 assert_eq!(deserialized.word_separators, None);
7735}
7736
7737#[test]
7738fn scroll_mode_sync_from_kdl() {
7739 let fake_config = r##"
7740 scroll_mode_sync false
7741 "##;
7742 let document: KdlDocument = fake_config.parse().unwrap();
7743 let deserialized = Options::from_kdl(&document).unwrap();
7744 assert_eq!(deserialized.scroll_mode_sync, Some(false));
7745
7746 let empty_document: KdlDocument = "".parse().unwrap();
7747 let deserialized_empty = Options::from_kdl(&empty_document).unwrap();
7748 assert_eq!(
7749 deserialized_empty.scroll_mode_sync, None,
7750 "an unspecified scroll_mode_sync stays None so the default applies"
7751 );
7752}
7753
7754#[test]
7755fn scroll_mode_sync_round_trips_through_kdl() {
7756 let fake_config = r##"
7757 scroll_mode_sync false
7758 "##;
7759 let document: KdlDocument = fake_config.parse().unwrap();
7760 let deserialized = Options::from_kdl(&document).unwrap();
7761 let mut serialized = Options::to_kdl(&deserialized, false);
7762 let mut fake_document = KdlDocument::new();
7763 fake_document.nodes_mut().append(&mut serialized);
7764 let deserialized_from_serialized =
7765 Options::from_kdl(&fake_document.to_string().parse::<KdlDocument>().unwrap()).unwrap();
7766 assert_eq!(
7767 deserialized_from_serialized.scroll_mode_sync,
7768 Some(false),
7769 "scroll_mode_sync survives a serialize/parse round trip"
7770 );
7771}
7772
7773#[test]
7774fn explicit_theme_hue_from_kdl() {
7775 let fake_config = r##"
7776 explicit_theme_hue "light"
7777 "##;
7778 let document: KdlDocument = fake_config.parse().unwrap();
7779 let deserialized = Options::from_kdl(&document).unwrap();
7780 assert_eq!(deserialized.explicit_theme_hue, Some(ThemeHue::Light));
7781
7782 let empty_document: KdlDocument = "".parse().unwrap();
7783 let deserialized_empty = Options::from_kdl(&empty_document).unwrap();
7784 assert_eq!(
7785 deserialized_empty.explicit_theme_hue, None,
7786 "an unspecified explicit_theme_hue leaves the host terminal in charge"
7787 );
7788}
7789
7790#[test]
7791fn explicit_theme_hue_rejects_unknown_values() {
7792 let fake_config = r##"
7793 explicit_theme_hue "sepia"
7794 "##;
7795 let document: KdlDocument = fake_config.parse().unwrap();
7796 assert!(
7797 Options::from_kdl(&document).is_err(),
7798 "only 'dark' and 'light' are accepted"
7799 );
7800}
7801
7802#[test]
7803fn explicit_theme_hue_round_trips_through_kdl() {
7804 let fake_config = r##"
7805 explicit_theme_hue "dark"
7806 "##;
7807 let document: KdlDocument = fake_config.parse().unwrap();
7808 let deserialized = Options::from_kdl(&document).unwrap();
7809 let mut serialized = Options::to_kdl(&deserialized, false);
7810 let mut fake_document = KdlDocument::new();
7811 fake_document.nodes_mut().append(&mut serialized);
7812 let deserialized_from_serialized =
7813 Options::from_kdl(&fake_document.to_string().parse::<KdlDocument>().unwrap()).unwrap();
7814 assert_eq!(
7815 deserialized_from_serialized.explicit_theme_hue,
7816 Some(ThemeHue::Dark),
7817 "explicit_theme_hue survives a serialize/parse round trip"
7818 );
7819}
7820
7821#[test]
7822fn config_options_to_string() {
7823 let fake_config = r##"
7824 simplified_ui true
7825 theme "dracula"
7826 default_mode "locked"
7827 default_shell "fish"
7828 default_cwd "/tmp/foo"
7829 default_layout "compact"
7830 layout_dir "/tmp/layouts"
7831 theme_dir "/tmp/themes"
7832 mouse_mode false
7833 pane_frames false
7834 mirror_session true
7835 on_force_close "quit"
7836 scroll_buffer_size 100
7837 copy_command "pbcopy"
7838 copy_clipboard "system"
7839 copy_on_select false
7840 scrollback_editor "vim"
7841 session_name "my_cool_session"
7842 attach_to_session false
7843 auto_layout false
7844 session_serialization true
7845 serialize_pane_viewport false
7846 scrollback_lines_to_serialize 1000
7847 styled_underlines false
7848 serialization_interval 1
7849 disable_session_metadata true
7850 support_kitty_keyboard_protocol false
7851 web_server true
7852 web_sharing "disabled"
7853 "##;
7854 let document: KdlDocument = fake_config.parse().unwrap();
7855 let deserialized = Options::from_kdl(&document).unwrap();
7856 let mut serialized = Options::to_kdl(&deserialized, false);
7857 let mut fake_document = KdlDocument::new();
7858 fake_document.nodes_mut().append(&mut serialized);
7859 let deserialized_from_serialized =
7860 Options::from_kdl(&fake_document.to_string().parse::<KdlDocument>().unwrap()).unwrap();
7861 assert_eq!(
7862 deserialized, deserialized_from_serialized,
7863 "Deserialized serialized config equals original config"
7864 );
7865 insta::assert_snapshot!(fake_document.to_string());
7866}
7867
7868#[test]
7869fn config_options_to_string_with_comments() {
7870 let fake_config = r##"
7871 simplified_ui true
7872 theme "dracula"
7873 default_mode "locked"
7874 default_shell "fish"
7875 default_cwd "/tmp/foo"
7876 default_layout "compact"
7877 layout_dir "/tmp/layouts"
7878 theme_dir "/tmp/themes"
7879 mouse_mode false
7880 pane_frames false
7881 mirror_session true
7882 on_force_close "quit"
7883 scroll_buffer_size 100
7884 copy_command "pbcopy"
7885 copy_clipboard "system"
7886 copy_on_select false
7887 scrollback_editor "vim"
7888 session_name "my_cool_session"
7889 attach_to_session false
7890 auto_layout false
7891 session_serialization true
7892 serialize_pane_viewport false
7893 scrollback_lines_to_serialize 1000
7894 styled_underlines false
7895 serialization_interval 1
7896 disable_session_metadata true
7897 support_kitty_keyboard_protocol false
7898 web_server true
7899 web_sharing "disabled"
7900 "##;
7901 let document: KdlDocument = fake_config.parse().unwrap();
7902 let deserialized = Options::from_kdl(&document).unwrap();
7903 let mut serialized = Options::to_kdl(&deserialized, true);
7904 let mut fake_document = KdlDocument::new();
7905 fake_document.nodes_mut().append(&mut serialized);
7906 let deserialized_from_serialized =
7907 Options::from_kdl(&fake_document.to_string().parse::<KdlDocument>().unwrap()).unwrap();
7908 assert_eq!(
7909 deserialized, deserialized_from_serialized,
7910 "Deserialized serialized config equals original config"
7911 );
7912 insta::assert_snapshot!(fake_document.to_string());
7913}
7914
7915#[test]
7916fn config_options_to_string_without_options() {
7917 let fake_config = r##"
7918 "##;
7919 let document: KdlDocument = fake_config.parse().unwrap();
7920 let deserialized = Options::from_kdl(&document).unwrap();
7921 let mut serialized = Options::to_kdl(&deserialized, false);
7922 let mut fake_document = KdlDocument::new();
7923 fake_document.nodes_mut().append(&mut serialized);
7924 let deserialized_from_serialized =
7925 Options::from_kdl(&fake_document.to_string().parse::<KdlDocument>().unwrap()).unwrap();
7926 assert_eq!(
7927 deserialized, deserialized_from_serialized,
7928 "Deserialized serialized config equals original config"
7929 );
7930 insta::assert_snapshot!(fake_document.to_string());
7931}
7932
7933#[test]
7934fn nested_session_handling_kdl_round_trip_for_every_variant() {
7935 use crate::input::options::NestedSessionHandling;
7936 let cases = [
7937 ("ask", NestedSessionHandling::Ask),
7938 ("fullscreen", NestedSessionHandling::Fullscreen),
7939 ("descend", NestedSessionHandling::Descend),
7940 ("never", NestedSessionHandling::Never),
7941 ];
7942 for (value, expected) in cases {
7943 let fake_config = format!(
7944 r##"
7945 nested_session_handling "{value}"
7946 "##
7947 );
7948 let document: KdlDocument = fake_config.parse().unwrap();
7949 let parsed = Options::from_kdl(&document).unwrap();
7950 assert_eq!(
7951 parsed.nested_session_handling,
7952 Some(expected),
7953 "case: {value}"
7954 );
7955
7956 let mut serialized = Options::to_kdl(&parsed, false);
7957 let mut fake_document = KdlDocument::new();
7958 fake_document.nodes_mut().append(&mut serialized);
7959 let reparsed =
7960 Options::from_kdl(&fake_document.to_string().parse::<KdlDocument>().unwrap()).unwrap();
7961 assert_eq!(parsed, reparsed, "round-trip mismatch for {value}");
7962 }
7963}
7964
7965#[test]
7966fn host_notification_protocol_kdl_round_trip_for_every_variant() {
7967 use crate::input::options::HostNotificationProtocol;
7968 let cases = [
7969 ("auto", HostNotificationProtocol::Auto),
7970 ("osc9", HostNotificationProtocol::Osc9),
7971 ("osc99", HostNotificationProtocol::Osc99),
7972 ("bell", HostNotificationProtocol::Bell),
7973 ("off", HostNotificationProtocol::Off),
7974 ];
7975 for (value, expected) in cases {
7976 let fake_config = format!(
7977 r##"
7978 host_notification_protocol "{value}"
7979 "##
7980 );
7981 let document: KdlDocument = fake_config.parse().unwrap();
7982 let parsed = Options::from_kdl(&document).unwrap();
7983 assert_eq!(
7984 parsed.host_notification_protocol,
7985 Some(expected),
7986 "case: {value}"
7987 );
7988
7989 let mut serialized = Options::to_kdl(&parsed, false);
7990 let mut fake_document = KdlDocument::new();
7991 fake_document.nodes_mut().append(&mut serialized);
7992 let reparsed =
7993 Options::from_kdl(&fake_document.to_string().parse::<KdlDocument>().unwrap()).unwrap();
7994 assert_eq!(parsed, reparsed, "round-trip mismatch for {value}");
7995 }
7996}
7997
7998#[test]
7999fn an_unknown_host_notification_protocol_is_a_config_error() {
8000 let fake_config = r##"
8001 host_notification_protocol "carrier-pigeon"
8002 "##;
8003 let document: KdlDocument = fake_config.parse().unwrap();
8004 assert!(Options::from_kdl(&document).is_err());
8005}
8006
8007#[test]
8008fn an_unset_host_notification_protocol_parses_as_none() {
8009 let document: KdlDocument = r##"
8010 simplified_ui true
8011 "##
8012 .parse()
8013 .unwrap();
8014 let parsed = Options::from_kdl(&document).unwrap();
8015 assert_eq!(parsed.host_notification_protocol, None);
8016}
8017
8018#[test]
8019fn config_options_to_string_with_some_options() {
8020 let fake_config = r##"
8021 default_layout "compact"
8022 "##;
8023 let document: KdlDocument = fake_config.parse().unwrap();
8024 let deserialized = Options::from_kdl(&document).unwrap();
8025 let mut serialized = Options::to_kdl(&deserialized, false);
8026 let mut fake_document = KdlDocument::new();
8027 fake_document.nodes_mut().append(&mut serialized);
8028 let deserialized_from_serialized =
8029 Options::from_kdl(&fake_document.to_string().parse::<KdlDocument>().unwrap()).unwrap();
8030 assert_eq!(
8031 deserialized, deserialized_from_serialized,
8032 "Deserialized serialized config equals original config"
8033 );
8034 insta::assert_snapshot!(fake_document.to_string());
8035}
8036
8037#[test]
8038fn bare_config_from_default_assets_to_string() {
8039 let fake_config = Config::from_default_assets().unwrap();
8040 let fake_config_stringified = fake_config.to_string(false);
8041 let deserialized_from_serialized = Config::from_kdl(&fake_config_stringified, None).unwrap();
8042 assert_eq!(
8043 fake_config, deserialized_from_serialized,
8044 "Deserialized serialized config equals original config"
8045 );
8046 insta::assert_snapshot!(fake_config_stringified);
8047}
8048
8049#[test]
8050fn bare_config_from_default_assets_to_string_with_comments() {
8051 let fake_config = Config::from_default_assets().unwrap();
8052 let fake_config_stringified = fake_config.to_string(true);
8053 let deserialized_from_serialized = Config::from_kdl(&fake_config_stringified, None).unwrap();
8054 assert_eq!(
8055 fake_config, deserialized_from_serialized,
8056 "Deserialized serialized config equals original config"
8057 );
8058 insta::assert_snapshot!(fake_config_stringified);
8059}
8060
8061#[test]
8062fn osc8_hyperlinks_config_parsing() {
8063 let config_with_osc8_disabled = r#"
8064 osc8_hyperlinks false
8065 "#;
8066 let config = Config::from_kdl(config_with_osc8_disabled, None).unwrap();
8067 assert_eq!(config.options.osc8_hyperlinks, Some(false));
8068
8069 let config_with_osc8_enabled = r#"
8070 osc8_hyperlinks true
8071 "#;
8072 let config = Config::from_kdl(config_with_osc8_enabled, None).unwrap();
8073 assert_eq!(config.options.osc8_hyperlinks, Some(true));
8074
8075 let serialized = config.to_string(false);
8077 let deserialized = Config::from_kdl(&serialized, None).unwrap();
8078 assert_eq!(deserialized.options.osc8_hyperlinks, Some(true));
8079}