Skip to main content

zellij_utils/kdl/
mod.rs

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