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