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, 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 default_mode =
2787 match kdl_property_first_arg_as_string_or_error!(kdl_options, "default_mode") {
2788 Some((string, entry)) => Some(InputMode::from_str(string).map_err(|_| {
2789 kdl_parsing_error!(format!("Invalid input mode: '{}'", string), entry)
2790 })?),
2791 None => None,
2792 };
2793 let default_layout =
2794 kdl_property_first_arg_as_string_or_error!(kdl_options, "default_layout")
2795 .map(|(string, _entry)| PathBuf::from(string));
2796 let layout_dir = kdl_property_first_arg_as_string_or_error!(kdl_options, "layout_dir")
2797 .map(|(string, _entry)| PathBuf::from(string));
2798 let theme_dir = kdl_property_first_arg_as_string_or_error!(kdl_options, "theme_dir")
2799 .map(|(string, _entry)| PathBuf::from(string));
2800 let mouse_mode =
2801 kdl_property_first_arg_as_bool_or_error!(kdl_options, "mouse_mode").map(|(v, _)| v);
2802 let scroll_buffer_size =
2803 kdl_property_first_arg_as_i64_or_error!(kdl_options, "scroll_buffer_size")
2804 .map(|(scroll_buffer_size, _entry)| scroll_buffer_size as usize);
2805 let copy_command = kdl_property_first_arg_as_string_or_error!(kdl_options, "copy_command")
2806 .map(|(copy_command, _entry)| copy_command.to_string());
2807 let copy_clipboard =
2808 match kdl_property_first_arg_as_string_or_error!(kdl_options, "copy_clipboard") {
2809 Some((string, entry)) => Some(Clipboard::from_str(string).map_err(|_| {
2810 kdl_parsing_error!(
2811 format!("Invalid value for copy_clipboard: '{}'", string),
2812 entry
2813 )
2814 })?),
2815 None => None,
2816 };
2817 let copy_on_select =
2818 kdl_property_first_arg_as_bool_or_error!(kdl_options, "copy_on_select").map(|(v, _)| v);
2819 let osc8_hyperlinks =
2820 kdl_property_first_arg_as_bool_or_error!(kdl_options, "osc8_hyperlinks")
2821 .map(|(v, _)| v);
2822 let scrollback_editor =
2823 kdl_property_first_arg_as_string_or_error!(kdl_options, "scrollback_editor")
2824 .map(|(string, _entry)| PathBuf::from(string));
2825 let mirror_session =
2826 kdl_property_first_arg_as_bool_or_error!(kdl_options, "mirror_session").map(|(v, _)| v);
2827 let session_name = kdl_property_first_arg_as_string_or_error!(kdl_options, "session_name")
2828 .map(|(session_name, _entry)| session_name.to_string());
2829 let attach_to_session =
2830 kdl_property_first_arg_as_bool_or_error!(kdl_options, "attach_to_session")
2831 .map(|(v, _)| v);
2832 let session_serialization =
2833 kdl_property_first_arg_as_bool_or_error!(kdl_options, "session_serialization")
2834 .map(|(v, _)| v);
2835 let serialize_pane_viewport =
2836 kdl_property_first_arg_as_bool_or_error!(kdl_options, "serialize_pane_viewport")
2837 .map(|(v, _)| v);
2838 let scrollback_lines_to_serialize =
2839 kdl_property_first_arg_as_i64_or_error!(kdl_options, "scrollback_lines_to_serialize")
2840 .map(|(v, _)| v as usize);
2841 let styled_underlines =
2842 kdl_property_first_arg_as_bool_or_error!(kdl_options, "styled_underlines")
2843 .map(|(v, _)| v);
2844 let serialization_interval =
2845 kdl_property_first_arg_as_i64_or_error!(kdl_options, "serialization_interval")
2846 .map(|(scroll_buffer_size, _entry)| scroll_buffer_size as u64);
2847 let disable_session_metadata =
2848 kdl_property_first_arg_as_bool_or_error!(kdl_options, "disable_session_metadata")
2849 .map(|(v, _)| v);
2850 let support_kitty_keyboard_protocol = kdl_property_first_arg_as_bool_or_error!(
2851 kdl_options,
2852 "support_kitty_keyboard_protocol"
2853 )
2854 .map(|(v, _)| v);
2855 let support_kitty_graphics_protocol = kdl_property_first_arg_as_bool_or_error!(
2856 kdl_options,
2857 "support_kitty_graphics_protocol"
2858 )
2859 .map(|(v, _)| v);
2860 let web_server =
2861 kdl_property_first_arg_as_bool_or_error!(kdl_options, "web_server").map(|(v, _)| v);
2862 let web_sharing =
2863 match kdl_property_first_arg_as_string_or_error!(kdl_options, "web_sharing") {
2864 Some((string, entry)) => Some(WebSharing::from_str(string).map_err(|_| {
2865 kdl_parsing_error!(
2866 format!("Invalid value for web_sharing: '{}'", string),
2867 entry
2868 )
2869 })?),
2870 None => None,
2871 };
2872 let stacked_resize =
2873 kdl_property_first_arg_as_bool_or_error!(kdl_options, "stacked_resize").map(|(v, _)| v);
2874 let stacked_pane_list =
2875 kdl_property_first_arg_as_bool_or_error!(kdl_options, "stacked_pane_list")
2876 .map(|(v, _)| v);
2877 let show_startup_tips =
2878 kdl_property_first_arg_as_bool_or_error!(kdl_options, "show_startup_tips")
2879 .map(|(v, _)| v);
2880 let show_release_notes =
2881 kdl_property_first_arg_as_bool_or_error!(kdl_options, "show_release_notes")
2882 .map(|(v, _)| v);
2883 let advanced_mouse_actions =
2884 kdl_property_first_arg_as_bool_or_error!(kdl_options, "advanced_mouse_actions")
2885 .map(|(v, _)| v);
2886 let mouse_scroll_resize =
2887 kdl_property_first_arg_as_bool_or_error!(kdl_options, "mouse_scroll_resize")
2888 .map(|(v, _)| v);
2889 let mouse_hover_effects =
2890 kdl_property_first_arg_as_bool_or_error!(kdl_options, "mouse_hover_effects")
2891 .map(|(v, _)| v);
2892 let mouse_hover_tips =
2893 kdl_property_first_arg_as_bool_or_error!(kdl_options, "mouse_hover_tips")
2894 .map(|(v, _)| v);
2895 let web_server_ip =
2896 match kdl_property_first_arg_as_string_or_error!(kdl_options, "web_server_ip") {
2897 Some((string, entry)) => Some(IpAddr::from_str(string).map_err(|_| {
2898 kdl_parsing_error!(
2899 format!("Invalid value for web_server_ip: '{}'", string),
2900 entry
2901 )
2902 })?),
2903 None => None,
2904 };
2905 let web_server_port =
2906 kdl_property_first_arg_as_i64_or_error!(kdl_options, "web_server_port")
2907 .map(|(web_server_port, _entry)| web_server_port as u16);
2908 let web_server_cert =
2909 kdl_property_first_arg_as_string_or_error!(kdl_options, "web_server_cert")
2910 .map(|(string, _entry)| PathBuf::from(string));
2911 let web_server_key =
2912 kdl_property_first_arg_as_string_or_error!(kdl_options, "web_server_key")
2913 .map(|(string, _entry)| PathBuf::from(string));
2914 let enforce_https_for_localhost =
2915 kdl_property_first_arg_as_bool_or_error!(kdl_options, "enforce_https_for_localhost")
2916 .map(|(v, _)| v);
2917 let post_command_discovery_hook =
2918 kdl_property_first_arg_as_string_or_error!(kdl_options, "post_command_discovery_hook")
2919 .map(|(hook, _entry)| hook.to_string());
2920 let client_async_worker_tasks =
2921 match kdl_property_first_arg_as_i64_or_error!(kdl_options, "client_async_worker_tasks")
2922 {
2923 Some((value, _)) if value >= 0 => Some(value as usize),
2924 Some((value, entry)) => {
2925 return Err(kdl_parsing_error!(
2926 format!(
2927 "Number of client async worker tasks must be greater than 0, found '{}'",
2928 value
2929 ),
2930 entry
2931 ));
2932 },
2933 None => None,
2934 };
2935 let visual_bell =
2936 kdl_property_first_arg_as_bool_or_error!(kdl_options, "visual_bell").map(|(v, _)| v);
2937 let focus_follows_mouse =
2938 kdl_property_first_arg_as_bool_or_error!(kdl_options, "focus_follows_mouse")
2939 .map(|(v, _)| v);
2940 let mouse_click_through =
2941 kdl_property_first_arg_as_bool_or_error!(kdl_options, "mouse_click_through")
2942 .map(|(v, _)| v);
2943 let osc133_command_selection =
2944 kdl_property_first_arg_as_bool_or_error!(kdl_options, "osc133_command_selection")
2945 .map(|(v, _)| v);
2946 let word_separators =
2947 kdl_property_first_arg_as_string_or_error!(kdl_options, "word_separators")
2948 .map(|(separators, _entry)| separators.to_string());
2949 let nested_session_handling = match kdl_property_first_arg_as_string_or_error!(
2950 kdl_options,
2951 "nested_session_handling"
2952 ) {
2953 Some((value, entry)) => {
2954 use crate::input::options::NestedSessionHandling;
2955 match value.parse::<NestedSessionHandling>() {
2956 Ok(v) => Some(v),
2957 Err(e) => return Err(kdl_parsing_error!(e, entry)),
2958 }
2959 },
2960 None => None,
2961 };
2962 let host_notification_protocol = match kdl_property_first_arg_as_string_or_error!(
2963 kdl_options,
2964 "host_notification_protocol"
2965 ) {
2966 Some((value, entry)) => {
2967 use crate::input::options::HostNotificationProtocol;
2968 match value.parse::<HostNotificationProtocol>() {
2969 Ok(v) => Some(v),
2970 Err(e) => return Err(kdl_parsing_error!(e, entry)),
2971 }
2972 },
2973 None => None,
2974 };
2975 let dangerously_enable_paste_buffer_read = kdl_property_first_arg_as_bool_or_error!(
2976 kdl_options,
2977 "dangerously_enable_paste_buffer_read"
2978 )
2979 .map(|(v, _)| v);
2980
2981 Ok(Options {
2982 simplified_ui,
2983 theme,
2984 theme_dark,
2985 theme_light,
2986 default_mode,
2987 default_shell,
2988 default_cwd,
2989 default_layout,
2990 layout_dir,
2991 theme_dir,
2992 mouse_mode,
2993 pane_frames,
2994 pane_frame_style,
2995 mirror_session,
2996 on_force_close,
2997 scroll_buffer_size,
2998 copy_command,
2999 copy_clipboard,
3000 copy_on_select,
3001 osc8_hyperlinks,
3002 scrollback_editor,
3003 session_name,
3004 attach_to_session,
3005 auto_layout,
3006 session_serialization,
3007 serialize_pane_viewport,
3008 scrollback_lines_to_serialize,
3009 styled_underlines,
3010 serialization_interval,
3011 disable_session_metadata,
3012 support_kitty_keyboard_protocol,
3013 support_kitty_graphics_protocol,
3014 web_server,
3015 web_sharing,
3016 stacked_resize,
3017 stacked_pane_list,
3018 show_startup_tips,
3019 show_release_notes,
3020 advanced_mouse_actions,
3021 mouse_scroll_resize,
3022 mouse_hover_effects,
3023 mouse_hover_tips,
3024 visual_bell,
3025 focus_follows_mouse,
3026 mouse_click_through,
3027 osc133_command_selection,
3028 word_separators,
3029 host_notification_protocol,
3030 web_server_ip,
3031 web_server_port,
3032 web_server_cert,
3033 web_server_key,
3034 enforce_https_for_localhost,
3035 post_command_discovery_hook,
3036 client_async_worker_tasks,
3037 nested_session_handling,
3038 dangerously_enable_paste_buffer_read,
3039 })
3040 }
3041 pub fn from_string(stringified_keybindings: &String) -> Result<Self, ConfigError> {
3042 let document: KdlDocument = stringified_keybindings.parse()?;
3043 Options::from_kdl(&document)
3044 }
3045 fn simplified_ui_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3046 let comment_text = format!(
3047 "{}\n{}\n{}\n{}\n{}\n{}",
3048 " ",
3049 "// Use a simplified UI without special fonts (arrow glyphs)",
3050 "// Options:",
3051 "// - true",
3052 "// - false (Default)",
3053 "// ",
3054 );
3055
3056 let create_node = |node_value: bool| -> KdlNode {
3057 let mut node = KdlNode::new("simplified_ui");
3058 node.push(KdlValue::Bool(node_value));
3059 node
3060 };
3061 if let Some(simplified_ui) = self.simplified_ui {
3062 let mut node = create_node(simplified_ui);
3063 if add_comments {
3064 node.set_leading(format!("{}\n", comment_text));
3065 }
3066 Some(node)
3067 } else if add_comments {
3068 let mut node = create_node(true);
3069 node.set_leading(format!("{}\n// ", comment_text));
3070 Some(node)
3071 } else {
3072 None
3073 }
3074 }
3075 fn osc8_hyperlinks_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3076 let comment_text = format!(
3077 "{}\n{}\n{}\n{}\n{}\n{}",
3078 " ",
3079 "// Enable OSC8 hyperlink output",
3080 "// Options:",
3081 "// - true (Default)",
3082 "// - false",
3083 "// ",
3084 );
3085
3086 let create_node = |node_value: bool| -> KdlNode {
3087 let mut node = KdlNode::new("osc8_hyperlinks");
3088 node.push(KdlValue::Bool(node_value));
3089 node
3090 };
3091 if let Some(osc8_hyperlinks) = self.osc8_hyperlinks {
3092 let mut node = create_node(osc8_hyperlinks);
3093 if add_comments {
3094 node.set_leading(format!("{}\n", comment_text));
3095 }
3096 Some(node)
3097 } else if add_comments {
3098 let mut node = create_node(true);
3099 node.set_leading(format!("{}\n// ", comment_text));
3100 Some(node)
3101 } else {
3102 None
3103 }
3104 }
3105 fn theme_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3106 let comment_text = format!(
3107 "{}\n{}\n{}\n{}",
3108 " ",
3109 "// Choose the theme that is specified in the themes section.",
3110 "// Default: default",
3111 "// ",
3112 );
3113
3114 let create_node = |node_value: &str| -> KdlNode {
3115 let mut node = KdlNode::new("theme");
3116 node.push(node_value.to_owned());
3117 node
3118 };
3119 if let Some(theme) = &self.theme {
3120 let mut node = create_node(theme);
3121 if add_comments {
3122 node.set_leading(format!("{}\n", comment_text));
3123 }
3124 Some(node)
3125 } else if add_comments {
3126 let mut node = create_node("dracula");
3127 node.set_leading(format!("{}\n// ", comment_text));
3128 Some(node)
3129 } else {
3130 None
3131 }
3132 }
3133 fn theme_dark_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3134 let comment_text = format!(
3135 "{}\n{}\n{}\n{}",
3136 " ",
3137 "// Theme to use when the host terminal reports a dark color palette.",
3138 "// Requires `theme_light` to also be set; otherwise `theme` is used.",
3139 "// ",
3140 );
3141
3142 let create_node = |node_value: &str| -> KdlNode {
3143 let mut node = KdlNode::new("theme_dark");
3144 node.push(node_value.to_owned());
3145 node
3146 };
3147 if let Some(theme) = &self.theme_dark {
3148 let mut node = create_node(theme);
3149 if add_comments {
3150 node.set_leading(format!("{}\n", comment_text));
3151 }
3152 Some(node)
3153 } else if add_comments {
3154 let mut node = create_node("dracula");
3155 node.set_leading(format!("{}\n// ", comment_text));
3156 Some(node)
3157 } else {
3158 None
3159 }
3160 }
3161 fn theme_light_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3162 let comment_text = format!(
3163 "{}\n{}\n{}\n{}",
3164 " ",
3165 "// Theme to use when the host terminal reports a light color palette.",
3166 "// Requires `theme_dark` to also be set; otherwise `theme` is used.",
3167 "// ",
3168 );
3169
3170 let create_node = |node_value: &str| -> KdlNode {
3171 let mut node = KdlNode::new("theme_light");
3172 node.push(node_value.to_owned());
3173 node
3174 };
3175 if let Some(theme) = &self.theme_light {
3176 let mut node = create_node(theme);
3177 if add_comments {
3178 node.set_leading(format!("{}\n", comment_text));
3179 }
3180 Some(node)
3181 } else if add_comments {
3182 let mut node = create_node("solarized-light");
3183 node.set_leading(format!("{}\n// ", comment_text));
3184 Some(node)
3185 } else {
3186 None
3187 }
3188 }
3189 fn default_mode_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3190 let comment_text = format!(
3191 "{}\n{}\n{}\n{}",
3192 " ", "// Choose the base input mode of zellij.", "// Default: normal", "// "
3193 );
3194
3195 let create_node = |default_mode: &InputMode| -> KdlNode {
3196 let mut node = KdlNode::new("default_mode");
3197 node.push(format!("{:?}", default_mode).to_lowercase());
3198 node
3199 };
3200 if let Some(default_mode) = &self.default_mode {
3201 let mut node = create_node(default_mode);
3202 if add_comments {
3203 node.set_leading(format!("{}\n", comment_text));
3204 }
3205 Some(node)
3206 } else if add_comments {
3207 let mut node = create_node(&InputMode::Locked);
3208 node.set_leading(format!("{}\n// ", comment_text));
3209 Some(node)
3210 } else {
3211 None
3212 }
3213 }
3214 fn default_shell_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3215 let comment_text =
3216 format!("{}\n{}\n{}\n{}",
3217 " ",
3218 "// Choose the path to the default shell that zellij will use for opening new panes",
3219 "// Default: $SHELL",
3220 "// ",
3221 );
3222
3223 let create_node = |node_value: &str| -> KdlNode {
3224 let mut node = KdlNode::new("default_shell");
3225 node.push(node_value.to_owned());
3226 node
3227 };
3228 if let Some(default_shell) = &self.default_shell {
3229 let mut node = create_node(&default_shell.display().to_string());
3230 if add_comments {
3231 node.set_leading(format!("{}\n", comment_text));
3232 }
3233 Some(node)
3234 } else if add_comments {
3235 let mut node = create_node("fish");
3236 node.set_leading(format!("{}\n// ", comment_text));
3237 Some(node)
3238 } else {
3239 None
3240 }
3241 }
3242 fn default_cwd_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3243 let comment_text = format!(
3244 "{}\n{}\n{}",
3245 " ",
3246 "// Choose the path to override cwd that zellij will use for opening new panes",
3247 "// ",
3248 );
3249
3250 let create_node = |node_value: &str| -> KdlNode {
3251 let mut node = KdlNode::new("default_cwd");
3252 node.push(node_value.to_owned());
3253 node
3254 };
3255 if let Some(default_cwd) = &self.default_cwd {
3256 let mut node = create_node(&default_cwd.display().to_string());
3257 if add_comments {
3258 node.set_leading(format!("{}\n", comment_text));
3259 }
3260 Some(node)
3261 } else if add_comments {
3262 let mut node = create_node("/tmp");
3263 node.set_leading(format!("{}\n// ", comment_text));
3264 Some(node)
3265 } else {
3266 None
3267 }
3268 }
3269 fn default_layout_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3270 let comment_text = format!(
3271 "{}\n{}\n{}\n{}",
3272 " ",
3273 "// The name of the default layout to load on startup",
3274 "// Default: \"default\"",
3275 "// ",
3276 );
3277
3278 let create_node = |node_value: &str| -> KdlNode {
3279 let mut node = KdlNode::new("default_layout");
3280 node.push(node_value.to_owned());
3281 node
3282 };
3283 if let Some(default_layout) = &self.default_layout {
3284 let mut node = create_node(&default_layout.display().to_string());
3285 if add_comments {
3286 node.set_leading(format!("{}\n", comment_text));
3287 }
3288 Some(node)
3289 } else if add_comments {
3290 let mut node = create_node("compact");
3291 node.set_leading(format!("{}\n// ", comment_text));
3292 Some(node)
3293 } else {
3294 None
3295 }
3296 }
3297 fn layout_dir_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3298 let comment_text = format!(
3299 "{}\n{}\n{}\n{}",
3300 " ",
3301 "// The folder in which Zellij will look for layouts",
3302 "// (Requires restart)",
3303 "// ",
3304 );
3305
3306 let create_node = |node_value: &str| -> KdlNode {
3307 let mut node = KdlNode::new("layout_dir");
3308 node.push(node_value.to_owned());
3309 node
3310 };
3311 if let Some(layout_dir) = &self.layout_dir {
3312 let mut node = create_node(&layout_dir.display().to_string());
3313 if add_comments {
3314 node.set_leading(format!("{}\n", comment_text));
3315 }
3316 Some(node)
3317 } else if add_comments {
3318 let mut node = create_node("/tmp");
3319 node.set_leading(format!("{}\n// ", comment_text));
3320 Some(node)
3321 } else {
3322 None
3323 }
3324 }
3325 fn theme_dir_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3326 let comment_text = format!(
3327 "{}\n{}\n{}\n{}",
3328 " ",
3329 "// The folder in which Zellij will look for themes",
3330 "// (Requires restart)",
3331 "// ",
3332 );
3333
3334 let create_node = |node_value: &str| -> KdlNode {
3335 let mut node = KdlNode::new("theme_dir");
3336 node.push(node_value.to_owned());
3337 node
3338 };
3339 if let Some(theme_dir) = &self.theme_dir {
3340 let mut node = create_node(&theme_dir.display().to_string());
3341 if add_comments {
3342 node.set_leading(format!("{}\n", comment_text));
3343 }
3344 Some(node)
3345 } else if add_comments {
3346 let mut node = create_node("/tmp");
3347 node.set_leading(format!("{}\n// ", comment_text));
3348 Some(node)
3349 } else {
3350 None
3351 }
3352 }
3353 fn mouse_mode_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3354 let comment_text = format!(
3355 "{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}",
3356 " ",
3357 "// Toggle enabling the mouse mode.",
3358 "// On certain configurations, or terminals this could",
3359 "// potentially interfere with copying text.",
3360 "// Options:",
3361 "// - true (default)",
3362 "// - false",
3363 "// ",
3364 );
3365
3366 let create_node = |node_value: bool| -> KdlNode {
3367 let mut node = KdlNode::new("mouse_mode");
3368 node.push(KdlValue::Bool(node_value));
3369 node
3370 };
3371 if let Some(mouse_mode) = self.mouse_mode {
3372 let mut node = create_node(mouse_mode);
3373 if add_comments {
3374 node.set_leading(format!("{}\n", comment_text));
3375 }
3376 Some(node)
3377 } else if add_comments {
3378 let mut node = create_node(false);
3379 node.set_leading(format!("{}\n// ", comment_text));
3380 Some(node)
3381 } else {
3382 None
3383 }
3384 }
3385 fn pane_frames_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3386 let comment_text = format!(
3387 "{}\n{}\n{}\n{}\n{}\n{}",
3388 " ",
3389 "// Toggle having pane frames around the panes",
3390 "// Options:",
3391 "// - true (default, enabled)",
3392 "// - false",
3393 "// ",
3394 );
3395
3396 let create_node = |node_value: bool| -> KdlNode {
3397 let mut node = KdlNode::new("pane_frames");
3398 node.push(KdlValue::Bool(node_value));
3399 node
3400 };
3401 if let Some(pane_frames) = self.pane_frames {
3402 let mut node = create_node(pane_frames);
3403 if add_comments {
3404 node.set_leading(format!("{}\n", comment_text));
3405 }
3406 Some(node)
3407 } else if add_comments {
3408 let mut node = create_node(false);
3409 node.set_leading(format!("{}\n// ", comment_text));
3410 Some(node)
3411 } else {
3412 None
3413 }
3414 }
3415 fn pane_frame_style_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3416 let comment_text = format!(
3417 "{}\n{}\n{}\n{}\n{}\n{}",
3418 " ",
3419 "// Set the pane frame style when pane_frames is enabled",
3420 "// Options:",
3421 "// - full",
3422 "// - titles (default)",
3423 "// ",
3424 );
3425
3426 let style_as_str = |style: &PaneFrameStyle| -> &'static str {
3427 match style {
3428 PaneFrameStyle::Full => "full",
3429 PaneFrameStyle::Titles => "titles",
3430 PaneFrameStyle::None => "none",
3431 }
3432 };
3433
3434 let create_node = |node_value: &str| -> KdlNode {
3435 let mut node = KdlNode::new("pane_frame_style");
3436 node.push(node_value.to_owned());
3437 node
3438 };
3439 if let Some(pane_frame_style) = &self.pane_frame_style {
3440 let mut node = create_node(style_as_str(pane_frame_style));
3441 if add_comments {
3442 node.set_leading(format!("{}\n", comment_text));
3443 }
3444 Some(node)
3445 } else if add_comments {
3446 let mut node = create_node("titles");
3447 node.set_leading(format!("{}\n// ", comment_text));
3448 Some(node)
3449 } else {
3450 None
3451 }
3452 }
3453 fn mirror_session_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3454 let comment_text = format!(
3455 "{}\n{}\n{}\n{}\n{}\n{}\n{}",
3456 " ",
3457 "// When attaching to an existing session with other users,",
3458 "// should the session be mirrored (true)",
3459 "// or should each user have their own cursor (false)",
3460 "// (Requires restart)",
3461 "// Default: false",
3462 "// ",
3463 );
3464
3465 let create_node = |node_value: bool| -> KdlNode {
3466 let mut node = KdlNode::new("mirror_session");
3467 node.push(KdlValue::Bool(node_value));
3468 node
3469 };
3470 if let Some(mirror_session) = self.mirror_session {
3471 let mut node = create_node(mirror_session);
3472 if add_comments {
3473 node.set_leading(format!("{}\n", comment_text));
3474 }
3475 Some(node)
3476 } else if add_comments {
3477 let mut node = create_node(true);
3478 node.set_leading(format!("{}\n// ", comment_text));
3479 Some(node)
3480 } else {
3481 None
3482 }
3483 }
3484 fn on_force_close_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3485 let comment_text = format!(
3486 "{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}",
3487 " ",
3488 "// Choose what to do when zellij receives SIGTERM, SIGINT, SIGQUIT or SIGHUP",
3489 "// eg. when terminal window with an active zellij session is closed",
3490 "// (Requires restart)",
3491 "// Options:",
3492 "// - detach (Default)",
3493 "// - quit",
3494 "// ",
3495 );
3496
3497 let create_node = |node_value: &str| -> KdlNode {
3498 let mut node = KdlNode::new("on_force_close");
3499 node.push(node_value.to_owned());
3500 node
3501 };
3502 if let Some(on_force_close) = &self.on_force_close {
3503 let mut node = match on_force_close {
3504 OnForceClose::Detach => create_node("detach"),
3505 OnForceClose::Quit => create_node("quit"),
3506 };
3507 if add_comments {
3508 node.set_leading(format!("{}\n", comment_text));
3509 }
3510 Some(node)
3511 } else if add_comments {
3512 let mut node = create_node("quit");
3513 node.set_leading(format!("{}\n// ", comment_text));
3514 Some(node)
3515 } else {
3516 None
3517 }
3518 }
3519 fn scroll_buffer_size_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3520 let comment_text = format!(
3521 "{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}",
3522 " ",
3523 "// Configure the scroll back buffer size",
3524 "// This is the number of lines zellij stores for each pane in the scroll back",
3525 "// buffer. Excess number of lines are discarded in a FIFO fashion.",
3526 "// (Requires restart)",
3527 "// Valid values: positive integers",
3528 "// Default value: 10000",
3529 "// ",
3530 );
3531
3532 let create_node = |node_value: usize| -> KdlNode {
3533 let mut node = KdlNode::new("scroll_buffer_size");
3534 node.push(KdlValue::Base10(node_value as i64));
3535 node
3536 };
3537 if let Some(scroll_buffer_size) = self.scroll_buffer_size {
3538 let mut node = create_node(scroll_buffer_size);
3539 if add_comments {
3540 node.set_leading(format!("{}\n", comment_text));
3541 }
3542 Some(node)
3543 } else if add_comments {
3544 let mut node = create_node(10000);
3545 node.set_leading(format!("{}\n// ", comment_text));
3546 Some(node)
3547 } else {
3548 None
3549 }
3550 }
3551 fn copy_command_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3552 let comment_text = format!(
3553 "{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}",
3554 " ",
3555 "// Provide a command to execute when copying text. The text will be piped to",
3556 "// the stdin of the program to perform the copy. This can be used with",
3557 "// terminal emulators which do not support the OSC 52 ANSI control sequence",
3558 "// that will be used by default if this option is not set.",
3559 "// Examples:",
3560 "//",
3561 "// copy_command \"xclip -selection clipboard\" // x11",
3562 "// copy_command \"wl-copy\" // wayland",
3563 "// copy_command \"pbcopy\" // osx",
3564 "// ",
3565 );
3566
3567 let create_node = |node_value: &str| -> KdlNode {
3568 let mut node = KdlNode::new("copy_command");
3569 node.push(node_value.to_owned());
3570 node
3571 };
3572 if let Some(copy_command) = &self.copy_command {
3573 let mut node = create_node(copy_command);
3574 if add_comments {
3575 node.set_leading(format!("{}\n", comment_text));
3576 }
3577 Some(node)
3578 } else if add_comments {
3579 let mut node = create_node("pbcopy");
3580 node.set_leading(format!("{}\n// ", comment_text));
3581 Some(node)
3582 } else {
3583 None
3584 }
3585 }
3586 fn copy_clipboard_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3587 let comment_text = format!("{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}",
3588 " ",
3589 "// Choose the destination for copied text",
3590 "// Allows using the primary selection buffer (on x11/wayland) instead of the system clipboard.",
3591 "// Does not apply when using copy_command.",
3592 "// Options:",
3593 "// - system (default)",
3594 "// - primary",
3595 "// ",
3596 );
3597
3598 let create_node = |node_value: &str| -> KdlNode {
3599 let mut node = KdlNode::new("copy_clipboard");
3600 node.push(node_value.to_owned());
3601 node
3602 };
3603 if let Some(copy_clipboard) = &self.copy_clipboard {
3604 let mut node = match copy_clipboard {
3605 Clipboard::Primary => create_node("primary"),
3606 Clipboard::System => create_node("system"),
3607 };
3608 if add_comments {
3609 node.set_leading(format!("{}\n", comment_text));
3610 }
3611 Some(node)
3612 } else if add_comments {
3613 let mut node = create_node("primary");
3614 node.set_leading(format!("{}\n// ", comment_text));
3615 Some(node)
3616 } else {
3617 None
3618 }
3619 }
3620 fn copy_on_select_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3621 let comment_text = format!(
3622 "{}\n{}\n{}\n{}",
3623 " ",
3624 "// Enable automatic copying (and clearing) of selection when releasing mouse",
3625 "// Default: true",
3626 "// ",
3627 );
3628
3629 let create_node = |node_value: bool| -> KdlNode {
3630 let mut node = KdlNode::new("copy_on_select");
3631 node.push(KdlValue::Bool(node_value));
3632 node
3633 };
3634 if let Some(copy_on_select) = self.copy_on_select {
3635 let mut node = create_node(copy_on_select);
3636 if add_comments {
3637 node.set_leading(format!("{}\n", comment_text));
3638 }
3639 Some(node)
3640 } else if add_comments {
3641 let mut node = create_node(true);
3642 node.set_leading(format!("{}\n// ", comment_text));
3643 Some(node)
3644 } else {
3645 None
3646 }
3647 }
3648 fn scrollback_editor_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3649 let comment_text = format!(
3650 "{}\n{}\n{}",
3651 " ",
3652 "// Path to the default editor to use to edit pane scrollbuffer",
3653 "// Default: $EDITOR or $VISUAL",
3654 );
3655
3656 let create_node = |node_value: &str| -> KdlNode {
3657 let mut node = KdlNode::new("scrollback_editor");
3658 node.push(node_value.to_owned());
3659 node
3660 };
3661 if let Some(scrollback_editor) = &self.scrollback_editor {
3662 let mut node = create_node(&scrollback_editor.display().to_string());
3663 if add_comments {
3664 node.set_leading(format!("{}\n", comment_text));
3665 }
3666 Some(node)
3667 } else if add_comments {
3668 let mut node = create_node("/usr/bin/vim");
3669 node.set_leading(format!("{}\n// ", comment_text));
3670 Some(node)
3671 } else {
3672 None
3673 }
3674 }
3675 fn session_name_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3676 let comment_text = format!(
3677 "{}\n{}\n{}\n{}\n{}\n{}",
3678 " ",
3679 "// A fixed name to always give the Zellij session.",
3680 "// Consider also setting `attach_to_session true,`",
3681 "// otherwise this will error if such a session exists.",
3682 "// Default: <RANDOM>",
3683 "// ",
3684 );
3685
3686 let create_node = |node_value: &str| -> KdlNode {
3687 let mut node = KdlNode::new("session_name");
3688 node.push(node_value.to_owned());
3689 node
3690 };
3691 if let Some(session_name) = &self.session_name {
3692 let mut node = create_node(&session_name);
3693 if add_comments {
3694 node.set_leading(format!("{}\n", comment_text));
3695 }
3696 Some(node)
3697 } else if add_comments {
3698 let mut node = create_node("My singleton session");
3699 node.set_leading(format!("{}\n// ", comment_text));
3700 Some(node)
3701 } else {
3702 None
3703 }
3704 }
3705 fn attach_to_session_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3706 let comment_text = format!(
3707 "{}\n{}\n{}\n{}\n{}",
3708 " ",
3709 "// When `session_name` is provided, attaches to that session",
3710 "// if it is already running or creates it otherwise.",
3711 "// Default: false",
3712 "// ",
3713 );
3714
3715 let create_node = |node_value: bool| -> KdlNode {
3716 let mut node = KdlNode::new("attach_to_session");
3717 node.push(KdlValue::Bool(node_value));
3718 node
3719 };
3720 if let Some(attach_to_session) = self.attach_to_session {
3721 let mut node = create_node(attach_to_session);
3722 if add_comments {
3723 node.set_leading(format!("{}\n", comment_text));
3724 }
3725 Some(node)
3726 } else if add_comments {
3727 let mut node = create_node(true);
3728 node.set_leading(format!("{}\n// ", comment_text));
3729 Some(node)
3730 } else {
3731 None
3732 }
3733 }
3734 fn auto_layout_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3735 let comment_text = format!("{}\n{}\n{}\n{}\n{}\n{}",
3736 " ",
3737 "// Toggle between having Zellij lay out panes according to a predefined set of layouts whenever possible",
3738 "// Options:",
3739 "// - true (default)",
3740 "// - false",
3741 "// ",
3742 );
3743
3744 let create_node = |node_value: bool| -> KdlNode {
3745 let mut node = KdlNode::new("auto_layout");
3746 node.push(KdlValue::Bool(node_value));
3747 node
3748 };
3749 if let Some(auto_layout) = self.auto_layout {
3750 let mut node = create_node(auto_layout);
3751 if add_comments {
3752 node.set_leading(format!("{}\n", comment_text));
3753 }
3754 Some(node)
3755 } else if add_comments {
3756 let mut node = create_node(false);
3757 node.set_leading(format!("{}\n// ", comment_text));
3758 Some(node)
3759 } else {
3760 None
3761 }
3762 }
3763 fn session_serialization_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3764 let comment_text = format!("{}\n{}\n{}\n{}\n{}\n{}",
3765 " ",
3766 "// Whether sessions should be serialized to the cache folder (including their tabs/panes, cwds and running commands) so that they can later be resurrected",
3767 "// Options:",
3768 "// - true (default)",
3769 "// - false",
3770 "// ",
3771 );
3772
3773 let create_node = |node_value: bool| -> KdlNode {
3774 let mut node = KdlNode::new("session_serialization");
3775 node.push(KdlValue::Bool(node_value));
3776 node
3777 };
3778 if let Some(session_serialization) = self.session_serialization {
3779 let mut node = create_node(session_serialization);
3780 if add_comments {
3781 node.set_leading(format!("{}\n", comment_text));
3782 }
3783 Some(node)
3784 } else if add_comments {
3785 let mut node = create_node(false);
3786 node.set_leading(format!("{}\n// ", comment_text));
3787 Some(node)
3788 } else {
3789 None
3790 }
3791 }
3792 fn serialize_pane_viewport_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3793 let comment_text = format!(
3794 "{}\n{}\n{}\n{}\n{}\n{}",
3795 " ",
3796 "// Whether pane viewports are serialized along with the session, default is false",
3797 "// Options:",
3798 "// - true",
3799 "// - false (default)",
3800 "// ",
3801 );
3802
3803 let create_node = |node_value: bool| -> KdlNode {
3804 let mut node = KdlNode::new("serialize_pane_viewport");
3805 node.push(KdlValue::Bool(node_value));
3806 node
3807 };
3808 if let Some(serialize_pane_viewport) = self.serialize_pane_viewport {
3809 let mut node = create_node(serialize_pane_viewport);
3810 if add_comments {
3811 node.set_leading(format!("{}\n", comment_text));
3812 }
3813 Some(node)
3814 } else if add_comments {
3815 let mut node = create_node(false);
3816 node.set_leading(format!("{}\n// ", comment_text));
3817 Some(node)
3818 } else {
3819 None
3820 }
3821 }
3822 fn scrollback_lines_to_serialize_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3823 let comment_text = format!("{}\n{}\n{}\n{}\n{}",
3824 " ",
3825 "// Scrollback lines to serialize along with the pane viewport when serializing sessions, 0",
3826 "// defaults to the scrollback size. If this number is higher than the scrollback size, it will",
3827 "// also default to the scrollback size. This does nothing if `serialize_pane_viewport` is not true.",
3828 "// ",
3829 );
3830
3831 let create_node = |node_value: usize| -> KdlNode {
3832 let mut node = KdlNode::new("scrollback_lines_to_serialize");
3833 node.push(KdlValue::Base10(node_value as i64));
3834 node
3835 };
3836 if let Some(scrollback_lines_to_serialize) = self.scrollback_lines_to_serialize {
3837 let mut node = create_node(scrollback_lines_to_serialize);
3838 if add_comments {
3839 node.set_leading(format!("{}\n", comment_text));
3840 }
3841 Some(node)
3842 } else if add_comments {
3843 let mut node = create_node(10000);
3844 node.set_leading(format!("{}\n// ", comment_text));
3845 Some(node)
3846 } else {
3847 None
3848 }
3849 }
3850 fn styled_underlines_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3851 let comment_text = format!(
3852 "{}\n{}\n{}\n{}\n{}\n{}",
3853 " ",
3854 "// Enable or disable the rendering of styled and colored underlines (undercurl).",
3855 "// May need to be disabled for certain unsupported terminals",
3856 "// (Requires restart)",
3857 "// Default: true",
3858 "// ",
3859 );
3860
3861 let create_node = |node_value: bool| -> KdlNode {
3862 let mut node = KdlNode::new("styled_underlines");
3863 node.push(KdlValue::Bool(node_value));
3864 node
3865 };
3866 if let Some(styled_underlines) = self.styled_underlines {
3867 let mut node = create_node(styled_underlines);
3868 if add_comments {
3869 node.set_leading(format!("{}\n", comment_text));
3870 }
3871 Some(node)
3872 } else if add_comments {
3873 let mut node = create_node(false);
3874 node.set_leading(format!("{}\n// ", comment_text));
3875 Some(node)
3876 } else {
3877 None
3878 }
3879 }
3880 fn serialization_interval_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3881 let comment_text = format!(
3882 "{}\n{}\n{}",
3883 " ", "// How often in seconds sessions are serialized", "// ",
3884 );
3885
3886 let create_node = |node_value: u64| -> KdlNode {
3887 let mut node = KdlNode::new("serialization_interval");
3888 node.push(KdlValue::Base10(node_value as i64));
3889 node
3890 };
3891 if let Some(serialization_interval) = self.serialization_interval {
3892 let mut node = create_node(serialization_interval);
3893 if add_comments {
3894 node.set_leading(format!("{}\n", comment_text));
3895 }
3896 Some(node)
3897 } else if add_comments {
3898 let mut node = create_node(10000);
3899 node.set_leading(format!("{}\n// ", comment_text));
3900 Some(node)
3901 } else {
3902 None
3903 }
3904 }
3905 fn disable_session_metadata_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3906 let comment_text = format!("{}\n{}\n{}\n{}\n{}\n{}",
3907 " ",
3908 "// Enable or disable writing of session metadata to disk (if disabled, other sessions might not know",
3909 "// metadata info on this session)",
3910 "// (Requires restart)",
3911 "// Default: false",
3912 "// ",
3913 );
3914
3915 let create_node = |node_value: bool| -> KdlNode {
3916 let mut node = KdlNode::new("disable_session_metadata");
3917 node.push(KdlValue::Bool(node_value));
3918 node
3919 };
3920 if let Some(disable_session_metadata) = self.disable_session_metadata {
3921 let mut node = create_node(disable_session_metadata);
3922 if add_comments {
3923 node.set_leading(format!("{}\n", comment_text));
3924 }
3925 Some(node)
3926 } else if add_comments {
3927 let mut node = create_node(false);
3928 node.set_leading(format!("{}\n// ", comment_text));
3929 Some(node)
3930 } else {
3931 None
3932 }
3933 }
3934 fn support_kitty_keyboard_protocol_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3935 let comment_text = format!("{}\n{}\n{}\n{}\n{}",
3936 " ",
3937 "// Enable or disable support for the enhanced Kitty Keyboard Protocol (the host terminal must also support it)",
3938 "// (Requires restart)",
3939 "// Default: true (if the host terminal supports it)",
3940 "// ",
3941 );
3942
3943 let create_node = |node_value: bool| -> KdlNode {
3944 let mut node = KdlNode::new("support_kitty_keyboard_protocol");
3945 node.push(KdlValue::Bool(node_value));
3946 node
3947 };
3948 if let Some(support_kitty_keyboard_protocol) = self.support_kitty_keyboard_protocol {
3949 let mut node = create_node(support_kitty_keyboard_protocol);
3950 if add_comments {
3951 node.set_leading(format!("{}\n", comment_text));
3952 }
3953 Some(node)
3954 } else if add_comments {
3955 let mut node = create_node(false);
3956 node.set_leading(format!("{}\n// ", comment_text));
3957 Some(node)
3958 } else {
3959 None
3960 }
3961 }
3962 fn support_kitty_graphics_protocol_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3963 let comment_text = format!("{}\n{}\n{}\n{}\n{}",
3964 " ",
3965 "// Enable or disable support for the Kitty Graphics Protocol, used to display images (the host terminal must also support it)",
3966 "// (Requires restart)",
3967 "// Default: true (if the host terminal supports it)",
3968 "// ",
3969 );
3970
3971 let create_node = |node_value: bool| -> KdlNode {
3972 let mut node = KdlNode::new("support_kitty_graphics_protocol");
3973 node.push(KdlValue::Bool(node_value));
3974 node
3975 };
3976 if let Some(support_kitty_graphics_protocol) = self.support_kitty_graphics_protocol {
3977 let mut node = create_node(support_kitty_graphics_protocol);
3978 if add_comments {
3979 node.set_leading(format!("{}\n", comment_text));
3980 }
3981 Some(node)
3982 } else if add_comments {
3983 let mut node = create_node(false);
3984 node.set_leading(format!("{}\n// ", comment_text));
3985 Some(node)
3986 } else {
3987 None
3988 }
3989 }
3990 fn web_server_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3991 let comment_text = format!(
3992 "{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}",
3993 "// Whether to make sure a local web server is running when a new Zellij session starts.",
3994 "// This web server will allow creating new sessions and attaching to existing ones that have",
3995 "// opted in to being shared in the browser.",
3996 "// When enabled, navigate to http://127.0.0.1:8082",
3997 "// (Requires restart)",
3998 "// ",
3999 "// Note: a local web server can still be manually started from within a Zellij session or from the CLI.",
4000 "// If this is not desired, one can use a version of Zellij compiled without",
4001 "// `web_server_capability`",
4002 "// ",
4003 "// Possible values:",
4004 "// - true",
4005 "// - false",
4006 "// Default: false",
4007 "// ",
4008 );
4009
4010 let create_node = |node_value: bool| -> KdlNode {
4011 let mut node = KdlNode::new("web_server");
4012 node.push(KdlValue::Bool(node_value));
4013 node
4014 };
4015 if let Some(web_server) = self.web_server {
4016 let mut node = create_node(web_server);
4017 if add_comments {
4018 node.set_leading(format!("{}\n", comment_text));
4019 }
4020 Some(node)
4021 } else if add_comments {
4022 let mut node = create_node(false);
4023 node.set_leading(format!("{}\n// ", comment_text));
4024 Some(node)
4025 } else {
4026 None
4027 }
4028 }
4029 fn web_sharing_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4030 let comment_text = format!(
4031 "{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}",
4032 "// Whether to allow sessions started in the terminal to be shared through a local web server, assuming one is",
4033 "// running (see the `web_server` option for more details).",
4034 "// (Requires restart)",
4035 "// ",
4036 "// Note: This is an administrative separation and not intended as a security measure.",
4037 "// ",
4038 "// Possible values:",
4039 "// - \"on\" (allow web sharing through the local web server if it",
4040 "// is online)",
4041 "// - \"off\" (do not allow web sharing unless sessions explicitly opt-in to it)",
4042 "// - \"disabled\" (do not allow web sharing and do not permit sessions started in the terminal to opt-in to it)",
4043 "// Default: \"off\"",
4044 "// ",
4045 );
4046
4047 let create_node = |node_value: &str| -> KdlNode {
4048 let mut node = KdlNode::new("web_sharing");
4049 node.push(node_value.to_owned());
4050 node
4051 };
4052 if let Some(web_sharing) = &self.web_sharing {
4053 let mut node = match web_sharing {
4054 WebSharing::On => create_node("on"),
4055 WebSharing::Off => create_node("off"),
4056 WebSharing::Disabled => create_node("disabled"),
4057 };
4058 if add_comments {
4059 node.set_leading(format!("{}\n", comment_text));
4060 }
4061 Some(node)
4062 } else if add_comments {
4063 let mut node = create_node("off");
4064 node.set_leading(format!("{}\n// ", comment_text));
4065 Some(node)
4066 } else {
4067 None
4068 }
4069 }
4070 fn web_server_cert_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4071 let comment_text = format!(
4072 "{}\n{}\n{}",
4073 "// A path to a certificate file to be used when setting up the web client to serve the",
4074 "// connection over HTTPs",
4075 "// ",
4076 );
4077 let create_node = |node_value: &str| -> KdlNode {
4078 let mut node = KdlNode::new("web_server_cert");
4079 node.push(node_value.to_owned());
4080 node
4081 };
4082 if let Some(web_server_cert) = &self.web_server_cert {
4083 let mut node = create_node(&web_server_cert.display().to_string());
4084 if add_comments {
4085 node.set_leading(format!("{}\n", comment_text));
4086 }
4087 Some(node)
4088 } else if add_comments {
4089 let mut node = create_node("/path/to/cert.pem");
4090 node.set_leading(format!("{}\n// ", comment_text));
4091 Some(node)
4092 } else {
4093 None
4094 }
4095 }
4096 fn web_server_key_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4097 let comment_text = format!(
4098 "{}\n{}\n{}",
4099 "// A path to a key file to be used when setting up the web client to serve the",
4100 "// connection over HTTPs",
4101 "// ",
4102 );
4103 let create_node = |node_value: &str| -> KdlNode {
4104 let mut node = KdlNode::new("web_server_key");
4105 node.push(node_value.to_owned());
4106 node
4107 };
4108 if let Some(web_server_key) = &self.web_server_key {
4109 let mut node = create_node(&web_server_key.display().to_string());
4110 if add_comments {
4111 node.set_leading(format!("{}\n", comment_text));
4112 }
4113 Some(node)
4114 } else if add_comments {
4115 let mut node = create_node("/path/to/key.pem");
4116 node.set_leading(format!("{}\n// ", comment_text));
4117 Some(node)
4118 } else {
4119 None
4120 }
4121 }
4122 fn enforce_https_for_localhost_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4123 let comment_text = format!(
4124 "{}\n{}\n{}\n{}\n{}\n{}\n{}",
4125 "/// Whether to enforce https connections to the web server when it is bound to localhost",
4126 "/// (127.0.0.0/8)",
4127 "///",
4128 "/// Note: https is ALWAYS enforced when bound to non-local interfaces",
4129 "///",
4130 "/// Default: false",
4131 "// ",
4132 );
4133
4134 let create_node = |node_value: bool| -> KdlNode {
4135 let mut node = KdlNode::new("enforce_https_for_localhost");
4136 node.push(KdlValue::Bool(node_value));
4137 node
4138 };
4139 if let Some(enforce_https_for_localhost) = self.enforce_https_for_localhost {
4140 let mut node = create_node(enforce_https_for_localhost);
4141 if add_comments {
4142 node.set_leading(format!("{}\n", comment_text));
4143 }
4144 Some(node)
4145 } else if add_comments {
4146 let mut node = create_node(false);
4147 node.set_leading(format!("{}\n// ", comment_text));
4148 Some(node)
4149 } else {
4150 None
4151 }
4152 }
4153 fn stacked_resize_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4154 let comment_text = format!(
4155 "{}\n{}\n{}\n{}",
4156 " ",
4157 "// Whether to stack panes when resizing beyond a certain size",
4158 "// Default: true",
4159 "// ",
4160 );
4161
4162 let create_node = |node_value: bool| -> KdlNode {
4163 let mut node = KdlNode::new("stacked_resize");
4164 node.push(KdlValue::Bool(node_value));
4165 node
4166 };
4167 if let Some(stacked_resize) = self.stacked_resize {
4168 let mut node = create_node(stacked_resize);
4169 if add_comments {
4170 node.set_leading(format!("{}\n", comment_text));
4171 }
4172 Some(node)
4173 } else if add_comments {
4174 let mut node = create_node(false);
4175 node.set_leading(format!("{}\n// ", comment_text));
4176 Some(node)
4177 } else {
4178 None
4179 }
4180 }
4181 fn stacked_pane_list_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4182 let comment_text = format!(
4183 "{}\n{}\n{}\n{}",
4184 " ",
4185 "// Whether stacked panes display as a list with the expanded pane pinned to the bottom",
4186 "// Default: true",
4187 "// ",
4188 );
4189
4190 let create_node = |node_value: bool| -> KdlNode {
4191 let mut node = KdlNode::new("stacked_pane_list");
4192 node.push(KdlValue::Bool(node_value));
4193 node
4194 };
4195 if let Some(stacked_pane_list) = self.stacked_pane_list {
4196 let mut node = create_node(stacked_pane_list);
4197 if add_comments {
4198 node.set_leading(format!("{}\n", comment_text));
4199 }
4200 Some(node)
4201 } else if add_comments {
4202 let mut node = create_node(false);
4203 node.set_leading(format!("{}\n// ", comment_text));
4204 Some(node)
4205 } else {
4206 None
4207 }
4208 }
4209 fn show_startup_tips_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4210 let comment_text = format!(
4211 "{}\n{}\n{}\n{}",
4212 " ", "// Whether to show tips on startup", "// Default: true", "// ",
4213 );
4214
4215 let create_node = |node_value: bool| -> KdlNode {
4216 let mut node = KdlNode::new("show_startup_tips");
4217 node.push(KdlValue::Bool(node_value));
4218 node
4219 };
4220 if let Some(show_startup_tips) = self.show_startup_tips {
4221 let mut node = create_node(show_startup_tips);
4222 if add_comments {
4223 node.set_leading(format!("{}\n", comment_text));
4224 }
4225 Some(node)
4226 } else if add_comments {
4227 let mut node = create_node(false);
4228 node.set_leading(format!("{}\n// ", comment_text));
4229 Some(node)
4230 } else {
4231 None
4232 }
4233 }
4234 fn show_release_notes_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4235 let comment_text = format!(
4236 "{}\n{}\n{}\n{}",
4237 " ", "// Whether to show release notes on first version run", "// Default: true", "// ",
4238 );
4239
4240 let create_node = |node_value: bool| -> KdlNode {
4241 let mut node = KdlNode::new("show_release_notes");
4242 node.push(KdlValue::Bool(node_value));
4243 node
4244 };
4245 if let Some(show_release_notes) = self.show_release_notes {
4246 let mut node = create_node(show_release_notes);
4247 if add_comments {
4248 node.set_leading(format!("{}\n", comment_text));
4249 }
4250 Some(node)
4251 } else if add_comments {
4252 let mut node = create_node(false);
4253 node.set_leading(format!("{}\n// ", comment_text));
4254 Some(node)
4255 } else {
4256 None
4257 }
4258 }
4259 fn advanced_mouse_actions_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4260 let comment_text = format!(
4261 "{}\n{}\n{}",
4262 " ",
4263 "// Whether to enable mouse hover effects and pane grouping functionality",
4264 "// default is true",
4265 );
4266
4267 let create_node = |node_value: bool| -> KdlNode {
4268 let mut node = KdlNode::new("advanced_mouse_actions");
4269 node.push(KdlValue::Bool(node_value));
4270 node
4271 };
4272 if let Some(advanced_mouse_actions) = self.advanced_mouse_actions {
4273 let mut node = create_node(advanced_mouse_actions);
4274 if add_comments {
4275 node.set_leading(format!("{}\n", comment_text));
4276 }
4277 Some(node)
4278 } else if add_comments {
4279 let mut node = create_node(false);
4280 node.set_leading(format!("{}\n// ", comment_text));
4281 Some(node)
4282 } else {
4283 None
4284 }
4285 }
4286 fn mouse_scroll_resize_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4287 let comment_text = format!(
4288 "{}\n{}\n{}",
4289 " ", "// Whether Ctrl+ScrollWheel resizes panes", "// default is true",
4290 );
4291
4292 let create_node = |node_value: bool| -> KdlNode {
4293 let mut node = KdlNode::new("mouse_scroll_resize");
4294 node.push(KdlValue::Bool(node_value));
4295 node
4296 };
4297 if let Some(mouse_scroll_resize) = self.mouse_scroll_resize {
4298 let mut node = create_node(mouse_scroll_resize);
4299 if add_comments {
4300 node.set_leading(format!("{}\n", comment_text));
4301 }
4302 Some(node)
4303 } else if add_comments {
4304 let mut node = create_node(false);
4305 node.set_leading(format!("{}\n// ", comment_text));
4306 Some(node)
4307 } else {
4308 None
4309 }
4310 }
4311 fn mouse_hover_tips_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4312 let comment_text = format!(
4313 "{}\n{}\n{}",
4314 " ",
4315 "// Whether to show mouse hover help-text tips (resize help and group shortcuts)",
4316 "// default is true",
4317 );
4318
4319 let create_node = |node_value: bool| -> KdlNode {
4320 let mut node = KdlNode::new("mouse_hover_tips");
4321 node.push(KdlValue::Bool(node_value));
4322 node
4323 };
4324 if let Some(mouse_hover_tips) = self.mouse_hover_tips {
4325 let mut node = create_node(mouse_hover_tips);
4326 if add_comments {
4327 node.set_leading(format!("{}\n", comment_text));
4328 }
4329 Some(node)
4330 } else if add_comments {
4331 let mut node = create_node(false);
4332 node.set_leading(format!("{}\n// ", comment_text));
4333 Some(node)
4334 } else {
4335 None
4336 }
4337 }
4338 fn mouse_hover_effects_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4339 let comment_text = format!(
4340 "{}\n{}\n{}",
4341 " ",
4342 "// Whether to enable mouse hover visual effects (frame highlight and help text)",
4343 "// default is true",
4344 );
4345
4346 let create_node = |node_value: bool| -> KdlNode {
4347 let mut node = KdlNode::new("mouse_hover_effects");
4348 node.push(KdlValue::Bool(node_value));
4349 node
4350 };
4351 if let Some(mouse_hover_effects) = self.mouse_hover_effects {
4352 let mut node = create_node(mouse_hover_effects);
4353 if add_comments {
4354 node.set_leading(format!("{}\n", comment_text));
4355 }
4356 Some(node)
4357 } else if add_comments {
4358 let mut node = create_node(false);
4359 node.set_leading(format!("{}\n// ", comment_text));
4360 Some(node)
4361 } else {
4362 None
4363 }
4364 }
4365 fn visual_bell_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4366 let comment_text = format!(
4367 "{}\n{}\n{}",
4368 " ",
4369 "// Whether to show visual bell indicators (pane/tab frame flash and [!] suffix)",
4370 "// default is true",
4371 );
4372
4373 let create_node = |node_value: bool| -> KdlNode {
4374 let mut node = KdlNode::new("visual_bell");
4375 node.push(KdlValue::Bool(node_value));
4376 node
4377 };
4378 if let Some(visual_bell) = self.visual_bell {
4379 let mut node = create_node(visual_bell);
4380 if add_comments {
4381 node.set_leading(format!("{}\n", comment_text));
4382 }
4383 Some(node)
4384 } else if add_comments {
4385 let mut node = create_node(true);
4386 node.set_leading(format!("{}\n// ", comment_text));
4387 Some(node)
4388 } else {
4389 None
4390 }
4391 }
4392 fn focus_follows_mouse_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4393 let comment_text = format!(
4394 "{}\n{}\n{}",
4395 " ", "// Whether to focus panes on mouse hover", "// default is false",
4396 );
4397
4398 let create_node = |node_value: bool| -> KdlNode {
4399 let mut node = KdlNode::new("focus_follows_mouse");
4400 node.push(KdlValue::Bool(node_value));
4401 node
4402 };
4403 if let Some(focus_follows_mouse) = self.focus_follows_mouse {
4404 let mut node = create_node(focus_follows_mouse);
4405 if add_comments {
4406 node.set_leading(format!("{}\n", comment_text));
4407 }
4408 Some(node)
4409 } else if add_comments {
4410 let mut node = create_node(false);
4411 node.set_leading(format!("{}\n// ", comment_text));
4412 Some(node)
4413 } else {
4414 None
4415 }
4416 }
4417 fn mouse_click_through_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4418 let comment_text = format!(
4419 "{}\n{}\n{}",
4420 " ",
4421 "// Whether clicking a pane to focus it also sends the click into the pane",
4422 "// default is false",
4423 );
4424
4425 let create_node = |node_value: bool| -> KdlNode {
4426 let mut node = KdlNode::new("mouse_click_through");
4427 node.push(KdlValue::Bool(node_value));
4428 node
4429 };
4430 if let Some(mouse_click_through) = self.mouse_click_through {
4431 let mut node = create_node(mouse_click_through);
4432 if add_comments {
4433 node.set_leading(format!("{}\n", comment_text));
4434 }
4435 Some(node)
4436 } else if add_comments {
4437 let mut node = create_node(false);
4438 node.set_leading(format!("{}\n// ", comment_text));
4439 Some(node)
4440 } else {
4441 None
4442 }
4443 }
4444 fn osc133_command_selection_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4445 let comment_text = format!(
4446 "{}\n{}\n{}\n{}",
4447 " ",
4448 "// Whether triple-clicking inside command output marked by the shell (OSC 133) selects",
4449 "// the command and its output instead of the logical line",
4450 "// default is true",
4451 );
4452
4453 let create_node = |node_value: bool| -> KdlNode {
4454 let mut node = KdlNode::new("osc133_command_selection");
4455 node.push(KdlValue::Bool(node_value));
4456 node
4457 };
4458 if let Some(osc133_command_selection) = self.osc133_command_selection {
4459 let mut node = create_node(osc133_command_selection);
4460 if add_comments {
4461 node.set_leading(format!("{}\n", comment_text));
4462 }
4463 Some(node)
4464 } else if add_comments {
4465 let mut node = create_node(false);
4466 node.set_leading(format!("{}\n// ", comment_text));
4467 Some(node)
4468 } else {
4469 None
4470 }
4471 }
4472 fn word_separators_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4473 let comment_text = format!(
4474 "{}\n{}\n{}\n{}",
4475 " ",
4476 "// Characters that terminate a word when double-clicking to select it",
4477 "// whitespace is always a separator and need not be listed here",
4478 "// default is \"[]{}<>()\"",
4479 );
4480
4481 let create_node = |node_value: &str| -> KdlNode {
4482 let mut node = KdlNode::new("word_separators");
4483 node.push(node_value.to_owned());
4484 node
4485 };
4486 if let Some(word_separators) = &self.word_separators {
4487 let mut node = create_node(word_separators);
4488 if add_comments {
4489 node.set_leading(format!("{}\n", comment_text));
4490 }
4491 Some(node)
4492 } else if add_comments {
4493 let mut node = create_node(DEFAULT_WORD_SEPARATORS);
4494 node.set_leading(format!("{}\n// ", comment_text));
4495 Some(node)
4496 } else {
4497 None
4498 }
4499 }
4500 fn web_server_ip_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4501 let comment_text = format!(
4502 "{}\n{}\n{}\n{}",
4503 " ",
4504 "// The ip address the web server should listen on when it starts",
4505 "// Default: \"127.0.0.1\"",
4506 "// (Requires restart)",
4507 );
4508
4509 let create_node = |node_value: IpAddr| -> KdlNode {
4510 let mut node = KdlNode::new("web_server_ip");
4511 node.push(KdlValue::String(node_value.to_string()));
4512 node
4513 };
4514 if let Some(web_server_ip) = self.web_server_ip {
4515 let mut node = create_node(web_server_ip);
4516 if add_comments {
4517 node.set_leading(format!("{}\n", comment_text));
4518 }
4519 Some(node)
4520 } else if add_comments {
4521 let mut node = create_node(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
4522 node.set_leading(format!("{}\n// ", comment_text));
4523 Some(node)
4524 } else {
4525 None
4526 }
4527 }
4528 fn web_server_port_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4529 let comment_text = format!(
4530 "{}\n{}\n{}\n{}",
4531 " ",
4532 "// The port the web server should listen on when it starts",
4533 "// Default: 8082",
4534 "// (Requires restart)",
4535 );
4536
4537 let create_node = |node_value: u16| -> KdlNode {
4538 let mut node = KdlNode::new("web_server_port");
4539 node.push(KdlValue::Base10(node_value as i64));
4540 node
4541 };
4542 if let Some(web_server_port) = self.web_server_port {
4543 let mut node = create_node(web_server_port);
4544 if add_comments {
4545 node.set_leading(format!("{}\n", comment_text));
4546 }
4547 Some(node)
4548 } else if add_comments {
4549 let mut node = create_node(8082);
4550 node.set_leading(format!("{}\n// ", comment_text));
4551 Some(node)
4552 } else {
4553 None
4554 }
4555 }
4556 fn post_command_discovery_hook_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4557 let comment_text = format!(
4558 "{}\n{}\n{}\n{}\n{}\n{}",
4559 " ",
4560 "// A command to run (will be wrapped with sh -c and provided the RESURRECT_COMMAND env variable) ",
4561 "// after Zellij attempts to discover a command inside a pane when resurrecting sessions, the STDOUT",
4562 "// of this command will be used instead of the discovered RESURRECT_COMMAND",
4563 "// can be useful for removing wrappers around commands",
4564 "// Note: be sure to escape backslashes and similar characters properly",
4565 );
4566
4567 let create_node = |node_value: &str| -> KdlNode {
4568 let mut node = KdlNode::new("post_command_discovery_hook");
4569 node.push(node_value.to_owned());
4570 node
4571 };
4572 if let Some(post_command_discovery_hook) = &self.post_command_discovery_hook {
4573 let mut node = create_node(&post_command_discovery_hook);
4574 if add_comments {
4575 node.set_leading(format!("{}\n", comment_text));
4576 }
4577 Some(node)
4578 } else if add_comments {
4579 let mut node = create_node("echo $RESURRECT_COMMAND | sed <your_regex_here>");
4580 node.set_leading(format!("{}\n// ", comment_text));
4581 Some(node)
4582 } else {
4583 None
4584 }
4585 }
4586 fn nested_session_handling_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4587 use crate::input::options::NestedSessionHandling;
4588 let comment_text = format!(
4589 "{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}",
4590 " ",
4591 "// How to handle a nested Zellij session detected inside a pane.",
4592 "// Options:",
4593 "// - \"ask\" (Default — prompt with a modal)",
4594 "// - \"fullscreen\" (always zoom into the nested session)",
4595 "// - \"descend\" (always control the nested session on focus)",
4596 "// - \"never\" (never prompt or descend; do it manually)",
4597 "// ",
4598 );
4599 let create_node = |value: NestedSessionHandling| -> KdlNode {
4600 let mut node = KdlNode::new("nested_session_handling");
4601 let s = match value {
4602 NestedSessionHandling::Ask => "ask",
4603 NestedSessionHandling::Fullscreen => "fullscreen",
4604 NestedSessionHandling::Descend => "descend",
4605 NestedSessionHandling::Never => "never",
4606 };
4607 node.push(KdlValue::String(s.to_string()));
4608 node
4609 };
4610 if let Some(value) = self.nested_session_handling {
4611 let mut node = create_node(value);
4612 if add_comments {
4613 node.set_leading(format!("{}\n", comment_text));
4614 }
4615 Some(node)
4616 } else if add_comments {
4617 let mut node = create_node(NestedSessionHandling::Ask);
4618 node.set_leading(format!("{}\n// ", comment_text));
4619 Some(node)
4620 } else {
4621 None
4622 }
4623 }
4624 fn host_notification_protocol_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4625 use crate::input::options::HostNotificationProtocol;
4626 let comment_text = format!(
4627 "{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}",
4628 " ",
4629 "// Which escape sequence desktop notifications coming from panes are",
4630 "// forwarded to the host terminal with.",
4631 "// Options:",
4632 "// - \"auto\" (Default — detect from the host terminal environment)",
4633 "// - \"osc9\" (the legacy iTerm2 protocol, understood by most terminals)",
4634 "// - \"osc99\" (kitty's notification protocol)",
4635 "// - \"bell\" (ring the terminal bell instead)",
4636 "// - \"off\" (do not forward notifications to the host terminal)",
4637 "// ",
4638 );
4639 let create_node = |value: HostNotificationProtocol| -> KdlNode {
4640 let mut node = KdlNode::new("host_notification_protocol");
4641 node.push(KdlValue::String(value.as_str().to_string()));
4642 node
4643 };
4644 if let Some(value) = self.host_notification_protocol {
4645 let mut node = create_node(value);
4646 if add_comments {
4647 node.set_leading(format!("{}\n", comment_text));
4648 }
4649 Some(node)
4650 } else if add_comments {
4651 let mut node = create_node(HostNotificationProtocol::Auto);
4652 node.set_leading(format!("{}\n// ", comment_text));
4653 Some(node)
4654 } else {
4655 None
4656 }
4657 }
4658 fn dangerously_enable_paste_buffer_read_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4659 let comment_text = format!(
4660 "{}\n{}\n{}\n{}\n{}\n{}",
4661 " ",
4662 "// Whether to let programs running inside panes read the paste buffer",
4663 "// (clipboard) with the OSC 52 escape sequence. When enabled, any program",
4664 "// in any pane - including one running on a remote machine over SSH - can",
4665 "// read the clipboard without the user being asked.",
4666 "// Default: false",
4667 );
4668 let create_node = |node_value: bool| -> KdlNode {
4669 let mut node = KdlNode::new("dangerously_enable_paste_buffer_read");
4670 node.push(KdlValue::Bool(node_value));
4671 node
4672 };
4673 if let Some(value) = self.dangerously_enable_paste_buffer_read {
4674 let mut node = create_node(value);
4675 if add_comments {
4676 node.set_leading(format!("{}\n", comment_text));
4677 }
4678 Some(node)
4679 } else if add_comments {
4680 let mut node = create_node(false);
4681 node.set_leading(format!("{}\n// ", comment_text));
4682 Some(node)
4683 } else {
4684 None
4685 }
4686 }
4687 fn client_async_worker_tasks_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
4688 let comment_text = r#"
4689// Number of async worker tasks to spawn per active client.
4690//
4691// Allocating few tasks may result in resource contention and lags. Small values (around 4) should
4692// typically work best. Set to 0 to use the number of (physical) CPU cores.
4693// Note: This only applies to web clients at the moment."#;
4694 let create_node = |node_value: usize| -> KdlNode {
4695 let mut node = KdlNode::new("client_async_worker_tasks");
4696 node.push(KdlValue::Base10(node_value as i64));
4697 node
4698 };
4699 if let Some(client_async_worker_tasks) = self.client_async_worker_tasks {
4700 let mut node = create_node(client_async_worker_tasks);
4701 if add_comments {
4702 node.set_leading(format!("{}\n", comment_text));
4703 }
4704 Some(node)
4705 } else if add_comments {
4706 let mut node = create_node(4usize);
4707 node.set_leading(format!("{}\n// ", comment_text));
4708 Some(node)
4709 } else {
4710 None
4711 }
4712 }
4713 pub fn to_kdl(&self, add_comments: bool) -> Vec<KdlNode> {
4714 let mut nodes = vec![];
4715 if let Some(simplified_ui_node) = self.simplified_ui_to_kdl(add_comments) {
4716 nodes.push(simplified_ui_node);
4717 }
4718 if let Some(osc8_hyperlinks_node) = self.osc8_hyperlinks_to_kdl(add_comments) {
4719 nodes.push(osc8_hyperlinks_node);
4720 }
4721 if let Some(theme_node) = self.theme_to_kdl(add_comments) {
4722 nodes.push(theme_node);
4723 }
4724 if let Some(theme_dark_node) = self.theme_dark_to_kdl(add_comments) {
4725 nodes.push(theme_dark_node);
4726 }
4727 if let Some(theme_light_node) = self.theme_light_to_kdl(add_comments) {
4728 nodes.push(theme_light_node);
4729 }
4730 if let Some(default_mode) = self.default_mode_to_kdl(add_comments) {
4731 nodes.push(default_mode);
4732 }
4733 if let Some(default_shell) = self.default_shell_to_kdl(add_comments) {
4734 nodes.push(default_shell);
4735 }
4736 if let Some(default_cwd) = self.default_cwd_to_kdl(add_comments) {
4737 nodes.push(default_cwd);
4738 }
4739 if let Some(default_layout) = self.default_layout_to_kdl(add_comments) {
4740 nodes.push(default_layout);
4741 }
4742 if let Some(layout_dir) = self.layout_dir_to_kdl(add_comments) {
4743 nodes.push(layout_dir);
4744 }
4745 if let Some(theme_dir) = self.theme_dir_to_kdl(add_comments) {
4746 nodes.push(theme_dir);
4747 }
4748 if let Some(mouse_mode) = self.mouse_mode_to_kdl(add_comments) {
4749 nodes.push(mouse_mode);
4750 }
4751 if let Some(pane_frames) = self.pane_frames_to_kdl(add_comments) {
4752 nodes.push(pane_frames);
4753 }
4754 if let Some(pane_frame_style) = self.pane_frame_style_to_kdl(add_comments) {
4755 nodes.push(pane_frame_style);
4756 }
4757 if let Some(mirror_session) = self.mirror_session_to_kdl(add_comments) {
4758 nodes.push(mirror_session);
4759 }
4760 if let Some(on_force_close) = self.on_force_close_to_kdl(add_comments) {
4761 nodes.push(on_force_close);
4762 }
4763 if let Some(scroll_buffer_size) = self.scroll_buffer_size_to_kdl(add_comments) {
4764 nodes.push(scroll_buffer_size);
4765 }
4766 if let Some(copy_command) = self.copy_command_to_kdl(add_comments) {
4767 nodes.push(copy_command);
4768 }
4769 if let Some(copy_clipboard) = self.copy_clipboard_to_kdl(add_comments) {
4770 nodes.push(copy_clipboard);
4771 }
4772 if let Some(copy_on_select) = self.copy_on_select_to_kdl(add_comments) {
4773 nodes.push(copy_on_select);
4774 }
4775 if let Some(scrollback_editor) = self.scrollback_editor_to_kdl(add_comments) {
4776 nodes.push(scrollback_editor);
4777 }
4778 if let Some(session_name) = self.session_name_to_kdl(add_comments) {
4779 nodes.push(session_name);
4780 }
4781 if let Some(attach_to_session) = self.attach_to_session_to_kdl(add_comments) {
4782 nodes.push(attach_to_session);
4783 }
4784 if let Some(auto_layout) = self.auto_layout_to_kdl(add_comments) {
4785 nodes.push(auto_layout);
4786 }
4787 if let Some(session_serialization) = self.session_serialization_to_kdl(add_comments) {
4788 nodes.push(session_serialization);
4789 }
4790 if let Some(serialize_pane_viewport) = self.serialize_pane_viewport_to_kdl(add_comments) {
4791 nodes.push(serialize_pane_viewport);
4792 }
4793 if let Some(scrollback_lines_to_serialize) =
4794 self.scrollback_lines_to_serialize_to_kdl(add_comments)
4795 {
4796 nodes.push(scrollback_lines_to_serialize);
4797 }
4798 if let Some(styled_underlines) = self.styled_underlines_to_kdl(add_comments) {
4799 nodes.push(styled_underlines);
4800 }
4801 if let Some(serialization_interval) = self.serialization_interval_to_kdl(add_comments) {
4802 nodes.push(serialization_interval);
4803 }
4804 if let Some(disable_session_metadata) = self.disable_session_metadata_to_kdl(add_comments) {
4805 nodes.push(disable_session_metadata);
4806 }
4807 if let Some(support_kitty_keyboard_protocol) =
4808 self.support_kitty_keyboard_protocol_to_kdl(add_comments)
4809 {
4810 nodes.push(support_kitty_keyboard_protocol);
4811 }
4812 if let Some(support_kitty_graphics_protocol) =
4813 self.support_kitty_graphics_protocol_to_kdl(add_comments)
4814 {
4815 nodes.push(support_kitty_graphics_protocol);
4816 }
4817 if let Some(web_server) = self.web_server_to_kdl(add_comments) {
4818 nodes.push(web_server);
4819 }
4820 if let Some(web_sharing) = self.web_sharing_to_kdl(add_comments) {
4821 nodes.push(web_sharing);
4822 }
4823 if let Some(web_server_cert) = self.web_server_cert_to_kdl(add_comments) {
4824 nodes.push(web_server_cert);
4825 }
4826 if let Some(web_server_key) = self.web_server_key_to_kdl(add_comments) {
4827 nodes.push(web_server_key);
4828 }
4829 if let Some(enforce_https_for_localhost) =
4830 self.enforce_https_for_localhost_to_kdl(add_comments)
4831 {
4832 nodes.push(enforce_https_for_localhost);
4833 }
4834 if let Some(stacked_resize) = self.stacked_resize_to_kdl(add_comments) {
4835 nodes.push(stacked_resize);
4836 }
4837 if let Some(stacked_pane_list) = self.stacked_pane_list_to_kdl(add_comments) {
4838 nodes.push(stacked_pane_list);
4839 }
4840 if let Some(show_startup_tips) = self.show_startup_tips_to_kdl(add_comments) {
4841 nodes.push(show_startup_tips);
4842 }
4843 if let Some(show_release_notes) = self.show_release_notes_to_kdl(add_comments) {
4844 nodes.push(show_release_notes);
4845 }
4846 if let Some(advanced_mouse_actions) = self.advanced_mouse_actions_to_kdl(add_comments) {
4847 nodes.push(advanced_mouse_actions);
4848 }
4849 if let Some(mouse_scroll_resize) = self.mouse_scroll_resize_to_kdl(add_comments) {
4850 nodes.push(mouse_scroll_resize);
4851 }
4852 if let Some(mouse_hover_effects) = self.mouse_hover_effects_to_kdl(add_comments) {
4853 nodes.push(mouse_hover_effects);
4854 }
4855 if let Some(mouse_hover_tips) = self.mouse_hover_tips_to_kdl(add_comments) {
4856 nodes.push(mouse_hover_tips);
4857 }
4858 if let Some(visual_bell) = self.visual_bell_to_kdl(add_comments) {
4859 nodes.push(visual_bell);
4860 }
4861 if let Some(focus_follows_mouse) = self.focus_follows_mouse_to_kdl(add_comments) {
4862 nodes.push(focus_follows_mouse);
4863 }
4864 if let Some(mouse_click_through) = self.mouse_click_through_to_kdl(add_comments) {
4865 nodes.push(mouse_click_through);
4866 }
4867 if let Some(osc133_command_selection) = self.osc133_command_selection_to_kdl(add_comments) {
4868 nodes.push(osc133_command_selection);
4869 }
4870 if let Some(word_separators) = self.word_separators_to_kdl(add_comments) {
4871 nodes.push(word_separators);
4872 }
4873 if let Some(web_server_ip) = self.web_server_ip_to_kdl(add_comments) {
4874 nodes.push(web_server_ip);
4875 }
4876 if let Some(web_server_port) = self.web_server_port_to_kdl(add_comments) {
4877 nodes.push(web_server_port);
4878 }
4879 if let Some(post_command_discovery_hook) =
4880 self.post_command_discovery_hook_to_kdl(add_comments)
4881 {
4882 nodes.push(post_command_discovery_hook);
4883 }
4884 if let Some(client_async_worker_tasks) = self.client_async_worker_tasks_to_kdl(add_comments)
4885 {
4886 nodes.push(client_async_worker_tasks);
4887 }
4888 if let Some(dangerously_enable_paste_buffer_read) =
4889 self.dangerously_enable_paste_buffer_read_to_kdl(add_comments)
4890 {
4891 nodes.push(dangerously_enable_paste_buffer_read);
4892 }
4893 if let Some(nested_session_handling) = self.nested_session_handling_to_kdl(add_comments) {
4894 nodes.push(nested_session_handling);
4895 }
4896 if let Some(host_notification_protocol) =
4897 self.host_notification_protocol_to_kdl(add_comments)
4898 {
4899 nodes.push(host_notification_protocol);
4900 }
4901 nodes
4902 }
4903}
4904
4905impl Layout {
4906 pub fn from_kdl(
4907 raw_layout: &str,
4908 file_name: Option<String>,
4909 raw_swap_layouts: Option<(&str, &str)>, cwd: Option<PathBuf>,
4911 ) -> Result<Self, ConfigError> {
4912 let mut kdl_layout_parser = KdlLayoutParser::new(raw_layout, cwd, file_name.clone());
4913 let layout = kdl_layout_parser.parse().map_err(|e| match e {
4914 ConfigError::KdlError(kdl_error) => ConfigError::KdlError(kdl_error.add_src(
4915 file_name.unwrap_or_else(|| "N/A".to_owned()),
4916 String::from(raw_layout),
4917 )),
4918 ConfigError::KdlDeserializationError(kdl_error) => kdl_layout_error(
4919 kdl_error,
4920 file_name.unwrap_or_else(|| "N/A".to_owned()),
4921 raw_layout,
4922 ),
4923 e => e,
4924 })?;
4925 match raw_swap_layouts {
4926 Some((raw_swap_layout_filename, raw_swap_layout)) => {
4927 kdl_layout_parser
4930 .parse_external_swap_layouts(raw_swap_layout, layout)
4931 .map_err(|e| match e {
4932 ConfigError::KdlError(kdl_error) => {
4933 ConfigError::KdlError(kdl_error.add_src(
4934 String::from(raw_swap_layout_filename),
4935 String::from(raw_swap_layout),
4936 ))
4937 },
4938 ConfigError::KdlDeserializationError(kdl_error) => kdl_layout_error(
4939 kdl_error,
4940 raw_swap_layout_filename.into(),
4941 raw_swap_layout,
4942 ),
4943 e => e,
4944 })
4945 },
4946 None => Ok(layout),
4947 }
4948 }
4949}
4950
4951fn kdl_layout_error(kdl_error: kdl::KdlError, file_name: String, raw_layout: &str) -> ConfigError {
4952 let error_message = match kdl_error.kind {
4953 kdl::KdlErrorKind::Context("valid node terminator") => {
4954 format!("Failed to deserialize KDL node. \nPossible reasons:\n{}\n{}\n{}\n{}",
4955 "- Missing `;` after a node name, eg. { node; another_node; }",
4956 "- Missing quotations (\") around an argument node eg. { first_node \"argument_node\"; }",
4957 "- Missing an equal sign (=) between node arguments on a title line. eg. argument=\"value\"",
4958 "- Found an extraneous equal sign (=) between node child arguments and their values. eg. { argument=\"value\" }")
4959 },
4960 _ => String::from(kdl_error.help.unwrap_or("Kdl Deserialization Error")),
4961 };
4962 let kdl_error = KdlError {
4963 error_message,
4964 src: Some(NamedSource::new(file_name, String::from(raw_layout))),
4965 offset: Some(kdl_error.span.offset()),
4966 len: Some(kdl_error.span.len()),
4967 help_message: None,
4968 };
4969 ConfigError::KdlError(kdl_error)
4970}
4971
4972impl EnvironmentVariables {
4973 pub fn from_kdl(kdl_env_variables: &KdlNode) -> Result<Self, ConfigError> {
4974 let mut env: HashMap<String, String> = HashMap::new();
4975 for env_var in kdl_children_nodes_or_error!(kdl_env_variables, "empty env variable block") {
4976 let env_var_name = kdl_name!(env_var);
4977 let env_var_str_value =
4978 kdl_first_entry_as_string!(env_var).map(|s| format!("{}", s.to_string()));
4979 let env_var_int_value =
4980 kdl_first_entry_as_i64!(env_var).map(|s| format!("{}", s.to_string()));
4981 let env_var_value =
4982 env_var_str_value
4983 .or(env_var_int_value)
4984 .ok_or(ConfigError::new_kdl_error(
4985 format!("Failed to parse env var: {:?}", env_var_name),
4986 env_var.span().offset(),
4987 env_var.span().len(),
4988 ))?;
4989 env.insert(env_var_name.into(), env_var_value);
4990 }
4991 Ok(EnvironmentVariables::from_data(env))
4992 }
4993 pub fn to_kdl(&self) -> Option<KdlNode> {
4994 let mut has_env_vars = false;
4995 let mut env = KdlNode::new("env");
4996 let mut env_vars = KdlDocument::new();
4997
4998 let mut stable_sorted = BTreeMap::new();
4999 for (env_var_name, env_var_value) in self.inner() {
5000 stable_sorted.insert(env_var_name, env_var_value);
5001 }
5002 for (env_key, env_value) in stable_sorted {
5003 has_env_vars = true;
5004 let mut variable_key = KdlNode::new(env_key.to_owned());
5005 variable_key.push(env_value.to_owned());
5006 env_vars.nodes_mut().push(variable_key);
5007 }
5008
5009 if has_env_vars {
5010 env.set_children(env_vars);
5011 Some(env)
5012 } else {
5013 None
5014 }
5015 }
5016}
5017
5018impl Keybinds {
5019 fn bind_keys_in_block(
5020 block: &KdlNode,
5021 input_mode_keybinds: &mut HashMap<KeyWithModifier, Vec<Action>>,
5022 config_options: &Options,
5023 ) -> Result<(), ConfigError> {
5024 let all_nodes = kdl_children_nodes_or_error!(block, "no keybinding block for mode");
5025 let bind_nodes = all_nodes.iter().filter(|n| kdl_name!(n) == "bind");
5026 let unbind_nodes = all_nodes.iter().filter(|n| kdl_name!(n) == "unbind");
5027 for key_block in bind_nodes {
5028 Keybinds::bind_actions_for_each_key(key_block, input_mode_keybinds, config_options)?;
5029 }
5030 for key_block in unbind_nodes {
5032 Keybinds::unbind_keys(key_block, input_mode_keybinds)?;
5033 }
5034 for key_block in all_nodes {
5035 if kdl_name!(key_block) != "bind" && kdl_name!(key_block) != "unbind" {
5036 return Err(ConfigError::new_kdl_error(
5037 format!("Unknown keybind instruction: '{}'", kdl_name!(key_block)),
5038 key_block.span().offset(),
5039 key_block.span().len(),
5040 ));
5041 }
5042 }
5043 Ok(())
5044 }
5045 pub fn from_kdl(
5046 kdl_keybinds: &KdlNode,
5047 base_keybinds: Keybinds,
5048 config_options: &Options,
5049 ) -> Result<Self, ConfigError> {
5050 let clear_defaults = kdl_arg_is_truthy!(kdl_keybinds, "clear-defaults");
5051 let mut keybinds_from_config = if clear_defaults {
5052 Keybinds::default()
5053 } else {
5054 base_keybinds
5055 };
5056 for block in kdl_children_nodes_or_error!(kdl_keybinds, "keybindings with no children") {
5057 if kdl_name!(block) == "shared_except" || kdl_name!(block) == "shared" {
5058 let mut modes_to_exclude = vec![];
5059 for mode_name in kdl_string_arguments!(block) {
5060 modes_to_exclude.push(InputMode::from_str(mode_name).map_err(|_| {
5061 ConfigError::new_kdl_error(
5062 format!("Invalid mode: '{}'", mode_name),
5063 block.name().span().offset(),
5064 block.name().span().len(),
5065 )
5066 })?);
5067 }
5068 for mode in InputMode::iter() {
5069 if modes_to_exclude.contains(&mode) {
5070 continue;
5071 }
5072 let mut input_mode_keybinds = keybinds_from_config.get_input_mode_mut(&mode);
5073 Keybinds::bind_keys_in_block(block, &mut input_mode_keybinds, config_options)?;
5074 }
5075 }
5076 if kdl_name!(block) == "shared_among" {
5077 let mut modes_to_include = vec![];
5078 for mode_name in kdl_string_arguments!(block) {
5079 modes_to_include.push(InputMode::from_str(mode_name)?);
5080 }
5081 for mode in InputMode::iter() {
5082 if !modes_to_include.contains(&mode) {
5083 continue;
5084 }
5085 let mut input_mode_keybinds = keybinds_from_config.get_input_mode_mut(&mode);
5086 Keybinds::bind_keys_in_block(block, &mut input_mode_keybinds, config_options)?;
5087 }
5088 }
5089 }
5090 for mode in kdl_children_nodes_or_error!(kdl_keybinds, "keybindings with no children") {
5091 if kdl_name!(mode) == "unbind"
5092 || kdl_name!(mode) == "shared_except"
5093 || kdl_name!(mode) == "shared_among"
5094 || kdl_name!(mode) == "shared"
5095 {
5096 continue;
5097 }
5098 let mut input_mode_keybinds =
5099 Keybinds::input_mode_keybindings(mode, &mut keybinds_from_config)?;
5100 Keybinds::bind_keys_in_block(mode, &mut input_mode_keybinds, config_options)?;
5101 }
5102 if let Some(global_unbind) = kdl_keybinds.children().and_then(|c| c.get("unbind")) {
5103 Keybinds::unbind_keys_in_all_modes(global_unbind, &mut keybinds_from_config)?;
5104 };
5105 Ok(keybinds_from_config)
5106 }
5107 fn bind_actions_for_each_key(
5108 key_block: &KdlNode,
5109 input_mode_keybinds: &mut HashMap<KeyWithModifier, Vec<Action>>,
5110 config_options: &Options,
5111 ) -> Result<(), ConfigError> {
5112 let keys: Vec<KeyWithModifier> = keys_from_kdl!(key_block);
5113 let actions: Vec<Action> = actions_from_kdl!(key_block, config_options);
5114 for key in keys {
5115 input_mode_keybinds.insert(key, actions.clone());
5116 }
5117 Ok(())
5118 }
5119 fn unbind_keys(
5120 key_block: &KdlNode,
5121 input_mode_keybinds: &mut HashMap<KeyWithModifier, Vec<Action>>,
5122 ) -> Result<(), ConfigError> {
5123 let keys: Vec<KeyWithModifier> = keys_from_kdl!(key_block);
5124 for key in keys {
5125 input_mode_keybinds.remove(&key);
5126 }
5127 Ok(())
5128 }
5129 fn unbind_keys_in_all_modes(
5130 global_unbind: &KdlNode,
5131 keybinds_from_config: &mut Keybinds,
5132 ) -> Result<(), ConfigError> {
5133 let keys: Vec<KeyWithModifier> = keys_from_kdl!(global_unbind);
5134 for mode in keybinds_from_config.0.values_mut() {
5135 for key in &keys {
5136 mode.remove(&key);
5137 }
5138 }
5139 Ok(())
5140 }
5141 fn input_mode_keybindings<'a>(
5142 mode: &KdlNode,
5143 keybinds_from_config: &'a mut Keybinds,
5144 ) -> Result<&'a mut HashMap<KeyWithModifier, Vec<Action>>, ConfigError> {
5145 let mode_name = kdl_name!(mode);
5146 let input_mode = InputMode::from_str(mode_name).map_err(|_| {
5147 ConfigError::new_kdl_error(
5148 format!("Invalid mode: '{}'", mode_name),
5149 mode.name().span().offset(),
5150 mode.name().span().len(),
5151 )
5152 })?;
5153 let input_mode_keybinds = keybinds_from_config.get_input_mode_mut(&input_mode);
5154 let clear_defaults_for_mode = kdl_arg_is_truthy!(mode, "clear-defaults");
5155 if clear_defaults_for_mode {
5156 input_mode_keybinds.clear();
5157 }
5158 Ok(input_mode_keybinds)
5159 }
5160 pub fn from_string(
5161 stringified_keybindings: String,
5162 base_keybinds: Keybinds,
5163 config_options: &Options,
5164 ) -> Result<Self, ConfigError> {
5165 let document: KdlDocument = stringified_keybindings.parse()?;
5166 if let Some(kdl_keybinds) = document.get("keybinds") {
5167 Keybinds::from_kdl(&kdl_keybinds, base_keybinds, config_options)
5168 } else {
5169 Err(ConfigError::new_kdl_error(
5170 format!("Could not find keybinds node"),
5171 document.span().offset(),
5172 document.span().len(),
5173 ))
5174 }
5175 }
5176 fn minimize_entries(
5179 &self,
5180 ) -> BTreeMap<BTreeSet<InputMode>, BTreeMap<KeyWithModifier, Vec<Action>>> {
5181 let mut minimized: BTreeMap<BTreeSet<InputMode>, BTreeMap<KeyWithModifier, Vec<Action>>> =
5182 BTreeMap::new();
5183 let mut flattened: Vec<BTreeMap<KeyWithModifier, Vec<Action>>> = self
5184 .0
5185 .iter()
5186 .map(|(_input_mode, keybind)| keybind.clone().into_iter().collect())
5187 .collect();
5188 for keybind in flattened.drain(..) {
5189 for (key, actions) in keybind.into_iter() {
5190 let mut appears_in_modes: BTreeSet<InputMode> = BTreeSet::new();
5191 for (input_mode, keybinds) in self.0.iter() {
5192 if keybinds.get(&key) == Some(&actions) {
5193 appears_in_modes.insert(*input_mode);
5194 }
5195 }
5196 minimized
5197 .entry(appears_in_modes)
5198 .or_insert_with(Default::default)
5199 .insert(key, actions);
5200 }
5201 }
5202 minimized
5203 }
5204 fn serialize_mode_title_node(&self, input_modes: &BTreeSet<InputMode>) -> KdlNode {
5205 let all_modes: Vec<InputMode> = InputMode::iter().collect();
5206 let total_input_mode_count = all_modes.len();
5207 if input_modes.len() == 1 {
5208 let input_mode_name =
5209 format!("{:?}", input_modes.iter().next().unwrap()).to_lowercase();
5210 KdlNode::new(input_mode_name)
5211 } else if input_modes.len() == total_input_mode_count {
5212 KdlNode::new("shared")
5213 } else if input_modes.len() < total_input_mode_count / 2 {
5214 let mut node = KdlNode::new("shared_among");
5215 for input_mode in input_modes {
5216 node.push(format!("{:?}", input_mode).to_lowercase());
5217 }
5218 node
5219 } else {
5220 let mut node = KdlNode::new("shared_except");
5221 let mut modes = all_modes.clone();
5222 for input_mode in input_modes {
5223 modes.retain(|m| m != input_mode)
5224 }
5225 for mode in modes {
5226 node.push(format!("{:?}", mode).to_lowercase());
5227 }
5228 node
5229 }
5230 }
5231 fn serialize_mode_keybinds(
5232 &self,
5233 keybinds: &BTreeMap<KeyWithModifier, Vec<Action>>,
5234 ) -> KdlDocument {
5235 let mut mode_keybinds = KdlDocument::new();
5236 for keybind in keybinds {
5237 let mut keybind_node = KdlNode::new("bind");
5238 keybind_node.push(keybind.0.to_kdl());
5239 let mut actions = KdlDocument::new();
5240 let mut actions_have_children = false;
5241 for action in keybind.1 {
5242 if let Some(kdl_action) = action.to_kdl() {
5243 if kdl_action.children().is_some() {
5244 actions_have_children = true;
5245 }
5246 actions.nodes_mut().push(kdl_action);
5247 }
5248 }
5249 if !actions_have_children {
5250 for action in actions.nodes_mut() {
5251 action.set_leading("");
5252 action.set_trailing("; ");
5253 }
5254 actions.set_leading(" ");
5255 actions.set_trailing("");
5256 }
5257 keybind_node.set_children(actions);
5258 mode_keybinds.nodes_mut().push(keybind_node);
5259 }
5260 mode_keybinds
5261 }
5262 pub fn to_kdl(&self, should_clear_defaults: bool) -> KdlNode {
5263 let mut keybinds_node = KdlNode::new("keybinds");
5264 if should_clear_defaults {
5265 keybinds_node.insert("clear-defaults", true);
5266 }
5267 let mut minimized = self.minimize_entries();
5268 let mut keybinds_children = KdlDocument::new();
5269
5270 macro_rules! encode_single_input_mode {
5271 ($mode_name:ident) => {{
5272 if let Some(keybinds) = minimized.remove(&BTreeSet::from([InputMode::$mode_name])) {
5273 let mut mode_node =
5274 KdlNode::new(format!("{:?}", InputMode::$mode_name).to_lowercase());
5275 let mode_keybinds = self.serialize_mode_keybinds(&keybinds);
5276 mode_node.set_children(mode_keybinds);
5277 keybinds_children.nodes_mut().push(mode_node);
5278 }
5279 }};
5280 }
5281 encode_single_input_mode!(Normal);
5284 encode_single_input_mode!(Locked);
5285 encode_single_input_mode!(Pane);
5286 encode_single_input_mode!(Tab);
5287 encode_single_input_mode!(Resize);
5288 encode_single_input_mode!(Move);
5289 encode_single_input_mode!(Scroll);
5290 encode_single_input_mode!(Search);
5291 encode_single_input_mode!(Session);
5292
5293 for (input_modes, keybinds) in minimized {
5294 if input_modes.is_empty() {
5295 log::error!("invalid input mode for keybinds: {:#?}", keybinds);
5296 continue;
5297 }
5298 let mut mode_node = self.serialize_mode_title_node(&input_modes);
5299 let mode_keybinds = self.serialize_mode_keybinds(&keybinds);
5300 mode_node.set_children(mode_keybinds);
5301 keybinds_children.nodes_mut().push(mode_node);
5302 }
5303 keybinds_node.set_children(keybinds_children);
5304 keybinds_node
5305 }
5306}
5307
5308impl KeyWithModifier {
5309 pub fn to_kdl(&self) -> String {
5310 if self.key_modifiers.is_empty() {
5311 self.bare_key.to_kdl()
5312 } else {
5313 format!(
5314 "{} {}",
5315 self.key_modifiers
5316 .iter()
5317 .map(|m| m.to_string())
5318 .collect::<Vec<_>>()
5319 .join(" "),
5320 self.bare_key.to_kdl()
5321 )
5322 }
5323 }
5324}
5325
5326impl BareKey {
5327 pub fn to_kdl(&self) -> String {
5328 match self {
5329 BareKey::PageDown => format!("PageDown"),
5330 BareKey::PageUp => format!("PageUp"),
5331 BareKey::Left => format!("left"),
5332 BareKey::Down => format!("down"),
5333 BareKey::Up => format!("up"),
5334 BareKey::Right => format!("right"),
5335 BareKey::Home => format!("home"),
5336 BareKey::End => format!("end"),
5337 BareKey::Backspace => format!("backspace"),
5338 BareKey::Delete => format!("del"),
5339 BareKey::Insert => format!("insert"),
5340 BareKey::F(index) => format!("F{}", index),
5341 BareKey::Char(' ') => format!("space"),
5342 BareKey::Char(character) => format!("{}", character),
5343 BareKey::Tab => format!("tab"),
5344 BareKey::Esc => format!("esc"),
5345 BareKey::Enter => format!("enter"),
5346 BareKey::CapsLock => format!("capslock"),
5347 BareKey::ScrollLock => format!("scrolllock"),
5348 BareKey::NumLock => format!("numlock"),
5349 BareKey::PrintScreen => format!("printscreen"),
5350 BareKey::Pause => format!("pause"),
5351 BareKey::Menu => format!("menu"),
5352 }
5353 }
5354}
5355
5356impl Config {
5357 pub fn from_kdl(kdl_config: &str, base_config: Option<Config>) -> Result<Config, ConfigError> {
5358 let mut config = base_config.unwrap_or_else(|| Config::default());
5359 let kdl_config: KdlDocument = kdl_config.parse()?;
5360
5361 let config_options = Options::from_kdl(&kdl_config)?;
5362 config.options = config.options.merge(config_options);
5363
5364 if let Some(kdl_keybinds) = kdl_config.get("keybinds") {
5367 config.keybinds = Keybinds::from_kdl(&kdl_keybinds, config.keybinds, &config.options)?;
5368 }
5369 if let Some(kdl_themes) = kdl_config.get("themes") {
5370 let sourced_from_external_file = false;
5371 let config_themes = Themes::from_kdl(kdl_themes, sourced_from_external_file)?;
5372 config.themes = config.themes.merge(config_themes);
5373 }
5374 if let Some(kdl_plugin_aliases) = kdl_config.get("plugins") {
5375 let config_plugins = PluginAliases::from_kdl(kdl_plugin_aliases)?;
5376 config.plugins.merge(config_plugins);
5377 }
5378 if let Some(kdl_load_plugins) = kdl_config.get("load_plugins") {
5379 let load_plugins = load_plugins_from_kdl(kdl_load_plugins)?;
5380 config.background_plugins = load_plugins;
5381 }
5382 if let Some(kdl_ui_config) = kdl_config.get("ui") {
5383 let config_ui = UiConfig::from_kdl(&kdl_ui_config)?;
5384 config.ui = config.ui.merge(config_ui);
5385 }
5386 if let Some(env_config) = kdl_config.get("env") {
5387 let config_env = EnvironmentVariables::from_kdl(&env_config)?;
5388 config.env = config.env.merge(config_env);
5389 }
5390 if let Some(web_client_config) = kdl_config.get("web_client") {
5391 let config_web_client = WebClientConfig::from_kdl(&web_client_config)?;
5392 config.web_client = config.web_client.merge(config_web_client);
5393 }
5394 Ok(config)
5395 }
5396 pub fn to_string(&self, add_comments: bool) -> String {
5397 let mut document = KdlDocument::new();
5398
5399 let clear_defaults = true;
5400 let keybinds = self.keybinds.to_kdl(clear_defaults);
5401 document.nodes_mut().push(keybinds);
5402
5403 if let Some(themes) = self.themes.to_kdl() {
5404 document.nodes_mut().push(themes);
5405 }
5406
5407 let plugins = self.plugins.to_kdl(add_comments);
5408 document.nodes_mut().push(plugins);
5409
5410 let load_plugins = load_plugins_to_kdl(&self.background_plugins, add_comments);
5411 document.nodes_mut().push(load_plugins);
5412
5413 if let Some(ui_config) = self.ui.to_kdl() {
5414 document.nodes_mut().push(ui_config);
5415 }
5416
5417 if let Some(env) = self.env.to_kdl() {
5418 document.nodes_mut().push(env);
5419 }
5420
5421 document.nodes_mut().push(self.web_client.to_kdl());
5422
5423 document
5424 .nodes_mut()
5425 .append(&mut self.options.to_kdl(add_comments));
5426
5427 document.to_string()
5428 }
5429}
5430
5431impl PluginAliases {
5432 pub fn from_kdl(kdl_plugin_aliases: &KdlNode) -> Result<PluginAliases, ConfigError> {
5433 let mut aliases: BTreeMap<String, RunPlugin> = BTreeMap::new();
5434 if let Some(kdl_plugin_aliases) = kdl_children_nodes!(kdl_plugin_aliases) {
5435 for alias_definition in kdl_plugin_aliases {
5436 let alias_name = kdl_name!(alias_definition);
5437 if let Some(string_url) =
5438 kdl_get_string_property_or_child_value!(alias_definition, "location")
5439 {
5440 let configuration =
5441 KdlLayoutParser::parse_plugin_user_configuration(&alias_definition)?;
5442 let initial_cwd =
5443 kdl_get_string_property_or_child_value!(alias_definition, "cwd")
5444 .map(|s| PathBuf::from(s));
5445 let run_plugin = RunPlugin::from_url(string_url)?
5446 .with_configuration(configuration.inner().clone())
5447 .with_initial_cwd(initial_cwd);
5448 aliases.insert(alias_name.to_owned(), run_plugin);
5449 }
5450 }
5451 }
5452 Ok(PluginAliases { aliases })
5453 }
5454 pub fn to_kdl(&self, add_comments: bool) -> KdlNode {
5455 let mut plugins = KdlNode::new("plugins");
5456 let mut plugins_children = KdlDocument::new();
5457 for (alias_name, plugin_alias) in self.aliases.iter() {
5458 let mut plugin_alias_node = KdlNode::new(alias_name.clone());
5459 let mut plugin_alias_children = KdlDocument::new();
5460 let location_string = plugin_alias.location.display();
5461
5462 plugin_alias_node.insert("location", location_string);
5463 let cwd = plugin_alias.initial_cwd.as_ref();
5464 let mut has_children = false;
5465 if let Some(cwd) = cwd {
5466 has_children = true;
5467 let mut cwd_node = KdlNode::new("cwd");
5468 cwd_node.push(cwd.display().to_string());
5469 plugin_alias_children.nodes_mut().push(cwd_node);
5470 }
5471 let configuration = plugin_alias.configuration.inner();
5472 if !configuration.is_empty() {
5473 has_children = true;
5474 for (config_key, config_value) in configuration {
5475 let mut node = KdlNode::new(config_key.to_owned());
5476 if config_value == "true" {
5477 node.push(KdlValue::Bool(true));
5478 } else if config_value == "false" {
5479 node.push(KdlValue::Bool(false));
5480 } else {
5481 node.push(config_value.to_string());
5482 }
5483 plugin_alias_children.nodes_mut().push(node);
5484 }
5485 }
5486 if has_children {
5487 plugin_alias_node.set_children(plugin_alias_children);
5488 }
5489 plugins_children.nodes_mut().push(plugin_alias_node);
5490 }
5491 plugins.set_children(plugins_children);
5492
5493 if add_comments {
5494 plugins.set_leading(format!(
5495 "\n{}\n{}\n",
5496 "// Plugin aliases - can be used to change the implementation of Zellij",
5497 "// changing these requires a restart to take effect",
5498 ));
5499 }
5500 plugins
5501 }
5502}
5503
5504pub fn load_plugins_to_kdl(
5505 background_plugins: &HashSet<RunPluginOrAlias>,
5506 add_comments: bool,
5507) -> KdlNode {
5508 let mut load_plugins = KdlNode::new("load_plugins");
5509 let mut load_plugins_children = KdlDocument::new();
5510 for run_plugin_or_alias in background_plugins.iter() {
5511 let mut background_plugin_node = KdlNode::new(run_plugin_or_alias.location_string());
5512 let mut background_plugin_children = KdlDocument::new();
5513
5514 let cwd = match run_plugin_or_alias {
5515 RunPluginOrAlias::RunPlugin(run_plugin) => run_plugin.initial_cwd.clone(),
5516 RunPluginOrAlias::Alias(plugin_alias) => plugin_alias.initial_cwd.clone(),
5517 };
5518 let mut has_children = false;
5519 if let Some(cwd) = cwd.as_ref() {
5520 has_children = true;
5521 let mut cwd_node = KdlNode::new("cwd");
5522 cwd_node.push(cwd.display().to_string());
5523 background_plugin_children.nodes_mut().push(cwd_node);
5524 }
5525 let configuration = match run_plugin_or_alias {
5526 RunPluginOrAlias::RunPlugin(run_plugin) => {
5527 Some(run_plugin.configuration.inner().clone())
5528 },
5529 RunPluginOrAlias::Alias(plugin_alias) => plugin_alias
5530 .configuration
5531 .as_ref()
5532 .map(|c| c.inner().clone()),
5533 };
5534 if let Some(configuration) = configuration {
5535 if !configuration.is_empty() {
5536 has_children = true;
5537 for (config_key, config_value) in configuration {
5538 let mut node = KdlNode::new(config_key.to_owned());
5539 if config_value == "true" {
5540 node.push(KdlValue::Bool(true));
5541 } else if config_value == "false" {
5542 node.push(KdlValue::Bool(false));
5543 } else {
5544 node.push(config_value.to_string());
5545 }
5546 background_plugin_children.nodes_mut().push(node);
5547 }
5548 }
5549 }
5550 if has_children {
5551 background_plugin_node.set_children(background_plugin_children);
5552 }
5553 load_plugins_children
5554 .nodes_mut()
5555 .push(background_plugin_node);
5556 }
5557 load_plugins.set_children(load_plugins_children);
5558
5559 if add_comments {
5560 load_plugins.set_leading(format!(
5561 "\n{}\n{}\n{}\n",
5562 "// Plugins to load in the background when a new session starts",
5563 "// eg. \"file:/path/to/my-plugin.wasm\"",
5564 "// eg. \"https://example.com/my-plugin.wasm\"",
5565 ));
5566 }
5567 load_plugins
5568}
5569
5570fn load_plugins_from_kdl(
5571 kdl_load_plugins: &KdlNode,
5572) -> Result<HashSet<RunPluginOrAlias>, ConfigError> {
5573 let mut load_plugins: HashSet<RunPluginOrAlias> = HashSet::new();
5574 if let Some(kdl_load_plugins) = kdl_children_nodes!(kdl_load_plugins) {
5575 for plugin_block in kdl_load_plugins {
5576 let url_node = plugin_block.name();
5577 let string_url = url_node.value();
5578 let configuration = KdlLayoutParser::parse_plugin_user_configuration(&plugin_block)?;
5579 let cwd = kdl_get_string_property_or_child_value!(&plugin_block, "cwd")
5580 .map(|s| PathBuf::from(s));
5581 let run_plugin_or_alias = RunPluginOrAlias::from_url(
5582 &string_url,
5583 &Some(configuration.inner().clone()),
5584 None,
5585 cwd.clone(),
5586 )
5587 .map_err(|e| {
5588 ConfigError::new_kdl_error(
5589 format!("Failed to parse plugin: {}", e),
5590 url_node.span().offset(),
5591 url_node.span().len(),
5592 )
5593 })?
5594 .with_initial_cwd(cwd);
5595 load_plugins.insert(run_plugin_or_alias);
5596 }
5597 }
5598 Ok(load_plugins)
5599}
5600
5601impl UiConfig {
5602 pub fn from_kdl(kdl_ui_config: &KdlNode) -> Result<UiConfig, ConfigError> {
5603 let mut ui_config = UiConfig::default();
5604 if let Some(pane_frames) = kdl_get_child!(kdl_ui_config, "pane_frames") {
5605 let rounded_corners =
5606 kdl_children_property_first_arg_as_bool!(pane_frames, "rounded_corners")
5607 .unwrap_or(false);
5608 let hide_session_name =
5609 kdl_get_child_entry_bool_value!(pane_frames, "hide_session_name").unwrap_or(false);
5610 let frame_config = FrameConfig {
5611 rounded_corners,
5612 hide_session_name,
5613 };
5614 ui_config.pane_frames = frame_config;
5615 }
5616 Ok(ui_config)
5617 }
5618 pub fn to_kdl(&self) -> Option<KdlNode> {
5619 let mut ui_config = KdlNode::new("ui");
5620 let mut ui_config_children = KdlDocument::new();
5621 let mut frame_config = KdlNode::new("pane_frames");
5622 let mut frame_config_children = KdlDocument::new();
5623 let mut has_ui_config = false;
5624 if self.pane_frames.rounded_corners {
5625 has_ui_config = true;
5626 let mut rounded_corners = KdlNode::new("rounded_corners");
5627 rounded_corners.push(KdlValue::Bool(true));
5628 frame_config_children.nodes_mut().push(rounded_corners);
5629 }
5630 if self.pane_frames.hide_session_name {
5631 has_ui_config = true;
5632 let mut hide_session_name = KdlNode::new("hide_session_name");
5633 hide_session_name.push(KdlValue::Bool(true));
5634 frame_config_children.nodes_mut().push(hide_session_name);
5635 }
5636 if has_ui_config {
5637 frame_config.set_children(frame_config_children);
5638 ui_config_children.nodes_mut().push(frame_config);
5639 ui_config.set_children(ui_config_children);
5640 Some(ui_config)
5641 } else {
5642 None
5643 }
5644 }
5645}
5646
5647impl Themes {
5648 fn style_declaration_from_node(
5649 style_node: &KdlNode,
5650 style_descriptor: &str,
5651 ) -> Result<Option<StyleDeclaration>, ConfigError> {
5652 let descriptor_node = kdl_child_with_name!(style_node, style_descriptor);
5653
5654 match descriptor_node {
5655 Some(descriptor) => {
5656 let colors = kdl_children_or_error!(
5657 descriptor,
5658 format!("Missing colors for {}", style_descriptor)
5659 );
5660 Ok(Some(StyleDeclaration {
5661 base: PaletteColor::try_from(("base", colors))?,
5662 background: PaletteColor::try_from(("background", colors)).unwrap_or_default(),
5663 emphasis_0: PaletteColor::try_from(("emphasis_0", colors))?,
5664 emphasis_1: PaletteColor::try_from(("emphasis_1", colors))?,
5665 emphasis_2: PaletteColor::try_from(("emphasis_2", colors))?,
5666 emphasis_3: PaletteColor::try_from(("emphasis_3", colors))?,
5667 }))
5668 },
5669 None => Ok(None),
5670 }
5671 }
5672
5673 fn multiplayer_colors(style_node: &KdlNode) -> Result<MultiplayerColors, ConfigError> {
5674 let descriptor_node = kdl_child_with_name!(style_node, "multiplayer_user_colors");
5675 match descriptor_node {
5676 Some(descriptor) => {
5677 let colors = kdl_children_or_error!(
5678 descriptor,
5679 format!("Missing colors for {}", "multiplayer_user_colors")
5680 );
5681 Ok(MultiplayerColors {
5682 player_1: PaletteColor::try_from(("player_1", colors))
5683 .unwrap_or(DEFAULT_STYLES.multiplayer_user_colors.player_1),
5684 player_2: PaletteColor::try_from(("player_2", colors))
5685 .unwrap_or(DEFAULT_STYLES.multiplayer_user_colors.player_2),
5686 player_3: PaletteColor::try_from(("player_3", colors))
5687 .unwrap_or(DEFAULT_STYLES.multiplayer_user_colors.player_3),
5688 player_4: PaletteColor::try_from(("player_4", colors))
5689 .unwrap_or(DEFAULT_STYLES.multiplayer_user_colors.player_4),
5690 player_5: PaletteColor::try_from(("player_5", colors))
5691 .unwrap_or(DEFAULT_STYLES.multiplayer_user_colors.player_5),
5692 player_6: PaletteColor::try_from(("player_6", colors))
5693 .unwrap_or(DEFAULT_STYLES.multiplayer_user_colors.player_6),
5694 player_7: PaletteColor::try_from(("player_7", colors))
5695 .unwrap_or(DEFAULT_STYLES.multiplayer_user_colors.player_7),
5696 player_8: PaletteColor::try_from(("player_8", colors))
5697 .unwrap_or(DEFAULT_STYLES.multiplayer_user_colors.player_8),
5698 player_9: PaletteColor::try_from(("player_9", colors))
5699 .unwrap_or(DEFAULT_STYLES.multiplayer_user_colors.player_9),
5700 player_10: PaletteColor::try_from(("player_10", colors))
5701 .unwrap_or(DEFAULT_STYLES.multiplayer_user_colors.player_10),
5702 })
5703 },
5704 None => Ok(DEFAULT_STYLES.multiplayer_user_colors),
5705 }
5706 }
5707
5708 pub fn from_kdl(
5709 themes_from_kdl: &KdlNode,
5710 sourced_from_external_file: bool,
5711 ) -> Result<Self, ConfigError> {
5712 let mut themes: HashMap<String, Theme> = HashMap::new();
5713 for theme_config in kdl_children_nodes_or_error!(themes_from_kdl, "no themes found") {
5714 let theme_name = kdl_name!(theme_config);
5715 let theme_colors = kdl_children_or_error!(theme_config, "empty theme");
5716 let palette_color_names = HashSet::from([
5717 "fg", "bg", "red", "green", "blue", "yellow", "magenta", "orange", "cyan", "black",
5718 "white",
5719 ]);
5720 let theme = if theme_colors
5721 .nodes()
5722 .iter()
5723 .all(|n| palette_color_names.contains(n.name().value()))
5724 {
5725 let palette = Palette {
5727 fg: PaletteColor::try_from(("fg", theme_colors))?,
5728 bg: PaletteColor::try_from(("bg", theme_colors))?,
5729 red: PaletteColor::try_from(("red", theme_colors))?,
5730 green: PaletteColor::try_from(("green", theme_colors))?,
5731 yellow: PaletteColor::try_from(("yellow", theme_colors))?,
5732 blue: PaletteColor::try_from(("blue", theme_colors))?,
5733 magenta: PaletteColor::try_from(("magenta", theme_colors))?,
5734 orange: PaletteColor::try_from(("orange", theme_colors))?,
5735 cyan: PaletteColor::try_from(("cyan", theme_colors))?,
5736 black: PaletteColor::try_from(("black", theme_colors))?,
5737 white: PaletteColor::try_from(("white", theme_colors))?,
5738 ..Default::default()
5739 };
5740 Theme {
5741 palette: palette.into(),
5742 sourced_from_external_file,
5743 }
5744 } else {
5745 let s = Styling {
5747 text_unselected: Themes::style_declaration_from_node(
5748 theme_config,
5749 "text_unselected",
5750 )
5751 .map(|maybe_style| maybe_style.unwrap_or(DEFAULT_STYLES.text_unselected))?,
5752 text_selected: Themes::style_declaration_from_node(
5753 theme_config,
5754 "text_selected",
5755 )
5756 .map(|maybe_style| maybe_style.unwrap_or(DEFAULT_STYLES.text_selected))?,
5757 ribbon_unselected: Themes::style_declaration_from_node(
5758 theme_config,
5759 "ribbon_unselected",
5760 )
5761 .map(|maybe_style| maybe_style.unwrap_or(DEFAULT_STYLES.ribbon_unselected))?,
5762 ribbon_selected: Themes::style_declaration_from_node(
5763 theme_config,
5764 "ribbon_selected",
5765 )
5766 .map(|maybe_style| maybe_style.unwrap_or(DEFAULT_STYLES.ribbon_selected))?,
5767 table_title: Themes::style_declaration_from_node(theme_config, "table_title")
5768 .map(|maybe_style| {
5769 maybe_style.unwrap_or(DEFAULT_STYLES.table_title)
5770 })?,
5771 table_cell_unselected: Themes::style_declaration_from_node(
5772 theme_config,
5773 "table_cell_unselected",
5774 )
5775 .map(|maybe_style| {
5776 maybe_style.unwrap_or(DEFAULT_STYLES.table_cell_unselected)
5777 })?,
5778 table_cell_selected: Themes::style_declaration_from_node(
5779 theme_config,
5780 "table_cell_selected",
5781 )
5782 .map(|maybe_style| maybe_style.unwrap_or(DEFAULT_STYLES.table_cell_selected))?,
5783 list_unselected: Themes::style_declaration_from_node(
5784 theme_config,
5785 "list_unselected",
5786 )
5787 .map(|maybe_style| maybe_style.unwrap_or(DEFAULT_STYLES.list_unselected))?,
5788 list_selected: Themes::style_declaration_from_node(
5789 theme_config,
5790 "list_selected",
5791 )
5792 .map(|maybe_style| maybe_style.unwrap_or(DEFAULT_STYLES.list_selected))?,
5793 frame_unselected: Themes::style_declaration_from_node(
5794 theme_config,
5795 "frame_unselected",
5796 )?,
5797 frame_selected: Themes::style_declaration_from_node(
5798 theme_config,
5799 "frame_selected",
5800 )
5801 .map(|maybe_style| maybe_style.unwrap_or(DEFAULT_STYLES.frame_selected))?,
5802 frame_highlight: Themes::style_declaration_from_node(
5803 theme_config,
5804 "frame_highlight",
5805 )
5806 .map(|maybe_style| maybe_style.unwrap_or(DEFAULT_STYLES.frame_highlight))?,
5807 exit_code_success: Themes::style_declaration_from_node(
5808 theme_config,
5809 "exit_code_success",
5810 )
5811 .map(|maybe_style| maybe_style.unwrap_or(DEFAULT_STYLES.exit_code_success))?,
5812 exit_code_error: Themes::style_declaration_from_node(
5813 theme_config,
5814 "exit_code_error",
5815 )
5816 .map(|maybe_style| maybe_style.unwrap_or(DEFAULT_STYLES.exit_code_error))?,
5817 multiplayer_user_colors: Themes::multiplayer_colors(theme_config)
5818 .unwrap_or_default(),
5819 };
5820
5821 Theme {
5822 palette: s,
5823 sourced_from_external_file,
5824 }
5825 };
5826 themes.insert(theme_name.into(), theme);
5827 }
5828 let themes = Themes::from_data(themes);
5829 Ok(themes)
5830 }
5831
5832 pub fn from_string(
5833 raw_string: &String,
5834 sourced_from_external_file: bool,
5835 ) -> Result<Self, ConfigError> {
5836 let kdl_config: KdlDocument = raw_string.parse()?;
5837 let kdl_themes = kdl_config.get("themes").ok_or(ConfigError::new_kdl_error(
5838 "No theme node found in file".into(),
5839 kdl_config.span().offset(),
5840 kdl_config.span().len(),
5841 ))?;
5842 let all_themes_in_file = Themes::from_kdl(kdl_themes, sourced_from_external_file)?;
5843 Ok(all_themes_in_file)
5844 }
5845
5846 pub fn from_path(path_to_theme_file: PathBuf) -> Result<Self, ConfigError> {
5847 let kdl_config = std::fs::read_to_string(&path_to_theme_file)
5849 .map_err(|e| ConfigError::IoPath(e, path_to_theme_file.clone()))?;
5850 let sourced_from_external_file = true;
5851 Themes::from_string(&kdl_config, sourced_from_external_file).map_err(|e| match e {
5852 ConfigError::KdlError(kdl_error) => ConfigError::KdlError(
5853 kdl_error.add_src(path_to_theme_file.display().to_string(), kdl_config),
5854 ),
5855 e => e,
5856 })
5857 }
5858
5859 pub fn from_dir(path_to_theme_dir: PathBuf) -> Result<Self, ConfigError> {
5860 let mut themes = Themes::default();
5861 for entry in std::fs::read_dir(&path_to_theme_dir)
5862 .map_err(|e| ConfigError::IoPath(e, path_to_theme_dir.clone()))?
5863 {
5864 let entry = entry.map_err(|e| ConfigError::IoPath(e, path_to_theme_dir.clone()))?;
5865 let path = entry.path();
5866 if let Some(extension) = path.extension() {
5867 if extension == "kdl" {
5868 themes = themes.merge(Themes::from_path(path)?);
5869 }
5870 }
5871 }
5872 Ok(themes)
5873 }
5874 pub fn to_kdl(&self) -> Option<KdlNode> {
5875 let mut theme_node = KdlNode::new("themes");
5876 let mut themes = KdlDocument::new();
5877 let mut has_themes = false;
5878 let sorted_themes: BTreeMap<String, Theme> = self.inner().clone().into_iter().collect();
5879 for (theme_name, theme) in sorted_themes {
5880 if theme.sourced_from_external_file {
5881 continue;
5884 }
5885 has_themes = true;
5886 let mut current_theme_node = KdlNode::new(theme_name.clone());
5887 let mut current_theme_node_children = KdlDocument::new();
5888
5889 current_theme_node_children
5890 .nodes_mut()
5891 .push(theme.palette.text_unselected.to_kdl("text_unselected"));
5892 current_theme_node_children
5893 .nodes_mut()
5894 .push(theme.palette.text_selected.to_kdl("text_selected"));
5895 current_theme_node_children
5896 .nodes_mut()
5897 .push(theme.palette.ribbon_selected.to_kdl("ribbon_selected"));
5898 current_theme_node_children
5899 .nodes_mut()
5900 .push(theme.palette.ribbon_unselected.to_kdl("ribbon_unselected"));
5901 current_theme_node_children
5902 .nodes_mut()
5903 .push(theme.palette.table_title.to_kdl("table_title"));
5904 current_theme_node_children.nodes_mut().push(
5905 theme
5906 .palette
5907 .table_cell_selected
5908 .to_kdl("table_cell_selected"),
5909 );
5910 current_theme_node_children.nodes_mut().push(
5911 theme
5912 .palette
5913 .table_cell_unselected
5914 .to_kdl("table_cell_unselected"),
5915 );
5916 current_theme_node_children
5917 .nodes_mut()
5918 .push(theme.palette.list_selected.to_kdl("list_selected"));
5919 current_theme_node_children
5920 .nodes_mut()
5921 .push(theme.palette.list_unselected.to_kdl("list_unselected"));
5922 current_theme_node_children
5923 .nodes_mut()
5924 .push(theme.palette.frame_selected.to_kdl("frame_selected"));
5925
5926 match theme.palette.frame_unselected {
5927 None => {},
5928 Some(frame_unselected_style) => {
5929 current_theme_node_children
5930 .nodes_mut()
5931 .push(frame_unselected_style.to_kdl("frame_unselected"));
5932 },
5933 }
5934 current_theme_node_children
5935 .nodes_mut()
5936 .push(theme.palette.frame_highlight.to_kdl("frame_highlight"));
5937 current_theme_node_children
5938 .nodes_mut()
5939 .push(theme.palette.exit_code_success.to_kdl("exit_code_success"));
5940 current_theme_node_children
5941 .nodes_mut()
5942 .push(theme.palette.exit_code_error.to_kdl("exit_code_error"));
5943 current_theme_node_children
5944 .nodes_mut()
5945 .push(theme.palette.multiplayer_user_colors.to_kdl());
5946 current_theme_node.set_children(current_theme_node_children);
5947 themes.nodes_mut().push(current_theme_node);
5948 }
5949 if has_themes {
5950 theme_node.set_children(themes);
5951 Some(theme_node)
5952 } else {
5953 None
5954 }
5955 }
5956}
5957
5958impl PermissionCache {
5959 pub fn from_string(raw_string: String) -> Result<GrantedPermission, ConfigError> {
5960 let kdl_document: KdlDocument = raw_string.parse()?;
5961
5962 let mut granted_permission = GrantedPermission::default();
5963
5964 for node in kdl_document.nodes() {
5965 if let Some(children) = node.children() {
5966 let key = kdl_name!(node);
5967 let permissions: Vec<PermissionType> = children
5968 .nodes()
5969 .iter()
5970 .filter_map(|p| {
5971 let v = kdl_name!(p);
5972 PermissionType::from_str(v).ok()
5973 })
5974 .collect();
5975
5976 granted_permission.insert(key.into(), permissions);
5977 }
5978 }
5979
5980 Ok(granted_permission)
5981 }
5982
5983 pub fn to_string(granted: &GrantedPermission) -> String {
5984 let mut kdl_doucment = KdlDocument::new();
5985
5986 granted.iter().for_each(|(k, v)| {
5987 let mut node = KdlNode::new(k.as_str());
5988 let mut children = KdlDocument::new();
5989
5990 let permissions: HashSet<PermissionType> = v.clone().into_iter().collect();
5991 permissions.iter().for_each(|f| {
5992 let n = KdlNode::new(f.to_string().as_str());
5993 children.nodes_mut().push(n);
5994 });
5995
5996 node.set_children(children);
5997 kdl_doucment.nodes_mut().push(node);
5998 });
5999
6000 kdl_doucment.fmt();
6001 kdl_doucment.to_string()
6002 }
6003}
6004
6005impl SessionInfo {
6006 pub fn from_string(raw_session_info: &str, current_session_name: &str) -> Result<Self, String> {
6007 let kdl_document: KdlDocument = raw_session_info
6008 .parse()
6009 .map_err(|e| format!("Failed to parse kdl document: {}", e))?;
6010 let name = kdl_document
6011 .get("name")
6012 .and_then(|n| n.entries().iter().next())
6013 .and_then(|e| e.value().as_string())
6014 .map(|s| s.to_owned())
6015 .ok_or("Failed to parse session name")?;
6016 let connected_clients = kdl_document
6017 .get("connected_clients")
6018 .and_then(|n| n.entries().iter().next())
6019 .and_then(|e| e.value().as_i64())
6020 .map(|c| c as usize)
6021 .ok_or("Failed to parse connected_clients")?;
6022 let tabs: Vec<TabInfo> = kdl_document
6023 .get("tabs")
6024 .and_then(|t| t.children())
6025 .and_then(|c| {
6026 let mut tab_nodes = vec![];
6027 for tab_node in c.nodes() {
6028 if let Some(tab) = tab_node.children() {
6029 tab_nodes.push(TabInfo::decode_from_kdl(tab).ok()?);
6030 }
6031 }
6032 Some(tab_nodes)
6033 })
6034 .ok_or("Failed to parse tabs")?;
6035 let panes: PaneManifest = kdl_document
6036 .get("panes")
6037 .and_then(|p| p.children())
6038 .map(|p| PaneManifest::decode_from_kdl(p))
6039 .ok_or("Failed to parse panes")?;
6040 let available_layouts: Vec<LayoutInfo> = kdl_document
6041 .get("available_layouts")
6042 .and_then(|p| p.children())
6043 .map(|e| {
6044 e.nodes()
6045 .iter()
6046 .filter_map(|n| {
6047 let layout_name = n.name().value().to_owned();
6048 let layout_source = n
6049 .entries()
6050 .iter()
6051 .find(|e| e.name().map(|n| n.value()) == Some("source"))
6052 .and_then(|e| e.value().as_string());
6053 match layout_source {
6054 Some(layout_source) => match layout_source {
6055 "built-in" => Some(LayoutInfo::BuiltIn(layout_name)),
6056 "file" => {
6057 Some(LayoutInfo::File(layout_name, LayoutMetadata::default()))
6058 },
6059 _ => None,
6060 },
6061 None => None,
6062 }
6063 })
6064 .collect()
6065 })
6066 .ok_or("Failed to parse available_layouts")?;
6067 let web_client_count = kdl_document
6068 .get("web_client_count")
6069 .and_then(|n| n.entries().iter().next())
6070 .and_then(|e| e.value().as_i64())
6071 .map(|c| c as usize)
6072 .unwrap_or(0);
6073 let web_clients_allowed = kdl_document
6074 .get("web_clients_allowed")
6075 .and_then(|n| n.entries().iter().next())
6076 .and_then(|e| e.value().as_bool())
6077 .unwrap_or(false);
6078 let is_current_session = name == current_session_name;
6079 let mut tab_history = BTreeMap::new();
6080 if let Some(kdl_tab_history) = kdl_document.get("tab_history").and_then(|p| p.children()) {
6081 for client_node in kdl_tab_history.nodes() {
6082 if let Some(client_id) = client_node.children().and_then(|c| {
6083 c.get("id")
6084 .and_then(|c| c.entries().iter().next().and_then(|e| e.value().as_i64()))
6085 }) {
6086 let mut history = vec![];
6087 if let Some(history_entries) = client_node
6088 .children()
6089 .and_then(|c| c.get("history"))
6090 .map(|h| h.entries())
6091 {
6092 for entry in history_entries {
6093 if let Some(entry) = entry.value().as_i64() {
6094 history.push(entry as usize);
6095 }
6096 }
6097 }
6098 tab_history.insert(client_id as u16, history);
6099 }
6100 }
6101 }
6102 let mut pane_history = BTreeMap::new();
6103 if let Some(kdl_pane_history) = kdl_document.get("pane_history").and_then(|p| p.children())
6104 {
6105 for client_node in kdl_pane_history.nodes() {
6106 if let Some(client_id) = client_node.children().and_then(|c| {
6107 c.get("id")
6108 .and_then(|c| c.entries().iter().next().and_then(|e| e.value().as_i64()))
6109 }) {
6110 let mut history = vec![];
6111 if let Some(history_node) =
6112 client_node.children().and_then(|c| c.get("history"))
6113 {
6114 if let Some(history_children) = history_node.children() {
6115 for pane_id_node in history_children.nodes() {
6116 if pane_id_node.name().value() == "pane_id" {
6117 let pane_type = pane_id_node
6118 .entries()
6119 .iter()
6120 .find(|e| e.name().map(|n| n.value()) == Some("type"))
6121 .and_then(|e| e.value().as_string());
6122 let id = pane_id_node
6123 .entries()
6124 .iter()
6125 .find(|e| e.name().is_none())
6126 .and_then(|e| e.value().as_i64())
6127 .map(|i| i as u32);
6128 if let (Some(pane_type), Some(id)) = (pane_type, id) {
6129 let pane_id = match pane_type {
6130 "terminal" => Some(PaneId::Terminal(id)),
6131 "plugin" => Some(PaneId::Plugin(id)),
6132 _ => None,
6133 };
6134 if let Some(pane_id) = pane_id {
6135 history.push(pane_id);
6136 }
6137 }
6138 }
6139 }
6140 }
6141 }
6142 pane_history.insert(client_id as u16, history);
6143 }
6144 }
6145 }
6146 let creation_time = kdl_document
6147 .get("creation_time")
6148 .and_then(|n| n.entries().iter().next())
6149 .and_then(|e| e.value().as_i64())
6150 .map(|c| Duration::from_secs(c as u64))
6151 .unwrap_or_default();
6152 Ok(SessionInfo {
6153 name,
6154 tabs,
6155 panes,
6156 connected_clients,
6157 is_current_session,
6158 available_layouts,
6159 web_client_count,
6160 web_clients_allowed,
6161 plugins: Default::default(), tab_history,
6163 pane_history,
6164 creation_time,
6165 })
6166 }
6167 pub fn to_string(&self) -> String {
6168 let mut kdl_document = KdlDocument::new();
6169
6170 let mut name = KdlNode::new("name");
6171 name.push(self.name.clone());
6172
6173 let mut connected_clients = KdlNode::new("connected_clients");
6174 connected_clients.push(self.connected_clients as i64);
6175
6176 let mut tabs = KdlNode::new("tabs");
6177 let mut tab_children = KdlDocument::new();
6178 for tab_info in &self.tabs {
6179 let mut tab = KdlNode::new("tab");
6180 let kdl_tab_info = tab_info.encode_to_kdl();
6181 tab.set_children(kdl_tab_info);
6182 tab_children.nodes_mut().push(tab);
6183 }
6184 tabs.set_children(tab_children);
6185
6186 let mut panes = KdlNode::new("panes");
6187 panes.set_children(self.panes.encode_to_kdl());
6188
6189 let mut web_client_count = KdlNode::new("web_client_count");
6190 web_client_count.push(self.web_client_count as i64);
6191
6192 let mut web_clients_allowed = KdlNode::new("web_clients_allowed");
6193 web_clients_allowed.push(self.web_clients_allowed);
6194
6195 let mut available_layouts = KdlNode::new("available_layouts");
6196 let mut available_layouts_children = KdlDocument::new();
6197 for layout_info in &self.available_layouts {
6198 let (layout_name, layout_source) = match layout_info {
6199 LayoutInfo::File(name, _layout_metadata) => (name.clone(), "file"),
6200 LayoutInfo::BuiltIn(name) => (name.clone(), "built-in"),
6201 LayoutInfo::Url(url) => (url.clone(), "url"),
6202 LayoutInfo::Stringified(_stringified) => ("stringified-layout".to_owned(), "N/A"),
6203 };
6204 let mut layout_node = KdlNode::new(format!("{}", layout_name));
6205 let layout_source = KdlEntry::new_prop("source", layout_source);
6206 layout_node.entries_mut().push(layout_source);
6207 available_layouts_children.nodes_mut().push(layout_node);
6208 }
6209 available_layouts.set_children(available_layouts_children);
6210
6211 let mut tab_history = KdlNode::new("tab_history");
6212 let mut tab_history_children = KdlDocument::new();
6213 for (client_id, client_tab_history) in &self.tab_history {
6214 let mut client_document = KdlDocument::new();
6215 let mut client_node = KdlNode::new("client");
6216 let mut id = KdlNode::new("id");
6217 id.push(*client_id as i64);
6218 client_document.nodes_mut().push(id);
6219 let mut history = KdlNode::new("history");
6220 for entry in client_tab_history {
6221 history.push(*entry as i64);
6222 }
6223 client_document.nodes_mut().push(history);
6224 client_node.set_children(client_document);
6225 tab_history_children.nodes_mut().push(client_node);
6226 }
6227 tab_history.set_children(tab_history_children);
6228
6229 let mut pane_history = KdlNode::new("pane_history");
6230 let mut pane_history_children = KdlDocument::new();
6231 for (client_id, client_pane_history) in &self.pane_history {
6232 let mut client_document = KdlDocument::new();
6233 let mut client_node = KdlNode::new("client");
6234 let mut id = KdlNode::new("id");
6235 id.push(*client_id as i64);
6236 client_document.nodes_mut().push(id);
6237 let mut history = KdlNode::new("history");
6238 for pane_id in client_pane_history {
6239 let mut pane_id_node = KdlNode::new("pane_id");
6240 match pane_id {
6241 PaneId::Terminal(id) => {
6242 pane_id_node.push(KdlEntry::new_prop("type", "terminal"));
6243 pane_id_node.push(*id as i64);
6244 },
6245 PaneId::Plugin(id) => {
6246 pane_id_node.push(KdlEntry::new_prop("type", "plugin"));
6247 pane_id_node.push(*id as i64);
6248 },
6249 }
6250 history.ensure_children().nodes_mut().push(pane_id_node);
6251 }
6252 client_document.nodes_mut().push(history);
6253 client_node.set_children(client_document);
6254 pane_history_children.nodes_mut().push(client_node);
6255 }
6256 pane_history.set_children(pane_history_children);
6257
6258 kdl_document.nodes_mut().push(name);
6259 kdl_document.nodes_mut().push(tabs);
6260 kdl_document.nodes_mut().push(panes);
6261 kdl_document.nodes_mut().push(connected_clients);
6262 kdl_document.nodes_mut().push(web_clients_allowed);
6263 kdl_document.nodes_mut().push(web_client_count);
6264 kdl_document.nodes_mut().push(available_layouts);
6265 kdl_document.nodes_mut().push(tab_history);
6266 kdl_document.nodes_mut().push(pane_history);
6267
6268 let mut creation_time_node = KdlNode::new("creation_time");
6269 creation_time_node.push(self.creation_time.as_secs() as i64);
6270 kdl_document.nodes_mut().push(creation_time_node);
6271
6272 kdl_document.fmt();
6273 kdl_document.to_string()
6274 }
6275}
6276
6277impl TabInfo {
6278 pub fn decode_from_kdl(kdl_document: &KdlDocument) -> Result<Self, String> {
6279 macro_rules! int_node {
6280 ($name:expr, $type:ident) => {{
6281 kdl_document
6282 .get($name)
6283 .and_then(|n| n.entries().iter().next())
6284 .and_then(|e| e.value().as_i64())
6285 .map(|e| e as $type)
6286 .ok_or(format!("Failed to parse tab {}", $name))?
6287 }};
6288 }
6289 macro_rules! string_node {
6290 ($name:expr) => {{
6291 kdl_document
6292 .get($name)
6293 .and_then(|n| n.entries().iter().next())
6294 .and_then(|e| e.value().as_string())
6295 .map(|s| s.to_owned())
6296 .ok_or(format!("Failed to parse tab {}", $name))?
6297 }};
6298 }
6299 macro_rules! optional_string_node {
6300 ($name:expr) => {{
6301 kdl_document
6302 .get($name)
6303 .and_then(|n| n.entries().iter().next())
6304 .and_then(|e| e.value().as_string())
6305 .map(|s| s.to_owned())
6306 }};
6307 }
6308 macro_rules! optional_int_node {
6309 ($name:expr, $type:ident) => {{
6310 kdl_document
6311 .get($name)
6312 .and_then(|n| n.entries().iter().next())
6313 .and_then(|e| e.value().as_i64())
6314 .map(|e| e as $type)
6315 }};
6316 }
6317 macro_rules! bool_node {
6318 ($name:expr) => {{
6319 kdl_document
6320 .get($name)
6321 .and_then(|n| n.entries().iter().next())
6322 .and_then(|e| e.value().as_bool())
6323 .ok_or(format!("Failed to parse tab {}", $name))?
6324 }};
6325 }
6326
6327 let position = int_node!("position", usize);
6328 let name = string_node!("name");
6329 let active = bool_node!("active");
6330 let panes_to_hide = int_node!("panes_to_hide", usize);
6331 let is_fullscreen_active = bool_node!("is_fullscreen_active");
6332 let is_sync_panes_active = bool_node!("is_sync_panes_active");
6333 let are_floating_panes_visible = bool_node!("are_floating_panes_visible");
6334 let mut other_focused_clients = vec![];
6335 if let Some(tab_other_focused_clients) = kdl_document
6336 .get("other_focused_clients")
6337 .map(|n| n.entries())
6338 {
6339 for entry in tab_other_focused_clients {
6340 if let Some(entry_parsed) = entry.value().as_i64() {
6341 other_focused_clients.push(entry_parsed as u16);
6342 }
6343 }
6344 }
6345 let active_swap_layout_name = optional_string_node!("active_swap_layout_name");
6346 let viewport_rows = optional_int_node!("viewport_rows", usize).unwrap_or(0);
6347 let viewport_columns = optional_int_node!("viewport_columns", usize).unwrap_or(0);
6348 let display_area_rows = optional_int_node!("display_area_rows", usize).unwrap_or(0);
6349 let display_area_columns = optional_int_node!("display_area_columns", usize).unwrap_or(0);
6350 let is_swap_layout_dirty = bool_node!("is_swap_layout_dirty");
6351 let selectable_tiled_panes_count =
6352 optional_int_node!("selectable_tiled_panes_count", usize).unwrap_or(0);
6353 let selectable_floating_panes_count =
6354 optional_int_node!("selectable_floating_panes_count", usize).unwrap_or(0);
6355 let tab_id = optional_int_node!("tab_id", usize).unwrap_or(0);
6356 Ok(TabInfo {
6357 position,
6358 name,
6359 active,
6360 panes_to_hide,
6361 is_fullscreen_active,
6362 is_sync_panes_active,
6363 are_floating_panes_visible,
6364 other_focused_clients,
6365 active_swap_layout_name,
6366 is_swap_layout_dirty,
6367 viewport_rows,
6368 viewport_columns,
6369 display_area_rows,
6370 display_area_columns,
6371 selectable_tiled_panes_count,
6372 selectable_floating_panes_count,
6373 tab_id,
6374 has_bell_notification: false,
6375 is_flashing_bell: false,
6376 })
6377 }
6378 pub fn encode_to_kdl(&self) -> KdlDocument {
6379 let mut kdl_doucment = KdlDocument::new();
6380
6381 let mut position = KdlNode::new("position");
6382 position.push(self.position as i64);
6383 kdl_doucment.nodes_mut().push(position);
6384
6385 let mut name = KdlNode::new("name");
6386 name.push(self.name.clone());
6387 kdl_doucment.nodes_mut().push(name);
6388
6389 let mut active = KdlNode::new("active");
6390 active.push(self.active);
6391 kdl_doucment.nodes_mut().push(active);
6392
6393 let mut panes_to_hide = KdlNode::new("panes_to_hide");
6394 panes_to_hide.push(self.panes_to_hide as i64);
6395 kdl_doucment.nodes_mut().push(panes_to_hide);
6396
6397 let mut is_fullscreen_active = KdlNode::new("is_fullscreen_active");
6398 is_fullscreen_active.push(self.is_fullscreen_active);
6399 kdl_doucment.nodes_mut().push(is_fullscreen_active);
6400
6401 let mut is_sync_panes_active = KdlNode::new("is_sync_panes_active");
6402 is_sync_panes_active.push(self.is_sync_panes_active);
6403 kdl_doucment.nodes_mut().push(is_sync_panes_active);
6404
6405 let mut are_floating_panes_visible = KdlNode::new("are_floating_panes_visible");
6406 are_floating_panes_visible.push(self.are_floating_panes_visible);
6407 kdl_doucment.nodes_mut().push(are_floating_panes_visible);
6408
6409 if !self.other_focused_clients.is_empty() {
6410 let mut other_focused_clients = KdlNode::new("other_focused_clients");
6411 for client_id in &self.other_focused_clients {
6412 other_focused_clients.push(*client_id as i64);
6413 }
6414 kdl_doucment.nodes_mut().push(other_focused_clients);
6415 }
6416
6417 if let Some(active_swap_layout_name) = self.active_swap_layout_name.as_ref() {
6418 let mut active_swap_layout = KdlNode::new("active_swap_layout_name");
6419 active_swap_layout.push(active_swap_layout_name.to_string());
6420 kdl_doucment.nodes_mut().push(active_swap_layout);
6421 }
6422
6423 let mut viewport_rows = KdlNode::new("viewport_rows");
6424 viewport_rows.push(self.viewport_rows as i64);
6425 kdl_doucment.nodes_mut().push(viewport_rows);
6426
6427 let mut viewport_columns = KdlNode::new("viewport_columns");
6428 viewport_columns.push(self.viewport_columns as i64);
6429 kdl_doucment.nodes_mut().push(viewport_columns);
6430
6431 let mut display_area_columns = KdlNode::new("display_area_columns");
6432 display_area_columns.push(self.display_area_columns as i64);
6433 kdl_doucment.nodes_mut().push(display_area_columns);
6434
6435 let mut display_area_rows = KdlNode::new("display_area_rows");
6436 display_area_rows.push(self.display_area_rows as i64);
6437 kdl_doucment.nodes_mut().push(display_area_rows);
6438
6439 let mut is_swap_layout_dirty = KdlNode::new("is_swap_layout_dirty");
6440 is_swap_layout_dirty.push(self.is_swap_layout_dirty);
6441 kdl_doucment.nodes_mut().push(is_swap_layout_dirty);
6442
6443 let mut selectable_tiled_panes_count = KdlNode::new("selectable_tiled_panes_count");
6444 selectable_tiled_panes_count.push(self.selectable_tiled_panes_count as i64);
6445 kdl_doucment.nodes_mut().push(selectable_tiled_panes_count);
6446
6447 let mut selectable_floating_panes_count = KdlNode::new("selectable_floating_panes_count");
6448 selectable_floating_panes_count.push(self.selectable_floating_panes_count as i64);
6449 kdl_doucment
6450 .nodes_mut()
6451 .push(selectable_floating_panes_count);
6452
6453 let mut tab_id = KdlNode::new("tab_id");
6454 tab_id.push(self.tab_id as i64);
6455 kdl_doucment.nodes_mut().push(tab_id);
6456
6457 kdl_doucment
6458 }
6459}
6460
6461impl PaneManifest {
6462 pub fn decode_from_kdl(kdl_doucment: &KdlDocument) -> Self {
6463 let mut panes: HashMap<usize, Vec<PaneInfo>> = HashMap::new();
6464 for node in kdl_doucment.nodes() {
6465 if node.name().to_string() == "pane" {
6466 if let Some(pane_document) = node.children() {
6467 if let Ok((tab_position, pane_info)) = PaneInfo::decode_from_kdl(pane_document)
6468 {
6469 let panes_in_tab_position =
6470 panes.entry(tab_position).or_insert_with(Vec::new);
6471 panes_in_tab_position.push(pane_info);
6472 }
6473 }
6474 }
6475 }
6476 PaneManifest { panes }
6477 }
6478 pub fn encode_to_kdl(&self) -> KdlDocument {
6479 let mut kdl_doucment = KdlDocument::new();
6480 for (tab_position, panes) in &self.panes {
6481 for pane in panes {
6482 let mut pane_node = KdlNode::new("pane");
6483 let mut pane = pane.encode_to_kdl();
6484
6485 let mut position_node = KdlNode::new("tab_position");
6486 position_node.push(*tab_position as i64);
6487 pane.nodes_mut().push(position_node);
6488
6489 pane_node.set_children(pane);
6490 kdl_doucment.nodes_mut().push(pane_node);
6491 }
6492 }
6493 kdl_doucment
6494 }
6495}
6496
6497impl PaneInfo {
6498 pub fn decode_from_kdl(kdl_document: &KdlDocument) -> Result<(usize, Self), String> {
6499 macro_rules! int_node {
6501 ($name:expr, $type:ident) => {{
6502 kdl_document
6503 .get($name)
6504 .and_then(|n| n.entries().iter().next())
6505 .and_then(|e| e.value().as_i64())
6506 .map(|e| e as $type)
6507 .ok_or(format!("Failed to parse pane {}", $name))?
6508 }};
6509 }
6510 macro_rules! optional_int_node {
6511 ($name:expr, $type:ident) => {{
6512 kdl_document
6513 .get($name)
6514 .and_then(|n| n.entries().iter().next())
6515 .and_then(|e| e.value().as_i64())
6516 .map(|e| e as $type)
6517 }};
6518 }
6519 macro_rules! bool_node {
6520 ($name:expr) => {{
6521 kdl_document
6522 .get($name)
6523 .and_then(|n| n.entries().iter().next())
6524 .and_then(|e| e.value().as_bool())
6525 .ok_or(format!("Failed to parse pane {}", $name))?
6526 }};
6527 }
6528 macro_rules! string_node {
6529 ($name:expr) => {{
6530 kdl_document
6531 .get($name)
6532 .and_then(|n| n.entries().iter().next())
6533 .and_then(|e| e.value().as_string())
6534 .map(|s| s.to_owned())
6535 .ok_or(format!("Failed to parse pane {}", $name))?
6536 }};
6537 }
6538 macro_rules! optional_string_node {
6539 ($name:expr) => {{
6540 kdl_document
6541 .get($name)
6542 .and_then(|n| n.entries().iter().next())
6543 .and_then(|e| e.value().as_string())
6544 .map(|s| s.to_owned())
6545 }};
6546 }
6547 let tab_position = int_node!("tab_position", usize);
6548 let id = int_node!("id", u32);
6549
6550 let is_plugin = bool_node!("is_plugin");
6551 let is_focused = bool_node!("is_focused");
6552 let is_fullscreen = bool_node!("is_fullscreen");
6553 let is_floating = bool_node!("is_floating");
6554 let is_suppressed = bool_node!("is_suppressed");
6555 let title = string_node!("title");
6556 let exited = bool_node!("exited");
6557 let exit_status = optional_int_node!("exit_status", i32);
6558 let is_held = bool_node!("is_held");
6559 let pane_x = int_node!("pane_x", usize);
6560 let pane_content_x = int_node!("pane_content_x", usize);
6561 let pane_y = int_node!("pane_y", usize);
6562 let pane_content_y = int_node!("pane_content_y", usize);
6563 let pane_rows = int_node!("pane_rows", usize);
6564 let pane_content_rows = int_node!("pane_content_rows", usize);
6565 let pane_columns = int_node!("pane_columns", usize);
6566 let pane_content_columns = int_node!("pane_content_columns", usize);
6567 let cursor_coordinates_in_pane = kdl_document
6568 .get("cursor_coordinates_in_pane")
6569 .map(|n| {
6570 let mut entries = n.entries().iter();
6571 (entries.next(), entries.next())
6572 })
6573 .and_then(|(x, y)| {
6574 let x = x.and_then(|x| x.value().as_i64()).map(|x| x as usize);
6575 let y = y.and_then(|y| y.value().as_i64()).map(|y| y as usize);
6576 match (x, y) {
6577 (Some(x), Some(y)) => Some((x, y)),
6578 _ => None,
6579 }
6580 });
6581 let terminal_command = optional_string_node!("terminal_command");
6582 let plugin_url = optional_string_node!("plugin_url");
6583 let is_selectable = bool_node!("is_selectable");
6584
6585 let pane_info = PaneInfo {
6586 id,
6587 is_plugin,
6588 is_focused,
6589 is_fullscreen,
6590 is_floating,
6591 is_suppressed,
6592 title,
6593 exited,
6594 exit_status,
6595 is_held,
6596 pane_x,
6597 pane_content_x,
6598 pane_y,
6599 pane_content_y,
6600 pane_rows,
6601 pane_content_rows,
6602 pane_columns,
6603 pane_content_columns,
6604 cursor_coordinates_in_pane,
6605 terminal_command,
6606 plugin_url,
6607 is_selectable,
6608 index_in_pane_group: Default::default(), default_fg: None,
6610 default_bg: None,
6611 };
6612 Ok((tab_position, pane_info))
6613 }
6614 pub fn encode_to_kdl(&self) -> KdlDocument {
6615 let mut kdl_doucment = KdlDocument::new();
6616 macro_rules! int_node {
6617 ($name:expr, $val:expr) => {{
6618 let mut att = KdlNode::new($name);
6619 att.push($val as i64);
6620 kdl_doucment.nodes_mut().push(att);
6621 }};
6622 }
6623 macro_rules! bool_node {
6624 ($name:expr, $val:expr) => {{
6625 let mut att = KdlNode::new($name);
6626 att.push($val);
6627 kdl_doucment.nodes_mut().push(att);
6628 }};
6629 }
6630 macro_rules! string_node {
6631 ($name:expr, $val:expr) => {{
6632 let mut att = KdlNode::new($name);
6633 att.push($val);
6634 kdl_doucment.nodes_mut().push(att);
6635 }};
6636 }
6637
6638 int_node!("id", self.id);
6639 bool_node!("is_plugin", self.is_plugin);
6640 bool_node!("is_focused", self.is_focused);
6641 bool_node!("is_fullscreen", self.is_fullscreen);
6642 bool_node!("is_floating", self.is_floating);
6643 bool_node!("is_suppressed", self.is_suppressed);
6644 string_node!("title", self.title.to_string());
6645 bool_node!("exited", self.exited);
6646 if let Some(exit_status) = self.exit_status {
6647 int_node!("exit_status", exit_status);
6648 }
6649 bool_node!("is_held", self.is_held);
6650 int_node!("pane_x", self.pane_x);
6651 int_node!("pane_content_x", self.pane_content_x);
6652 int_node!("pane_y", self.pane_y);
6653 int_node!("pane_content_y", self.pane_content_y);
6654 int_node!("pane_rows", self.pane_rows);
6655 int_node!("pane_content_rows", self.pane_content_rows);
6656 int_node!("pane_columns", self.pane_columns);
6657 int_node!("pane_content_columns", self.pane_content_columns);
6658 if let Some((cursor_x, cursor_y)) = self.cursor_coordinates_in_pane {
6659 let mut cursor_coordinates_in_pane = KdlNode::new("cursor_coordinates_in_pane");
6660 cursor_coordinates_in_pane.push(cursor_x as i64);
6661 cursor_coordinates_in_pane.push(cursor_y as i64);
6662 kdl_doucment.nodes_mut().push(cursor_coordinates_in_pane);
6663 }
6664 if let Some(terminal_command) = &self.terminal_command {
6665 string_node!("terminal_command", terminal_command.to_string());
6666 }
6667 if let Some(plugin_url) = &self.plugin_url {
6668 string_node!("plugin_url", plugin_url.to_string());
6669 }
6670 bool_node!("is_selectable", self.is_selectable);
6671 kdl_doucment
6672 }
6673}
6674
6675pub fn parse_plugin_user_configuration(
6676 plugin_block: &KdlNode,
6677) -> Result<BTreeMap<String, String>, ConfigError> {
6678 let mut configuration = BTreeMap::new();
6679 for user_configuration_entry in plugin_block.entries() {
6680 let name = user_configuration_entry.name();
6681 let value = user_configuration_entry.value();
6682 if let Some(name) = name {
6683 let name = name.to_string();
6684 if KdlLayoutParser::is_a_reserved_plugin_property(&name) {
6685 continue;
6686 }
6687 configuration.insert(name, value.to_string());
6688 }
6689 }
6690 if let Some(user_config) = kdl_children_nodes!(plugin_block) {
6691 for user_configuration_entry in user_config {
6692 let config_entry_name = kdl_name!(user_configuration_entry);
6693 if KdlLayoutParser::is_a_reserved_plugin_property(&config_entry_name) {
6694 continue;
6695 }
6696 let config_entry_str_value = kdl_first_entry_as_string!(user_configuration_entry)
6697 .map(|s| format!("{}", s.to_string()));
6698 let config_entry_int_value = kdl_first_entry_as_i64!(user_configuration_entry)
6699 .map(|s| format!("{}", s.to_string()));
6700 let config_entry_bool_value = kdl_first_entry_as_bool!(user_configuration_entry)
6701 .map(|s| format!("{}", s.to_string()));
6702 let config_entry_children = user_configuration_entry
6703 .children()
6704 .map(|s| format!("{}", s.to_string().trim()));
6705 let config_entry_value = config_entry_str_value
6706 .or(config_entry_int_value)
6707 .or(config_entry_bool_value)
6708 .or(config_entry_children)
6709 .ok_or(ConfigError::new_kdl_error(
6710 format!(
6711 "Failed to parse plugin block configuration: {:?}",
6712 user_configuration_entry
6713 ),
6714 plugin_block.span().offset(),
6715 plugin_block.span().len(),
6716 ))?;
6717 configuration.insert(config_entry_name.into(), config_entry_value);
6718 }
6719 }
6720 Ok(configuration)
6721}
6722
6723#[test]
6724fn serialize_and_deserialize_session_info() {
6725 let session_info = SessionInfo::default();
6726 let serialized = session_info.to_string();
6727 let deserealized = SessionInfo::from_string(&serialized, "not this session").unwrap();
6728 assert_eq!(session_info, deserealized);
6729 insta::assert_snapshot!(serialized);
6730}
6731
6732#[test]
6733fn serialize_and_deserialize_session_info_with_data() {
6734 let panes_list = vec![
6735 PaneInfo {
6736 id: 1,
6737 is_plugin: false,
6738 is_focused: true,
6739 is_fullscreen: true,
6740 is_floating: false,
6741 is_suppressed: false,
6742 title: "pane 1".to_owned(),
6743 exited: false,
6744 exit_status: None,
6745 is_held: false,
6746 pane_x: 0,
6747 pane_content_x: 1,
6748 pane_y: 0,
6749 pane_content_y: 1,
6750 pane_rows: 5,
6751 pane_content_rows: 4,
6752 pane_columns: 22,
6753 pane_content_columns: 21,
6754 cursor_coordinates_in_pane: Some((0, 0)),
6755 terminal_command: Some("foo".to_owned()),
6756 plugin_url: None,
6757 is_selectable: true,
6758 index_in_pane_group: Default::default(), default_fg: None,
6760 default_bg: None,
6761 },
6762 PaneInfo {
6763 id: 1,
6764 is_plugin: true,
6765 is_focused: true,
6766 is_fullscreen: true,
6767 is_floating: false,
6768 is_suppressed: false,
6769 title: "pane 1".to_owned(),
6770 exited: false,
6771 exit_status: None,
6772 is_held: false,
6773 pane_x: 0,
6774 pane_content_x: 1,
6775 pane_y: 0,
6776 pane_content_y: 1,
6777 pane_rows: 5,
6778 pane_content_rows: 4,
6779 pane_columns: 22,
6780 pane_content_columns: 21,
6781 cursor_coordinates_in_pane: Some((0, 0)),
6782 terminal_command: None,
6783 plugin_url: Some("i_am_a_fake_plugin".to_owned()),
6784 is_selectable: true,
6785 index_in_pane_group: Default::default(), default_fg: None,
6787 default_bg: None,
6788 },
6789 ];
6790 let mut panes = HashMap::new();
6791 panes.insert(0, panes_list);
6792 let session_info = SessionInfo {
6793 name: "my session name".to_owned(),
6794 tabs: vec![
6795 TabInfo {
6796 position: 0,
6797 name: "tab 1".to_owned(),
6798 active: true,
6799 panes_to_hide: 1,
6800 is_fullscreen_active: true,
6801 is_sync_panes_active: false,
6802 are_floating_panes_visible: true,
6803 other_focused_clients: vec![2, 3],
6804 active_swap_layout_name: Some("BASE".to_owned()),
6805 is_swap_layout_dirty: true,
6806 viewport_rows: 10,
6807 viewport_columns: 10,
6808 display_area_rows: 10,
6809 display_area_columns: 10,
6810 selectable_tiled_panes_count: 10,
6811 selectable_floating_panes_count: 10,
6812 tab_id: 0,
6813 is_flashing_bell: false,
6814 has_bell_notification: false,
6815 },
6816 TabInfo {
6817 position: 1,
6818 name: "tab 2".to_owned(),
6819 active: true,
6820 panes_to_hide: 0,
6821 is_fullscreen_active: false,
6822 is_sync_panes_active: true,
6823 are_floating_panes_visible: true,
6824 other_focused_clients: vec![2, 3],
6825 active_swap_layout_name: None,
6826 is_swap_layout_dirty: false,
6827 viewport_rows: 10,
6828 viewport_columns: 10,
6829 display_area_rows: 10,
6830 display_area_columns: 10,
6831 selectable_tiled_panes_count: 10,
6832 selectable_floating_panes_count: 10,
6833 tab_id: 1,
6834 is_flashing_bell: false,
6835 has_bell_notification: false,
6836 },
6837 ],
6838 panes: PaneManifest { panes },
6839 connected_clients: 2,
6840 is_current_session: false,
6841 available_layouts: vec![
6842 LayoutInfo::File("layout1".to_owned(), LayoutMetadata::default()),
6843 LayoutInfo::BuiltIn("layout2".to_owned()),
6844 LayoutInfo::File("layout3".to_owned(), LayoutMetadata::default()),
6845 ],
6846 plugins: Default::default(),
6847 web_client_count: 2,
6848 web_clients_allowed: true,
6849 tab_history: Default::default(),
6850 pane_history: Default::default(),
6851 creation_time: Duration::from_secs(300),
6852 };
6853 let serialized = session_info.to_string();
6854 let deserealized = SessionInfo::from_string(&serialized, "not this session").unwrap();
6855 assert_eq!(session_info, deserealized);
6856 insta::assert_snapshot!(serialized);
6857}
6858
6859#[test]
6860fn keybinds_to_string() {
6861 let fake_config = r#"
6862 keybinds {
6863 normal {
6864 bind "Ctrl g" { SwitchToMode "Locked"; }
6865 }
6866 }"#;
6867 let document: KdlDocument = fake_config.parse().unwrap();
6868 let deserialized = Keybinds::from_kdl(
6869 document.get("keybinds").unwrap(),
6870 Default::default(),
6871 &Default::default(),
6872 )
6873 .unwrap();
6874 let clear_defaults = true;
6875 let serialized = Keybinds::to_kdl(&deserialized, clear_defaults);
6876 let deserialized_from_serialized = Keybinds::from_kdl(
6877 serialized
6878 .to_string()
6879 .parse::<KdlDocument>()
6880 .unwrap()
6881 .get("keybinds")
6882 .unwrap(),
6883 Default::default(),
6884 &Default::default(),
6885 )
6886 .unwrap();
6887 insta::assert_snapshot!(serialized.to_string());
6888 assert_eq!(
6889 deserialized, deserialized_from_serialized,
6890 "Deserialized serialized config equals original config"
6891 );
6892}
6893
6894#[test]
6895fn keybinds_to_string_without_clearing_defaults() {
6896 let fake_config = r#"
6897 keybinds {
6898 normal {
6899 bind "Ctrl g" { SwitchToMode "Locked"; }
6900 }
6901 }"#;
6902 let document: KdlDocument = fake_config.parse().unwrap();
6903 let deserialized = Keybinds::from_kdl(
6904 document.get("keybinds").unwrap(),
6905 Default::default(),
6906 &Default::default(),
6907 )
6908 .unwrap();
6909 let clear_defaults = false;
6910 let serialized = Keybinds::to_kdl(&deserialized, clear_defaults);
6911 let deserialized_from_serialized = Keybinds::from_kdl(
6912 serialized
6913 .to_string()
6914 .parse::<KdlDocument>()
6915 .unwrap()
6916 .get("keybinds")
6917 .unwrap(),
6918 Default::default(),
6919 &Default::default(),
6920 )
6921 .unwrap();
6922 insta::assert_snapshot!(serialized.to_string());
6923 assert_eq!(
6924 deserialized, deserialized_from_serialized,
6925 "Deserialized serialized config equals original config"
6926 );
6927}
6928
6929#[test]
6930fn keybinds_to_string_with_multiple_actions() {
6931 let fake_config = r#"
6932 keybinds {
6933 normal {
6934 bind "Ctrl n" { NewPane; SwitchToMode "Locked"; }
6935 }
6936 }"#;
6937 let document: KdlDocument = fake_config.parse().unwrap();
6938 let deserialized = Keybinds::from_kdl(
6939 document.get("keybinds").unwrap(),
6940 Default::default(),
6941 &Default::default(),
6942 )
6943 .unwrap();
6944 let clear_defaults = true;
6945 let serialized = Keybinds::to_kdl(&deserialized, clear_defaults);
6946 let deserialized_from_serialized = Keybinds::from_kdl(
6947 serialized
6948 .to_string()
6949 .parse::<KdlDocument>()
6950 .unwrap()
6951 .get("keybinds")
6952 .unwrap(),
6953 Default::default(),
6954 &Default::default(),
6955 )
6956 .unwrap();
6957 assert_eq!(
6958 deserialized, deserialized_from_serialized,
6959 "Deserialized serialized config equals original config"
6960 );
6961 insta::assert_snapshot!(serialized.to_string());
6962}
6963
6964#[test]
6965fn can_bind_theme_actions() {
6966 let fake_config = r#"
6970 keybinds {
6971 normal {
6972 bind "Ctrl t" { ToggleTheme; }
6973 bind "Ctrl d" { SetDarkTheme; }
6974 bind "Ctrl l" { SetLightTheme; }
6975 }
6976 }"#;
6977 let document: KdlDocument = fake_config.parse().unwrap();
6978 let deserialized = Keybinds::from_kdl(
6979 document.get("keybinds").unwrap(),
6980 Default::default(),
6981 &Default::default(),
6982 )
6983 .unwrap();
6984 let ctrl_t = KeyWithModifier::new(BareKey::Char('t')).with_ctrl_modifier();
6985 assert_eq!(
6986 deserialized.get_actions_for_key_in_mode(&InputMode::Normal, &ctrl_t),
6987 Some(&vec![Action::ToggleTheme])
6988 );
6989 let ctrl_d = KeyWithModifier::new(BareKey::Char('d')).with_ctrl_modifier();
6990 assert_eq!(
6991 deserialized.get_actions_for_key_in_mode(&InputMode::Normal, &ctrl_d),
6992 Some(&vec![Action::SetDarkTheme])
6993 );
6994 let ctrl_l = KeyWithModifier::new(BareKey::Char('l')).with_ctrl_modifier();
6995 assert_eq!(
6996 deserialized.get_actions_for_key_in_mode(&InputMode::Normal, &ctrl_l),
6997 Some(&vec![Action::SetLightTheme])
6998 );
6999 let serialized = Keybinds::to_kdl(&deserialized, true);
7001 let deserialized_from_serialized = Keybinds::from_kdl(
7002 serialized
7003 .to_string()
7004 .parse::<KdlDocument>()
7005 .unwrap()
7006 .get("keybinds")
7007 .unwrap(),
7008 Default::default(),
7009 &Default::default(),
7010 )
7011 .unwrap();
7012 assert_eq!(deserialized, deserialized_from_serialized);
7013}
7014
7015#[test]
7016fn keybinds_to_string_with_all_actions() {
7017 let fake_config = r#"
7018 keybinds {
7019 normal {
7020 bind "Ctrl a" { Quit; }
7021 bind "Ctrl b" { Write 102 111 111; }
7022 bind "Ctrl c" { WriteChars "hi there!"; }
7023 bind "Ctrl d" { SwitchToMode "Locked"; }
7024 bind "Ctrl e" { Resize "Increase"; }
7025 bind "Ctrl f" { FocusNextPane; }
7026 bind "Ctrl g" { FocusPreviousPane; }
7027 bind "Ctrl h" { SwitchFocus; }
7028 bind "Ctrl i" { MoveFocus "Right"; }
7029 bind "Ctrl j" { MoveFocusOrTab "Right"; }
7030 bind "Ctrl k" { MovePane "Right"; }
7031 bind "Ctrl l" { MovePaneBackwards; }
7032 bind "Ctrl m" { Resize "Decrease Down"; }
7033 bind "Ctrl n" { DumpScreen "/tmp/dumped"; }
7034 bind "Ctrl o" { DumpLayout "/tmp/dumped-layout"; }
7035 bind "Ctrl p" { EditScrollback; }
7036 bind "Ctrl q" { ScrollUp; }
7037 bind "Ctrl r" { ScrollDown; }
7038 bind "Ctrl s" { ScrollToBottom; }
7039 bind "Ctrl t" { ScrollToTop; }
7040 bind "Ctrl u" { PageScrollUp; }
7041 bind "Ctrl v" { PageScrollDown; }
7042 bind "Ctrl w" { HalfPageScrollUp; }
7043 bind "Ctrl x" { HalfPageScrollDown; }
7044 bind "Ctrl y" { ToggleFocusFullscreen; }
7045 bind "Ctrl z" { TogglePaneFrames; }
7046 bind "Alt a" { ToggleActiveSyncTab; }
7047 bind "Alt b" { NewPane "Right"; }
7048 bind "Alt c" { TogglePaneEmbedOrFloating; }
7049 bind "Alt d" { ToggleFloatingPanes; }
7050 bind "Alt e" { CloseFocus; }
7051 bind "Alt f" { PaneNameInput 0; }
7052 bind "Alt g" { UndoRenamePane; }
7053 bind "Alt h" { NewTab; }
7054 bind "Alt i" { GoToNextTab; }
7055 bind "Alt j" { GoToPreviousTab; }
7056 bind "Alt k" { CloseTab; }
7057 bind "Alt l" { GoToTab 1; }
7058 bind "Alt m" { ToggleTab; }
7059 bind "Alt n" { TabNameInput 0; }
7060 bind "Alt o" { UndoRenameTab; }
7061 bind "Alt p" { MoveTab "Right"; }
7062 bind "Alt q" {
7063 Run "ls" "-l" {
7064 hold_on_start true;
7065 hold_on_close false;
7066 cwd "/tmp";
7067 name "my cool pane";
7068 };
7069 }
7070 bind "Alt r" {
7071 Run "ls" "-l" {
7072 hold_on_start true;
7073 hold_on_close false;
7074 cwd "/tmp";
7075 name "my cool pane";
7076 floating true;
7077 };
7078 }
7079 bind "Alt s" {
7080 Run "ls" "-l" {
7081 hold_on_start true;
7082 hold_on_close false;
7083 cwd "/tmp";
7084 name "my cool pane";
7085 in_place true;
7086 };
7087 }
7088 bind "Alt t" { Detach; }
7089 bind "Alt u" {
7090 LaunchOrFocusPlugin "zellij:session-manager"{
7091 floating true;
7092 move_to_focused_tab true;
7093 skip_plugin_cache true;
7094 config_key_1 "config_value_1";
7095 config_key_2 "config_value_2";
7096 };
7097 }
7098 bind "Alt v" {
7099 LaunchOrFocusPlugin "zellij:session-manager"{
7100 in_place true;
7101 move_to_focused_tab true;
7102 skip_plugin_cache true;
7103 config_key_1 "config_value_1";
7104 config_key_2 "config_value_2";
7105 };
7106 }
7107 bind "Alt w" {
7108 LaunchPlugin "zellij:session-manager" {
7109 floating true;
7110 skip_plugin_cache true;
7111 config_key_1 "config_value_1";
7112 config_key_2 "config_value_2";
7113 };
7114 }
7115 bind "Alt x" {
7116 LaunchPlugin "zellij:session-manager"{
7117 in_place true;
7118 skip_plugin_cache true;
7119 config_key_1 "config_value_1";
7120 config_key_2 "config_value_2";
7121 };
7122 }
7123 bind "Alt y" { Copy; }
7124 bind "Alt z" { SearchInput 0; }
7125 bind "Ctrl Alt a" { Search "Up"; }
7126 bind "Ctrl Alt b" { SearchToggleOption "CaseSensitivity"; }
7127 bind "Ctrl Alt c" { ToggleMouseMode; }
7128 bind "Ctrl Alt d" { PreviousSwapLayout; }
7129 bind "Ctrl Alt e" { NextSwapLayout; }
7130 bind "Ctrl Alt g" { BreakPane; }
7131 bind "Ctrl Alt h" { BreakPaneRight; }
7132 bind "Ctrl Alt i" { BreakPaneLeft; }
7133 bind "Ctrl Alt i" { BreakPaneLeft; }
7134 bind "Ctrl Alt j" {
7135 MessagePlugin "zellij:session-manager"{
7136 name "message_name";
7137 payload "message_payload";
7138 cwd "/tmp";
7139 launch_new true;
7140 skip_cache true;
7141 floating true;
7142 title "plugin_title";
7143 config_key_1 "config_value_1";
7144 config_key_2 "config_value_2";
7145 };
7146 }
7147 bind "Ctrl Alt k" { FocusLastPane; }
7148 }
7149 }"#;
7150 let document: KdlDocument = fake_config.parse().unwrap();
7151 let deserialized = Keybinds::from_kdl(
7152 document.get("keybinds").unwrap(),
7153 Default::default(),
7154 &Default::default(),
7155 )
7156 .unwrap();
7157 let clear_defaults = true;
7158 let serialized = Keybinds::to_kdl(&deserialized, clear_defaults);
7159 let deserialized_from_serialized = Keybinds::from_kdl(
7160 serialized
7161 .to_string()
7162 .parse::<KdlDocument>()
7163 .unwrap()
7164 .get("keybinds")
7165 .unwrap(),
7166 Default::default(),
7167 &Default::default(),
7168 )
7169 .unwrap();
7170 assert_eq!(
7183 deserialized, deserialized_from_serialized,
7184 "Deserialized serialized config equals original config"
7185 );
7186 insta::assert_snapshot!(serialized.to_string());
7187}
7188
7189#[test]
7190fn keybinds_to_string_with_shared_modes() {
7191 let fake_config = r#"
7192 keybinds {
7193 normal {
7194 bind "Ctrl n" { NewPane; SwitchToMode "Locked"; }
7195 }
7196 locked {
7197 bind "Ctrl n" { NewPane; SwitchToMode "Locked"; }
7198 }
7199 shared_except "locked" "pane" {
7200 bind "Ctrl f" { TogglePaneEmbedOrFloating; }
7201 }
7202 shared_among "locked" "pane" {
7203 bind "Ctrl p" { WriteChars "foo"; }
7204 }
7205 }"#;
7206 let document: KdlDocument = fake_config.parse().unwrap();
7207 let deserialized = Keybinds::from_kdl(
7208 document.get("keybinds").unwrap(),
7209 Default::default(),
7210 &Default::default(),
7211 )
7212 .unwrap();
7213 let clear_defaults = true;
7214 let serialized = Keybinds::to_kdl(&deserialized, clear_defaults);
7215 let deserialized_from_serialized = Keybinds::from_kdl(
7216 serialized
7217 .to_string()
7218 .parse::<KdlDocument>()
7219 .unwrap()
7220 .get("keybinds")
7221 .unwrap(),
7222 Default::default(),
7223 &Default::default(),
7224 )
7225 .unwrap();
7226 assert_eq!(
7227 deserialized, deserialized_from_serialized,
7228 "Deserialized serialized config equals original config"
7229 );
7230 insta::assert_snapshot!(serialized.to_string());
7231}
7232
7233#[test]
7234fn keybinds_to_string_with_multiple_multiline_actions() {
7235 let fake_config = r#"
7236 keybinds {
7237 shared {
7238 bind "Ctrl n" {
7239 NewPane
7240 SwitchToMode "Locked"
7241 MessagePlugin "zellij:session-manager"{
7242 name "message_name";
7243 payload "message_payload";
7244 cwd "/tmp";
7245 launch_new true;
7246 skip_cache true;
7247 floating true;
7248 title "plugin_title";
7249 config_key_1 "config_value_1";
7250 config_key_2 "config_value_2";
7251 };
7252 }
7253 }
7254 }"#;
7255 let document: KdlDocument = fake_config.parse().unwrap();
7256 let deserialized = Keybinds::from_kdl(
7257 document.get("keybinds").unwrap(),
7258 Default::default(),
7259 &Default::default(),
7260 )
7261 .unwrap();
7262 let clear_defaults = true;
7263 let serialized = Keybinds::to_kdl(&deserialized, clear_defaults);
7264 let deserialized_from_serialized = Keybinds::from_kdl(
7265 serialized
7266 .to_string()
7267 .parse::<KdlDocument>()
7268 .unwrap()
7269 .get("keybinds")
7270 .unwrap(),
7271 Default::default(),
7272 &Default::default(),
7273 )
7274 .unwrap();
7275 assert_eq!(
7276 deserialized, deserialized_from_serialized,
7277 "Deserialized serialized config equals original config"
7278 );
7279 insta::assert_snapshot!(serialized.to_string());
7280}
7281
7282#[test]
7283fn themes_to_string() {
7284 let fake_config = r#"
7285 themes {
7286 dracula {
7287 fg 248 248 242
7288 bg 40 42 54
7289 black 0 0 0
7290 red 255 85 85
7291 green 80 250 123
7292 yellow 241 250 140
7293 blue 98 114 164
7294 magenta 255 121 198
7295 cyan 139 233 253
7296 white 255 255 255
7297 orange 255 184 108
7298 }
7299 }"#;
7300 let document: KdlDocument = fake_config.parse().unwrap();
7301 let sourced_from_external_file = false;
7302 let deserialized =
7303 Themes::from_kdl(document.get("themes").unwrap(), sourced_from_external_file).unwrap();
7304 let serialized = Themes::to_kdl(&deserialized).unwrap();
7305 let deserialized_from_serialized = Themes::from_kdl(
7306 serialized
7307 .to_string()
7308 .parse::<KdlDocument>()
7309 .unwrap()
7310 .get("themes")
7311 .unwrap(),
7312 sourced_from_external_file,
7313 )
7314 .unwrap();
7315 assert_eq!(
7316 deserialized, deserialized_from_serialized,
7317 "Deserialized serialized config equals original config",
7318 );
7319 insta::assert_snapshot!(serialized.to_string());
7320}
7321
7322#[test]
7323fn themes_to_string_with_hex_definitions() {
7324 let fake_config = r##"
7325 themes {
7326 nord {
7327 fg "#D8DEE9"
7328 bg "#2E3440"
7329 black "#3B4252"
7330 red "#BF616A"
7331 green "#A3BE8C"
7332 yellow "#EBCB8B"
7333 blue "#81A1C1"
7334 magenta "#B48EAD"
7335 cyan "#88C0D0"
7336 white "#E5E9F0"
7337 orange "#D08770"
7338 }
7339 }"##;
7340 let document: KdlDocument = fake_config.parse().unwrap();
7341 let sourced_from_external_file = false;
7342 let deserialized =
7343 Themes::from_kdl(document.get("themes").unwrap(), sourced_from_external_file).unwrap();
7344 let serialized = Themes::to_kdl(&deserialized).unwrap();
7345 let deserialized_from_serialized = Themes::from_kdl(
7346 serialized
7347 .to_string()
7348 .parse::<KdlDocument>()
7349 .unwrap()
7350 .get("themes")
7351 .unwrap(),
7352 sourced_from_external_file,
7353 )
7354 .unwrap();
7355 assert_eq!(
7356 deserialized, deserialized_from_serialized,
7357 "Deserialized serialized config equals original config"
7358 );
7359 insta::assert_snapshot!(serialized.to_string());
7360}
7361
7362#[test]
7363fn themes_to_string_with_eight_bit_definitions() {
7364 let fake_config = r##"
7365 themes {
7366 default {
7367 fg 1
7368 bg 10
7369 black 20
7370 red 30
7371 green 40
7372 yellow 50
7373 blue 60
7374 magenta 70
7375 cyan 80
7376 white 90
7377 orange 254
7378 }
7379 }"##;
7380 let document: KdlDocument = fake_config.parse().unwrap();
7381 let sourced_from_external_file = false;
7382 let deserialized =
7383 Themes::from_kdl(document.get("themes").unwrap(), sourced_from_external_file).unwrap();
7384 let serialized = Themes::to_kdl(&deserialized).unwrap();
7385 let deserialized_from_serialized = Themes::from_kdl(
7386 serialized
7387 .to_string()
7388 .parse::<KdlDocument>()
7389 .unwrap()
7390 .get("themes")
7391 .unwrap(),
7392 sourced_from_external_file,
7393 )
7394 .unwrap();
7395 assert_eq!(
7396 deserialized, deserialized_from_serialized,
7397 "Deserialized serialized config equals original config"
7398 );
7399 insta::assert_snapshot!(serialized.to_string());
7400}
7401
7402#[test]
7403fn themes_to_string_with_combined_definitions() {
7404 let fake_config = r##"
7405 themes {
7406 default {
7407 fg 1
7408 bg 10
7409 black 20
7410 red 30
7411 green 40
7412 yellow 50
7413 blue 60
7414 magenta 70
7415 cyan 80
7416 white 255 255 255
7417 orange "#D08770"
7418 }
7419 }"##;
7420 let document: KdlDocument = fake_config.parse().unwrap();
7421 let sourced_from_external_file = false;
7422 let deserialized =
7423 Themes::from_kdl(document.get("themes").unwrap(), sourced_from_external_file).unwrap();
7424 let serialized = Themes::to_kdl(&deserialized).unwrap();
7425 let deserialized_from_serialized = Themes::from_kdl(
7426 serialized
7427 .to_string()
7428 .parse::<KdlDocument>()
7429 .unwrap()
7430 .get("themes")
7431 .unwrap(),
7432 sourced_from_external_file,
7433 )
7434 .unwrap();
7435 assert_eq!(
7436 deserialized, deserialized_from_serialized,
7437 "Deserialized serialized config equals original config"
7438 );
7439 insta::assert_snapshot!(serialized.to_string());
7440}
7441
7442#[test]
7443fn themes_to_string_with_multiple_theme_definitions() {
7444 let fake_config = r##"
7445 themes {
7446 nord {
7447 fg "#D8DEE9"
7448 bg "#2E3440"
7449 black "#3B4252"
7450 red "#BF616A"
7451 green "#A3BE8C"
7452 yellow "#EBCB8B"
7453 blue "#81A1C1"
7454 magenta "#B48EAD"
7455 cyan "#88C0D0"
7456 white "#E5E9F0"
7457 orange "#D08770"
7458 }
7459 dracula {
7460 fg 248 248 242
7461 bg 40 42 54
7462 black 0 0 0
7463 red 255 85 85
7464 green 80 250 123
7465 yellow 241 250 140
7466 blue 98 114 164
7467 magenta 255 121 198
7468 cyan 139 233 253
7469 white 255 255 255
7470 orange 255 184 108
7471 }
7472 }"##;
7473 let document: KdlDocument = fake_config.parse().unwrap();
7474 let sourced_from_external_file = false;
7475 let deserialized =
7476 Themes::from_kdl(document.get("themes").unwrap(), sourced_from_external_file).unwrap();
7477 let serialized = Themes::to_kdl(&deserialized).unwrap();
7478 let deserialized_from_serialized = Themes::from_kdl(
7479 serialized
7480 .to_string()
7481 .parse::<KdlDocument>()
7482 .unwrap()
7483 .get("themes")
7484 .unwrap(),
7485 sourced_from_external_file,
7486 )
7487 .unwrap();
7488 assert_eq!(
7489 deserialized, deserialized_from_serialized,
7490 "Deserialized serialized config equals original config"
7491 );
7492 insta::assert_snapshot!(serialized.to_string());
7493}
7494
7495#[test]
7496fn plugins_to_string() {
7497 let fake_config = r##"
7498 plugins {
7499 tab-bar location="zellij:tab-bar"
7500 status-bar location="zellij:status-bar"
7501 strider location="zellij:strider"
7502 compact-bar location="zellij:compact-bar"
7503 session-manager location="zellij:session-manager"
7504 welcome-screen location="zellij:session-manager" {
7505 welcome_screen true
7506 }
7507 filepicker location="zellij:strider" {
7508 cwd "/"
7509 }
7510 }"##;
7511 let document: KdlDocument = fake_config.parse().unwrap();
7512 let deserialized = PluginAliases::from_kdl(document.get("plugins").unwrap()).unwrap();
7513 let serialized = PluginAliases::to_kdl(&deserialized, true);
7514 let deserialized_from_serialized = PluginAliases::from_kdl(
7515 serialized
7516 .to_string()
7517 .parse::<KdlDocument>()
7518 .unwrap()
7519 .get("plugins")
7520 .unwrap(),
7521 )
7522 .unwrap();
7523 assert_eq!(
7524 deserialized, deserialized_from_serialized,
7525 "Deserialized serialized config equals original config"
7526 );
7527 insta::assert_snapshot!(serialized.to_string());
7528}
7529
7530#[test]
7531fn plugins_to_string_with_file_and_web() {
7532 let fake_config = r##"
7533 plugins {
7534 tab-bar location="https://foo.com/plugin.wasm"
7535 filepicker location="file:/path/to/my/plugin.wasm" {
7536 cwd "/"
7537 }
7538 }"##;
7539 let document: KdlDocument = fake_config.parse().unwrap();
7540 let deserialized = PluginAliases::from_kdl(document.get("plugins").unwrap()).unwrap();
7541 let serialized = PluginAliases::to_kdl(&deserialized, true);
7542 let deserialized_from_serialized = PluginAliases::from_kdl(
7543 serialized
7544 .to_string()
7545 .parse::<KdlDocument>()
7546 .unwrap()
7547 .get("plugins")
7548 .unwrap(),
7549 )
7550 .unwrap();
7551 assert_eq!(
7552 deserialized, deserialized_from_serialized,
7553 "Deserialized serialized config equals original config"
7554 );
7555 insta::assert_snapshot!(serialized.to_string());
7556}
7557
7558#[test]
7559fn ui_config_to_string() {
7560 let fake_config = r##"
7561 ui {
7562 pane_frames {
7563 rounded_corners true
7564 hide_session_name true
7565 }
7566 }"##;
7567 let document: KdlDocument = fake_config.parse().unwrap();
7568 let deserialized = UiConfig::from_kdl(document.get("ui").unwrap()).unwrap();
7569 let serialized = UiConfig::to_kdl(&deserialized).unwrap();
7570 let deserialized_from_serialized = UiConfig::from_kdl(
7571 serialized
7572 .to_string()
7573 .parse::<KdlDocument>()
7574 .unwrap()
7575 .get("ui")
7576 .unwrap(),
7577 )
7578 .unwrap();
7579 assert_eq!(
7580 deserialized, deserialized_from_serialized,
7581 "Deserialized serialized config equals original config"
7582 );
7583 insta::assert_snapshot!(serialized.to_string());
7584}
7585
7586#[test]
7587fn ui_config_to_string_with_no_ui_config() {
7588 let fake_config = r##"
7589 ui {
7590 pane_frames {
7591 }
7592 }"##;
7593 let document: KdlDocument = fake_config.parse().unwrap();
7594 let deserialized = UiConfig::from_kdl(document.get("ui").unwrap()).unwrap();
7595 assert_eq!(UiConfig::to_kdl(&deserialized), None);
7596}
7597
7598#[test]
7599fn env_vars_to_string() {
7600 let fake_config = r##"
7601 env {
7602 foo "bar"
7603 bar "foo"
7604 thing 1
7605 baz "true"
7606 }"##;
7607 let document: KdlDocument = fake_config.parse().unwrap();
7608 let deserialized = EnvironmentVariables::from_kdl(document.get("env").unwrap()).unwrap();
7609 let serialized = EnvironmentVariables::to_kdl(&deserialized).unwrap();
7610 let deserialized_from_serialized = EnvironmentVariables::from_kdl(
7611 serialized
7612 .to_string()
7613 .parse::<KdlDocument>()
7614 .unwrap()
7615 .get("env")
7616 .unwrap(),
7617 )
7618 .unwrap();
7619 assert_eq!(
7620 deserialized, deserialized_from_serialized,
7621 "Deserialized serialized config equals original config"
7622 );
7623 insta::assert_snapshot!(serialized.to_string());
7624}
7625
7626#[test]
7627fn env_vars_to_string_with_no_env_vars() {
7628 let fake_config = r##"
7629 env {
7630 }"##;
7631 let document: KdlDocument = fake_config.parse().unwrap();
7632 let deserialized = EnvironmentVariables::from_kdl(document.get("env").unwrap()).unwrap();
7633 assert_eq!(EnvironmentVariables::to_kdl(&deserialized), None);
7634}
7635
7636#[test]
7637fn selection_options_from_kdl() {
7638 let fake_config = r##"
7639 osc133_command_selection false
7640 word_separators "[]{}<>():,"
7641 "##;
7642 let document: KdlDocument = fake_config.parse().unwrap();
7643 let deserialized = Options::from_kdl(&document).unwrap();
7644 assert_eq!(deserialized.osc133_command_selection, Some(false));
7645 assert_eq!(
7646 deserialized.word_separators,
7647 Some("[]{}<>():,".to_owned()),
7648 "word separators are parsed verbatim"
7649 );
7650}
7651
7652#[test]
7653fn selection_options_default_to_none_when_unspecified() {
7654 let document: KdlDocument = "".parse().unwrap();
7655 let deserialized = Options::from_kdl(&document).unwrap();
7656 assert_eq!(deserialized.osc133_command_selection, None);
7657 assert_eq!(deserialized.word_separators, None);
7658}
7659
7660#[test]
7661fn config_options_to_string() {
7662 let fake_config = r##"
7663 simplified_ui true
7664 theme "dracula"
7665 default_mode "locked"
7666 default_shell "fish"
7667 default_cwd "/tmp/foo"
7668 default_layout "compact"
7669 layout_dir "/tmp/layouts"
7670 theme_dir "/tmp/themes"
7671 mouse_mode false
7672 pane_frames false
7673 mirror_session true
7674 on_force_close "quit"
7675 scroll_buffer_size 100
7676 copy_command "pbcopy"
7677 copy_clipboard "system"
7678 copy_on_select false
7679 scrollback_editor "vim"
7680 session_name "my_cool_session"
7681 attach_to_session false
7682 auto_layout false
7683 session_serialization true
7684 serialize_pane_viewport false
7685 scrollback_lines_to_serialize 1000
7686 styled_underlines false
7687 serialization_interval 1
7688 disable_session_metadata true
7689 support_kitty_keyboard_protocol false
7690 web_server true
7691 web_sharing "disabled"
7692 "##;
7693 let document: KdlDocument = fake_config.parse().unwrap();
7694 let deserialized = Options::from_kdl(&document).unwrap();
7695 let mut serialized = Options::to_kdl(&deserialized, false);
7696 let mut fake_document = KdlDocument::new();
7697 fake_document.nodes_mut().append(&mut serialized);
7698 let deserialized_from_serialized =
7699 Options::from_kdl(&fake_document.to_string().parse::<KdlDocument>().unwrap()).unwrap();
7700 assert_eq!(
7701 deserialized, deserialized_from_serialized,
7702 "Deserialized serialized config equals original config"
7703 );
7704 insta::assert_snapshot!(fake_document.to_string());
7705}
7706
7707#[test]
7708fn config_options_to_string_with_comments() {
7709 let fake_config = r##"
7710 simplified_ui true
7711 theme "dracula"
7712 default_mode "locked"
7713 default_shell "fish"
7714 default_cwd "/tmp/foo"
7715 default_layout "compact"
7716 layout_dir "/tmp/layouts"
7717 theme_dir "/tmp/themes"
7718 mouse_mode false
7719 pane_frames false
7720 mirror_session true
7721 on_force_close "quit"
7722 scroll_buffer_size 100
7723 copy_command "pbcopy"
7724 copy_clipboard "system"
7725 copy_on_select false
7726 scrollback_editor "vim"
7727 session_name "my_cool_session"
7728 attach_to_session false
7729 auto_layout false
7730 session_serialization true
7731 serialize_pane_viewport false
7732 scrollback_lines_to_serialize 1000
7733 styled_underlines false
7734 serialization_interval 1
7735 disable_session_metadata true
7736 support_kitty_keyboard_protocol false
7737 web_server true
7738 web_sharing "disabled"
7739 "##;
7740 let document: KdlDocument = fake_config.parse().unwrap();
7741 let deserialized = Options::from_kdl(&document).unwrap();
7742 let mut serialized = Options::to_kdl(&deserialized, true);
7743 let mut fake_document = KdlDocument::new();
7744 fake_document.nodes_mut().append(&mut serialized);
7745 let deserialized_from_serialized =
7746 Options::from_kdl(&fake_document.to_string().parse::<KdlDocument>().unwrap()).unwrap();
7747 assert_eq!(
7748 deserialized, deserialized_from_serialized,
7749 "Deserialized serialized config equals original config"
7750 );
7751 insta::assert_snapshot!(fake_document.to_string());
7752}
7753
7754#[test]
7755fn config_options_to_string_without_options() {
7756 let fake_config = r##"
7757 "##;
7758 let document: KdlDocument = fake_config.parse().unwrap();
7759 let deserialized = Options::from_kdl(&document).unwrap();
7760 let mut serialized = Options::to_kdl(&deserialized, false);
7761 let mut fake_document = KdlDocument::new();
7762 fake_document.nodes_mut().append(&mut serialized);
7763 let deserialized_from_serialized =
7764 Options::from_kdl(&fake_document.to_string().parse::<KdlDocument>().unwrap()).unwrap();
7765 assert_eq!(
7766 deserialized, deserialized_from_serialized,
7767 "Deserialized serialized config equals original config"
7768 );
7769 insta::assert_snapshot!(fake_document.to_string());
7770}
7771
7772#[test]
7773fn nested_session_handling_kdl_round_trip_for_every_variant() {
7774 use crate::input::options::NestedSessionHandling;
7775 let cases = [
7776 ("ask", NestedSessionHandling::Ask),
7777 ("fullscreen", NestedSessionHandling::Fullscreen),
7778 ("descend", NestedSessionHandling::Descend),
7779 ("never", NestedSessionHandling::Never),
7780 ];
7781 for (value, expected) in cases {
7782 let fake_config = format!(
7783 r##"
7784 nested_session_handling "{value}"
7785 "##
7786 );
7787 let document: KdlDocument = fake_config.parse().unwrap();
7788 let parsed = Options::from_kdl(&document).unwrap();
7789 assert_eq!(
7790 parsed.nested_session_handling,
7791 Some(expected),
7792 "case: {value}"
7793 );
7794
7795 let mut serialized = Options::to_kdl(&parsed, false);
7796 let mut fake_document = KdlDocument::new();
7797 fake_document.nodes_mut().append(&mut serialized);
7798 let reparsed =
7799 Options::from_kdl(&fake_document.to_string().parse::<KdlDocument>().unwrap()).unwrap();
7800 assert_eq!(parsed, reparsed, "round-trip mismatch for {value}");
7801 }
7802}
7803
7804#[test]
7805fn host_notification_protocol_kdl_round_trip_for_every_variant() {
7806 use crate::input::options::HostNotificationProtocol;
7807 let cases = [
7808 ("auto", HostNotificationProtocol::Auto),
7809 ("osc9", HostNotificationProtocol::Osc9),
7810 ("osc99", HostNotificationProtocol::Osc99),
7811 ("bell", HostNotificationProtocol::Bell),
7812 ("off", HostNotificationProtocol::Off),
7813 ];
7814 for (value, expected) in cases {
7815 let fake_config = format!(
7816 r##"
7817 host_notification_protocol "{value}"
7818 "##
7819 );
7820 let document: KdlDocument = fake_config.parse().unwrap();
7821 let parsed = Options::from_kdl(&document).unwrap();
7822 assert_eq!(
7823 parsed.host_notification_protocol,
7824 Some(expected),
7825 "case: {value}"
7826 );
7827
7828 let mut serialized = Options::to_kdl(&parsed, false);
7829 let mut fake_document = KdlDocument::new();
7830 fake_document.nodes_mut().append(&mut serialized);
7831 let reparsed =
7832 Options::from_kdl(&fake_document.to_string().parse::<KdlDocument>().unwrap()).unwrap();
7833 assert_eq!(parsed, reparsed, "round-trip mismatch for {value}");
7834 }
7835}
7836
7837#[test]
7838fn an_unknown_host_notification_protocol_is_a_config_error() {
7839 let fake_config = r##"
7840 host_notification_protocol "carrier-pigeon"
7841 "##;
7842 let document: KdlDocument = fake_config.parse().unwrap();
7843 assert!(Options::from_kdl(&document).is_err());
7844}
7845
7846#[test]
7847fn an_unset_host_notification_protocol_parses_as_none() {
7848 let document: KdlDocument = r##"
7849 simplified_ui true
7850 "##
7851 .parse()
7852 .unwrap();
7853 let parsed = Options::from_kdl(&document).unwrap();
7854 assert_eq!(parsed.host_notification_protocol, None);
7855}
7856
7857#[test]
7858fn config_options_to_string_with_some_options() {
7859 let fake_config = r##"
7860 default_layout "compact"
7861 "##;
7862 let document: KdlDocument = fake_config.parse().unwrap();
7863 let deserialized = Options::from_kdl(&document).unwrap();
7864 let mut serialized = Options::to_kdl(&deserialized, false);
7865 let mut fake_document = KdlDocument::new();
7866 fake_document.nodes_mut().append(&mut serialized);
7867 let deserialized_from_serialized =
7868 Options::from_kdl(&fake_document.to_string().parse::<KdlDocument>().unwrap()).unwrap();
7869 assert_eq!(
7870 deserialized, deserialized_from_serialized,
7871 "Deserialized serialized config equals original config"
7872 );
7873 insta::assert_snapshot!(fake_document.to_string());
7874}
7875
7876#[test]
7877fn bare_config_from_default_assets_to_string() {
7878 let fake_config = Config::from_default_assets().unwrap();
7879 let fake_config_stringified = fake_config.to_string(false);
7880 let deserialized_from_serialized = Config::from_kdl(&fake_config_stringified, None).unwrap();
7881 assert_eq!(
7882 fake_config, deserialized_from_serialized,
7883 "Deserialized serialized config equals original config"
7884 );
7885 insta::assert_snapshot!(fake_config_stringified);
7886}
7887
7888#[test]
7889fn bare_config_from_default_assets_to_string_with_comments() {
7890 let fake_config = Config::from_default_assets().unwrap();
7891 let fake_config_stringified = fake_config.to_string(true);
7892 let deserialized_from_serialized = Config::from_kdl(&fake_config_stringified, None).unwrap();
7893 assert_eq!(
7894 fake_config, deserialized_from_serialized,
7895 "Deserialized serialized config equals original config"
7896 );
7897 insta::assert_snapshot!(fake_config_stringified);
7898}
7899
7900#[test]
7901fn osc8_hyperlinks_config_parsing() {
7902 let config_with_osc8_disabled = r#"
7903 osc8_hyperlinks false
7904 "#;
7905 let config = Config::from_kdl(config_with_osc8_disabled, None).unwrap();
7906 assert_eq!(config.options.osc8_hyperlinks, Some(false));
7907
7908 let config_with_osc8_enabled = r#"
7909 osc8_hyperlinks true
7910 "#;
7911 let config = Config::from_kdl(config_with_osc8_enabled, None).unwrap();
7912 assert_eq!(config.options.osc8_hyperlinks, Some(true));
7913
7914 let serialized = config.to_string(false);
7916 let deserialized = Config::from_kdl(&serialized, None).unwrap();
7917 assert_eq!(deserialized.options.osc8_hyperlinks, Some(true));
7918}