zellij_utils/kdl/
mod.rs

1mod kdl_layout_parser;
2use crate::data::{
3    BareKey, Direction, FloatingPaneCoordinates, InputMode, KeyWithModifier, LayoutInfo, Palette,
4    PaletteColor, PaneInfo, PaneManifest, PermissionType, Resize, SessionInfo, TabInfo,
5};
6use crate::envs::EnvironmentVariables;
7use crate::home::{find_default_config_dir, get_layout_dir};
8use crate::input::config::{Config, ConfigError, KdlError};
9use crate::input::keybinds::Keybinds;
10use crate::input::layout::{
11    Layout, PluginUserConfiguration, RunPlugin, RunPluginOrAlias, SplitSize,
12};
13use crate::input::options::{Clipboard, OnForceClose, Options};
14use crate::input::permission::{GrantedPermission, PermissionCache};
15use crate::input::plugins::PluginAliases;
16use crate::input::theme::{FrameConfig, Theme, Themes, UiConfig};
17use kdl_layout_parser::KdlLayoutParser;
18use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
19use strum::IntoEnumIterator;
20use uuid::Uuid;
21
22use miette::NamedSource;
23
24use kdl::{KdlDocument, KdlEntry, KdlNode, KdlValue};
25
26use std::path::PathBuf;
27use std::str::FromStr;
28
29use crate::input::actions::{Action, SearchDirection, SearchOption};
30use crate::input::command::RunCommandAction;
31
32#[macro_export]
33macro_rules! parse_kdl_action_arguments {
34    ( $action_name:expr, $action_arguments:expr, $action_node:expr ) => {{
35        if !$action_arguments.is_empty() {
36            Err(ConfigError::new_kdl_error(
37                format!("Action '{}' must have arguments", $action_name),
38                $action_node.span().offset(),
39                $action_node.span().len(),
40            ))
41        } else {
42            match $action_name {
43                "Quit" => Ok(Action::Quit),
44                "FocusNextPane" => Ok(Action::FocusNextPane),
45                "FocusPreviousPane" => Ok(Action::FocusPreviousPane),
46                "SwitchFocus" => Ok(Action::SwitchFocus),
47                "EditScrollback" => Ok(Action::EditScrollback),
48                "ScrollUp" => Ok(Action::ScrollUp),
49                "ScrollDown" => Ok(Action::ScrollDown),
50                "ScrollToBottom" => Ok(Action::ScrollToBottom),
51                "ScrollToTop" => Ok(Action::ScrollToTop),
52                "PageScrollUp" => Ok(Action::PageScrollUp),
53                "PageScrollDown" => Ok(Action::PageScrollDown),
54                "HalfPageScrollUp" => Ok(Action::HalfPageScrollUp),
55                "HalfPageScrollDown" => Ok(Action::HalfPageScrollDown),
56                "ToggleFocusFullscreen" => Ok(Action::ToggleFocusFullscreen),
57                "TogglePaneFrames" => Ok(Action::TogglePaneFrames),
58                "ToggleActiveSyncTab" => Ok(Action::ToggleActiveSyncTab),
59                "TogglePaneEmbedOrFloating" => Ok(Action::TogglePaneEmbedOrFloating),
60                "ToggleFloatingPanes" => Ok(Action::ToggleFloatingPanes),
61                "CloseFocus" => Ok(Action::CloseFocus),
62                "UndoRenamePane" => Ok(Action::UndoRenamePane),
63                "NoOp" => Ok(Action::NoOp),
64                "GoToNextTab" => Ok(Action::GoToNextTab),
65                "GoToPreviousTab" => Ok(Action::GoToPreviousTab),
66                "CloseTab" => Ok(Action::CloseTab),
67                "ToggleTab" => Ok(Action::ToggleTab),
68                "UndoRenameTab" => Ok(Action::UndoRenameTab),
69                "Detach" => Ok(Action::Detach),
70                "Copy" => Ok(Action::Copy),
71                "Confirm" => Ok(Action::Confirm),
72                "Deny" => Ok(Action::Deny),
73                "ToggleMouseMode" => Ok(Action::ToggleMouseMode),
74                "PreviousSwapLayout" => Ok(Action::PreviousSwapLayout),
75                "NextSwapLayout" => Ok(Action::NextSwapLayout),
76                "Clear" => Ok(Action::ClearScreen),
77                _ => Err(ConfigError::new_kdl_error(
78                    format!("Unsupported action: {:?}", $action_name),
79                    $action_node.span().offset(),
80                    $action_node.span().len(),
81                )),
82            }
83        }
84    }};
85}
86
87#[macro_export]
88macro_rules! parse_kdl_action_u8_arguments {
89    ( $action_name:expr, $action_arguments:expr, $action_node:expr ) => {{
90        let mut bytes = vec![];
91        for kdl_entry in $action_arguments.iter() {
92            match kdl_entry.value().as_i64() {
93                Some(int_value) => bytes.push(int_value as u8),
94                None => {
95                    return Err(ConfigError::new_kdl_error(
96                        format!("Arguments for '{}' must be integers", $action_name),
97                        kdl_entry.span().offset(),
98                        kdl_entry.span().len(),
99                    ));
100                },
101            }
102        }
103        Action::new_from_bytes($action_name, bytes, $action_node)
104    }};
105}
106
107#[macro_export]
108macro_rules! kdl_parsing_error {
109    ( $message:expr, $entry:expr ) => {
110        ConfigError::new_kdl_error($message, $entry.span().offset(), $entry.span().len())
111    };
112}
113
114#[macro_export]
115macro_rules! kdl_entries_as_i64 {
116    ( $node:expr ) => {
117        $node
118            .entries()
119            .iter()
120            .map(|kdl_node| kdl_node.value().as_i64())
121    };
122}
123
124#[macro_export]
125macro_rules! kdl_first_entry_as_string {
126    ( $node:expr ) => {
127        $node
128            .entries()
129            .iter()
130            .next()
131            .and_then(|s| s.value().as_string())
132    };
133}
134
135#[macro_export]
136macro_rules! kdl_first_entry_as_i64 {
137    ( $node:expr ) => {
138        $node
139            .entries()
140            .iter()
141            .next()
142            .and_then(|i| i.value().as_i64())
143    };
144}
145
146#[macro_export]
147macro_rules! kdl_first_entry_as_bool {
148    ( $node:expr ) => {
149        $node
150            .entries()
151            .iter()
152            .next()
153            .and_then(|i| i.value().as_bool())
154    };
155}
156
157#[macro_export]
158macro_rules! entry_count {
159    ( $node:expr ) => {{
160        $node.entries().iter().len()
161    }};
162}
163
164#[macro_export]
165macro_rules! parse_kdl_action_char_or_string_arguments {
166    ( $action_name:expr, $action_arguments:expr, $action_node:expr ) => {{
167        let mut chars_to_write = String::new();
168        for kdl_entry in $action_arguments.iter() {
169            match kdl_entry.value().as_string() {
170                Some(string_value) => chars_to_write.push_str(string_value),
171                None => {
172                    return Err(ConfigError::new_kdl_error(
173                        format!("All entries for action '{}' must be strings", $action_name),
174                        kdl_entry.span().offset(),
175                        kdl_entry.span().len(),
176                    ))
177                },
178            }
179        }
180        Action::new_from_string($action_name, chars_to_write, $action_node)
181    }};
182}
183
184#[macro_export]
185macro_rules! kdl_arg_is_truthy {
186    ( $kdl_node:expr, $arg_name:expr ) => {
187        match $kdl_node.get($arg_name) {
188            Some(arg) => match arg.value().as_bool() {
189                Some(value) => value,
190                None => {
191                    return Err(ConfigError::new_kdl_error(
192                        format!("Argument must be true or false, found: {}", arg.value()),
193                        arg.span().offset(),
194                        arg.span().len(),
195                    ))
196                },
197            },
198            None => false,
199        }
200    };
201}
202
203#[macro_export]
204macro_rules! kdl_children_nodes_or_error {
205    ( $kdl_node:expr, $error:expr ) => {
206        $kdl_node
207            .children()
208            .ok_or(ConfigError::new_kdl_error(
209                $error.into(),
210                $kdl_node.span().offset(),
211                $kdl_node.span().len(),
212            ))?
213            .nodes()
214    };
215}
216
217#[macro_export]
218macro_rules! kdl_children_nodes {
219    ( $kdl_node:expr ) => {
220        $kdl_node.children().map(|c| c.nodes())
221    };
222}
223
224#[macro_export]
225macro_rules! kdl_property_nodes {
226    ( $kdl_node:expr ) => {{
227        $kdl_node
228            .entries()
229            .iter()
230            .filter_map(|e| e.name())
231            .map(|e| e.value())
232    }};
233}
234
235#[macro_export]
236macro_rules! kdl_children_or_error {
237    ( $kdl_node:expr, $error:expr ) => {
238        $kdl_node.children().ok_or(ConfigError::new_kdl_error(
239            $error.into(),
240            $kdl_node.span().offset(),
241            $kdl_node.span().len(),
242        ))?
243    };
244}
245
246#[macro_export]
247macro_rules! kdl_children {
248    ( $kdl_node:expr ) => {
249        $kdl_node.children().iter().copied().collect()
250    };
251}
252
253#[macro_export]
254macro_rules! kdl_get_string_property_or_child_value {
255    ( $kdl_node:expr, $name:expr ) => {
256        $kdl_node
257            .get($name)
258            .and_then(|e| e.value().as_string())
259            .or_else(|| {
260                $kdl_node
261                    .children()
262                    .and_then(|c| c.get($name))
263                    .and_then(|c| c.get(0))
264                    .and_then(|c| c.value().as_string())
265            })
266    };
267}
268
269#[macro_export]
270macro_rules! kdl_string_arguments {
271    ( $kdl_node:expr ) => {{
272        let res: Result<Vec<_>, _> = $kdl_node
273            .entries()
274            .iter()
275            .map(|e| {
276                e.value().as_string().ok_or(ConfigError::new_kdl_error(
277                    "Not a string".into(),
278                    e.span().offset(),
279                    e.span().len(),
280                ))
281            })
282            .collect();
283        res?
284    }};
285}
286
287#[macro_export]
288macro_rules! kdl_property_names {
289    ( $kdl_node:expr ) => {{
290        $kdl_node
291            .entries()
292            .iter()
293            .filter_map(|e| e.name())
294            .map(|e| e.value())
295    }};
296}
297
298#[macro_export]
299macro_rules! kdl_argument_values {
300    ( $kdl_node:expr ) => {
301        $kdl_node.entries().iter().collect()
302    };
303}
304
305#[macro_export]
306macro_rules! kdl_name {
307    ( $kdl_node:expr ) => {
308        $kdl_node.name().value()
309    };
310}
311
312#[macro_export]
313macro_rules! kdl_document_name {
314    ( $kdl_node:expr ) => {
315        $kdl_node.node().name().value()
316    };
317}
318
319#[macro_export]
320macro_rules! keys_from_kdl {
321    ( $kdl_node:expr ) => {
322        kdl_string_arguments!($kdl_node)
323            .iter()
324            .map(|k| {
325                KeyWithModifier::from_str(k).map_err(|_| {
326                    ConfigError::new_kdl_error(
327                        format!("Invalid key: '{}'", k),
328                        $kdl_node.span().offset(),
329                        $kdl_node.span().len(),
330                    )
331                })
332            })
333            .collect::<Result<_, _>>()?
334    };
335}
336
337#[macro_export]
338macro_rules! actions_from_kdl {
339    ( $kdl_node:expr, $config_options:expr ) => {
340        kdl_children_nodes_or_error!($kdl_node, "no actions found for key_block")
341            .iter()
342            .map(|kdl_action| Action::try_from((kdl_action, $config_options)))
343            .collect::<Result<_, _>>()?
344    };
345}
346
347pub fn kdl_arguments_that_are_strings<'a>(
348    arguments: impl Iterator<Item = &'a KdlEntry>,
349) -> Result<Vec<String>, ConfigError> {
350    let mut args: Vec<String> = vec![];
351    for kdl_entry in arguments {
352        match kdl_entry.value().as_string() {
353            Some(string_value) => args.push(string_value.to_string()),
354            None => {
355                return Err(ConfigError::new_kdl_error(
356                    format!("Argument must be a string"),
357                    kdl_entry.span().offset(),
358                    kdl_entry.span().len(),
359                ));
360            },
361        }
362    }
363    Ok(args)
364}
365
366pub fn kdl_arguments_that_are_digits<'a>(
367    arguments: impl Iterator<Item = &'a KdlEntry>,
368) -> Result<Vec<i64>, ConfigError> {
369    let mut args: Vec<i64> = vec![];
370    for kdl_entry in arguments {
371        match kdl_entry.value().as_i64() {
372            Some(digit_value) => {
373                args.push(digit_value);
374            },
375            None => {
376                return Err(ConfigError::new_kdl_error(
377                    format!("Argument must be a digit"),
378                    kdl_entry.span().offset(),
379                    kdl_entry.span().len(),
380                ));
381            },
382        }
383    }
384    Ok(args)
385}
386
387pub fn kdl_child_string_value_for_entry<'a>(
388    command_metadata: &'a KdlDocument,
389    entry_name: &'a str,
390) -> Option<&'a str> {
391    command_metadata
392        .get(entry_name)
393        .and_then(|cwd| cwd.entries().iter().next())
394        .and_then(|cwd_value| cwd_value.value().as_string())
395}
396
397pub fn kdl_child_bool_value_for_entry<'a>(
398    command_metadata: &'a KdlDocument,
399    entry_name: &'a str,
400) -> Option<bool> {
401    command_metadata
402        .get(entry_name)
403        .and_then(|cwd| cwd.entries().iter().next())
404        .and_then(|cwd_value| cwd_value.value().as_bool())
405}
406
407impl Action {
408    pub fn new_from_bytes(
409        action_name: &str,
410        bytes: Vec<u8>,
411        action_node: &KdlNode,
412    ) -> Result<Self, ConfigError> {
413        match action_name {
414            "Write" => Ok(Action::Write(None, bytes, false)),
415            "PaneNameInput" => Ok(Action::PaneNameInput(bytes)),
416            "TabNameInput" => Ok(Action::TabNameInput(bytes)),
417            "SearchInput" => Ok(Action::SearchInput(bytes)),
418            "GoToTab" => {
419                let tab_index = *bytes.get(0).ok_or_else(|| {
420                    ConfigError::new_kdl_error(
421                        format!("Missing tab index"),
422                        action_node.span().offset(),
423                        action_node.span().len(),
424                    )
425                })? as u32;
426                Ok(Action::GoToTab(tab_index))
427            },
428            _ => Err(ConfigError::new_kdl_error(
429                "Failed to parse action".into(),
430                action_node.span().offset(),
431                action_node.span().len(),
432            )),
433        }
434    }
435    pub fn new_from_string(
436        action_name: &str,
437        string: String,
438        action_node: &KdlNode,
439    ) -> Result<Self, ConfigError> {
440        match action_name {
441            "WriteChars" => Ok(Action::WriteChars(string)),
442            "SwitchToMode" => match InputMode::from_str(string.as_str()) {
443                Ok(input_mode) => Ok(Action::SwitchToMode(input_mode)),
444                Err(_e) => {
445                    return Err(ConfigError::new_kdl_error(
446                        format!("Unknown InputMode '{}'", string),
447                        action_node.span().offset(),
448                        action_node.span().len(),
449                    ))
450                },
451            },
452            "Resize" => {
453                let mut resize: Option<Resize> = None;
454                let mut direction: Option<Direction> = None;
455                for word in string.to_ascii_lowercase().split_whitespace() {
456                    match Resize::from_str(word) {
457                        Ok(value) => resize = Some(value),
458                        Err(_) => match Direction::from_str(word) {
459                            Ok(value) => direction = Some(value),
460                            Err(_) => {
461                                return Err(ConfigError::new_kdl_error(
462                                    format!(
463                                    "failed to read either of resize type or direction from '{}'",
464                                    word
465                                ),
466                                    action_node.span().offset(),
467                                    action_node.span().len(),
468                                ))
469                            },
470                        },
471                    }
472                }
473                let resize = resize.unwrap_or(Resize::Increase);
474                Ok(Action::Resize(resize, direction))
475            },
476            "MoveFocus" => {
477                let direction = Direction::from_str(string.as_str()).map_err(|_| {
478                    ConfigError::new_kdl_error(
479                        format!("Invalid direction: '{}'", string),
480                        action_node.span().offset(),
481                        action_node.span().len(),
482                    )
483                })?;
484                Ok(Action::MoveFocus(direction))
485            },
486            "MoveFocusOrTab" => {
487                let direction = Direction::from_str(string.as_str()).map_err(|_| {
488                    ConfigError::new_kdl_error(
489                        format!("Invalid direction: '{}'", string),
490                        action_node.span().offset(),
491                        action_node.span().len(),
492                    )
493                })?;
494                Ok(Action::MoveFocusOrTab(direction))
495            },
496            "MoveTab" => {
497                let direction = Direction::from_str(string.as_str()).map_err(|_| {
498                    ConfigError::new_kdl_error(
499                        format!("Invalid direction: '{}'", string),
500                        action_node.span().offset(),
501                        action_node.span().len(),
502                    )
503                })?;
504                if direction.is_vertical() {
505                    Err(ConfigError::new_kdl_error(
506                        format!("Invalid horizontal direction: '{}'", string),
507                        action_node.span().offset(),
508                        action_node.span().len(),
509                    ))
510                } else {
511                    Ok(Action::MoveTab(direction))
512                }
513            },
514            "MovePane" => {
515                if string.is_empty() {
516                    return Ok(Action::MovePane(None));
517                } else {
518                    let direction = Direction::from_str(string.as_str()).map_err(|_| {
519                        ConfigError::new_kdl_error(
520                            format!("Invalid direction: '{}'", string),
521                            action_node.span().offset(),
522                            action_node.span().len(),
523                        )
524                    })?;
525                    Ok(Action::MovePane(Some(direction)))
526                }
527            },
528            "MovePaneBackwards" => Ok(Action::MovePaneBackwards),
529            "DumpScreen" => Ok(Action::DumpScreen(string, false)),
530            "DumpLayout" => Ok(Action::DumpLayout),
531            "NewPane" => {
532                if string.is_empty() {
533                    return Ok(Action::NewPane(None, None, false));
534                } else {
535                    let direction = Direction::from_str(string.as_str()).map_err(|_| {
536                        ConfigError::new_kdl_error(
537                            format!("Invalid direction: '{}'", string),
538                            action_node.span().offset(),
539                            action_node.span().len(),
540                        )
541                    })?;
542                    Ok(Action::NewPane(Some(direction), None, false))
543                }
544            },
545            "SearchToggleOption" => {
546                let toggle_option = SearchOption::from_str(string.as_str()).map_err(|_| {
547                    ConfigError::new_kdl_error(
548                        format!("Invalid direction: '{}'", string),
549                        action_node.span().offset(),
550                        action_node.span().len(),
551                    )
552                })?;
553                Ok(Action::SearchToggleOption(toggle_option))
554            },
555            "Search" => {
556                let search_direction =
557                    SearchDirection::from_str(string.as_str()).map_err(|_| {
558                        ConfigError::new_kdl_error(
559                            format!("Invalid direction: '{}'", string),
560                            action_node.span().offset(),
561                            action_node.span().len(),
562                        )
563                    })?;
564                Ok(Action::Search(search_direction))
565            },
566            "RenameSession" => Ok(Action::RenameSession(string)),
567            _ => Err(ConfigError::new_kdl_error(
568                format!("Unsupported action: {}", action_name),
569                action_node.span().offset(),
570                action_node.span().len(),
571            )),
572        }
573    }
574    pub fn to_kdl(&self) -> Option<KdlNode> {
575        match self {
576            Action::Quit => Some(KdlNode::new("Quit")),
577            Action::Write(_key, bytes, _is_kitty) => {
578                let mut node = KdlNode::new("Write");
579                for byte in bytes {
580                    node.push(KdlValue::Base10(*byte as i64));
581                }
582                Some(node)
583            },
584            Action::WriteChars(string) => {
585                let mut node = KdlNode::new("WriteChars");
586                node.push(string.clone());
587                Some(node)
588            },
589            Action::SwitchToMode(input_mode) => {
590                let mut node = KdlNode::new("SwitchToMode");
591                node.push(format!("{:?}", input_mode).to_lowercase());
592                Some(node)
593            },
594            Action::Resize(resize, resize_direction) => {
595                let mut node = KdlNode::new("Resize");
596                let resize = match resize {
597                    Resize::Increase => "Increase",
598                    Resize::Decrease => "Decrease",
599                };
600                if let Some(resize_direction) = resize_direction {
601                    let resize_direction = match resize_direction {
602                        Direction::Left => "left",
603                        Direction::Right => "right",
604                        Direction::Up => "up",
605                        Direction::Down => "down",
606                    };
607                    node.push(format!("{} {}", resize, resize_direction));
608                } else {
609                    node.push(format!("{}", resize));
610                }
611                Some(node)
612            },
613            Action::FocusNextPane => Some(KdlNode::new("FocusNextPane")),
614            Action::FocusPreviousPane => Some(KdlNode::new("FocusPreviousPane")),
615            Action::SwitchFocus => Some(KdlNode::new("SwitchFocus")),
616            Action::MoveFocus(direction) => {
617                let mut node = KdlNode::new("MoveFocus");
618                let direction = match direction {
619                    Direction::Left => "left",
620                    Direction::Right => "right",
621                    Direction::Up => "up",
622                    Direction::Down => "down",
623                };
624                node.push(direction);
625                Some(node)
626            },
627            Action::MoveFocusOrTab(direction) => {
628                let mut node = KdlNode::new("MoveFocusOrTab");
629                let direction = match direction {
630                    Direction::Left => "left",
631                    Direction::Right => "right",
632                    Direction::Up => "up",
633                    Direction::Down => "down",
634                };
635                node.push(direction);
636                Some(node)
637            },
638            Action::MovePane(direction) => {
639                let mut node = KdlNode::new("MovePane");
640                if let Some(direction) = direction {
641                    let direction = match direction {
642                        Direction::Left => "left",
643                        Direction::Right => "right",
644                        Direction::Up => "up",
645                        Direction::Down => "down",
646                    };
647                    node.push(direction);
648                }
649                Some(node)
650            },
651            Action::MovePaneBackwards => Some(KdlNode::new("MovePaneBackwards")),
652            Action::DumpScreen(file, _) => {
653                let mut node = KdlNode::new("DumpScreen");
654                node.push(file.clone());
655                Some(node)
656            },
657            Action::DumpLayout => Some(KdlNode::new("DumpLayout")),
658            Action::EditScrollback => Some(KdlNode::new("EditScrollback")),
659            Action::ScrollUp => Some(KdlNode::new("ScrollUp")),
660            Action::ScrollDown => Some(KdlNode::new("ScrollDown")),
661            Action::ScrollToBottom => Some(KdlNode::new("ScrollToBottom")),
662            Action::ScrollToTop => Some(KdlNode::new("ScrollToTop")),
663            Action::PageScrollUp => Some(KdlNode::new("PageScrollUp")),
664            Action::PageScrollDown => Some(KdlNode::new("PageScrollDown")),
665            Action::HalfPageScrollUp => Some(KdlNode::new("HalfPageScrollUp")),
666            Action::HalfPageScrollDown => Some(KdlNode::new("HalfPageScrollDown")),
667            Action::ToggleFocusFullscreen => Some(KdlNode::new("ToggleFocusFullscreen")),
668            Action::TogglePaneFrames => Some(KdlNode::new("TogglePaneFrames")),
669            Action::ToggleActiveSyncTab => Some(KdlNode::new("ToggleActiveSyncTab")),
670            Action::NewPane(direction, _, _) => {
671                let mut node = KdlNode::new("NewPane");
672                if let Some(direction) = direction {
673                    let direction = match direction {
674                        Direction::Left => "left",
675                        Direction::Right => "right",
676                        Direction::Up => "up",
677                        Direction::Down => "down",
678                    };
679                    node.push(direction);
680                }
681                Some(node)
682            },
683            Action::TogglePaneEmbedOrFloating => Some(KdlNode::new("TogglePaneEmbedOrFloating")),
684            Action::ToggleFloatingPanes => Some(KdlNode::new("ToggleFloatingPanes")),
685            Action::CloseFocus => Some(KdlNode::new("CloseFocus")),
686            Action::PaneNameInput(bytes) => {
687                let mut node = KdlNode::new("PaneNameInput");
688                for byte in bytes {
689                    node.push(KdlValue::Base10(*byte as i64));
690                }
691                Some(node)
692            },
693            Action::UndoRenamePane => Some(KdlNode::new("UndoRenamePane")),
694            Action::NewTab(_, _, _, _, name, should_change_focus_to_new_tab) => {
695                log::warn!("Converting new tab action without arguments, original action saved to .bak.kdl file");
696                let mut node = KdlNode::new("NewTab");
697                if let Some(name) = name {
698                    let mut children = KdlDocument::new();
699                    let mut name_node = KdlNode::new("name");
700                    if !should_change_focus_to_new_tab {
701                        let mut should_change_focus_to_new_tab_node =
702                            KdlNode::new("should_change_focus_to_new_tab");
703                        should_change_focus_to_new_tab_node.push(KdlValue::Bool(false));
704                        children
705                            .nodes_mut()
706                            .push(should_change_focus_to_new_tab_node);
707                    }
708                    name_node.push(name.clone());
709                    children.nodes_mut().push(name_node);
710                    node.set_children(children);
711                }
712                Some(node)
713            },
714            Action::GoToNextTab => Some(KdlNode::new("GoToNextTab")),
715            Action::GoToPreviousTab => Some(KdlNode::new("GoToPreviousTab")),
716            Action::CloseTab => Some(KdlNode::new("CloseTab")),
717            Action::GoToTab(index) => {
718                let mut node = KdlNode::new("GoToTab");
719                node.push(KdlValue::Base10(*index as i64));
720                Some(node)
721            },
722            Action::ToggleTab => Some(KdlNode::new("ToggleTab")),
723            Action::TabNameInput(bytes) => {
724                let mut node = KdlNode::new("TabNameInput");
725                for byte in bytes {
726                    node.push(KdlValue::Base10(*byte as i64));
727                }
728                Some(node)
729            },
730            Action::UndoRenameTab => Some(KdlNode::new("UndoRenameTab")),
731            Action::MoveTab(direction) => {
732                let mut node = KdlNode::new("MoveTab");
733                let direction = match direction {
734                    Direction::Left => "left",
735                    Direction::Right => "right",
736                    Direction::Up => "up",
737                    Direction::Down => "down",
738                };
739                node.push(direction);
740                Some(node)
741            },
742            Action::NewTiledPane(direction, run_command_action, name) => {
743                let mut node = KdlNode::new("Run");
744                let mut node_children = KdlDocument::new();
745                if let Some(run_command_action) = run_command_action {
746                    node.push(run_command_action.command.display().to_string());
747                    for arg in &run_command_action.args {
748                        node.push(arg.clone());
749                    }
750                    if let Some(cwd) = &run_command_action.cwd {
751                        let mut cwd_node = KdlNode::new("cwd");
752                        cwd_node.push(cwd.display().to_string());
753                        node_children.nodes_mut().push(cwd_node);
754                    }
755                    if run_command_action.hold_on_start {
756                        let mut hos_node = KdlNode::new("hold_on_start");
757                        hos_node.push(KdlValue::Bool(true));
758                        node_children.nodes_mut().push(hos_node);
759                    }
760                    if !run_command_action.hold_on_close {
761                        let mut hoc_node = KdlNode::new("hold_on_close");
762                        hoc_node.push(KdlValue::Bool(false));
763                        node_children.nodes_mut().push(hoc_node);
764                    }
765                }
766                if let Some(name) = name {
767                    let mut name_node = KdlNode::new("name");
768                    name_node.push(name.clone());
769                    node_children.nodes_mut().push(name_node);
770                }
771                if let Some(direction) = direction {
772                    let mut direction_node = KdlNode::new("direction");
773                    let direction = match direction {
774                        Direction::Left => "left",
775                        Direction::Right => "right",
776                        Direction::Up => "up",
777                        Direction::Down => "down",
778                    };
779                    direction_node.push(direction);
780                    node_children.nodes_mut().push(direction_node);
781                }
782                if !node_children.nodes().is_empty() {
783                    node.set_children(node_children);
784                }
785                Some(node)
786            },
787            Action::NewFloatingPane(run_command_action, name, floating_pane_coordinates) => {
788                let mut node = KdlNode::new("Run");
789                let mut node_children = KdlDocument::new();
790                let mut floating_pane = KdlNode::new("floating");
791                floating_pane.push(KdlValue::Bool(true));
792                node_children.nodes_mut().push(floating_pane);
793                if let Some(run_command_action) = run_command_action {
794                    node.push(run_command_action.command.display().to_string());
795                    for arg in &run_command_action.args {
796                        node.push(arg.clone());
797                    }
798                    if let Some(cwd) = &run_command_action.cwd {
799                        let mut cwd_node = KdlNode::new("cwd");
800                        cwd_node.push(cwd.display().to_string());
801                        node_children.nodes_mut().push(cwd_node);
802                    }
803                    if run_command_action.hold_on_start {
804                        let mut hos_node = KdlNode::new("hold_on_start");
805                        hos_node.push(KdlValue::Bool(true));
806                        node_children.nodes_mut().push(hos_node);
807                    }
808                    if !run_command_action.hold_on_close {
809                        let mut hoc_node = KdlNode::new("hold_on_close");
810                        hoc_node.push(KdlValue::Bool(false));
811                        node_children.nodes_mut().push(hoc_node);
812                    }
813                }
814                if let Some(floating_pane_coordinates) = floating_pane_coordinates {
815                    if let Some(x) = floating_pane_coordinates.x {
816                        let mut x_node = KdlNode::new("x");
817                        match x {
818                            SplitSize::Percent(x) => {
819                                x_node.push(format!("{}%", x));
820                            },
821                            SplitSize::Fixed(x) => {
822                                x_node.push(KdlValue::Base10(x as i64));
823                            },
824                        };
825                        node_children.nodes_mut().push(x_node);
826                    }
827                    if let Some(y) = floating_pane_coordinates.y {
828                        let mut y_node = KdlNode::new("y");
829                        match y {
830                            SplitSize::Percent(y) => {
831                                y_node.push(format!("{}%", y));
832                            },
833                            SplitSize::Fixed(y) => {
834                                y_node.push(KdlValue::Base10(y as i64));
835                            },
836                        };
837                        node_children.nodes_mut().push(y_node);
838                    }
839                    if let Some(width) = floating_pane_coordinates.width {
840                        let mut width_node = KdlNode::new("width");
841                        match width {
842                            SplitSize::Percent(width) => {
843                                width_node.push(format!("{}%", width));
844                            },
845                            SplitSize::Fixed(width) => {
846                                width_node.push(KdlValue::Base10(width as i64));
847                            },
848                        };
849                        node_children.nodes_mut().push(width_node);
850                    }
851                    if let Some(height) = floating_pane_coordinates.height {
852                        let mut height_node = KdlNode::new("height");
853                        match height {
854                            SplitSize::Percent(height) => {
855                                height_node.push(format!("{}%", height));
856                            },
857                            SplitSize::Fixed(height) => {
858                                height_node.push(KdlValue::Base10(height as i64));
859                            },
860                        };
861                        node_children.nodes_mut().push(height_node);
862                    }
863                }
864                if let Some(name) = name {
865                    let mut name_node = KdlNode::new("name");
866                    name_node.push(name.clone());
867                    node_children.nodes_mut().push(name_node);
868                }
869                if !node_children.nodes().is_empty() {
870                    node.set_children(node_children);
871                }
872                Some(node)
873            },
874            Action::NewInPlacePane(run_command_action, name) => {
875                let mut node = KdlNode::new("Run");
876                let mut node_children = KdlDocument::new();
877                if let Some(run_command_action) = run_command_action {
878                    node.push(run_command_action.command.display().to_string());
879                    for arg in &run_command_action.args {
880                        node.push(arg.clone());
881                    }
882                    let mut in_place_node = KdlNode::new("in_place");
883                    in_place_node.push(KdlValue::Bool(true));
884                    node_children.nodes_mut().push(in_place_node);
885                    if let Some(cwd) = &run_command_action.cwd {
886                        let mut cwd_node = KdlNode::new("cwd");
887                        cwd_node.push(cwd.display().to_string());
888                        node_children.nodes_mut().push(cwd_node);
889                    }
890                    if run_command_action.hold_on_start {
891                        let mut hos_node = KdlNode::new("hold_on_start");
892                        hos_node.push(KdlValue::Bool(true));
893                        node_children.nodes_mut().push(hos_node);
894                    }
895                    if !run_command_action.hold_on_close {
896                        let mut hoc_node = KdlNode::new("hold_on_close");
897                        hoc_node.push(KdlValue::Bool(false));
898                        node_children.nodes_mut().push(hoc_node);
899                    }
900                }
901                if let Some(name) = name {
902                    let mut name_node = KdlNode::new("name");
903                    name_node.push(name.clone());
904                    node_children.nodes_mut().push(name_node);
905                }
906                if !node_children.nodes().is_empty() {
907                    node.set_children(node_children);
908                }
909                Some(node)
910            },
911            Action::Detach => Some(KdlNode::new("Detach")),
912            Action::LaunchOrFocusPlugin(
913                run_plugin_or_alias,
914                should_float,
915                move_to_focused_tab,
916                should_open_in_place,
917                skip_plugin_cache,
918            ) => {
919                let mut node = KdlNode::new("LaunchOrFocusPlugin");
920                let mut node_children = KdlDocument::new();
921                let location = run_plugin_or_alias.location_string();
922                node.push(location);
923                if *should_float {
924                    let mut should_float_node = KdlNode::new("floating");
925                    should_float_node.push(KdlValue::Bool(true));
926                    node_children.nodes_mut().push(should_float_node);
927                }
928                if *move_to_focused_tab {
929                    let mut move_to_focused_tab_node = KdlNode::new("move_to_focused_tab");
930                    move_to_focused_tab_node.push(KdlValue::Bool(true));
931                    node_children.nodes_mut().push(move_to_focused_tab_node);
932                }
933                if *should_open_in_place {
934                    let mut should_open_in_place_node = KdlNode::new("in_place");
935                    should_open_in_place_node.push(KdlValue::Bool(true));
936                    node_children.nodes_mut().push(should_open_in_place_node);
937                }
938                if *skip_plugin_cache {
939                    let mut skip_plugin_cache_node = KdlNode::new("skip_plugin_cache");
940                    skip_plugin_cache_node.push(KdlValue::Bool(true));
941                    node_children.nodes_mut().push(skip_plugin_cache_node);
942                }
943                if let Some(configuration) = run_plugin_or_alias.get_configuration() {
944                    for (config_key, config_value) in configuration.inner().iter() {
945                        let mut node = KdlNode::new(config_key.clone());
946                        node.push(config_value.clone());
947                        node_children.nodes_mut().push(node);
948                    }
949                }
950                if !node_children.nodes().is_empty() {
951                    node.set_children(node_children);
952                }
953                Some(node)
954            },
955            Action::LaunchPlugin(
956                run_plugin_or_alias,
957                should_float,
958                should_open_in_place,
959                skip_plugin_cache,
960                cwd,
961            ) => {
962                let mut node = KdlNode::new("LaunchPlugin");
963                let mut node_children = KdlDocument::new();
964                let location = run_plugin_or_alias.location_string();
965                node.push(location);
966                if *should_float {
967                    let mut should_float_node = KdlNode::new("floating");
968                    should_float_node.push(KdlValue::Bool(true));
969                    node_children.nodes_mut().push(should_float_node);
970                }
971                if *should_open_in_place {
972                    let mut should_open_in_place_node = KdlNode::new("in_place");
973                    should_open_in_place_node.push(KdlValue::Bool(true));
974                    node_children.nodes_mut().push(should_open_in_place_node);
975                }
976                if *skip_plugin_cache {
977                    let mut skip_plugin_cache_node = KdlNode::new("skip_plugin_cache");
978                    skip_plugin_cache_node.push(KdlValue::Bool(true));
979                    node_children.nodes_mut().push(skip_plugin_cache_node);
980                }
981                if let Some(cwd) = &cwd {
982                    let mut cwd_node = KdlNode::new("cwd");
983                    cwd_node.push(cwd.display().to_string());
984                    node_children.nodes_mut().push(cwd_node);
985                } else if let Some(cwd) = run_plugin_or_alias.get_initial_cwd() {
986                    let mut cwd_node = KdlNode::new("cwd");
987                    cwd_node.push(cwd.display().to_string());
988                    node_children.nodes_mut().push(cwd_node);
989                }
990                if let Some(configuration) = run_plugin_or_alias.get_configuration() {
991                    for (config_key, config_value) in configuration.inner().iter() {
992                        let mut node = KdlNode::new(config_key.clone());
993                        node.push(config_value.clone());
994                        node_children.nodes_mut().push(node);
995                    }
996                }
997                if !node_children.nodes().is_empty() {
998                    node.set_children(node_children);
999                }
1000                Some(node)
1001            },
1002            Action::Copy => Some(KdlNode::new("Copy")),
1003            Action::SearchInput(bytes) => {
1004                let mut node = KdlNode::new("SearchInput");
1005                for byte in bytes {
1006                    node.push(KdlValue::Base10(*byte as i64));
1007                }
1008                Some(node)
1009            },
1010            Action::Search(search_direction) => {
1011                let mut node = KdlNode::new("Search");
1012                let direction = match search_direction {
1013                    SearchDirection::Down => "down",
1014                    SearchDirection::Up => "up",
1015                };
1016                node.push(direction);
1017                Some(node)
1018            },
1019            Action::SearchToggleOption(search_toggle_option) => {
1020                let mut node = KdlNode::new("SearchToggleOption");
1021                node.push(format!("{:?}", search_toggle_option));
1022                Some(node)
1023            },
1024            Action::ToggleMouseMode => Some(KdlNode::new("ToggleMouseMode")),
1025            Action::PreviousSwapLayout => Some(KdlNode::new("PreviousSwapLayout")),
1026            Action::NextSwapLayout => Some(KdlNode::new("NextSwapLayout")),
1027            Action::BreakPane => Some(KdlNode::new("BreakPane")),
1028            Action::BreakPaneRight => Some(KdlNode::new("BreakPaneRight")),
1029            Action::BreakPaneLeft => Some(KdlNode::new("BreakPaneLeft")),
1030            Action::KeybindPipe {
1031                name,
1032                payload,
1033                args: _, // currently unsupported
1034                plugin,
1035                configuration,
1036                launch_new,
1037                skip_cache,
1038                floating,
1039                in_place: _, // currently unsupported
1040                cwd,
1041                pane_title,
1042                plugin_id,
1043            } => {
1044                if plugin_id.is_some() {
1045                    log::warn!("Not serializing temporary keybinding MessagePluginId");
1046                    return None;
1047                }
1048                let mut node = KdlNode::new("MessagePlugin");
1049                let mut node_children = KdlDocument::new();
1050                if let Some(plugin) = plugin {
1051                    node.push(plugin.clone());
1052                }
1053                if let Some(name) = name {
1054                    let mut name_node = KdlNode::new("name");
1055                    name_node.push(name.clone());
1056                    node_children.nodes_mut().push(name_node);
1057                }
1058                if let Some(cwd) = cwd {
1059                    let mut cwd_node = KdlNode::new("cwd");
1060                    cwd_node.push(cwd.display().to_string());
1061                    node_children.nodes_mut().push(cwd_node);
1062                }
1063                if let Some(payload) = payload {
1064                    let mut payload_node = KdlNode::new("payload");
1065                    payload_node.push(payload.clone());
1066                    node_children.nodes_mut().push(payload_node);
1067                }
1068                if *launch_new {
1069                    let mut launch_new_node = KdlNode::new("launch_new");
1070                    launch_new_node.push(KdlValue::Bool(true));
1071                    node_children.nodes_mut().push(launch_new_node);
1072                }
1073                if *skip_cache {
1074                    let mut skip_cache_node = KdlNode::new("skip_cache");
1075                    skip_cache_node.push(KdlValue::Bool(true));
1076                    node_children.nodes_mut().push(skip_cache_node);
1077                }
1078                if let Some(floating) = floating {
1079                    let mut floating_node = KdlNode::new("floating");
1080                    floating_node.push(KdlValue::Bool(*floating));
1081                    node_children.nodes_mut().push(floating_node);
1082                }
1083                if let Some(title) = pane_title {
1084                    let mut title_node = KdlNode::new("title");
1085                    title_node.push(title.clone());
1086                    node_children.nodes_mut().push(title_node);
1087                }
1088                if let Some(configuration) = configuration {
1089                    // we do this because the constructor removes the relevant config fields from
1090                    // above, otherwise we would have duplicates
1091                    let configuration = PluginUserConfiguration::new(configuration.clone());
1092                    let configuration = configuration.inner();
1093                    for (config_key, config_value) in configuration.iter() {
1094                        let mut node = KdlNode::new(config_key.clone());
1095                        node.push(config_value.clone());
1096                        node_children.nodes_mut().push(node);
1097                    }
1098                }
1099                if !node_children.nodes().is_empty() {
1100                    node.set_children(node_children);
1101                }
1102                Some(node)
1103            },
1104            _ => None,
1105        }
1106    }
1107}
1108
1109impl TryFrom<(&str, &KdlDocument)> for PaletteColor {
1110    type Error = ConfigError;
1111
1112    fn try_from(
1113        (color_name, theme_colors): (&str, &KdlDocument),
1114    ) -> Result<PaletteColor, Self::Error> {
1115        let color = theme_colors
1116            .get(color_name)
1117            .ok_or(ConfigError::new_kdl_error(
1118                format!("Missing theme color: {}", color_name),
1119                theme_colors.span().offset(),
1120                theme_colors.span().len(),
1121            ))?;
1122        let entry_count = entry_count!(color);
1123        let is_rgb = || entry_count == 3;
1124        let is_three_digit_hex = || {
1125            match kdl_first_entry_as_string!(color) {
1126                // 4 including the '#' character
1127                Some(s) => entry_count == 1 && s.starts_with('#') && s.len() == 4,
1128                None => false,
1129            }
1130        };
1131        let is_six_digit_hex = || {
1132            match kdl_first_entry_as_string!(color) {
1133                // 7 including the '#' character
1134                Some(s) => entry_count == 1 && s.starts_with('#') && s.len() == 7,
1135                None => false,
1136            }
1137        };
1138        let is_eight_bit = || kdl_first_entry_as_i64!(color).is_some() && entry_count == 1;
1139        if is_rgb() {
1140            let mut channels = kdl_entries_as_i64!(color);
1141            let r = channels.next().unwrap().ok_or(ConfigError::new_kdl_error(
1142                format!("invalid rgb color"),
1143                color.span().offset(),
1144                color.span().len(),
1145            ))? as u8;
1146            let g = channels.next().unwrap().ok_or(ConfigError::new_kdl_error(
1147                format!("invalid rgb color"),
1148                color.span().offset(),
1149                color.span().len(),
1150            ))? as u8;
1151            let b = channels.next().unwrap().ok_or(ConfigError::new_kdl_error(
1152                format!("invalid rgb color"),
1153                color.span().offset(),
1154                color.span().len(),
1155            ))? as u8;
1156            Ok(PaletteColor::Rgb((r, g, b)))
1157        } else if is_three_digit_hex() {
1158            // eg. #fff (hex, will be converted to rgb)
1159            let mut s = String::from(kdl_first_entry_as_string!(color).unwrap());
1160            s.remove(0);
1161            let r = u8::from_str_radix(&s[0..1], 16).map_err(|_| {
1162                ConfigError::new_kdl_error(
1163                    "Failed to parse hex color".into(),
1164                    color.span().offset(),
1165                    color.span().len(),
1166                )
1167            })? * 0x11;
1168            let g = u8::from_str_radix(&s[1..2], 16).map_err(|_| {
1169                ConfigError::new_kdl_error(
1170                    "Failed to parse hex color".into(),
1171                    color.span().offset(),
1172                    color.span().len(),
1173                )
1174            })? * 0x11;
1175            let b = u8::from_str_radix(&s[2..3], 16).map_err(|_| {
1176                ConfigError::new_kdl_error(
1177                    "Failed to parse hex color".into(),
1178                    color.span().offset(),
1179                    color.span().len(),
1180                )
1181            })? * 0x11;
1182            Ok(PaletteColor::Rgb((r, g, b)))
1183        } else if is_six_digit_hex() {
1184            // eg. #ffffff (hex, will be converted to rgb)
1185            let mut s = String::from(kdl_first_entry_as_string!(color).unwrap());
1186            s.remove(0);
1187            let r = u8::from_str_radix(&s[0..2], 16).map_err(|_| {
1188                ConfigError::new_kdl_error(
1189                    "Failed to parse hex color".into(),
1190                    color.span().offset(),
1191                    color.span().len(),
1192                )
1193            })?;
1194            let g = u8::from_str_radix(&s[2..4], 16).map_err(|_| {
1195                ConfigError::new_kdl_error(
1196                    "Failed to parse hex color".into(),
1197                    color.span().offset(),
1198                    color.span().len(),
1199                )
1200            })?;
1201            let b = u8::from_str_radix(&s[4..6], 16).map_err(|_| {
1202                ConfigError::new_kdl_error(
1203                    "Failed to parse hex color".into(),
1204                    color.span().offset(),
1205                    color.span().len(),
1206                )
1207            })?;
1208            Ok(PaletteColor::Rgb((r, g, b)))
1209        } else if is_eight_bit() {
1210            let n = kdl_first_entry_as_i64!(color).ok_or(ConfigError::new_kdl_error(
1211                "Failed to parse color".into(),
1212                color.span().offset(),
1213                color.span().len(),
1214            ))?;
1215            Ok(PaletteColor::EightBit(n as u8))
1216        } else {
1217            Err(ConfigError::new_kdl_error(
1218                "Failed to parse color".into(),
1219                color.span().offset(),
1220                color.span().len(),
1221            ))
1222        }
1223    }
1224}
1225
1226impl PaletteColor {
1227    pub fn to_kdl(&self, color_name: &str) -> KdlNode {
1228        let mut node = KdlNode::new(color_name);
1229        match self {
1230            PaletteColor::Rgb((r, g, b)) => {
1231                node.push(KdlValue::Base10(*r as i64));
1232                node.push(KdlValue::Base10(*g as i64));
1233                node.push(KdlValue::Base10(*b as i64));
1234            },
1235            PaletteColor::EightBit(color_index) => {
1236                node.push(KdlValue::Base10(*color_index as i64));
1237            },
1238        }
1239        node
1240    }
1241}
1242
1243impl TryFrom<(&KdlNode, &Options)> for Action {
1244    type Error = ConfigError;
1245    fn try_from((kdl_action, config_options): (&KdlNode, &Options)) -> Result<Self, Self::Error> {
1246        let action_name = kdl_name!(kdl_action);
1247        let action_arguments: Vec<&KdlEntry> = kdl_argument_values!(kdl_action);
1248        let action_children: Vec<&KdlDocument> = kdl_children!(kdl_action);
1249        match action_name {
1250            "Quit" => parse_kdl_action_arguments!(action_name, action_arguments, kdl_action),
1251            "FocusNextPane" => {
1252                parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
1253            },
1254            "FocusPreviousPane" => {
1255                parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
1256            },
1257            "SwitchFocus" => parse_kdl_action_arguments!(action_name, action_arguments, kdl_action),
1258            "EditScrollback" => {
1259                parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
1260            },
1261            "ScrollUp" => parse_kdl_action_arguments!(action_name, action_arguments, kdl_action),
1262            "ScrollDown" => parse_kdl_action_arguments!(action_name, action_arguments, kdl_action),
1263            "ScrollToBottom" => {
1264                parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
1265            },
1266            "ScrollToTop" => {
1267                parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
1268            },
1269            "PageScrollUp" => {
1270                parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
1271            },
1272            "PageScrollDown" => {
1273                parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
1274            },
1275            "HalfPageScrollUp" => {
1276                parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
1277            },
1278            "HalfPageScrollDown" => {
1279                parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
1280            },
1281            "ToggleFocusFullscreen" => {
1282                parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
1283            },
1284            "TogglePaneFrames" => {
1285                parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
1286            },
1287            "ToggleActiveSyncTab" => {
1288                parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
1289            },
1290            "TogglePaneEmbedOrFloating" => {
1291                parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
1292            },
1293            "ToggleFloatingPanes" => {
1294                parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
1295            },
1296            "CloseFocus" => parse_kdl_action_arguments!(action_name, action_arguments, kdl_action),
1297            "UndoRenamePane" => {
1298                parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
1299            },
1300            "NoOp" => parse_kdl_action_arguments!(action_name, action_arguments, kdl_action),
1301            "GoToNextTab" => parse_kdl_action_arguments!(action_name, action_arguments, kdl_action),
1302            "GoToPreviousTab" => {
1303                parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
1304            },
1305            "CloseTab" => parse_kdl_action_arguments!(action_name, action_arguments, kdl_action),
1306            "ToggleTab" => parse_kdl_action_arguments!(action_name, action_arguments, kdl_action),
1307            "UndoRenameTab" => {
1308                parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
1309            },
1310            "ToggleMouseMode" => {
1311                parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
1312            },
1313            "Detach" => parse_kdl_action_arguments!(action_name, action_arguments, kdl_action),
1314            "Copy" => parse_kdl_action_arguments!(action_name, action_arguments, kdl_action),
1315            "Clear" => parse_kdl_action_arguments!(action_name, action_arguments, kdl_action),
1316            "Confirm" => parse_kdl_action_arguments!(action_name, action_arguments, kdl_action),
1317            "Deny" => parse_kdl_action_arguments!(action_name, action_arguments, kdl_action),
1318            "Write" => parse_kdl_action_u8_arguments!(action_name, action_arguments, kdl_action),
1319            "WriteChars" => parse_kdl_action_char_or_string_arguments!(
1320                action_name,
1321                action_arguments,
1322                kdl_action
1323            ),
1324            "SwitchToMode" => parse_kdl_action_char_or_string_arguments!(
1325                action_name,
1326                action_arguments,
1327                kdl_action
1328            ),
1329            "Search" => parse_kdl_action_char_or_string_arguments!(
1330                action_name,
1331                action_arguments,
1332                kdl_action
1333            ),
1334            "Resize" => parse_kdl_action_char_or_string_arguments!(
1335                action_name,
1336                action_arguments,
1337                kdl_action
1338            ),
1339            "ResizeNew" => parse_kdl_action_char_or_string_arguments!(
1340                action_name,
1341                action_arguments,
1342                kdl_action
1343            ),
1344            "MoveFocus" => parse_kdl_action_char_or_string_arguments!(
1345                action_name,
1346                action_arguments,
1347                kdl_action
1348            ),
1349            "MoveTab" => parse_kdl_action_char_or_string_arguments!(
1350                action_name,
1351                action_arguments,
1352                kdl_action
1353            ),
1354            "MoveFocusOrTab" => parse_kdl_action_char_or_string_arguments!(
1355                action_name,
1356                action_arguments,
1357                kdl_action
1358            ),
1359            "MovePane" => parse_kdl_action_char_or_string_arguments!(
1360                action_name,
1361                action_arguments,
1362                kdl_action
1363            ),
1364            "MovePaneBackwards" => parse_kdl_action_char_or_string_arguments!(
1365                action_name,
1366                action_arguments,
1367                kdl_action
1368            ),
1369            "DumpScreen" => parse_kdl_action_char_or_string_arguments!(
1370                action_name,
1371                action_arguments,
1372                kdl_action
1373            ),
1374            "DumpLayout" => parse_kdl_action_char_or_string_arguments!(
1375                action_name,
1376                action_arguments,
1377                kdl_action
1378            ),
1379            "NewPane" => parse_kdl_action_char_or_string_arguments!(
1380                action_name,
1381                action_arguments,
1382                kdl_action
1383            ),
1384            "PaneNameInput" => {
1385                parse_kdl_action_u8_arguments!(action_name, action_arguments, kdl_action)
1386            },
1387            "NewTab" => {
1388                let command_metadata = action_children.iter().next();
1389                if command_metadata.is_none() {
1390                    return Ok(Action::NewTab(None, vec![], None, None, None, true));
1391                }
1392
1393                let current_dir = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
1394
1395                let layout = command_metadata
1396                    .and_then(|c_m| kdl_child_string_value_for_entry(c_m, "layout"))
1397                    .map(|layout_string| PathBuf::from(layout_string))
1398                    .or_else(|| config_options.default_layout.clone());
1399                let cwd = command_metadata
1400                    .and_then(|c_m| kdl_child_string_value_for_entry(c_m, "cwd"))
1401                    .map(|cwd_string| PathBuf::from(cwd_string))
1402                    .map(|cwd| current_dir.join(cwd));
1403                let name = command_metadata
1404                    .and_then(|c_m| kdl_child_string_value_for_entry(c_m, "name"))
1405                    .map(|name_string| name_string.to_string());
1406
1407                let layout_dir = config_options
1408                    .layout_dir
1409                    .clone()
1410                    .or_else(|| get_layout_dir(find_default_config_dir()));
1411                let (path_to_raw_layout, raw_layout, swap_layouts) =
1412                    Layout::stringified_from_path_or_default(layout.as_ref(), layout_dir).map_err(
1413                        |e| {
1414                            ConfigError::new_kdl_error(
1415                                format!("Failed to load layout: {}", e),
1416                                kdl_action.span().offset(),
1417                                kdl_action.span().len(),
1418                            )
1419                        },
1420                    )?;
1421
1422                let layout = Layout::from_str(
1423                    &raw_layout,
1424                    path_to_raw_layout,
1425                    swap_layouts.as_ref().map(|(f, p)| (f.as_str(), p.as_str())),
1426                    cwd,
1427                )
1428                .map_err(|e| {
1429                    ConfigError::new_kdl_error(
1430                        format!("Failed to load layout: {}", e),
1431                        kdl_action.span().offset(),
1432                        kdl_action.span().len(),
1433                    )
1434                })?;
1435
1436                let swap_tiled_layouts = Some(layout.swap_tiled_layouts.clone());
1437                let swap_floating_layouts = Some(layout.swap_floating_layouts.clone());
1438
1439                let mut tabs = layout.tabs();
1440                if tabs.len() > 1 {
1441                    return Err(ConfigError::new_kdl_error(
1442                        "Tab layout cannot itself have tabs".to_string(),
1443                        kdl_action.span().offset(),
1444                        kdl_action.span().len(),
1445                    ));
1446                } else if !tabs.is_empty() {
1447                    let (tab_name, layout, floating_panes_layout) = tabs.drain(..).next().unwrap();
1448                    let name = tab_name.or(name);
1449                    let should_change_focus_to_new_tab = layout.focus.unwrap_or(true);
1450
1451                    Ok(Action::NewTab(
1452                        Some(layout),
1453                        floating_panes_layout,
1454                        swap_tiled_layouts,
1455                        swap_floating_layouts,
1456                        name,
1457                        should_change_focus_to_new_tab,
1458                    ))
1459                } else {
1460                    let (layout, floating_panes_layout) = layout.new_tab();
1461                    let should_change_focus_to_new_tab = layout.focus.unwrap_or(true);
1462
1463                    Ok(Action::NewTab(
1464                        Some(layout),
1465                        floating_panes_layout,
1466                        swap_tiled_layouts,
1467                        swap_floating_layouts,
1468                        name,
1469                        should_change_focus_to_new_tab,
1470                    ))
1471                }
1472            },
1473            "GoToTab" => parse_kdl_action_u8_arguments!(action_name, action_arguments, kdl_action),
1474            "TabNameInput" => {
1475                parse_kdl_action_u8_arguments!(action_name, action_arguments, kdl_action)
1476            },
1477            "SearchInput" => {
1478                parse_kdl_action_u8_arguments!(action_name, action_arguments, kdl_action)
1479            },
1480            "SearchToggleOption" => parse_kdl_action_char_or_string_arguments!(
1481                action_name,
1482                action_arguments,
1483                kdl_action
1484            ),
1485            "Run" => {
1486                let arguments = action_arguments.iter().copied();
1487                let mut args = kdl_arguments_that_are_strings(arguments)?;
1488                if args.is_empty() {
1489                    return Err(ConfigError::new_kdl_error(
1490                        "No command found in Run action".into(),
1491                        kdl_action.span().offset(),
1492                        kdl_action.span().len(),
1493                    ));
1494                }
1495                let command = args.remove(0);
1496                let command_metadata = action_children.iter().next();
1497                let cwd = command_metadata
1498                    .and_then(|c_m| kdl_child_string_value_for_entry(c_m, "cwd"))
1499                    .map(|cwd_string| PathBuf::from(cwd_string));
1500                let name = command_metadata
1501                    .and_then(|c_m| kdl_child_string_value_for_entry(c_m, "name"))
1502                    .map(|name_string| name_string.to_string());
1503                let direction = command_metadata
1504                    .and_then(|c_m| kdl_child_string_value_for_entry(c_m, "direction"))
1505                    .and_then(|direction_string| Direction::from_str(direction_string).ok());
1506                let hold_on_close = command_metadata
1507                    .and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "close_on_exit"))
1508                    .and_then(|close_on_exit| Some(!close_on_exit))
1509                    .unwrap_or(true);
1510                let hold_on_start = command_metadata
1511                    .and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "start_suspended"))
1512                    .unwrap_or(false);
1513                let floating = command_metadata
1514                    .and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "floating"))
1515                    .unwrap_or(false);
1516                let in_place = command_metadata
1517                    .and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "in_place"))
1518                    .unwrap_or(false);
1519                let run_command_action = RunCommandAction {
1520                    command: PathBuf::from(command),
1521                    args,
1522                    cwd,
1523                    direction,
1524                    hold_on_close,
1525                    hold_on_start,
1526                    ..Default::default()
1527                };
1528                let x = command_metadata
1529                    .and_then(|c_m| kdl_child_string_value_for_entry(c_m, "x"))
1530                    .map(|s| s.to_owned());
1531                let y = command_metadata
1532                    .and_then(|c_m| kdl_child_string_value_for_entry(c_m, "y"))
1533                    .map(|s| s.to_owned());
1534                let width = command_metadata
1535                    .and_then(|c_m| kdl_child_string_value_for_entry(c_m, "width"))
1536                    .map(|s| s.to_owned());
1537                let height = command_metadata
1538                    .and_then(|c_m| kdl_child_string_value_for_entry(c_m, "height"))
1539                    .map(|s| s.to_owned());
1540                if floating {
1541                    Ok(Action::NewFloatingPane(
1542                        Some(run_command_action),
1543                        name,
1544                        FloatingPaneCoordinates::new(x, y, width, height),
1545                    ))
1546                } else if in_place {
1547                    Ok(Action::NewInPlacePane(Some(run_command_action), name))
1548                } else {
1549                    Ok(Action::NewTiledPane(
1550                        direction,
1551                        Some(run_command_action),
1552                        name,
1553                    ))
1554                }
1555            },
1556            "LaunchOrFocusPlugin" => {
1557                let arguments = action_arguments.iter().copied();
1558                let mut args = kdl_arguments_that_are_strings(arguments)?;
1559                if args.is_empty() {
1560                    return Err(ConfigError::new_kdl_error(
1561                        "No plugin found to launch in LaunchOrFocusPlugin".into(),
1562                        kdl_action.span().offset(),
1563                        kdl_action.span().len(),
1564                    ));
1565                }
1566                let plugin_path = args.remove(0);
1567
1568                let command_metadata = action_children.iter().next();
1569                let should_float = command_metadata
1570                    .and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "floating"))
1571                    .unwrap_or(false);
1572                let move_to_focused_tab = command_metadata
1573                    .and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "move_to_focused_tab"))
1574                    .unwrap_or(false);
1575                let should_open_in_place = command_metadata
1576                    .and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "in_place"))
1577                    .unwrap_or(false);
1578                let skip_plugin_cache = command_metadata
1579                    .and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "skip_plugin_cache"))
1580                    .unwrap_or(false);
1581                let current_dir = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
1582                let configuration = KdlLayoutParser::parse_plugin_user_configuration(&kdl_action)?;
1583                let initial_cwd = kdl_get_string_property_or_child_value!(kdl_action, "cwd")
1584                    .map(|s| PathBuf::from(s));
1585                let run_plugin_or_alias = RunPluginOrAlias::from_url(
1586                    &plugin_path,
1587                    &Some(configuration.inner().clone()),
1588                    None,
1589                    Some(current_dir),
1590                )
1591                .map_err(|e| {
1592                    ConfigError::new_kdl_error(
1593                        format!("Failed to parse plugin: {}", e),
1594                        kdl_action.span().offset(),
1595                        kdl_action.span().len(),
1596                    )
1597                })?
1598                .with_initial_cwd(initial_cwd);
1599                Ok(Action::LaunchOrFocusPlugin(
1600                    run_plugin_or_alias,
1601                    should_float,
1602                    move_to_focused_tab,
1603                    should_open_in_place,
1604                    skip_plugin_cache,
1605                ))
1606            },
1607            "LaunchPlugin" => {
1608                let arguments = action_arguments.iter().copied();
1609                let mut args = kdl_arguments_that_are_strings(arguments)?;
1610                if args.is_empty() {
1611                    return Err(ConfigError::new_kdl_error(
1612                        "No plugin found to launch in LaunchPlugin".into(),
1613                        kdl_action.span().offset(),
1614                        kdl_action.span().len(),
1615                    ));
1616                }
1617                let plugin_path = args.remove(0);
1618
1619                let command_metadata = action_children.iter().next();
1620                let should_float = command_metadata
1621                    .and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "floating"))
1622                    .unwrap_or(false);
1623                let should_open_in_place = command_metadata
1624                    .and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "in_place"))
1625                    .unwrap_or(false);
1626                let skip_plugin_cache = command_metadata
1627                    .and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "skip_plugin_cache"))
1628                    .unwrap_or(false);
1629                let current_dir = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
1630                let configuration = KdlLayoutParser::parse_plugin_user_configuration(&kdl_action)?;
1631                let run_plugin_or_alias = RunPluginOrAlias::from_url(
1632                    &plugin_path,
1633                    &Some(configuration.inner().clone()),
1634                    None,
1635                    Some(current_dir),
1636                )
1637                .map_err(|e| {
1638                    ConfigError::new_kdl_error(
1639                        format!("Failed to parse plugin: {}", e),
1640                        kdl_action.span().offset(),
1641                        kdl_action.span().len(),
1642                    )
1643                })?;
1644                Ok(Action::LaunchPlugin(
1645                    run_plugin_or_alias,
1646                    should_float,
1647                    should_open_in_place,
1648                    skip_plugin_cache,
1649                    None, // we explicitly do not send the current dir here so that it will be
1650                          // filled from the active pane == better UX
1651                ))
1652            },
1653            "PreviousSwapLayout" => Ok(Action::PreviousSwapLayout),
1654            "NextSwapLayout" => Ok(Action::NextSwapLayout),
1655            "BreakPane" => Ok(Action::BreakPane),
1656            "BreakPaneRight" => Ok(Action::BreakPaneRight),
1657            "BreakPaneLeft" => Ok(Action::BreakPaneLeft),
1658            "RenameSession" => parse_kdl_action_char_or_string_arguments!(
1659                action_name,
1660                action_arguments,
1661                kdl_action
1662            ),
1663            "MessagePlugin" => {
1664                let arguments = action_arguments.iter().copied();
1665                let mut args = kdl_arguments_that_are_strings(arguments)?;
1666                let plugin_path = if args.is_empty() {
1667                    None
1668                } else {
1669                    Some(args.remove(0))
1670                };
1671
1672                let command_metadata = action_children.iter().next();
1673                let launch_new = command_metadata
1674                    .and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "launch_new"))
1675                    .unwrap_or(false);
1676                let skip_cache = command_metadata
1677                    .and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "skip_cache"))
1678                    .unwrap_or(false);
1679                let should_float = command_metadata
1680                    .and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "floating"))
1681                    .unwrap_or(false);
1682                let name = command_metadata
1683                    .and_then(|c_m| kdl_child_string_value_for_entry(c_m, "name"))
1684                    .map(|n| n.to_owned());
1685                let payload = command_metadata
1686                    .and_then(|c_m| kdl_child_string_value_for_entry(c_m, "payload"))
1687                    .map(|p| p.to_owned());
1688                let title = command_metadata
1689                    .and_then(|c_m| kdl_child_string_value_for_entry(c_m, "title"))
1690                    .map(|t| t.to_owned());
1691                let configuration = KdlLayoutParser::parse_plugin_user_configuration(&kdl_action)?;
1692                let configuration = if configuration.inner().is_empty() {
1693                    None
1694                } else {
1695                    Some(configuration.inner().clone())
1696                };
1697                let cwd = kdl_get_string_property_or_child_value!(kdl_action, "cwd")
1698                    .map(|s| PathBuf::from(s));
1699
1700                let name = name
1701                    // first we try to take the explicitly supplied message name
1702                    // then we use the plugin, to facilitate using aliases
1703                    .or_else(|| plugin_path.clone())
1704                    // then we use a uuid to at least have some sort of identifier for this message
1705                    .or_else(|| Some(Uuid::new_v4().to_string()));
1706
1707                Ok(Action::KeybindPipe {
1708                    name,
1709                    payload,
1710                    args: None, // TODO: consider supporting this if there's a need
1711                    plugin: plugin_path,
1712                    configuration,
1713                    launch_new,
1714                    skip_cache,
1715                    floating: Some(should_float),
1716                    in_place: None, // TODO: support this
1717                    cwd,
1718                    pane_title: title,
1719                    plugin_id: None,
1720                })
1721            },
1722            "MessagePluginId" => {
1723                let arguments = action_arguments.iter().copied();
1724                let mut args = kdl_arguments_that_are_digits(arguments)?;
1725                let plugin_id = if args.is_empty() {
1726                    None
1727                } else {
1728                    Some(args.remove(0) as u32)
1729                };
1730
1731                let command_metadata = action_children.iter().next();
1732                let launch_new = false;
1733                let skip_cache = false;
1734                let name = command_metadata
1735                    .and_then(|c_m| kdl_child_string_value_for_entry(c_m, "name"))
1736                    .map(|n| n.to_owned());
1737                let payload = command_metadata
1738                    .and_then(|c_m| kdl_child_string_value_for_entry(c_m, "payload"))
1739                    .map(|p| p.to_owned());
1740                let configuration = None;
1741
1742                let name = name
1743                    // if no name is provided, we use a uuid to at least have some sort of identifier for this message
1744                    .or_else(|| Some(Uuid::new_v4().to_string()));
1745
1746                Ok(Action::KeybindPipe {
1747                    name,
1748                    payload,
1749                    args: None, // TODO: consider supporting this if there's a need
1750                    plugin: None,
1751                    configuration,
1752                    launch_new,
1753                    skip_cache,
1754                    floating: None,
1755                    in_place: None, // TODO: support this
1756                    cwd: None,
1757                    pane_title: None,
1758                    plugin_id,
1759                })
1760            },
1761            _ => Err(ConfigError::new_kdl_error(
1762                format!("Unsupported action: {}", action_name).into(),
1763                kdl_action.span().offset(),
1764                kdl_action.span().len(),
1765            )),
1766        }
1767    }
1768}
1769
1770#[macro_export]
1771macro_rules! kdl_property_first_arg_as_string {
1772    ( $kdl_node:expr, $property_name:expr ) => {
1773        $kdl_node
1774            .get($property_name)
1775            .and_then(|p| p.entries().iter().next())
1776            .and_then(|p| p.value().as_string())
1777    };
1778}
1779
1780#[macro_export]
1781macro_rules! kdl_property_first_arg_as_string_or_error {
1782    ( $kdl_node:expr, $property_name:expr ) => {{
1783        match $kdl_node.get($property_name) {
1784            Some(property) => match property.entries().iter().next() {
1785                Some(first_entry) => match first_entry.value().as_string() {
1786                    Some(string_entry) => Some((string_entry, first_entry)),
1787                    None => {
1788                        return Err(ConfigError::new_kdl_error(
1789                            format!(
1790                                "Property {} must be a string, found: {}",
1791                                $property_name,
1792                                first_entry.value()
1793                            ),
1794                            property.span().offset(),
1795                            property.span().len(),
1796                        ));
1797                    },
1798                },
1799                None => {
1800                    return Err(ConfigError::new_kdl_error(
1801                        format!("Property {} must have a value", $property_name),
1802                        property.span().offset(),
1803                        property.span().len(),
1804                    ));
1805                },
1806            },
1807            None => None,
1808        }
1809    }};
1810}
1811
1812#[macro_export]
1813macro_rules! kdl_property_first_arg_as_bool_or_error {
1814    ( $kdl_node:expr, $property_name:expr ) => {{
1815        match $kdl_node.get($property_name) {
1816            Some(property) => match property.entries().iter().next() {
1817                Some(first_entry) => match first_entry.value().as_bool() {
1818                    Some(bool_entry) => Some((bool_entry, first_entry)),
1819                    None => {
1820                        return Err(ConfigError::new_kdl_error(
1821                            format!(
1822                                "Property {} must be true or false, found {}",
1823                                $property_name,
1824                                first_entry.value()
1825                            ),
1826                            property.span().offset(),
1827                            property.span().len(),
1828                        ));
1829                    },
1830                },
1831                None => {
1832                    return Err(ConfigError::new_kdl_error(
1833                        format!("Property {} must have a value", $property_name),
1834                        property.span().offset(),
1835                        property.span().len(),
1836                    ));
1837                },
1838            },
1839            None => None,
1840        }
1841    }};
1842}
1843
1844#[macro_export]
1845macro_rules! kdl_property_first_arg_as_i64_or_error {
1846    ( $kdl_node:expr, $property_name:expr ) => {{
1847        match $kdl_node.get($property_name) {
1848            Some(property) => match property.entries().iter().next() {
1849                Some(first_entry) => match first_entry.value().as_i64() {
1850                    Some(int_entry) => Some((int_entry, first_entry)),
1851                    None => {
1852                        return Err(ConfigError::new_kdl_error(
1853                            format!(
1854                                "Property {} must be numeric, found {}",
1855                                $property_name,
1856                                first_entry.value()
1857                            ),
1858                            property.span().offset(),
1859                            property.span().len(),
1860                        ));
1861                    },
1862                },
1863                None => {
1864                    return Err(ConfigError::new_kdl_error(
1865                        format!("Property {} must have a value", $property_name),
1866                        property.span().offset(),
1867                        property.span().len(),
1868                    ));
1869                },
1870            },
1871            None => None,
1872        }
1873    }};
1874}
1875
1876#[macro_export]
1877macro_rules! kdl_has_string_argument {
1878    ( $kdl_node:expr, $string_argument:expr ) => {
1879        $kdl_node
1880            .entries()
1881            .iter()
1882            .find(|e| e.value().as_string() == Some($string_argument))
1883            .is_some()
1884    };
1885}
1886
1887#[macro_export]
1888macro_rules! kdl_children_property_first_arg_as_string {
1889    ( $kdl_node:expr, $property_name:expr ) => {
1890        $kdl_node
1891            .children()
1892            .and_then(|c| c.get($property_name))
1893            .and_then(|p| p.entries().iter().next())
1894            .and_then(|p| p.value().as_string())
1895    };
1896}
1897
1898#[macro_export]
1899macro_rules! kdl_property_first_arg_as_bool {
1900    ( $kdl_node:expr, $property_name:expr ) => {
1901        $kdl_node
1902            .get($property_name)
1903            .and_then(|p| p.entries().iter().next())
1904            .and_then(|p| p.value().as_bool())
1905    };
1906}
1907
1908#[macro_export]
1909macro_rules! kdl_children_property_first_arg_as_bool {
1910    ( $kdl_node:expr, $property_name:expr ) => {
1911        $kdl_node
1912            .children()
1913            .and_then(|c| c.get($property_name))
1914            .and_then(|p| p.entries().iter().next())
1915            .and_then(|p| p.value().as_bool())
1916    };
1917}
1918
1919#[macro_export]
1920macro_rules! kdl_property_first_arg_as_i64 {
1921    ( $kdl_node:expr, $property_name:expr ) => {
1922        $kdl_node
1923            .get($property_name)
1924            .and_then(|p| p.entries().iter().next())
1925            .and_then(|p| p.value().as_i64())
1926    };
1927}
1928
1929#[macro_export]
1930macro_rules! kdl_get_child {
1931    ( $kdl_node:expr, $child_name:expr ) => {
1932        $kdl_node.children().and_then(|c| c.get($child_name))
1933    };
1934}
1935
1936#[macro_export]
1937macro_rules! kdl_get_child_entry_bool_value {
1938    ( $kdl_node:expr, $child_name:expr ) => {
1939        $kdl_node
1940            .children()
1941            .and_then(|c| c.get($child_name))
1942            .and_then(|c| c.get(0))
1943            .and_then(|c| c.value().as_bool())
1944    };
1945}
1946
1947#[macro_export]
1948macro_rules! kdl_get_child_entry_string_value {
1949    ( $kdl_node:expr, $child_name:expr ) => {
1950        $kdl_node
1951            .children()
1952            .and_then(|c| c.get($child_name))
1953            .and_then(|c| c.get(0))
1954            .and_then(|c| c.value().as_string())
1955    };
1956}
1957
1958#[macro_export]
1959macro_rules! kdl_get_bool_property_or_child_value {
1960    ( $kdl_node:expr, $name:expr ) => {
1961        $kdl_node
1962            .get($name)
1963            .and_then(|e| e.value().as_bool())
1964            .or_else(|| {
1965                $kdl_node
1966                    .children()
1967                    .and_then(|c| c.get($name))
1968                    .and_then(|c| c.get(0))
1969                    .and_then(|c| c.value().as_bool())
1970            })
1971    };
1972}
1973
1974#[macro_export]
1975macro_rules! kdl_get_bool_property_or_child_value_with_error {
1976    ( $kdl_node:expr, $name:expr ) => {
1977        match $kdl_node.get($name) {
1978            Some(e) => match e.value().as_bool() {
1979                Some(bool_value) => Some(bool_value),
1980                None => {
1981                    return Err(kdl_parsing_error!(
1982                        format!(
1983                            "{} should be either true or false, found {}",
1984                            $name,
1985                            e.value()
1986                        ),
1987                        e
1988                    ))
1989                },
1990            },
1991            None => {
1992                let child_value = $kdl_node
1993                    .children()
1994                    .and_then(|c| c.get($name))
1995                    .and_then(|c| c.get(0));
1996                match child_value {
1997                    Some(e) => match e.value().as_bool() {
1998                        Some(bool_value) => Some(bool_value),
1999                        None => {
2000                            return Err(kdl_parsing_error!(
2001                                format!(
2002                                    "{} should be either true or false, found {}",
2003                                    $name,
2004                                    e.value()
2005                                ),
2006                                e
2007                            ))
2008                        },
2009                    },
2010                    None => {
2011                        if let Some(child_node) = kdl_child_with_name!($kdl_node, $name) {
2012                            return Err(kdl_parsing_error!(
2013                                format!(
2014                                    "{} must have a value, eg. '{} true'",
2015                                    child_node.name().value(),
2016                                    child_node.name().value()
2017                                ),
2018                                child_node
2019                            ));
2020                        }
2021                        None
2022                    },
2023                }
2024            },
2025        }
2026    };
2027}
2028
2029#[macro_export]
2030macro_rules! kdl_property_or_child_value_node {
2031    ( $kdl_node:expr, $name:expr ) => {
2032        $kdl_node.get($name).or_else(|| {
2033            $kdl_node
2034                .children()
2035                .and_then(|c| c.get($name))
2036                .and_then(|c| c.get(0))
2037        })
2038    };
2039}
2040
2041#[macro_export]
2042macro_rules! kdl_child_with_name {
2043    ( $kdl_node:expr, $name:expr ) => {{
2044        $kdl_node
2045            .children()
2046            .and_then(|children| children.nodes().iter().find(|c| c.name().value() == $name))
2047    }};
2048}
2049
2050#[macro_export]
2051macro_rules! kdl_get_string_property_or_child_value_with_error {
2052    ( $kdl_node:expr, $name:expr ) => {
2053        match $kdl_node.get($name) {
2054            Some(e) => match e.value().as_string() {
2055                Some(string_value) => Some(string_value),
2056                None => {
2057                    return Err(kdl_parsing_error!(
2058                        format!(
2059                            "{} should be a string, found {} - not a string",
2060                            $name,
2061                            e.value()
2062                        ),
2063                        e
2064                    ))
2065                },
2066            },
2067            None => {
2068                let child_value = $kdl_node
2069                    .children()
2070                    .and_then(|c| c.get($name))
2071                    .and_then(|c| c.get(0));
2072                match child_value {
2073                    Some(e) => match e.value().as_string() {
2074                        Some(string_value) => Some(string_value),
2075                        None => {
2076                            return Err(kdl_parsing_error!(
2077                                format!(
2078                                    "{} should be a string, found {} - not a string",
2079                                    $name,
2080                                    e.value()
2081                                ),
2082                                e
2083                            ))
2084                        },
2085                    },
2086                    None => {
2087                        if let Some(child_node) = kdl_child_with_name!($kdl_node, $name) {
2088                            return Err(kdl_parsing_error!(
2089                                format!(
2090                                    "{} must have a value, eg. '{} \"foo\"'",
2091                                    child_node.name().value(),
2092                                    child_node.name().value()
2093                                ),
2094                                child_node
2095                            ));
2096                        }
2097                        None
2098                    },
2099                }
2100            },
2101        }
2102    };
2103}
2104
2105#[macro_export]
2106macro_rules! kdl_get_property_or_child {
2107    ( $kdl_node:expr, $name:expr ) => {
2108        $kdl_node.get($name).or_else(|| {
2109            $kdl_node
2110                .children()
2111                .and_then(|c| c.get($name))
2112                .and_then(|c| c.get(0))
2113        })
2114    };
2115}
2116
2117#[macro_export]
2118macro_rules! kdl_get_int_property_or_child_value {
2119    ( $kdl_node:expr, $name:expr ) => {
2120        $kdl_node
2121            .get($name)
2122            .and_then(|e| e.value().as_i64())
2123            .or_else(|| {
2124                $kdl_node
2125                    .children()
2126                    .and_then(|c| c.get($name))
2127                    .and_then(|c| c.get(0))
2128                    .and_then(|c| c.value().as_i64())
2129            })
2130    };
2131}
2132
2133#[macro_export]
2134macro_rules! kdl_get_string_entry {
2135    ( $kdl_node:expr, $entry_name:expr ) => {
2136        $kdl_node
2137            .get($entry_name)
2138            .and_then(|e| e.value().as_string())
2139    };
2140}
2141
2142#[macro_export]
2143macro_rules! kdl_get_int_entry {
2144    ( $kdl_node:expr, $entry_name:expr ) => {
2145        $kdl_node.get($entry_name).and_then(|e| e.value().as_i64())
2146    };
2147}
2148
2149impl Options {
2150    pub fn from_kdl(kdl_options: &KdlDocument) -> Result<Self, ConfigError> {
2151        let on_force_close =
2152            match kdl_property_first_arg_as_string_or_error!(kdl_options, "on_force_close") {
2153                Some((string, entry)) => Some(OnForceClose::from_str(string).map_err(|_| {
2154                    kdl_parsing_error!(
2155                        format!("Invalid value for on_force_close: '{}'", string),
2156                        entry
2157                    )
2158                })?),
2159                None => None,
2160            };
2161        let simplified_ui =
2162            kdl_property_first_arg_as_bool_or_error!(kdl_options, "simplified_ui").map(|(v, _)| v);
2163        let default_shell =
2164            kdl_property_first_arg_as_string_or_error!(kdl_options, "default_shell")
2165                .map(|(string, _entry)| PathBuf::from(string));
2166        let default_cwd = kdl_property_first_arg_as_string_or_error!(kdl_options, "default_cwd")
2167            .map(|(string, _entry)| PathBuf::from(string));
2168        let pane_frames =
2169            kdl_property_first_arg_as_bool_or_error!(kdl_options, "pane_frames").map(|(v, _)| v);
2170        let auto_layout =
2171            kdl_property_first_arg_as_bool_or_error!(kdl_options, "auto_layout").map(|(v, _)| v);
2172        let theme = kdl_property_first_arg_as_string_or_error!(kdl_options, "theme")
2173            .map(|(theme, _entry)| theme.to_string());
2174        let default_mode =
2175            match kdl_property_first_arg_as_string_or_error!(kdl_options, "default_mode") {
2176                Some((string, entry)) => Some(InputMode::from_str(string).map_err(|_| {
2177                    kdl_parsing_error!(format!("Invalid input mode: '{}'", string), entry)
2178                })?),
2179                None => None,
2180            };
2181        let default_layout =
2182            kdl_property_first_arg_as_string_or_error!(kdl_options, "default_layout")
2183                .map(|(string, _entry)| PathBuf::from(string));
2184        let layout_dir = kdl_property_first_arg_as_string_or_error!(kdl_options, "layout_dir")
2185            .map(|(string, _entry)| PathBuf::from(string));
2186        let theme_dir = kdl_property_first_arg_as_string_or_error!(kdl_options, "theme_dir")
2187            .map(|(string, _entry)| PathBuf::from(string));
2188        let mouse_mode =
2189            kdl_property_first_arg_as_bool_or_error!(kdl_options, "mouse_mode").map(|(v, _)| v);
2190        let scroll_buffer_size =
2191            kdl_property_first_arg_as_i64_or_error!(kdl_options, "scroll_buffer_size")
2192                .map(|(scroll_buffer_size, _entry)| scroll_buffer_size as usize);
2193        let copy_command = kdl_property_first_arg_as_string_or_error!(kdl_options, "copy_command")
2194            .map(|(copy_command, _entry)| copy_command.to_string());
2195        let copy_clipboard =
2196            match kdl_property_first_arg_as_string_or_error!(kdl_options, "copy_clipboard") {
2197                Some((string, entry)) => Some(Clipboard::from_str(string).map_err(|_| {
2198                    kdl_parsing_error!(
2199                        format!("Invalid value for copy_clipboard: '{}'", string),
2200                        entry
2201                    )
2202                })?),
2203                None => None,
2204            };
2205        let copy_on_select =
2206            kdl_property_first_arg_as_bool_or_error!(kdl_options, "copy_on_select").map(|(v, _)| v);
2207        let scrollback_editor =
2208            kdl_property_first_arg_as_string_or_error!(kdl_options, "scrollback_editor")
2209                .map(|(string, _entry)| PathBuf::from(string));
2210        let mirror_session =
2211            kdl_property_first_arg_as_bool_or_error!(kdl_options, "mirror_session").map(|(v, _)| v);
2212        let session_name = kdl_property_first_arg_as_string_or_error!(kdl_options, "session_name")
2213            .map(|(session_name, _entry)| session_name.to_string());
2214        let attach_to_session =
2215            kdl_property_first_arg_as_bool_or_error!(kdl_options, "attach_to_session")
2216                .map(|(v, _)| v);
2217        let session_serialization =
2218            kdl_property_first_arg_as_bool_or_error!(kdl_options, "session_serialization")
2219                .map(|(v, _)| v);
2220        let serialize_pane_viewport =
2221            kdl_property_first_arg_as_bool_or_error!(kdl_options, "serialize_pane_viewport")
2222                .map(|(v, _)| v);
2223        let scrollback_lines_to_serialize =
2224            kdl_property_first_arg_as_i64_or_error!(kdl_options, "scrollback_lines_to_serialize")
2225                .map(|(v, _)| v as usize);
2226        let styled_underlines =
2227            kdl_property_first_arg_as_bool_or_error!(kdl_options, "styled_underlines")
2228                .map(|(v, _)| v);
2229        let serialization_interval =
2230            kdl_property_first_arg_as_i64_or_error!(kdl_options, "serialization_interval")
2231                .map(|(scroll_buffer_size, _entry)| scroll_buffer_size as u64);
2232        let disable_session_metadata =
2233            kdl_property_first_arg_as_bool_or_error!(kdl_options, "disable_session_metadata")
2234                .map(|(v, _)| v);
2235        let support_kitty_keyboard_protocol = kdl_property_first_arg_as_bool_or_error!(
2236            kdl_options,
2237            "support_kitty_keyboard_protocol"
2238        )
2239        .map(|(v, _)| v);
2240        Ok(Options {
2241            simplified_ui,
2242            theme,
2243            default_mode,
2244            default_shell,
2245            default_cwd,
2246            default_layout,
2247            layout_dir,
2248            theme_dir,
2249            mouse_mode,
2250            pane_frames,
2251            mirror_session,
2252            on_force_close,
2253            scroll_buffer_size,
2254            copy_command,
2255            copy_clipboard,
2256            copy_on_select,
2257            scrollback_editor,
2258            session_name,
2259            attach_to_session,
2260            auto_layout,
2261            session_serialization,
2262            serialize_pane_viewport,
2263            scrollback_lines_to_serialize,
2264            styled_underlines,
2265            serialization_interval,
2266            disable_session_metadata,
2267            support_kitty_keyboard_protocol,
2268        })
2269    }
2270    pub fn from_string(stringified_keybindings: &String) -> Result<Self, ConfigError> {
2271        let document: KdlDocument = stringified_keybindings.parse()?;
2272        Options::from_kdl(&document)
2273    }
2274    fn simplified_ui_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
2275        let comment_text = format!(
2276            "{}\n{}\n{}\n{}\n{}\n{}",
2277            " ",
2278            "// Use a simplified UI without special fonts (arrow glyphs)",
2279            "// Options:",
2280            "//   - true",
2281            "//   - false (Default)",
2282            "// ",
2283        );
2284
2285        let create_node = |node_value: bool| -> KdlNode {
2286            let mut node = KdlNode::new("simplified_ui");
2287            node.push(KdlValue::Bool(node_value));
2288            node
2289        };
2290        if let Some(simplified_ui) = self.simplified_ui {
2291            let mut node = create_node(simplified_ui);
2292            if add_comments {
2293                node.set_leading(format!("{}\n", comment_text));
2294            }
2295            Some(node)
2296        } else if add_comments {
2297            let mut node = create_node(true);
2298            node.set_leading(format!("{}\n// ", comment_text));
2299            Some(node)
2300        } else {
2301            None
2302        }
2303    }
2304    fn theme_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
2305        let comment_text = format!(
2306            "{}\n{}\n{}\n{}",
2307            " ",
2308            "// Choose the theme that is specified in the themes section.",
2309            "// Default: default",
2310            "// ",
2311        );
2312
2313        let create_node = |node_value: &str| -> KdlNode {
2314            let mut node = KdlNode::new("theme");
2315            node.push(node_value.to_owned());
2316            node
2317        };
2318        if let Some(theme) = &self.theme {
2319            let mut node = create_node(theme);
2320            if add_comments {
2321                node.set_leading(format!("{}\n", comment_text));
2322            }
2323            Some(node)
2324        } else if add_comments {
2325            let mut node = create_node("dracula");
2326            node.set_leading(format!("{}\n// ", comment_text));
2327            Some(node)
2328        } else {
2329            None
2330        }
2331    }
2332    fn default_mode_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
2333        let comment_text = format!(
2334            "{}\n{}\n{}\n{}",
2335            " ", "// Choose the base input mode of zellij.", "// Default: normal", "// "
2336        );
2337
2338        let create_node = |default_mode: &InputMode| -> KdlNode {
2339            let mut node = KdlNode::new("default_mode");
2340            node.push(format!("{:?}", default_mode).to_lowercase());
2341            node
2342        };
2343        if let Some(default_mode) = &self.default_mode {
2344            let mut node = create_node(default_mode);
2345            if add_comments {
2346                node.set_leading(format!("{}\n", comment_text));
2347            }
2348            Some(node)
2349        } else if add_comments {
2350            let mut node = create_node(&InputMode::Locked);
2351            node.set_leading(format!("{}\n// ", comment_text));
2352            Some(node)
2353        } else {
2354            None
2355        }
2356    }
2357    fn default_shell_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
2358        let comment_text =
2359            format!("{}\n{}\n{}\n{}",
2360            " ",
2361            "// Choose the path to the default shell that zellij will use for opening new panes",
2362            "// Default: $SHELL",
2363            "// ",
2364        );
2365
2366        let create_node = |node_value: &str| -> KdlNode {
2367            let mut node = KdlNode::new("default_shell");
2368            node.push(node_value.to_owned());
2369            node
2370        };
2371        if let Some(default_shell) = &self.default_shell {
2372            let mut node = create_node(&default_shell.display().to_string());
2373            if add_comments {
2374                node.set_leading(format!("{}\n", comment_text));
2375            }
2376            Some(node)
2377        } else if add_comments {
2378            let mut node = create_node("fish");
2379            node.set_leading(format!("{}\n// ", comment_text));
2380            Some(node)
2381        } else {
2382            None
2383        }
2384    }
2385    fn default_cwd_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
2386        let comment_text = format!(
2387            "{}\n{}\n{}",
2388            " ",
2389            "// Choose the path to override cwd that zellij will use for opening new panes",
2390            "// ",
2391        );
2392
2393        let create_node = |node_value: &str| -> KdlNode {
2394            let mut node = KdlNode::new("default_cwd");
2395            node.push(node_value.to_owned());
2396            node
2397        };
2398        if let Some(default_cwd) = &self.default_cwd {
2399            let mut node = create_node(&default_cwd.display().to_string());
2400            if add_comments {
2401                node.set_leading(format!("{}\n", comment_text));
2402            }
2403            Some(node)
2404        } else if add_comments {
2405            let mut node = create_node("/tmp");
2406            node.set_leading(format!("{}\n// ", comment_text));
2407            Some(node)
2408        } else {
2409            None
2410        }
2411    }
2412    fn default_layout_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
2413        let comment_text = format!(
2414            "{}\n{}\n{}\n{}",
2415            " ",
2416            "// The name of the default layout to load on startup",
2417            "// Default: \"default\"",
2418            "// ",
2419        );
2420
2421        let create_node = |node_value: &str| -> KdlNode {
2422            let mut node = KdlNode::new("default_layout");
2423            node.push(node_value.to_owned());
2424            node
2425        };
2426        if let Some(default_layout) = &self.default_layout {
2427            let mut node = create_node(&default_layout.display().to_string());
2428            if add_comments {
2429                node.set_leading(format!("{}\n", comment_text));
2430            }
2431            Some(node)
2432        } else if add_comments {
2433            let mut node = create_node("compact");
2434            node.set_leading(format!("{}\n// ", comment_text));
2435            Some(node)
2436        } else {
2437            None
2438        }
2439    }
2440    fn layout_dir_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
2441        let comment_text = format!(
2442            "{}\n{}\n{}\n{}",
2443            " ",
2444            "// The folder in which Zellij will look for layouts",
2445            "// (Requires restart)",
2446            "// ",
2447        );
2448
2449        let create_node = |node_value: &str| -> KdlNode {
2450            let mut node = KdlNode::new("layout_dir");
2451            node.push(node_value.to_owned());
2452            node
2453        };
2454        if let Some(layout_dir) = &self.layout_dir {
2455            let mut node = create_node(&layout_dir.display().to_string());
2456            if add_comments {
2457                node.set_leading(format!("{}\n", comment_text));
2458            }
2459            Some(node)
2460        } else if add_comments {
2461            let mut node = create_node("/tmp");
2462            node.set_leading(format!("{}\n// ", comment_text));
2463            Some(node)
2464        } else {
2465            None
2466        }
2467    }
2468    fn theme_dir_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
2469        let comment_text = format!(
2470            "{}\n{}\n{}\n{}",
2471            " ",
2472            "// The folder in which Zellij will look for themes",
2473            "// (Requires restart)",
2474            "// ",
2475        );
2476
2477        let create_node = |node_value: &str| -> KdlNode {
2478            let mut node = KdlNode::new("theme_dir");
2479            node.push(node_value.to_owned());
2480            node
2481        };
2482        if let Some(theme_dir) = &self.theme_dir {
2483            let mut node = create_node(&theme_dir.display().to_string());
2484            if add_comments {
2485                node.set_leading(format!("{}\n", comment_text));
2486            }
2487            Some(node)
2488        } else if add_comments {
2489            let mut node = create_node("/tmp");
2490            node.set_leading(format!("{}\n// ", comment_text));
2491            Some(node)
2492        } else {
2493            None
2494        }
2495    }
2496    fn mouse_mode_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
2497        let comment_text = format!(
2498            "{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}",
2499            " ",
2500            "// Toggle enabling the mouse mode.",
2501            "// On certain configurations, or terminals this could",
2502            "// potentially interfere with copying text.",
2503            "// Options:",
2504            "//   - true (default)",
2505            "//   - false",
2506            "// ",
2507        );
2508
2509        let create_node = |node_value: bool| -> KdlNode {
2510            let mut node = KdlNode::new("mouse_mode");
2511            node.push(KdlValue::Bool(node_value));
2512            node
2513        };
2514        if let Some(mouse_mode) = self.mouse_mode {
2515            let mut node = create_node(mouse_mode);
2516            if add_comments {
2517                node.set_leading(format!("{}\n", comment_text));
2518            }
2519            Some(node)
2520        } else if add_comments {
2521            let mut node = create_node(false);
2522            node.set_leading(format!("{}\n// ", comment_text));
2523            Some(node)
2524        } else {
2525            None
2526        }
2527    }
2528    fn pane_frames_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
2529        let comment_text = format!(
2530            "{}\n{}\n{}\n{}\n{}\n{}",
2531            " ",
2532            "// Toggle having pane frames around the panes",
2533            "// Options:",
2534            "//   - true (default, enabled)",
2535            "//   - false",
2536            "// ",
2537        );
2538
2539        let create_node = |node_value: bool| -> KdlNode {
2540            let mut node = KdlNode::new("pane_frames");
2541            node.push(KdlValue::Bool(node_value));
2542            node
2543        };
2544        if let Some(pane_frames) = self.pane_frames {
2545            let mut node = create_node(pane_frames);
2546            if add_comments {
2547                node.set_leading(format!("{}\n", comment_text));
2548            }
2549            Some(node)
2550        } else if add_comments {
2551            let mut node = create_node(false);
2552            node.set_leading(format!("{}\n// ", comment_text));
2553            Some(node)
2554        } else {
2555            None
2556        }
2557    }
2558    fn mirror_session_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
2559        let comment_text = format!(
2560            "{}\n{}\n{}\n{}\n{}\n{}\n{}",
2561            " ",
2562            "// When attaching to an existing session with other users,",
2563            "// should the session be mirrored (true)",
2564            "// or should each user have their own cursor (false)",
2565            "// (Requires restart)",
2566            "// Default: false",
2567            "// ",
2568        );
2569
2570        let create_node = |node_value: bool| -> KdlNode {
2571            let mut node = KdlNode::new("mirror_session");
2572            node.push(KdlValue::Bool(node_value));
2573            node
2574        };
2575        if let Some(mirror_session) = self.mirror_session {
2576            let mut node = create_node(mirror_session);
2577            if add_comments {
2578                node.set_leading(format!("{}\n", comment_text));
2579            }
2580            Some(node)
2581        } else if add_comments {
2582            let mut node = create_node(true);
2583            node.set_leading(format!("{}\n// ", comment_text));
2584            Some(node)
2585        } else {
2586            None
2587        }
2588    }
2589    fn on_force_close_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
2590        let comment_text = format!(
2591            "{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}",
2592            " ",
2593            "// Choose what to do when zellij receives SIGTERM, SIGINT, SIGQUIT or SIGHUP",
2594            "// eg. when terminal window with an active zellij session is closed",
2595            "// (Requires restart)",
2596            "// Options:",
2597            "//   - detach (Default)",
2598            "//   - quit",
2599            "// ",
2600        );
2601
2602        let create_node = |node_value: &str| -> KdlNode {
2603            let mut node = KdlNode::new("on_force_close");
2604            node.push(node_value.to_owned());
2605            node
2606        };
2607        if let Some(on_force_close) = &self.on_force_close {
2608            let mut node = match on_force_close {
2609                OnForceClose::Detach => create_node("detach"),
2610                OnForceClose::Quit => create_node("quit"),
2611            };
2612            if add_comments {
2613                node.set_leading(format!("{}\n", comment_text));
2614            }
2615            Some(node)
2616        } else if add_comments {
2617            let mut node = create_node("quit");
2618            node.set_leading(format!("{}\n// ", comment_text));
2619            Some(node)
2620        } else {
2621            None
2622        }
2623    }
2624    fn scroll_buffer_size_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
2625        let comment_text = format!(
2626            "{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}",
2627            " ",
2628            "// Configure the scroll back buffer size",
2629            "// This is the number of lines zellij stores for each pane in the scroll back",
2630            "// buffer. Excess number of lines are discarded in a FIFO fashion.",
2631            "// (Requires restart)",
2632            "// Valid values: positive integers",
2633            "// Default value: 10000",
2634            "// ",
2635        );
2636
2637        let create_node = |node_value: usize| -> KdlNode {
2638            let mut node = KdlNode::new("scroll_buffer_size");
2639            node.push(KdlValue::Base10(node_value as i64));
2640            node
2641        };
2642        if let Some(scroll_buffer_size) = self.scroll_buffer_size {
2643            let mut node = create_node(scroll_buffer_size);
2644            if add_comments {
2645                node.set_leading(format!("{}\n", comment_text));
2646            }
2647            Some(node)
2648        } else if add_comments {
2649            let mut node = create_node(10000);
2650            node.set_leading(format!("{}\n// ", comment_text));
2651            Some(node)
2652        } else {
2653            None
2654        }
2655    }
2656    fn copy_command_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
2657        let comment_text = format!(
2658            "{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}",
2659            " ",
2660            "// Provide a command to execute when copying text. The text will be piped to",
2661            "// the stdin of the program to perform the copy. This can be used with",
2662            "// terminal emulators which do not support the OSC 52 ANSI control sequence",
2663            "// that will be used by default if this option is not set.",
2664            "// Examples:",
2665            "//",
2666            "// copy_command \"xclip -selection clipboard\" // x11",
2667            "// copy_command \"wl-copy\"                    // wayland",
2668            "// copy_command \"pbcopy\"                     // osx",
2669            "// ",
2670        );
2671
2672        let create_node = |node_value: &str| -> KdlNode {
2673            let mut node = KdlNode::new("copy_command");
2674            node.push(node_value.to_owned());
2675            node
2676        };
2677        if let Some(copy_command) = &self.copy_command {
2678            let mut node = create_node(copy_command);
2679            if add_comments {
2680                node.set_leading(format!("{}\n", comment_text));
2681            }
2682            Some(node)
2683        } else if add_comments {
2684            let mut node = create_node("pbcopy");
2685            node.set_leading(format!("{}\n// ", comment_text));
2686            Some(node)
2687        } else {
2688            None
2689        }
2690    }
2691    fn copy_clipboard_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
2692        let comment_text = format!("{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}",
2693            " ",
2694            "// Choose the destination for copied text",
2695            "// Allows using the primary selection buffer (on x11/wayland) instead of the system clipboard.",
2696            "// Does not apply when using copy_command.",
2697            "// Options:",
2698            "//   - system (default)",
2699            "//   - primary",
2700            "// ",
2701        );
2702
2703        let create_node = |node_value: &str| -> KdlNode {
2704            let mut node = KdlNode::new("copy_clipboard");
2705            node.push(node_value.to_owned());
2706            node
2707        };
2708        if let Some(copy_clipboard) = &self.copy_clipboard {
2709            let mut node = match copy_clipboard {
2710                Clipboard::Primary => create_node("primary"),
2711                Clipboard::System => create_node("system"),
2712            };
2713            if add_comments {
2714                node.set_leading(format!("{}\n", comment_text));
2715            }
2716            Some(node)
2717        } else if add_comments {
2718            let mut node = create_node("primary");
2719            node.set_leading(format!("{}\n// ", comment_text));
2720            Some(node)
2721        } else {
2722            None
2723        }
2724    }
2725    fn copy_on_select_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
2726        let comment_text = format!(
2727            "{}\n{}\n{}\n{}",
2728            " ",
2729            "// Enable automatic copying (and clearing) of selection when releasing mouse",
2730            "// Default: true",
2731            "// ",
2732        );
2733
2734        let create_node = |node_value: bool| -> KdlNode {
2735            let mut node = KdlNode::new("copy_on_select");
2736            node.push(KdlValue::Bool(node_value));
2737            node
2738        };
2739        if let Some(copy_on_select) = self.copy_on_select {
2740            let mut node = create_node(copy_on_select);
2741            if add_comments {
2742                node.set_leading(format!("{}\n", comment_text));
2743            }
2744            Some(node)
2745        } else if add_comments {
2746            let mut node = create_node(true);
2747            node.set_leading(format!("{}\n// ", comment_text));
2748            Some(node)
2749        } else {
2750            None
2751        }
2752    }
2753    fn scrollback_editor_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
2754        let comment_text = format!(
2755            "{}\n{}\n{}",
2756            " ",
2757            "// Path to the default editor to use to edit pane scrollbuffer",
2758            "// Default: $EDITOR or $VISUAL",
2759        );
2760
2761        let create_node = |node_value: &str| -> KdlNode {
2762            let mut node = KdlNode::new("scrollback_editor");
2763            node.push(node_value.to_owned());
2764            node
2765        };
2766        if let Some(scrollback_editor) = &self.scrollback_editor {
2767            let mut node = create_node(&scrollback_editor.display().to_string());
2768            if add_comments {
2769                node.set_leading(format!("{}\n", comment_text));
2770            }
2771            Some(node)
2772        } else if add_comments {
2773            let mut node = create_node("/usr/bin/vim");
2774            node.set_leading(format!("{}\n// ", comment_text));
2775            Some(node)
2776        } else {
2777            None
2778        }
2779    }
2780    fn session_name_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
2781        let comment_text = format!(
2782            "{}\n{}\n{}\n{}\n{}\n{}",
2783            " ",
2784            "// A fixed name to always give the Zellij session.",
2785            "// Consider also setting `attach_to_session true,`",
2786            "// otherwise this will error if such a session exists.",
2787            "// Default: <RANDOM>",
2788            "// ",
2789        );
2790
2791        let create_node = |node_value: &str| -> KdlNode {
2792            let mut node = KdlNode::new("session_name");
2793            node.push(node_value.to_owned());
2794            node
2795        };
2796        if let Some(session_name) = &self.session_name {
2797            let mut node = create_node(&session_name);
2798            if add_comments {
2799                node.set_leading(format!("{}\n", comment_text));
2800            }
2801            Some(node)
2802        } else if add_comments {
2803            let mut node = create_node("My singleton session");
2804            node.set_leading(format!("{}\n// ", comment_text));
2805            Some(node)
2806        } else {
2807            None
2808        }
2809    }
2810    fn attach_to_session_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
2811        let comment_text = format!(
2812            "{}\n{}\n{}\n{}\n{}",
2813            " ",
2814            "// When `session_name` is provided, attaches to that session",
2815            "// if it is already running or creates it otherwise.",
2816            "// Default: false",
2817            "// ",
2818        );
2819
2820        let create_node = |node_value: bool| -> KdlNode {
2821            let mut node = KdlNode::new("attach_to_session");
2822            node.push(KdlValue::Bool(node_value));
2823            node
2824        };
2825        if let Some(attach_to_session) = self.attach_to_session {
2826            let mut node = create_node(attach_to_session);
2827            if add_comments {
2828                node.set_leading(format!("{}\n", comment_text));
2829            }
2830            Some(node)
2831        } else if add_comments {
2832            let mut node = create_node(true);
2833            node.set_leading(format!("{}\n// ", comment_text));
2834            Some(node)
2835        } else {
2836            None
2837        }
2838    }
2839    fn auto_layout_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
2840        let comment_text = format!("{}\n{}\n{}\n{}\n{}\n{}",
2841            " ",
2842            "// Toggle between having Zellij lay out panes according to a predefined set of layouts whenever possible",
2843            "// Options:",
2844            "//   - true (default)",
2845            "//   - false",
2846            "// ",
2847        );
2848
2849        let create_node = |node_value: bool| -> KdlNode {
2850            let mut node = KdlNode::new("auto_layout");
2851            node.push(KdlValue::Bool(node_value));
2852            node
2853        };
2854        if let Some(auto_layout) = self.auto_layout {
2855            let mut node = create_node(auto_layout);
2856            if add_comments {
2857                node.set_leading(format!("{}\n", comment_text));
2858            }
2859            Some(node)
2860        } else if add_comments {
2861            let mut node = create_node(false);
2862            node.set_leading(format!("{}\n// ", comment_text));
2863            Some(node)
2864        } else {
2865            None
2866        }
2867    }
2868    fn session_serialization_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
2869        let comment_text = format!("{}\n{}\n{}\n{}\n{}\n{}",
2870            " ",
2871            "// Whether sessions should be serialized to the cache folder (including their tabs/panes, cwds and running commands) so that they can later be resurrected",
2872            "// Options:",
2873            "//   - true (default)",
2874            "//   - false",
2875            "// ",
2876        );
2877
2878        let create_node = |node_value: bool| -> KdlNode {
2879            let mut node = KdlNode::new("session_serialization");
2880            node.push(KdlValue::Bool(node_value));
2881            node
2882        };
2883        if let Some(session_serialization) = self.session_serialization {
2884            let mut node = create_node(session_serialization);
2885            if add_comments {
2886                node.set_leading(format!("{}\n", comment_text));
2887            }
2888            Some(node)
2889        } else if add_comments {
2890            let mut node = create_node(false);
2891            node.set_leading(format!("{}\n// ", comment_text));
2892            Some(node)
2893        } else {
2894            None
2895        }
2896    }
2897    fn serialize_pane_viewport_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
2898        let comment_text = format!(
2899            "{}\n{}\n{}\n{}\n{}\n{}",
2900            " ",
2901            "// Whether pane viewports are serialized along with the session, default is false",
2902            "// Options:",
2903            "//   - true",
2904            "//   - false (default)",
2905            "// ",
2906        );
2907
2908        let create_node = |node_value: bool| -> KdlNode {
2909            let mut node = KdlNode::new("serialize_pane_viewport");
2910            node.push(KdlValue::Bool(node_value));
2911            node
2912        };
2913        if let Some(serialize_pane_viewport) = self.serialize_pane_viewport {
2914            let mut node = create_node(serialize_pane_viewport);
2915            if add_comments {
2916                node.set_leading(format!("{}\n", comment_text));
2917            }
2918            Some(node)
2919        } else if add_comments {
2920            let mut node = create_node(false);
2921            node.set_leading(format!("{}\n// ", comment_text));
2922            Some(node)
2923        } else {
2924            None
2925        }
2926    }
2927    fn scrollback_lines_to_serialize_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
2928        let comment_text = format!("{}\n{}\n{}\n{}\n{}",
2929            " ",
2930            "// Scrollback lines to serialize along with the pane viewport when serializing sessions, 0",
2931            "// defaults to the scrollback size. If this number is higher than the scrollback size, it will",
2932            "// also default to the scrollback size. This does nothing if `serialize_pane_viewport` is not true.",
2933            "// ",
2934        );
2935
2936        let create_node = |node_value: usize| -> KdlNode {
2937            let mut node = KdlNode::new("scrollback_lines_to_serialize");
2938            node.push(KdlValue::Base10(node_value as i64));
2939            node
2940        };
2941        if let Some(scrollback_lines_to_serialize) = self.scrollback_lines_to_serialize {
2942            let mut node = create_node(scrollback_lines_to_serialize);
2943            if add_comments {
2944                node.set_leading(format!("{}\n", comment_text));
2945            }
2946            Some(node)
2947        } else if add_comments {
2948            let mut node = create_node(10000);
2949            node.set_leading(format!("{}\n// ", comment_text));
2950            Some(node)
2951        } else {
2952            None
2953        }
2954    }
2955    fn styled_underlines_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
2956        let comment_text = format!(
2957            "{}\n{}\n{}\n{}\n{}\n{}",
2958            " ",
2959            "// Enable or disable the rendering of styled and colored underlines (undercurl).",
2960            "// May need to be disabled for certain unsupported terminals",
2961            "// (Requires restart)",
2962            "// Default: true",
2963            "// ",
2964        );
2965
2966        let create_node = |node_value: bool| -> KdlNode {
2967            let mut node = KdlNode::new("styled_underlines");
2968            node.push(KdlValue::Bool(node_value));
2969            node
2970        };
2971        if let Some(styled_underlines) = self.styled_underlines {
2972            let mut node = create_node(styled_underlines);
2973            if add_comments {
2974                node.set_leading(format!("{}\n", comment_text));
2975            }
2976            Some(node)
2977        } else if add_comments {
2978            let mut node = create_node(false);
2979            node.set_leading(format!("{}\n// ", comment_text));
2980            Some(node)
2981        } else {
2982            None
2983        }
2984    }
2985    fn serialization_interval_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
2986        let comment_text = format!(
2987            "{}\n{}\n{}",
2988            " ", "// How often in seconds sessions are serialized", "// ",
2989        );
2990
2991        let create_node = |node_value: u64| -> KdlNode {
2992            let mut node = KdlNode::new("serialization_interval");
2993            node.push(KdlValue::Base10(node_value as i64));
2994            node
2995        };
2996        if let Some(serialization_interval) = self.serialization_interval {
2997            let mut node = create_node(serialization_interval);
2998            if add_comments {
2999                node.set_leading(format!("{}\n", comment_text));
3000            }
3001            Some(node)
3002        } else if add_comments {
3003            let mut node = create_node(10000);
3004            node.set_leading(format!("{}\n// ", comment_text));
3005            Some(node)
3006        } else {
3007            None
3008        }
3009    }
3010    fn disable_session_metadata_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3011        let comment_text = format!("{}\n{}\n{}\n{}\n{}\n{}",
3012            " ",
3013            "// Enable or disable writing of session metadata to disk (if disabled, other sessions might not know",
3014            "// metadata info on this session)",
3015            "// (Requires restart)",
3016            "// Default: false",
3017            "// ",
3018        );
3019
3020        let create_node = |node_value: bool| -> KdlNode {
3021            let mut node = KdlNode::new("disable_session_metadata");
3022            node.push(KdlValue::Bool(node_value));
3023            node
3024        };
3025        if let Some(disable_session_metadata) = self.disable_session_metadata {
3026            let mut node = create_node(disable_session_metadata);
3027            if add_comments {
3028                node.set_leading(format!("{}\n", comment_text));
3029            }
3030            Some(node)
3031        } else if add_comments {
3032            let mut node = create_node(false);
3033            node.set_leading(format!("{}\n// ", comment_text));
3034            Some(node)
3035        } else {
3036            None
3037        }
3038    }
3039    fn support_kitty_keyboard_protocol_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
3040        let comment_text = format!("{}\n{}\n{}\n{}\n{}",
3041            " ",
3042            "// Enable or disable support for the enhanced Kitty Keyboard Protocol (the host terminal must also support it)",
3043            "// (Requires restart)",
3044            "// Default: true (if the host terminal supports it)",
3045            "// ",
3046        );
3047
3048        let create_node = |node_value: bool| -> KdlNode {
3049            let mut node = KdlNode::new("support_kitty_keyboard_protocol");
3050            node.push(KdlValue::Bool(node_value));
3051            node
3052        };
3053        if let Some(support_kitty_keyboard_protocol) = self.support_kitty_keyboard_protocol {
3054            let mut node = create_node(support_kitty_keyboard_protocol);
3055            if add_comments {
3056                node.set_leading(format!("{}\n", comment_text));
3057            }
3058            Some(node)
3059        } else if add_comments {
3060            let mut node = create_node(false);
3061            node.set_leading(format!("{}\n// ", comment_text));
3062            Some(node)
3063        } else {
3064            None
3065        }
3066    }
3067    pub fn to_kdl(&self, add_comments: bool) -> Vec<KdlNode> {
3068        let mut nodes = vec![];
3069        if let Some(simplified_ui_node) = self.simplified_ui_to_kdl(add_comments) {
3070            nodes.push(simplified_ui_node);
3071        }
3072        if let Some(theme_node) = self.theme_to_kdl(add_comments) {
3073            nodes.push(theme_node);
3074        }
3075        if let Some(default_mode) = self.default_mode_to_kdl(add_comments) {
3076            nodes.push(default_mode);
3077        }
3078        if let Some(default_shell) = self.default_shell_to_kdl(add_comments) {
3079            nodes.push(default_shell);
3080        }
3081        if let Some(default_cwd) = self.default_cwd_to_kdl(add_comments) {
3082            nodes.push(default_cwd);
3083        }
3084        if let Some(default_layout) = self.default_layout_to_kdl(add_comments) {
3085            nodes.push(default_layout);
3086        }
3087        if let Some(layout_dir) = self.layout_dir_to_kdl(add_comments) {
3088            nodes.push(layout_dir);
3089        }
3090        if let Some(theme_dir) = self.theme_dir_to_kdl(add_comments) {
3091            nodes.push(theme_dir);
3092        }
3093        if let Some(mouse_mode) = self.mouse_mode_to_kdl(add_comments) {
3094            nodes.push(mouse_mode);
3095        }
3096        if let Some(pane_frames) = self.pane_frames_to_kdl(add_comments) {
3097            nodes.push(pane_frames);
3098        }
3099        if let Some(mirror_session) = self.mirror_session_to_kdl(add_comments) {
3100            nodes.push(mirror_session);
3101        }
3102        if let Some(on_force_close) = self.on_force_close_to_kdl(add_comments) {
3103            nodes.push(on_force_close);
3104        }
3105        if let Some(scroll_buffer_size) = self.scroll_buffer_size_to_kdl(add_comments) {
3106            nodes.push(scroll_buffer_size);
3107        }
3108        if let Some(copy_command) = self.copy_command_to_kdl(add_comments) {
3109            nodes.push(copy_command);
3110        }
3111        if let Some(copy_clipboard) = self.copy_clipboard_to_kdl(add_comments) {
3112            nodes.push(copy_clipboard);
3113        }
3114        if let Some(copy_on_select) = self.copy_on_select_to_kdl(add_comments) {
3115            nodes.push(copy_on_select);
3116        }
3117        if let Some(scrollback_editor) = self.scrollback_editor_to_kdl(add_comments) {
3118            nodes.push(scrollback_editor);
3119        }
3120        if let Some(session_name) = self.session_name_to_kdl(add_comments) {
3121            nodes.push(session_name);
3122        }
3123        if let Some(attach_to_session) = self.attach_to_session_to_kdl(add_comments) {
3124            nodes.push(attach_to_session);
3125        }
3126        if let Some(auto_layout) = self.auto_layout_to_kdl(add_comments) {
3127            nodes.push(auto_layout);
3128        }
3129        if let Some(session_serialization) = self.session_serialization_to_kdl(add_comments) {
3130            nodes.push(session_serialization);
3131        }
3132        if let Some(serialize_pane_viewport) = self.serialize_pane_viewport_to_kdl(add_comments) {
3133            nodes.push(serialize_pane_viewport);
3134        }
3135        if let Some(scrollback_lines_to_serialize) =
3136            self.scrollback_lines_to_serialize_to_kdl(add_comments)
3137        {
3138            nodes.push(scrollback_lines_to_serialize);
3139        }
3140        if let Some(styled_underlines) = self.styled_underlines_to_kdl(add_comments) {
3141            nodes.push(styled_underlines);
3142        }
3143        if let Some(serialization_interval) = self.serialization_interval_to_kdl(add_comments) {
3144            nodes.push(serialization_interval);
3145        }
3146        if let Some(disable_session_metadata) = self.disable_session_metadata_to_kdl(add_comments) {
3147            nodes.push(disable_session_metadata);
3148        }
3149        if let Some(support_kitty_keyboard_protocol) =
3150            self.support_kitty_keyboard_protocol_to_kdl(add_comments)
3151        {
3152            nodes.push(support_kitty_keyboard_protocol);
3153        }
3154        nodes
3155    }
3156}
3157
3158impl Layout {
3159    pub fn from_kdl(
3160        raw_layout: &str,
3161        file_name: Option<String>,
3162        raw_swap_layouts: Option<(&str, &str)>, // raw_swap_layouts swap_layouts_file_name
3163        cwd: Option<PathBuf>,
3164    ) -> Result<Self, ConfigError> {
3165        let mut kdl_layout_parser = KdlLayoutParser::new(raw_layout, cwd, file_name.clone());
3166        let layout = kdl_layout_parser.parse().map_err(|e| match e {
3167            ConfigError::KdlError(kdl_error) => ConfigError::KdlError(kdl_error.add_src(
3168                file_name.unwrap_or_else(|| "N/A".to_owned()),
3169                String::from(raw_layout),
3170            )),
3171            ConfigError::KdlDeserializationError(kdl_error) => kdl_layout_error(
3172                kdl_error,
3173                file_name.unwrap_or_else(|| "N/A".to_owned()),
3174                raw_layout,
3175            ),
3176            e => e,
3177        })?;
3178        match raw_swap_layouts {
3179            Some((raw_swap_layout_filename, raw_swap_layout)) => {
3180                // here we use the same parser to parse the swap layout so that we can reuse assets
3181                // (eg. pane and tab templates)
3182                kdl_layout_parser
3183                    .parse_external_swap_layouts(raw_swap_layout, layout)
3184                    .map_err(|e| match e {
3185                        ConfigError::KdlError(kdl_error) => {
3186                            ConfigError::KdlError(kdl_error.add_src(
3187                                String::from(raw_swap_layout_filename),
3188                                String::from(raw_swap_layout),
3189                            ))
3190                        },
3191                        ConfigError::KdlDeserializationError(kdl_error) => kdl_layout_error(
3192                            kdl_error,
3193                            raw_swap_layout_filename.into(),
3194                            raw_swap_layout,
3195                        ),
3196                        e => e,
3197                    })
3198            },
3199            None => Ok(layout),
3200        }
3201    }
3202}
3203
3204fn kdl_layout_error(kdl_error: kdl::KdlError, file_name: String, raw_layout: &str) -> ConfigError {
3205    let error_message = match kdl_error.kind {
3206        kdl::KdlErrorKind::Context("valid node terminator") => {
3207            format!("Failed to deserialize KDL node. \nPossible reasons:\n{}\n{}\n{}\n{}",
3208            "- Missing `;` after a node name, eg. { node; another_node; }",
3209            "- Missing quotations (\") around an argument node eg. { first_node \"argument_node\"; }",
3210            "- Missing an equal sign (=) between node arguments on a title line. eg. argument=\"value\"",
3211            "- Found an extraneous equal sign (=) between node child arguments and their values. eg. { argument=\"value\" }")
3212        },
3213        _ => String::from(kdl_error.help.unwrap_or("Kdl Deserialization Error")),
3214    };
3215    let kdl_error = KdlError {
3216        error_message,
3217        src: Some(NamedSource::new(file_name, String::from(raw_layout))),
3218        offset: Some(kdl_error.span.offset()),
3219        len: Some(kdl_error.span.len()),
3220        help_message: None,
3221    };
3222    ConfigError::KdlError(kdl_error)
3223}
3224
3225impl EnvironmentVariables {
3226    pub fn from_kdl(kdl_env_variables: &KdlNode) -> Result<Self, ConfigError> {
3227        let mut env: HashMap<String, String> = HashMap::new();
3228        for env_var in kdl_children_nodes_or_error!(kdl_env_variables, "empty env variable block") {
3229            let env_var_name = kdl_name!(env_var);
3230            let env_var_str_value =
3231                kdl_first_entry_as_string!(env_var).map(|s| format!("{}", s.to_string()));
3232            let env_var_int_value =
3233                kdl_first_entry_as_i64!(env_var).map(|s| format!("{}", s.to_string()));
3234            let env_var_value =
3235                env_var_str_value
3236                    .or(env_var_int_value)
3237                    .ok_or(ConfigError::new_kdl_error(
3238                        format!("Failed to parse env var: {:?}", env_var_name),
3239                        env_var.span().offset(),
3240                        env_var.span().len(),
3241                    ))?;
3242            env.insert(env_var_name.into(), env_var_value);
3243        }
3244        Ok(EnvironmentVariables::from_data(env))
3245    }
3246    pub fn to_kdl(&self) -> Option<KdlNode> {
3247        let mut has_env_vars = false;
3248        let mut env = KdlNode::new("env");
3249        let mut env_vars = KdlDocument::new();
3250
3251        let mut stable_sorted = BTreeMap::new();
3252        for (env_var_name, env_var_value) in self.inner() {
3253            stable_sorted.insert(env_var_name, env_var_value);
3254        }
3255        for (env_key, env_value) in stable_sorted {
3256            has_env_vars = true;
3257            let mut variable_key = KdlNode::new(env_key.to_owned());
3258            variable_key.push(env_value.to_owned());
3259            env_vars.nodes_mut().push(variable_key);
3260        }
3261
3262        if has_env_vars {
3263            env.set_children(env_vars);
3264            Some(env)
3265        } else {
3266            None
3267        }
3268    }
3269}
3270
3271impl Keybinds {
3272    fn bind_keys_in_block(
3273        block: &KdlNode,
3274        input_mode_keybinds: &mut HashMap<KeyWithModifier, Vec<Action>>,
3275        config_options: &Options,
3276    ) -> Result<(), ConfigError> {
3277        let all_nodes = kdl_children_nodes_or_error!(block, "no keybinding block for mode");
3278        let bind_nodes = all_nodes.iter().filter(|n| kdl_name!(n) == "bind");
3279        let unbind_nodes = all_nodes.iter().filter(|n| kdl_name!(n) == "unbind");
3280        for key_block in bind_nodes {
3281            Keybinds::bind_actions_for_each_key(key_block, input_mode_keybinds, config_options)?;
3282        }
3283        // we loop a second time so that the unbinds always happen after the binds
3284        for key_block in unbind_nodes {
3285            Keybinds::unbind_keys(key_block, input_mode_keybinds)?;
3286        }
3287        for key_block in all_nodes {
3288            if kdl_name!(key_block) != "bind" && kdl_name!(key_block) != "unbind" {
3289                return Err(ConfigError::new_kdl_error(
3290                    format!("Unknown keybind instruction: '{}'", kdl_name!(key_block)),
3291                    key_block.span().offset(),
3292                    key_block.span().len(),
3293                ));
3294            }
3295        }
3296        Ok(())
3297    }
3298    pub fn from_kdl(
3299        kdl_keybinds: &KdlNode,
3300        base_keybinds: Keybinds,
3301        config_options: &Options,
3302    ) -> Result<Self, ConfigError> {
3303        let clear_defaults = kdl_arg_is_truthy!(kdl_keybinds, "clear-defaults");
3304        let mut keybinds_from_config = if clear_defaults {
3305            Keybinds::default()
3306        } else {
3307            base_keybinds
3308        };
3309        for block in kdl_children_nodes_or_error!(kdl_keybinds, "keybindings with no children") {
3310            if kdl_name!(block) == "shared_except" || kdl_name!(block) == "shared" {
3311                let mut modes_to_exclude = vec![];
3312                for mode_name in kdl_string_arguments!(block) {
3313                    modes_to_exclude.push(InputMode::from_str(mode_name).map_err(|_| {
3314                        ConfigError::new_kdl_error(
3315                            format!("Invalid mode: '{}'", mode_name),
3316                            block.name().span().offset(),
3317                            block.name().span().len(),
3318                        )
3319                    })?);
3320                }
3321                for mode in InputMode::iter() {
3322                    if modes_to_exclude.contains(&mode) {
3323                        continue;
3324                    }
3325                    let mut input_mode_keybinds = keybinds_from_config.get_input_mode_mut(&mode);
3326                    Keybinds::bind_keys_in_block(block, &mut input_mode_keybinds, config_options)?;
3327                }
3328            }
3329            if kdl_name!(block) == "shared_among" {
3330                let mut modes_to_include = vec![];
3331                for mode_name in kdl_string_arguments!(block) {
3332                    modes_to_include.push(InputMode::from_str(mode_name)?);
3333                }
3334                for mode in InputMode::iter() {
3335                    if !modes_to_include.contains(&mode) {
3336                        continue;
3337                    }
3338                    let mut input_mode_keybinds = keybinds_from_config.get_input_mode_mut(&mode);
3339                    Keybinds::bind_keys_in_block(block, &mut input_mode_keybinds, config_options)?;
3340                }
3341            }
3342        }
3343        for mode in kdl_children_nodes_or_error!(kdl_keybinds, "keybindings with no children") {
3344            if kdl_name!(mode) == "unbind"
3345                || kdl_name!(mode) == "shared_except"
3346                || kdl_name!(mode) == "shared_among"
3347                || kdl_name!(mode) == "shared"
3348            {
3349                continue;
3350            }
3351            let mut input_mode_keybinds =
3352                Keybinds::input_mode_keybindings(mode, &mut keybinds_from_config)?;
3353            Keybinds::bind_keys_in_block(mode, &mut input_mode_keybinds, config_options)?;
3354        }
3355        if let Some(global_unbind) = kdl_keybinds.children().and_then(|c| c.get("unbind")) {
3356            Keybinds::unbind_keys_in_all_modes(global_unbind, &mut keybinds_from_config)?;
3357        };
3358        Ok(keybinds_from_config)
3359    }
3360    fn bind_actions_for_each_key(
3361        key_block: &KdlNode,
3362        input_mode_keybinds: &mut HashMap<KeyWithModifier, Vec<Action>>,
3363        config_options: &Options,
3364    ) -> Result<(), ConfigError> {
3365        let keys: Vec<KeyWithModifier> = keys_from_kdl!(key_block);
3366        let actions: Vec<Action> = actions_from_kdl!(key_block, config_options);
3367        for key in keys {
3368            input_mode_keybinds.insert(key, actions.clone());
3369        }
3370        Ok(())
3371    }
3372    fn unbind_keys(
3373        key_block: &KdlNode,
3374        input_mode_keybinds: &mut HashMap<KeyWithModifier, Vec<Action>>,
3375    ) -> Result<(), ConfigError> {
3376        let keys: Vec<KeyWithModifier> = keys_from_kdl!(key_block);
3377        for key in keys {
3378            input_mode_keybinds.remove(&key);
3379        }
3380        Ok(())
3381    }
3382    fn unbind_keys_in_all_modes(
3383        global_unbind: &KdlNode,
3384        keybinds_from_config: &mut Keybinds,
3385    ) -> Result<(), ConfigError> {
3386        let keys: Vec<KeyWithModifier> = keys_from_kdl!(global_unbind);
3387        for mode in keybinds_from_config.0.values_mut() {
3388            for key in &keys {
3389                mode.remove(&key);
3390            }
3391        }
3392        Ok(())
3393    }
3394    fn input_mode_keybindings<'a>(
3395        mode: &KdlNode,
3396        keybinds_from_config: &'a mut Keybinds,
3397    ) -> Result<&'a mut HashMap<KeyWithModifier, Vec<Action>>, ConfigError> {
3398        let mode_name = kdl_name!(mode);
3399        let input_mode = InputMode::from_str(mode_name).map_err(|_| {
3400            ConfigError::new_kdl_error(
3401                format!("Invalid mode: '{}'", mode_name),
3402                mode.name().span().offset(),
3403                mode.name().span().len(),
3404            )
3405        })?;
3406        let input_mode_keybinds = keybinds_from_config.get_input_mode_mut(&input_mode);
3407        let clear_defaults_for_mode = kdl_arg_is_truthy!(mode, "clear-defaults");
3408        if clear_defaults_for_mode {
3409            input_mode_keybinds.clear();
3410        }
3411        Ok(input_mode_keybinds)
3412    }
3413    pub fn from_string(
3414        stringified_keybindings: String,
3415        base_keybinds: Keybinds,
3416        config_options: &Options,
3417    ) -> Result<Self, ConfigError> {
3418        let document: KdlDocument = stringified_keybindings.parse()?;
3419        if let Some(kdl_keybinds) = document.get("keybinds") {
3420            Keybinds::from_kdl(&kdl_keybinds, base_keybinds, config_options)
3421        } else {
3422            Err(ConfigError::new_kdl_error(
3423                format!("Could not find keybinds node"),
3424                document.span().offset(),
3425                document.span().len(),
3426            ))
3427        }
3428    }
3429    // minimize keybind entries for serialization, so that duplicate entries will appear in
3430    // "shared" nodes later rather than once per mode
3431    fn minimize_entries(
3432        &self,
3433    ) -> BTreeMap<BTreeSet<InputMode>, BTreeMap<KeyWithModifier, Vec<Action>>> {
3434        let mut minimized: BTreeMap<BTreeSet<InputMode>, BTreeMap<KeyWithModifier, Vec<Action>>> =
3435            BTreeMap::new();
3436        let mut flattened: Vec<BTreeMap<KeyWithModifier, Vec<Action>>> = self
3437            .0
3438            .iter()
3439            .map(|(_input_mode, keybind)| keybind.clone().into_iter().collect())
3440            .collect();
3441        for keybind in flattened.drain(..) {
3442            for (key, actions) in keybind.into_iter() {
3443                let mut appears_in_modes: BTreeSet<InputMode> = BTreeSet::new();
3444                for (input_mode, keybinds) in self.0.iter() {
3445                    if keybinds.get(&key) == Some(&actions) {
3446                        appears_in_modes.insert(*input_mode);
3447                    }
3448                }
3449                minimized
3450                    .entry(appears_in_modes)
3451                    .or_insert_with(Default::default)
3452                    .insert(key, actions);
3453            }
3454        }
3455        minimized
3456    }
3457    fn serialize_mode_title_node(&self, input_modes: &BTreeSet<InputMode>) -> KdlNode {
3458        let all_modes: Vec<InputMode> = InputMode::iter().collect();
3459        let total_input_mode_count = all_modes.len();
3460        if input_modes.len() == 1 {
3461            let input_mode_name =
3462                format!("{:?}", input_modes.iter().next().unwrap()).to_lowercase();
3463            KdlNode::new(input_mode_name)
3464        } else if input_modes.len() == total_input_mode_count {
3465            KdlNode::new("shared")
3466        } else if input_modes.len() < total_input_mode_count / 2 {
3467            let mut node = KdlNode::new("shared_among");
3468            for input_mode in input_modes {
3469                node.push(format!("{:?}", input_mode).to_lowercase());
3470            }
3471            node
3472        } else {
3473            let mut node = KdlNode::new("shared_except");
3474            let mut modes = all_modes.clone();
3475            for input_mode in input_modes {
3476                modes.retain(|m| m != input_mode)
3477            }
3478            for mode in modes {
3479                node.push(format!("{:?}", mode).to_lowercase());
3480            }
3481            node
3482        }
3483    }
3484    fn serialize_mode_keybinds(
3485        &self,
3486        keybinds: &BTreeMap<KeyWithModifier, Vec<Action>>,
3487    ) -> KdlDocument {
3488        let mut mode_keybinds = KdlDocument::new();
3489        for keybind in keybinds {
3490            let mut keybind_node = KdlNode::new("bind");
3491            keybind_node.push(keybind.0.to_kdl());
3492            let mut actions = KdlDocument::new();
3493            let mut actions_have_children = false;
3494            for action in keybind.1 {
3495                if let Some(kdl_action) = action.to_kdl() {
3496                    if kdl_action.children().is_some() {
3497                        actions_have_children = true;
3498                    }
3499                    actions.nodes_mut().push(kdl_action);
3500                }
3501            }
3502            if !actions_have_children {
3503                for action in actions.nodes_mut() {
3504                    action.set_leading("");
3505                    action.set_trailing("; ");
3506                }
3507                actions.set_leading(" ");
3508                actions.set_trailing("");
3509            }
3510            keybind_node.set_children(actions);
3511            mode_keybinds.nodes_mut().push(keybind_node);
3512        }
3513        mode_keybinds
3514    }
3515    pub fn to_kdl(&self, should_clear_defaults: bool) -> KdlNode {
3516        let mut keybinds_node = KdlNode::new("keybinds");
3517        if should_clear_defaults {
3518            keybinds_node.insert("clear-defaults", true);
3519        }
3520        let mut minimized = self.minimize_entries();
3521        let mut keybinds_children = KdlDocument::new();
3522
3523        macro_rules! encode_single_input_mode {
3524            ($mode_name:ident) => {{
3525                if let Some(keybinds) = minimized.remove(&BTreeSet::from([InputMode::$mode_name])) {
3526                    let mut mode_node =
3527                        KdlNode::new(format!("{:?}", InputMode::$mode_name).to_lowercase());
3528                    let mode_keybinds = self.serialize_mode_keybinds(&keybinds);
3529                    mode_node.set_children(mode_keybinds);
3530                    keybinds_children.nodes_mut().push(mode_node);
3531                }
3532            }};
3533        }
3534        // we do this explicitly so that the sorting order of modes in the config is more Human
3535        // readable - this is actually less code (and clearer) than implementing Ord in this case
3536        encode_single_input_mode!(Normal);
3537        encode_single_input_mode!(Locked);
3538        encode_single_input_mode!(Pane);
3539        encode_single_input_mode!(Tab);
3540        encode_single_input_mode!(Resize);
3541        encode_single_input_mode!(Move);
3542        encode_single_input_mode!(Scroll);
3543        encode_single_input_mode!(Search);
3544        encode_single_input_mode!(Session);
3545
3546        for (input_modes, keybinds) in minimized {
3547            if input_modes.is_empty() {
3548                log::error!("invalid input mode for keybinds: {:#?}", keybinds);
3549                continue;
3550            }
3551            let mut mode_node = self.serialize_mode_title_node(&input_modes);
3552            let mode_keybinds = self.serialize_mode_keybinds(&keybinds);
3553            mode_node.set_children(mode_keybinds);
3554            keybinds_children.nodes_mut().push(mode_node);
3555        }
3556        keybinds_node.set_children(keybinds_children);
3557        keybinds_node
3558    }
3559}
3560
3561impl KeyWithModifier {
3562    pub fn to_kdl(&self) -> String {
3563        if self.key_modifiers.is_empty() {
3564            self.bare_key.to_kdl()
3565        } else {
3566            format!(
3567                "{} {}",
3568                self.key_modifiers
3569                    .iter()
3570                    .map(|m| m.to_string())
3571                    .collect::<Vec<_>>()
3572                    .join(" "),
3573                self.bare_key.to_kdl()
3574            )
3575        }
3576    }
3577}
3578
3579impl BareKey {
3580    pub fn to_kdl(&self) -> String {
3581        match self {
3582            BareKey::PageDown => format!("PageDown"),
3583            BareKey::PageUp => format!("PageUp"),
3584            BareKey::Left => format!("left"),
3585            BareKey::Down => format!("down"),
3586            BareKey::Up => format!("up"),
3587            BareKey::Right => format!("right"),
3588            BareKey::Home => format!("home"),
3589            BareKey::End => format!("end"),
3590            BareKey::Backspace => format!("backspace"),
3591            BareKey::Delete => format!("del"),
3592            BareKey::Insert => format!("insert"),
3593            BareKey::F(index) => format!("F{}", index),
3594            BareKey::Char(' ') => format!("space"),
3595            BareKey::Char(character) => format!("{}", character),
3596            BareKey::Tab => format!("tab"),
3597            BareKey::Esc => format!("esc"),
3598            BareKey::Enter => format!("enter"),
3599            BareKey::CapsLock => format!("capslock"),
3600            BareKey::ScrollLock => format!("scrolllock"),
3601            BareKey::NumLock => format!("numlock"),
3602            BareKey::PrintScreen => format!("printscreen"),
3603            BareKey::Pause => format!("pause"),
3604            BareKey::Menu => format!("menu"),
3605        }
3606    }
3607}
3608
3609impl Config {
3610    pub fn from_kdl(kdl_config: &str, base_config: Option<Config>) -> Result<Config, ConfigError> {
3611        let mut config = base_config.unwrap_or_else(|| Config::default());
3612        let kdl_config: KdlDocument = kdl_config.parse()?;
3613
3614        let config_options = Options::from_kdl(&kdl_config)?;
3615        config.options = config.options.merge(config_options);
3616
3617        // TODO: handle cases where we have more than one of these blocks (eg. two "keybinds")
3618        // this should give an informative parsing error
3619        if let Some(kdl_keybinds) = kdl_config.get("keybinds") {
3620            config.keybinds = Keybinds::from_kdl(&kdl_keybinds, config.keybinds, &config.options)?;
3621        }
3622        if let Some(kdl_themes) = kdl_config.get("themes") {
3623            let sourced_from_external_file = false;
3624            let config_themes = Themes::from_kdl(kdl_themes, sourced_from_external_file)?;
3625            config.themes = config.themes.merge(config_themes);
3626        }
3627        if let Some(kdl_plugin_aliases) = kdl_config.get("plugins") {
3628            let config_plugins = PluginAliases::from_kdl(kdl_plugin_aliases)?;
3629            config.plugins.merge(config_plugins);
3630        }
3631        if let Some(kdl_load_plugins) = kdl_config.get("load_plugins") {
3632            let load_plugins = load_plugins_from_kdl(kdl_load_plugins)?;
3633            config.background_plugins = load_plugins;
3634        }
3635        if let Some(kdl_ui_config) = kdl_config.get("ui") {
3636            let config_ui = UiConfig::from_kdl(&kdl_ui_config)?;
3637            config.ui = config.ui.merge(config_ui);
3638        }
3639        if let Some(env_config) = kdl_config.get("env") {
3640            let config_env = EnvironmentVariables::from_kdl(&env_config)?;
3641            config.env = config.env.merge(config_env);
3642        }
3643        Ok(config)
3644    }
3645    pub fn to_string(&self, add_comments: bool) -> String {
3646        let mut document = KdlDocument::new();
3647
3648        let clear_defaults = true;
3649        let keybinds = self.keybinds.to_kdl(clear_defaults);
3650        document.nodes_mut().push(keybinds);
3651
3652        if let Some(themes) = self.themes.to_kdl() {
3653            document.nodes_mut().push(themes);
3654        }
3655
3656        let plugins = self.plugins.to_kdl(add_comments);
3657        document.nodes_mut().push(plugins);
3658
3659        let load_plugins = load_plugins_to_kdl(&self.background_plugins, add_comments);
3660        document.nodes_mut().push(load_plugins);
3661
3662        if let Some(ui_config) = self.ui.to_kdl() {
3663            document.nodes_mut().push(ui_config);
3664        }
3665
3666        if let Some(env) = self.env.to_kdl() {
3667            document.nodes_mut().push(env);
3668        }
3669
3670        document
3671            .nodes_mut()
3672            .append(&mut self.options.to_kdl(add_comments));
3673
3674        document.to_string()
3675    }
3676}
3677
3678impl PluginAliases {
3679    pub fn from_kdl(kdl_plugin_aliases: &KdlNode) -> Result<PluginAliases, ConfigError> {
3680        let mut aliases: BTreeMap<String, RunPlugin> = BTreeMap::new();
3681        if let Some(kdl_plugin_aliases) = kdl_children_nodes!(kdl_plugin_aliases) {
3682            for alias_definition in kdl_plugin_aliases {
3683                let alias_name = kdl_name!(alias_definition);
3684                if let Some(string_url) =
3685                    kdl_get_string_property_or_child_value!(alias_definition, "location")
3686                {
3687                    let configuration =
3688                        KdlLayoutParser::parse_plugin_user_configuration(&alias_definition)?;
3689                    let initial_cwd =
3690                        kdl_get_string_property_or_child_value!(alias_definition, "cwd")
3691                            .map(|s| PathBuf::from(s));
3692                    let run_plugin = RunPlugin::from_url(string_url)?
3693                        .with_configuration(configuration.inner().clone())
3694                        .with_initial_cwd(initial_cwd);
3695                    aliases.insert(alias_name.to_owned(), run_plugin);
3696                }
3697            }
3698        }
3699        Ok(PluginAliases { aliases })
3700    }
3701    pub fn to_kdl(&self, add_comments: bool) -> KdlNode {
3702        let mut plugins = KdlNode::new("plugins");
3703        let mut plugins_children = KdlDocument::new();
3704        for (alias_name, plugin_alias) in self.aliases.iter() {
3705            let mut plugin_alias_node = KdlNode::new(alias_name.clone());
3706            let mut plugin_alias_children = KdlDocument::new();
3707            let location_string = plugin_alias.location.display();
3708
3709            plugin_alias_node.insert("location", location_string);
3710            let cwd = plugin_alias.initial_cwd.as_ref();
3711            let mut has_children = false;
3712            if let Some(cwd) = cwd {
3713                has_children = true;
3714                let mut cwd_node = KdlNode::new("cwd");
3715                cwd_node.push(cwd.display().to_string());
3716                plugin_alias_children.nodes_mut().push(cwd_node);
3717            }
3718            let configuration = plugin_alias.configuration.inner();
3719            if !configuration.is_empty() {
3720                has_children = true;
3721                for (config_key, config_value) in configuration {
3722                    let mut node = KdlNode::new(config_key.to_owned());
3723                    if config_value == "true" {
3724                        node.push(KdlValue::Bool(true));
3725                    } else if config_value == "false" {
3726                        node.push(KdlValue::Bool(false));
3727                    } else {
3728                        node.push(config_value.to_string());
3729                    }
3730                    plugin_alias_children.nodes_mut().push(node);
3731                }
3732            }
3733            if has_children {
3734                plugin_alias_node.set_children(plugin_alias_children);
3735            }
3736            plugins_children.nodes_mut().push(plugin_alias_node);
3737        }
3738        plugins.set_children(plugins_children);
3739
3740        if add_comments {
3741            plugins.set_leading(format!(
3742                "\n{}\n{}\n",
3743                "// Plugin aliases - can be used to change the implementation of Zellij",
3744                "// changing these requires a restart to take effect",
3745            ));
3746        }
3747        plugins
3748    }
3749}
3750
3751pub fn load_plugins_to_kdl(
3752    background_plugins: &HashSet<RunPluginOrAlias>,
3753    add_comments: bool,
3754) -> KdlNode {
3755    let mut load_plugins = KdlNode::new("load_plugins");
3756    let mut load_plugins_children = KdlDocument::new();
3757    for run_plugin_or_alias in background_plugins.iter() {
3758        let mut background_plugin_node = KdlNode::new(run_plugin_or_alias.location_string());
3759        let mut background_plugin_children = KdlDocument::new();
3760
3761        let cwd = match run_plugin_or_alias {
3762            RunPluginOrAlias::RunPlugin(run_plugin) => run_plugin.initial_cwd.clone(),
3763            RunPluginOrAlias::Alias(plugin_alias) => plugin_alias.initial_cwd.clone(),
3764        };
3765        let mut has_children = false;
3766        if let Some(cwd) = cwd.as_ref() {
3767            has_children = true;
3768            let mut cwd_node = KdlNode::new("cwd");
3769            cwd_node.push(cwd.display().to_string());
3770            background_plugin_children.nodes_mut().push(cwd_node);
3771        }
3772        let configuration = match run_plugin_or_alias {
3773            RunPluginOrAlias::RunPlugin(run_plugin) => {
3774                Some(run_plugin.configuration.inner().clone())
3775            },
3776            RunPluginOrAlias::Alias(plugin_alias) => plugin_alias
3777                .configuration
3778                .as_ref()
3779                .map(|c| c.inner().clone()),
3780        };
3781        if let Some(configuration) = configuration {
3782            if !configuration.is_empty() {
3783                has_children = true;
3784                for (config_key, config_value) in configuration {
3785                    let mut node = KdlNode::new(config_key.to_owned());
3786                    if config_value == "true" {
3787                        node.push(KdlValue::Bool(true));
3788                    } else if config_value == "false" {
3789                        node.push(KdlValue::Bool(false));
3790                    } else {
3791                        node.push(config_value.to_string());
3792                    }
3793                    background_plugin_children.nodes_mut().push(node);
3794                }
3795            }
3796        }
3797        if has_children {
3798            background_plugin_node.set_children(background_plugin_children);
3799        }
3800        load_plugins_children
3801            .nodes_mut()
3802            .push(background_plugin_node);
3803    }
3804    load_plugins.set_children(load_plugins_children);
3805
3806    if add_comments {
3807        load_plugins.set_leading(format!(
3808            "\n{}\n{}\n{}\n",
3809            "// Plugins to load in the background when a new session starts",
3810            "// eg. \"file:/path/to/my-plugin.wasm\"",
3811            "// eg. \"https://example.com/my-plugin.wasm\"",
3812        ));
3813    }
3814    load_plugins
3815}
3816
3817fn load_plugins_from_kdl(
3818    kdl_load_plugins: &KdlNode,
3819) -> Result<HashSet<RunPluginOrAlias>, ConfigError> {
3820    let mut load_plugins: HashSet<RunPluginOrAlias> = HashSet::new();
3821    if let Some(kdl_load_plugins) = kdl_children_nodes!(kdl_load_plugins) {
3822        for plugin_block in kdl_load_plugins {
3823            let url_node = plugin_block.name();
3824            let string_url = url_node.value();
3825            let configuration = KdlLayoutParser::parse_plugin_user_configuration(&plugin_block)?;
3826            let cwd = kdl_get_string_property_or_child_value!(&plugin_block, "cwd")
3827                .map(|s| PathBuf::from(s));
3828            let run_plugin_or_alias = RunPluginOrAlias::from_url(
3829                &string_url,
3830                &Some(configuration.inner().clone()),
3831                None,
3832                cwd.clone(),
3833            )
3834            .map_err(|e| {
3835                ConfigError::new_kdl_error(
3836                    format!("Failed to parse plugin: {}", e),
3837                    url_node.span().offset(),
3838                    url_node.span().len(),
3839                )
3840            })?
3841            .with_initial_cwd(cwd);
3842            load_plugins.insert(run_plugin_or_alias);
3843        }
3844    }
3845    Ok(load_plugins)
3846}
3847
3848impl UiConfig {
3849    pub fn from_kdl(kdl_ui_config: &KdlNode) -> Result<UiConfig, ConfigError> {
3850        let mut ui_config = UiConfig::default();
3851        if let Some(pane_frames) = kdl_get_child!(kdl_ui_config, "pane_frames") {
3852            let rounded_corners =
3853                kdl_children_property_first_arg_as_bool!(pane_frames, "rounded_corners")
3854                    .unwrap_or(false);
3855            let hide_session_name =
3856                kdl_get_child_entry_bool_value!(pane_frames, "hide_session_name").unwrap_or(false);
3857            let frame_config = FrameConfig {
3858                rounded_corners,
3859                hide_session_name,
3860            };
3861            ui_config.pane_frames = frame_config;
3862        }
3863        Ok(ui_config)
3864    }
3865    pub fn to_kdl(&self) -> Option<KdlNode> {
3866        let mut ui_config = KdlNode::new("ui");
3867        let mut ui_config_children = KdlDocument::new();
3868        let mut frame_config = KdlNode::new("pane_frames");
3869        let mut frame_config_children = KdlDocument::new();
3870        let mut has_ui_config = false;
3871        if self.pane_frames.rounded_corners {
3872            has_ui_config = true;
3873            let mut rounded_corners = KdlNode::new("rounded_corners");
3874            rounded_corners.push(KdlValue::Bool(true));
3875            frame_config_children.nodes_mut().push(rounded_corners);
3876        }
3877        if self.pane_frames.hide_session_name {
3878            has_ui_config = true;
3879            let mut hide_session_name = KdlNode::new("hide_session_name");
3880            hide_session_name.push(KdlValue::Bool(true));
3881            frame_config_children.nodes_mut().push(hide_session_name);
3882        }
3883        if has_ui_config {
3884            frame_config.set_children(frame_config_children);
3885            ui_config_children.nodes_mut().push(frame_config);
3886            ui_config.set_children(ui_config_children);
3887            Some(ui_config)
3888        } else {
3889            None
3890        }
3891    }
3892}
3893
3894impl Themes {
3895    pub fn from_kdl(
3896        themes_from_kdl: &KdlNode,
3897        sourced_from_external_file: bool,
3898    ) -> Result<Self, ConfigError> {
3899        let mut themes: HashMap<String, Theme> = HashMap::new();
3900        for theme_config in kdl_children_nodes_or_error!(themes_from_kdl, "no themes found") {
3901            let theme_name = kdl_name!(theme_config);
3902            let theme_colors = kdl_children_or_error!(theme_config, "empty theme");
3903            let theme = Theme {
3904                palette: Palette {
3905                    fg: PaletteColor::try_from(("fg", theme_colors))?,
3906                    bg: PaletteColor::try_from(("bg", theme_colors))?,
3907                    red: PaletteColor::try_from(("red", theme_colors))?,
3908                    green: PaletteColor::try_from(("green", theme_colors))?,
3909                    yellow: PaletteColor::try_from(("yellow", theme_colors))?,
3910                    blue: PaletteColor::try_from(("blue", theme_colors))?,
3911                    magenta: PaletteColor::try_from(("magenta", theme_colors))?,
3912                    orange: PaletteColor::try_from(("orange", theme_colors))?,
3913                    cyan: PaletteColor::try_from(("cyan", theme_colors))?,
3914                    black: PaletteColor::try_from(("black", theme_colors))?,
3915                    white: PaletteColor::try_from(("white", theme_colors))?,
3916                    ..Default::default()
3917                },
3918                sourced_from_external_file,
3919            };
3920            themes.insert(theme_name.into(), theme);
3921        }
3922        let themes = Themes::from_data(themes);
3923        Ok(themes)
3924    }
3925
3926    pub fn from_string(
3927        raw_string: &String,
3928        sourced_from_external_file: bool,
3929    ) -> Result<Self, ConfigError> {
3930        let kdl_config: KdlDocument = raw_string.parse()?;
3931        let kdl_themes = kdl_config.get("themes").ok_or(ConfigError::new_kdl_error(
3932            "No theme node found in file".into(),
3933            kdl_config.span().offset(),
3934            kdl_config.span().len(),
3935        ))?;
3936        let all_themes_in_file = Themes::from_kdl(kdl_themes, sourced_from_external_file)?;
3937        Ok(all_themes_in_file)
3938    }
3939
3940    pub fn from_path(path_to_theme_file: PathBuf) -> Result<Self, ConfigError> {
3941        // String is the theme name
3942        let kdl_config = std::fs::read_to_string(&path_to_theme_file)
3943            .map_err(|e| ConfigError::IoPath(e, path_to_theme_file.clone()))?;
3944        let sourced_from_external_file = true;
3945        Themes::from_string(&kdl_config, sourced_from_external_file).map_err(|e| match e {
3946            ConfigError::KdlError(kdl_error) => ConfigError::KdlError(
3947                kdl_error.add_src(path_to_theme_file.display().to_string(), kdl_config),
3948            ),
3949            e => e,
3950        })
3951    }
3952
3953    pub fn from_dir(path_to_theme_dir: PathBuf) -> Result<Self, ConfigError> {
3954        let mut themes = Themes::default();
3955        for entry in std::fs::read_dir(&path_to_theme_dir)
3956            .map_err(|e| ConfigError::IoPath(e, path_to_theme_dir.clone()))?
3957        {
3958            let entry = entry.map_err(|e| ConfigError::IoPath(e, path_to_theme_dir.clone()))?;
3959            let path = entry.path();
3960            if let Some(extension) = path.extension() {
3961                if extension == "kdl" {
3962                    themes = themes.merge(Themes::from_path(path)?);
3963                }
3964            }
3965        }
3966        Ok(themes)
3967    }
3968    pub fn to_kdl(&self) -> Option<KdlNode> {
3969        let mut theme_node = KdlNode::new("themes");
3970        let mut themes = KdlDocument::new();
3971        let mut has_themes = false;
3972        let sorted_themes: BTreeMap<String, Theme> = self.inner().clone().into_iter().collect();
3973        for (theme_name, theme) in sorted_themes {
3974            if theme.sourced_from_external_file {
3975                // we do not serialize themes that have been defined in external files so as not to
3976                // clog up the configuration file definitions
3977                continue;
3978            }
3979            has_themes = true;
3980            let mut current_theme_node = KdlNode::new(theme_name.clone());
3981            let mut current_theme_node_children = KdlDocument::new();
3982            current_theme_node_children
3983                .nodes_mut()
3984                .push(theme.palette.fg.to_kdl("fg"));
3985            current_theme_node_children
3986                .nodes_mut()
3987                .push(theme.palette.bg.to_kdl("bg"));
3988            current_theme_node_children
3989                .nodes_mut()
3990                .push(theme.palette.red.to_kdl("red"));
3991            current_theme_node_children
3992                .nodes_mut()
3993                .push(theme.palette.green.to_kdl("green"));
3994            current_theme_node_children
3995                .nodes_mut()
3996                .push(theme.palette.yellow.to_kdl("yellow"));
3997            current_theme_node_children
3998                .nodes_mut()
3999                .push(theme.palette.blue.to_kdl("blue"));
4000            current_theme_node_children
4001                .nodes_mut()
4002                .push(theme.palette.magenta.to_kdl("magenta"));
4003            current_theme_node_children
4004                .nodes_mut()
4005                .push(theme.palette.orange.to_kdl("orange"));
4006            current_theme_node_children
4007                .nodes_mut()
4008                .push(theme.palette.cyan.to_kdl("cyan"));
4009            current_theme_node_children
4010                .nodes_mut()
4011                .push(theme.palette.black.to_kdl("black"));
4012            current_theme_node_children
4013                .nodes_mut()
4014                .push(theme.palette.white.to_kdl("white"));
4015            current_theme_node.set_children(current_theme_node_children);
4016            themes.nodes_mut().push(current_theme_node);
4017        }
4018        if has_themes {
4019            theme_node.set_children(themes);
4020            Some(theme_node)
4021        } else {
4022            None
4023        }
4024    }
4025}
4026
4027impl PermissionCache {
4028    pub fn from_string(raw_string: String) -> Result<GrantedPermission, ConfigError> {
4029        let kdl_document: KdlDocument = raw_string.parse()?;
4030
4031        let mut granted_permission = GrantedPermission::default();
4032
4033        for node in kdl_document.nodes() {
4034            if let Some(children) = node.children() {
4035                let key = kdl_name!(node);
4036                let permissions: Vec<PermissionType> = children
4037                    .nodes()
4038                    .iter()
4039                    .filter_map(|p| {
4040                        let v = kdl_name!(p);
4041                        PermissionType::from_str(v).ok()
4042                    })
4043                    .collect();
4044
4045                granted_permission.insert(key.into(), permissions);
4046            }
4047        }
4048
4049        Ok(granted_permission)
4050    }
4051
4052    pub fn to_string(granted: &GrantedPermission) -> String {
4053        let mut kdl_doucment = KdlDocument::new();
4054
4055        granted.iter().for_each(|(k, v)| {
4056            let mut node = KdlNode::new(k.as_str());
4057            let mut children = KdlDocument::new();
4058
4059            let permissions: HashSet<PermissionType> = v.clone().into_iter().collect();
4060            permissions.iter().for_each(|f| {
4061                let n = KdlNode::new(f.to_string().as_str());
4062                children.nodes_mut().push(n);
4063            });
4064
4065            node.set_children(children);
4066            kdl_doucment.nodes_mut().push(node);
4067        });
4068
4069        kdl_doucment.fmt();
4070        kdl_doucment.to_string()
4071    }
4072}
4073
4074impl SessionInfo {
4075    pub fn from_string(raw_session_info: &str, current_session_name: &str) -> Result<Self, String> {
4076        let kdl_document: KdlDocument = raw_session_info
4077            .parse()
4078            .map_err(|e| format!("Failed to parse kdl document: {}", e))?;
4079        let name = kdl_document
4080            .get("name")
4081            .and_then(|n| n.entries().iter().next())
4082            .and_then(|e| e.value().as_string())
4083            .map(|s| s.to_owned())
4084            .ok_or("Failed to parse session name")?;
4085        let connected_clients = kdl_document
4086            .get("connected_clients")
4087            .and_then(|n| n.entries().iter().next())
4088            .and_then(|e| e.value().as_i64())
4089            .map(|c| c as usize)
4090            .ok_or("Failed to parse connected_clients")?;
4091        let tabs: Vec<TabInfo> = kdl_document
4092            .get("tabs")
4093            .and_then(|t| t.children())
4094            .and_then(|c| {
4095                let mut tab_nodes = vec![];
4096                for tab_node in c.nodes() {
4097                    if let Some(tab) = tab_node.children() {
4098                        tab_nodes.push(TabInfo::decode_from_kdl(tab).ok()?);
4099                    }
4100                }
4101                Some(tab_nodes)
4102            })
4103            .ok_or("Failed to parse tabs")?;
4104        let panes: PaneManifest = kdl_document
4105            .get("panes")
4106            .and_then(|p| p.children())
4107            .map(|p| PaneManifest::decode_from_kdl(p))
4108            .ok_or("Failed to parse panes")?;
4109        let available_layouts: Vec<LayoutInfo> = kdl_document
4110            .get("available_layouts")
4111            .and_then(|p| p.children())
4112            .map(|e| {
4113                e.nodes()
4114                    .iter()
4115                    .filter_map(|n| {
4116                        let layout_name = n.name().value().to_owned();
4117                        let layout_source = n
4118                            .entries()
4119                            .iter()
4120                            .find(|e| e.name().map(|n| n.value()) == Some("source"))
4121                            .and_then(|e| e.value().as_string());
4122                        match layout_source {
4123                            Some(layout_source) => match layout_source {
4124                                "built-in" => Some(LayoutInfo::BuiltIn(layout_name)),
4125                                "file" => Some(LayoutInfo::File(layout_name)),
4126                                _ => None,
4127                            },
4128                            None => None,
4129                        }
4130                    })
4131                    .collect()
4132            })
4133            .ok_or("Failed to parse available_layouts")?;
4134        let is_current_session = name == current_session_name;
4135        Ok(SessionInfo {
4136            name,
4137            tabs,
4138            panes,
4139            connected_clients,
4140            is_current_session,
4141            available_layouts,
4142            plugins: Default::default(), // we do not serialize plugin information
4143        })
4144    }
4145    pub fn to_string(&self) -> String {
4146        let mut kdl_document = KdlDocument::new();
4147
4148        let mut name = KdlNode::new("name");
4149        name.push(self.name.clone());
4150
4151        let mut connected_clients = KdlNode::new("connected_clients");
4152        connected_clients.push(self.connected_clients as i64);
4153
4154        let mut tabs = KdlNode::new("tabs");
4155        let mut tab_children = KdlDocument::new();
4156        for tab_info in &self.tabs {
4157            let mut tab = KdlNode::new("tab");
4158            let kdl_tab_info = tab_info.encode_to_kdl();
4159            tab.set_children(kdl_tab_info);
4160            tab_children.nodes_mut().push(tab);
4161        }
4162        tabs.set_children(tab_children);
4163
4164        let mut panes = KdlNode::new("panes");
4165        panes.set_children(self.panes.encode_to_kdl());
4166
4167        let mut available_layouts = KdlNode::new("available_layouts");
4168        let mut available_layouts_children = KdlDocument::new();
4169        for layout_info in &self.available_layouts {
4170            let (layout_name, layout_source) = match layout_info {
4171                LayoutInfo::File(name) => (name.clone(), "file"),
4172                LayoutInfo::BuiltIn(name) => (name.clone(), "built-in"),
4173                LayoutInfo::Url(url) => (url.clone(), "url"),
4174                LayoutInfo::Stringified(_stringified) => ("stringified-layout".to_owned(), "N/A"),
4175            };
4176            let mut layout_node = KdlNode::new(format!("{}", layout_name));
4177            let layout_source = KdlEntry::new_prop("source", layout_source);
4178            layout_node.entries_mut().push(layout_source);
4179            available_layouts_children.nodes_mut().push(layout_node);
4180        }
4181        available_layouts.set_children(available_layouts_children);
4182
4183        kdl_document.nodes_mut().push(name);
4184        kdl_document.nodes_mut().push(tabs);
4185        kdl_document.nodes_mut().push(panes);
4186        kdl_document.nodes_mut().push(connected_clients);
4187        kdl_document.nodes_mut().push(available_layouts);
4188        kdl_document.fmt();
4189        kdl_document.to_string()
4190    }
4191}
4192
4193impl TabInfo {
4194    pub fn decode_from_kdl(kdl_document: &KdlDocument) -> Result<Self, String> {
4195        macro_rules! int_node {
4196            ($name:expr, $type:ident) => {{
4197                kdl_document
4198                    .get($name)
4199                    .and_then(|n| n.entries().iter().next())
4200                    .and_then(|e| e.value().as_i64())
4201                    .map(|e| e as $type)
4202                    .ok_or(format!("Failed to parse tab {}", $name))?
4203            }};
4204        }
4205        macro_rules! string_node {
4206            ($name:expr) => {{
4207                kdl_document
4208                    .get($name)
4209                    .and_then(|n| n.entries().iter().next())
4210                    .and_then(|e| e.value().as_string())
4211                    .map(|s| s.to_owned())
4212                    .ok_or(format!("Failed to parse tab {}", $name))?
4213            }};
4214        }
4215        macro_rules! optional_string_node {
4216            ($name:expr) => {{
4217                kdl_document
4218                    .get($name)
4219                    .and_then(|n| n.entries().iter().next())
4220                    .and_then(|e| e.value().as_string())
4221                    .map(|s| s.to_owned())
4222            }};
4223        }
4224        macro_rules! bool_node {
4225            ($name:expr) => {{
4226                kdl_document
4227                    .get($name)
4228                    .and_then(|n| n.entries().iter().next())
4229                    .and_then(|e| e.value().as_bool())
4230                    .ok_or(format!("Failed to parse tab {}", $name))?
4231            }};
4232        }
4233
4234        let position = int_node!("position", usize);
4235        let name = string_node!("name");
4236        let active = bool_node!("active");
4237        let panes_to_hide = int_node!("panes_to_hide", usize);
4238        let is_fullscreen_active = bool_node!("is_fullscreen_active");
4239        let is_sync_panes_active = bool_node!("is_sync_panes_active");
4240        let are_floating_panes_visible = bool_node!("are_floating_panes_visible");
4241        let mut other_focused_clients = vec![];
4242        if let Some(tab_other_focused_clients) = kdl_document
4243            .get("other_focused_clients")
4244            .map(|n| n.entries())
4245        {
4246            for entry in tab_other_focused_clients {
4247                if let Some(entry_parsed) = entry.value().as_i64() {
4248                    other_focused_clients.push(entry_parsed as u16);
4249                }
4250            }
4251        }
4252        let active_swap_layout_name = optional_string_node!("active_swap_layout_name");
4253        let is_swap_layout_dirty = bool_node!("is_swap_layout_dirty");
4254        Ok(TabInfo {
4255            position,
4256            name,
4257            active,
4258            panes_to_hide,
4259            is_fullscreen_active,
4260            is_sync_panes_active,
4261            are_floating_panes_visible,
4262            other_focused_clients,
4263            active_swap_layout_name,
4264            is_swap_layout_dirty,
4265        })
4266    }
4267    pub fn encode_to_kdl(&self) -> KdlDocument {
4268        let mut kdl_doucment = KdlDocument::new();
4269
4270        let mut position = KdlNode::new("position");
4271        position.push(self.position as i64);
4272        kdl_doucment.nodes_mut().push(position);
4273
4274        let mut name = KdlNode::new("name");
4275        name.push(self.name.clone());
4276        kdl_doucment.nodes_mut().push(name);
4277
4278        let mut active = KdlNode::new("active");
4279        active.push(self.active);
4280        kdl_doucment.nodes_mut().push(active);
4281
4282        let mut panes_to_hide = KdlNode::new("panes_to_hide");
4283        panes_to_hide.push(self.panes_to_hide as i64);
4284        kdl_doucment.nodes_mut().push(panes_to_hide);
4285
4286        let mut is_fullscreen_active = KdlNode::new("is_fullscreen_active");
4287        is_fullscreen_active.push(self.is_fullscreen_active);
4288        kdl_doucment.nodes_mut().push(is_fullscreen_active);
4289
4290        let mut is_sync_panes_active = KdlNode::new("is_sync_panes_active");
4291        is_sync_panes_active.push(self.is_sync_panes_active);
4292        kdl_doucment.nodes_mut().push(is_sync_panes_active);
4293
4294        let mut are_floating_panes_visible = KdlNode::new("are_floating_panes_visible");
4295        are_floating_panes_visible.push(self.are_floating_panes_visible);
4296        kdl_doucment.nodes_mut().push(are_floating_panes_visible);
4297
4298        if !self.other_focused_clients.is_empty() {
4299            let mut other_focused_clients = KdlNode::new("other_focused_clients");
4300            for client_id in &self.other_focused_clients {
4301                other_focused_clients.push(*client_id as i64);
4302            }
4303            kdl_doucment.nodes_mut().push(other_focused_clients);
4304        }
4305
4306        if let Some(active_swap_layout_name) = self.active_swap_layout_name.as_ref() {
4307            let mut active_swap_layout = KdlNode::new("active_swap_layout_name");
4308            active_swap_layout.push(active_swap_layout_name.to_string());
4309            kdl_doucment.nodes_mut().push(active_swap_layout);
4310        }
4311
4312        let mut is_swap_layout_dirty = KdlNode::new("is_swap_layout_dirty");
4313        is_swap_layout_dirty.push(self.is_swap_layout_dirty);
4314        kdl_doucment.nodes_mut().push(is_swap_layout_dirty);
4315
4316        kdl_doucment
4317    }
4318}
4319
4320impl PaneManifest {
4321    pub fn decode_from_kdl(kdl_doucment: &KdlDocument) -> Self {
4322        let mut panes: HashMap<usize, Vec<PaneInfo>> = HashMap::new();
4323        for node in kdl_doucment.nodes() {
4324            if node.name().to_string() == "pane" {
4325                if let Some(pane_document) = node.children() {
4326                    if let Ok((tab_position, pane_info)) = PaneInfo::decode_from_kdl(pane_document)
4327                    {
4328                        let panes_in_tab_position =
4329                            panes.entry(tab_position).or_insert_with(Vec::new);
4330                        panes_in_tab_position.push(pane_info);
4331                    }
4332                }
4333            }
4334        }
4335        PaneManifest { panes }
4336    }
4337    pub fn encode_to_kdl(&self) -> KdlDocument {
4338        let mut kdl_doucment = KdlDocument::new();
4339        for (tab_position, panes) in &self.panes {
4340            for pane in panes {
4341                let mut pane_node = KdlNode::new("pane");
4342                let mut pane = pane.encode_to_kdl();
4343
4344                let mut position_node = KdlNode::new("tab_position");
4345                position_node.push(*tab_position as i64);
4346                pane.nodes_mut().push(position_node);
4347
4348                pane_node.set_children(pane);
4349                kdl_doucment.nodes_mut().push(pane_node);
4350            }
4351        }
4352        kdl_doucment
4353    }
4354}
4355
4356impl PaneInfo {
4357    pub fn decode_from_kdl(kdl_document: &KdlDocument) -> Result<(usize, Self), String> {
4358        // usize is the tab position
4359        macro_rules! int_node {
4360            ($name:expr, $type:ident) => {{
4361                kdl_document
4362                    .get($name)
4363                    .and_then(|n| n.entries().iter().next())
4364                    .and_then(|e| e.value().as_i64())
4365                    .map(|e| e as $type)
4366                    .ok_or(format!("Failed to parse pane {}", $name))?
4367            }};
4368        }
4369        macro_rules! optional_int_node {
4370            ($name:expr, $type:ident) => {{
4371                kdl_document
4372                    .get($name)
4373                    .and_then(|n| n.entries().iter().next())
4374                    .and_then(|e| e.value().as_i64())
4375                    .map(|e| e as $type)
4376            }};
4377        }
4378        macro_rules! bool_node {
4379            ($name:expr) => {{
4380                kdl_document
4381                    .get($name)
4382                    .and_then(|n| n.entries().iter().next())
4383                    .and_then(|e| e.value().as_bool())
4384                    .ok_or(format!("Failed to parse pane {}", $name))?
4385            }};
4386        }
4387        macro_rules! string_node {
4388            ($name:expr) => {{
4389                kdl_document
4390                    .get($name)
4391                    .and_then(|n| n.entries().iter().next())
4392                    .and_then(|e| e.value().as_string())
4393                    .map(|s| s.to_owned())
4394                    .ok_or(format!("Failed to parse pane {}", $name))?
4395            }};
4396        }
4397        macro_rules! optional_string_node {
4398            ($name:expr) => {{
4399                kdl_document
4400                    .get($name)
4401                    .and_then(|n| n.entries().iter().next())
4402                    .and_then(|e| e.value().as_string())
4403                    .map(|s| s.to_owned())
4404            }};
4405        }
4406        let tab_position = int_node!("tab_position", usize);
4407        let id = int_node!("id", u32);
4408
4409        let is_plugin = bool_node!("is_plugin");
4410        let is_focused = bool_node!("is_focused");
4411        let is_fullscreen = bool_node!("is_fullscreen");
4412        let is_floating = bool_node!("is_floating");
4413        let is_suppressed = bool_node!("is_suppressed");
4414        let title = string_node!("title");
4415        let exited = bool_node!("exited");
4416        let exit_status = optional_int_node!("exit_status", i32);
4417        let is_held = bool_node!("is_held");
4418        let pane_x = int_node!("pane_x", usize);
4419        let pane_content_x = int_node!("pane_content_x", usize);
4420        let pane_y = int_node!("pane_y", usize);
4421        let pane_content_y = int_node!("pane_content_y", usize);
4422        let pane_rows = int_node!("pane_rows", usize);
4423        let pane_content_rows = int_node!("pane_content_rows", usize);
4424        let pane_columns = int_node!("pane_columns", usize);
4425        let pane_content_columns = int_node!("pane_content_columns", usize);
4426        let cursor_coordinates_in_pane = kdl_document
4427            .get("cursor_coordinates_in_pane")
4428            .map(|n| {
4429                let mut entries = n.entries().iter();
4430                (entries.next(), entries.next())
4431            })
4432            .and_then(|(x, y)| {
4433                let x = x.and_then(|x| x.value().as_i64()).map(|x| x as usize);
4434                let y = y.and_then(|y| y.value().as_i64()).map(|y| y as usize);
4435                match (x, y) {
4436                    (Some(x), Some(y)) => Some((x, y)),
4437                    _ => None,
4438                }
4439            });
4440        let terminal_command = optional_string_node!("terminal_command");
4441        let plugin_url = optional_string_node!("plugin_url");
4442        let is_selectable = bool_node!("is_selectable");
4443
4444        let pane_info = PaneInfo {
4445            id,
4446            is_plugin,
4447            is_focused,
4448            is_fullscreen,
4449            is_floating,
4450            is_suppressed,
4451            title,
4452            exited,
4453            exit_status,
4454            is_held,
4455            pane_x,
4456            pane_content_x,
4457            pane_y,
4458            pane_content_y,
4459            pane_rows,
4460            pane_content_rows,
4461            pane_columns,
4462            pane_content_columns,
4463            cursor_coordinates_in_pane,
4464            terminal_command,
4465            plugin_url,
4466            is_selectable,
4467        };
4468        Ok((tab_position, pane_info))
4469    }
4470    pub fn encode_to_kdl(&self) -> KdlDocument {
4471        let mut kdl_doucment = KdlDocument::new();
4472        macro_rules! int_node {
4473            ($name:expr, $val:expr) => {{
4474                let mut att = KdlNode::new($name);
4475                att.push($val as i64);
4476                kdl_doucment.nodes_mut().push(att);
4477            }};
4478        }
4479        macro_rules! bool_node {
4480            ($name:expr, $val:expr) => {{
4481                let mut att = KdlNode::new($name);
4482                att.push($val);
4483                kdl_doucment.nodes_mut().push(att);
4484            }};
4485        }
4486        macro_rules! string_node {
4487            ($name:expr, $val:expr) => {{
4488                let mut att = KdlNode::new($name);
4489                att.push($val);
4490                kdl_doucment.nodes_mut().push(att);
4491            }};
4492        }
4493
4494        int_node!("id", self.id);
4495        bool_node!("is_plugin", self.is_plugin);
4496        bool_node!("is_focused", self.is_focused);
4497        bool_node!("is_fullscreen", self.is_fullscreen);
4498        bool_node!("is_floating", self.is_floating);
4499        bool_node!("is_suppressed", self.is_suppressed);
4500        string_node!("title", self.title.to_string());
4501        bool_node!("exited", self.exited);
4502        if let Some(exit_status) = self.exit_status {
4503            int_node!("exit_status", exit_status);
4504        }
4505        bool_node!("is_held", self.is_held);
4506        int_node!("pane_x", self.pane_x);
4507        int_node!("pane_content_x", self.pane_content_x);
4508        int_node!("pane_y", self.pane_y);
4509        int_node!("pane_content_y", self.pane_content_y);
4510        int_node!("pane_rows", self.pane_rows);
4511        int_node!("pane_content_rows", self.pane_content_rows);
4512        int_node!("pane_columns", self.pane_columns);
4513        int_node!("pane_content_columns", self.pane_content_columns);
4514        if let Some((cursor_x, cursor_y)) = self.cursor_coordinates_in_pane {
4515            let mut cursor_coordinates_in_pane = KdlNode::new("cursor_coordinates_in_pane");
4516            cursor_coordinates_in_pane.push(cursor_x as i64);
4517            cursor_coordinates_in_pane.push(cursor_y as i64);
4518            kdl_doucment.nodes_mut().push(cursor_coordinates_in_pane);
4519        }
4520        if let Some(terminal_command) = &self.terminal_command {
4521            string_node!("terminal_command", terminal_command.to_string());
4522        }
4523        if let Some(plugin_url) = &self.plugin_url {
4524            string_node!("plugin_url", plugin_url.to_string());
4525        }
4526        bool_node!("is_selectable", self.is_selectable);
4527        kdl_doucment
4528    }
4529}
4530
4531pub fn parse_plugin_user_configuration(
4532    plugin_block: &KdlNode,
4533) -> Result<BTreeMap<String, String>, ConfigError> {
4534    let mut configuration = BTreeMap::new();
4535    for user_configuration_entry in plugin_block.entries() {
4536        let name = user_configuration_entry.name();
4537        let value = user_configuration_entry.value();
4538        if let Some(name) = name {
4539            let name = name.to_string();
4540            if KdlLayoutParser::is_a_reserved_plugin_property(&name) {
4541                continue;
4542            }
4543            configuration.insert(name, value.to_string());
4544        }
4545    }
4546    if let Some(user_config) = kdl_children_nodes!(plugin_block) {
4547        for user_configuration_entry in user_config {
4548            let config_entry_name = kdl_name!(user_configuration_entry);
4549            if KdlLayoutParser::is_a_reserved_plugin_property(&config_entry_name) {
4550                continue;
4551            }
4552            let config_entry_str_value = kdl_first_entry_as_string!(user_configuration_entry)
4553                .map(|s| format!("{}", s.to_string()));
4554            let config_entry_int_value = kdl_first_entry_as_i64!(user_configuration_entry)
4555                .map(|s| format!("{}", s.to_string()));
4556            let config_entry_bool_value = kdl_first_entry_as_bool!(user_configuration_entry)
4557                .map(|s| format!("{}", s.to_string()));
4558            let config_entry_children = user_configuration_entry
4559                .children()
4560                .map(|s| format!("{}", s.to_string().trim()));
4561            let config_entry_value = config_entry_str_value
4562                .or(config_entry_int_value)
4563                .or(config_entry_bool_value)
4564                .or(config_entry_children)
4565                .ok_or(ConfigError::new_kdl_error(
4566                    format!(
4567                        "Failed to parse plugin block configuration: {:?}",
4568                        user_configuration_entry
4569                    ),
4570                    plugin_block.span().offset(),
4571                    plugin_block.span().len(),
4572                ))?;
4573            configuration.insert(config_entry_name.into(), config_entry_value);
4574        }
4575    }
4576    Ok(configuration)
4577}
4578
4579#[test]
4580fn serialize_and_deserialize_session_info() {
4581    let session_info = SessionInfo::default();
4582    let serialized = session_info.to_string();
4583    let deserealized = SessionInfo::from_string(&serialized, "not this session").unwrap();
4584    assert_eq!(session_info, deserealized);
4585    insta::assert_snapshot!(serialized);
4586}
4587
4588#[test]
4589fn serialize_and_deserialize_session_info_with_data() {
4590    let panes_list = vec![
4591        PaneInfo {
4592            id: 1,
4593            is_plugin: false,
4594            is_focused: true,
4595            is_fullscreen: true,
4596            is_floating: false,
4597            is_suppressed: false,
4598            title: "pane 1".to_owned(),
4599            exited: false,
4600            exit_status: None,
4601            is_held: false,
4602            pane_x: 0,
4603            pane_content_x: 1,
4604            pane_y: 0,
4605            pane_content_y: 1,
4606            pane_rows: 5,
4607            pane_content_rows: 4,
4608            pane_columns: 22,
4609            pane_content_columns: 21,
4610            cursor_coordinates_in_pane: Some((0, 0)),
4611            terminal_command: Some("foo".to_owned()),
4612            plugin_url: None,
4613            is_selectable: true,
4614        },
4615        PaneInfo {
4616            id: 1,
4617            is_plugin: true,
4618            is_focused: true,
4619            is_fullscreen: true,
4620            is_floating: false,
4621            is_suppressed: false,
4622            title: "pane 1".to_owned(),
4623            exited: false,
4624            exit_status: None,
4625            is_held: false,
4626            pane_x: 0,
4627            pane_content_x: 1,
4628            pane_y: 0,
4629            pane_content_y: 1,
4630            pane_rows: 5,
4631            pane_content_rows: 4,
4632            pane_columns: 22,
4633            pane_content_columns: 21,
4634            cursor_coordinates_in_pane: Some((0, 0)),
4635            terminal_command: None,
4636            plugin_url: Some("i_am_a_fake_plugin".to_owned()),
4637            is_selectable: true,
4638        },
4639    ];
4640    let mut panes = HashMap::new();
4641    panes.insert(0, panes_list);
4642    let session_info = SessionInfo {
4643        name: "my session name".to_owned(),
4644        tabs: vec![
4645            TabInfo {
4646                position: 0,
4647                name: "tab 1".to_owned(),
4648                active: true,
4649                panes_to_hide: 1,
4650                is_fullscreen_active: true,
4651                is_sync_panes_active: false,
4652                are_floating_panes_visible: true,
4653                other_focused_clients: vec![2, 3],
4654                active_swap_layout_name: Some("BASE".to_owned()),
4655                is_swap_layout_dirty: true,
4656            },
4657            TabInfo {
4658                position: 1,
4659                name: "tab 2".to_owned(),
4660                active: true,
4661                panes_to_hide: 0,
4662                is_fullscreen_active: false,
4663                is_sync_panes_active: true,
4664                are_floating_panes_visible: true,
4665                other_focused_clients: vec![2, 3],
4666                active_swap_layout_name: None,
4667                is_swap_layout_dirty: false,
4668            },
4669        ],
4670        panes: PaneManifest { panes },
4671        connected_clients: 2,
4672        is_current_session: false,
4673        available_layouts: vec![
4674            LayoutInfo::File("layout1".to_owned()),
4675            LayoutInfo::BuiltIn("layout2".to_owned()),
4676            LayoutInfo::File("layout3".to_owned()),
4677        ],
4678        plugins: Default::default(),
4679    };
4680    let serialized = session_info.to_string();
4681    let deserealized = SessionInfo::from_string(&serialized, "not this session").unwrap();
4682    assert_eq!(session_info, deserealized);
4683    insta::assert_snapshot!(serialized);
4684}
4685
4686#[test]
4687fn keybinds_to_string() {
4688    let fake_config = r#"
4689        keybinds {
4690            normal {
4691                bind "Ctrl g" { SwitchToMode "Locked"; }
4692            }
4693        }"#;
4694    let document: KdlDocument = fake_config.parse().unwrap();
4695    let deserialized = Keybinds::from_kdl(
4696        document.get("keybinds").unwrap(),
4697        Default::default(),
4698        &Default::default(),
4699    )
4700    .unwrap();
4701    let clear_defaults = true;
4702    let serialized = Keybinds::to_kdl(&deserialized, clear_defaults);
4703    let deserialized_from_serialized = Keybinds::from_kdl(
4704        serialized
4705            .to_string()
4706            .parse::<KdlDocument>()
4707            .unwrap()
4708            .get("keybinds")
4709            .unwrap(),
4710        Default::default(),
4711        &Default::default(),
4712    )
4713    .unwrap();
4714    insta::assert_snapshot!(serialized.to_string());
4715    assert_eq!(
4716        deserialized, deserialized_from_serialized,
4717        "Deserialized serialized config equals original config"
4718    );
4719}
4720
4721#[test]
4722fn keybinds_to_string_without_clearing_defaults() {
4723    let fake_config = r#"
4724        keybinds {
4725            normal {
4726                bind "Ctrl g" { SwitchToMode "Locked"; }
4727            }
4728        }"#;
4729    let document: KdlDocument = fake_config.parse().unwrap();
4730    let deserialized = Keybinds::from_kdl(
4731        document.get("keybinds").unwrap(),
4732        Default::default(),
4733        &Default::default(),
4734    )
4735    .unwrap();
4736    let clear_defaults = false;
4737    let serialized = Keybinds::to_kdl(&deserialized, clear_defaults);
4738    let deserialized_from_serialized = Keybinds::from_kdl(
4739        serialized
4740            .to_string()
4741            .parse::<KdlDocument>()
4742            .unwrap()
4743            .get("keybinds")
4744            .unwrap(),
4745        Default::default(),
4746        &Default::default(),
4747    )
4748    .unwrap();
4749    insta::assert_snapshot!(serialized.to_string());
4750    assert_eq!(
4751        deserialized, deserialized_from_serialized,
4752        "Deserialized serialized config equals original config"
4753    );
4754}
4755
4756#[test]
4757fn keybinds_to_string_with_multiple_actions() {
4758    let fake_config = r#"
4759        keybinds {
4760            normal {
4761                bind "Ctrl n" { NewPane; SwitchToMode "Locked"; }
4762            }
4763        }"#;
4764    let document: KdlDocument = fake_config.parse().unwrap();
4765    let deserialized = Keybinds::from_kdl(
4766        document.get("keybinds").unwrap(),
4767        Default::default(),
4768        &Default::default(),
4769    )
4770    .unwrap();
4771    let clear_defaults = true;
4772    let serialized = Keybinds::to_kdl(&deserialized, clear_defaults);
4773    let deserialized_from_serialized = Keybinds::from_kdl(
4774        serialized
4775            .to_string()
4776            .parse::<KdlDocument>()
4777            .unwrap()
4778            .get("keybinds")
4779            .unwrap(),
4780        Default::default(),
4781        &Default::default(),
4782    )
4783    .unwrap();
4784    assert_eq!(
4785        deserialized, deserialized_from_serialized,
4786        "Deserialized serialized config equals original config"
4787    );
4788    insta::assert_snapshot!(serialized.to_string());
4789}
4790
4791#[test]
4792fn keybinds_to_string_with_all_actions() {
4793    let fake_config = r#"
4794        keybinds {
4795            normal {
4796                bind "Ctrl a" { Quit; }
4797                bind "Ctrl b" { Write 102 111 111; }
4798                bind "Ctrl c" { WriteChars "hi there!"; }
4799                bind "Ctrl d" { SwitchToMode "Locked"; }
4800                bind "Ctrl e" { Resize "Increase"; }
4801                bind "Ctrl f" { FocusNextPane; }
4802                bind "Ctrl g" { FocusPreviousPane; }
4803                bind "Ctrl h" { SwitchFocus; }
4804                bind "Ctrl i" { MoveFocus "Right"; }
4805                bind "Ctrl j" { MoveFocusOrTab "Right"; }
4806                bind "Ctrl k" { MovePane "Right"; }
4807                bind "Ctrl l" { MovePaneBackwards; }
4808                bind "Ctrl m" { Resize "Decrease Down"; }
4809                bind "Ctrl n" { DumpScreen "/tmp/dumped"; }
4810                bind "Ctrl o" { DumpLayout "/tmp/dumped-layout"; }
4811                bind "Ctrl p" { EditScrollback; }
4812                bind "Ctrl q" { ScrollUp; }
4813                bind "Ctrl r" { ScrollDown; }
4814                bind "Ctrl s" { ScrollToBottom; }
4815                bind "Ctrl t" { ScrollToTop; }
4816                bind "Ctrl u" { PageScrollUp; }
4817                bind "Ctrl v" { PageScrollDown; }
4818                bind "Ctrl w" { HalfPageScrollUp; }
4819                bind "Ctrl x" { HalfPageScrollDown; }
4820                bind "Ctrl y" { ToggleFocusFullscreen; }
4821                bind "Ctrl z" { TogglePaneFrames; }
4822                bind "Alt a" { ToggleActiveSyncTab; }
4823                bind "Alt b" { NewPane "Right"; }
4824                bind "Alt c" { TogglePaneEmbedOrFloating; }
4825                bind "Alt d" { ToggleFloatingPanes; }
4826                bind "Alt e" { CloseFocus; }
4827                bind "Alt f" { PaneNameInput 0; }
4828                bind "Alt g" { UndoRenamePane; }
4829                bind "Alt h" { NewTab; }
4830                bind "Alt i" { GoToNextTab; }
4831                bind "Alt j" { GoToPreviousTab; }
4832                bind "Alt k" { CloseTab; }
4833                bind "Alt l" { GoToTab 1; }
4834                bind "Alt m" { ToggleTab; }
4835                bind "Alt n" { TabNameInput 0; }
4836                bind "Alt o" { UndoRenameTab; }
4837                bind "Alt p" { MoveTab "Right"; }
4838                bind "Alt q" {
4839                    Run "ls" "-l" {
4840                        hold_on_start true;
4841                        hold_on_close false;
4842                        cwd "/tmp";
4843                        name "my cool pane";
4844                    };
4845                }
4846                bind "Alt r" {
4847                    Run "ls" "-l" {
4848                        hold_on_start true;
4849                        hold_on_close false;
4850                        cwd "/tmp";
4851                        name "my cool pane";
4852                        floating true;
4853                    };
4854                }
4855                bind "Alt s" {
4856                    Run "ls" "-l" {
4857                        hold_on_start true;
4858                        hold_on_close false;
4859                        cwd "/tmp";
4860                        name "my cool pane";
4861                        in_place true;
4862                    };
4863                }
4864                bind "Alt t" { Detach; }
4865                bind "Alt u" {
4866                    LaunchOrFocusPlugin "zellij:session-manager"{
4867                        floating true;
4868                        move_to_focused_tab true;
4869                        skip_plugin_cache true;
4870                        config_key_1 "config_value_1";
4871                        config_key_2 "config_value_2";
4872                    };
4873                }
4874                bind "Alt v" {
4875                    LaunchOrFocusPlugin "zellij:session-manager"{
4876                        in_place true;
4877                        move_to_focused_tab true;
4878                        skip_plugin_cache true;
4879                        config_key_1 "config_value_1";
4880                        config_key_2 "config_value_2";
4881                    };
4882                }
4883                bind "Alt w" {
4884                    LaunchPlugin "zellij:session-manager" {
4885                        floating true;
4886                        skip_plugin_cache true;
4887                        config_key_1 "config_value_1";
4888                        config_key_2 "config_value_2";
4889                    };
4890                }
4891                bind "Alt x" {
4892                    LaunchPlugin "zellij:session-manager"{
4893                        in_place true;
4894                        skip_plugin_cache true;
4895                        config_key_1 "config_value_1";
4896                        config_key_2 "config_value_2";
4897                    };
4898                }
4899                bind "Alt y" { Copy; }
4900                bind "Alt z" { SearchInput 0; }
4901                bind "Ctrl Alt a" { Search "Up"; }
4902                bind "Ctrl Alt b" { SearchToggleOption "CaseSensitivity"; }
4903                bind "Ctrl Alt c" { ToggleMouseMode; }
4904                bind "Ctrl Alt d" { PreviousSwapLayout; }
4905                bind "Ctrl Alt e" { NextSwapLayout; }
4906                bind "Ctrl Alt g" { BreakPane; }
4907                bind "Ctrl Alt h" { BreakPaneRight; }
4908                bind "Ctrl Alt i" { BreakPaneLeft; }
4909                bind "Ctrl Alt i" { BreakPaneLeft; }
4910                bind "Ctrl Alt j" {
4911                    MessagePlugin "zellij:session-manager"{
4912                        name "message_name";
4913                        payload "message_payload";
4914                        cwd "/tmp";
4915                        launch_new true;
4916                        skip_cache true;
4917                        floating true;
4918                        title "plugin_title";
4919                        config_key_1 "config_value_1";
4920                        config_key_2 "config_value_2";
4921                    };
4922                }
4923            }
4924        }"#;
4925    let document: KdlDocument = fake_config.parse().unwrap();
4926    let deserialized = Keybinds::from_kdl(
4927        document.get("keybinds").unwrap(),
4928        Default::default(),
4929        &Default::default(),
4930    )
4931    .unwrap();
4932    let clear_defaults = true;
4933    let serialized = Keybinds::to_kdl(&deserialized, clear_defaults);
4934    let deserialized_from_serialized = Keybinds::from_kdl(
4935        serialized
4936            .to_string()
4937            .parse::<KdlDocument>()
4938            .unwrap()
4939            .get("keybinds")
4940            .unwrap(),
4941        Default::default(),
4942        &Default::default(),
4943    )
4944    .unwrap();
4945    // uncomment the below lines for more easily debugging a failed assertion here
4946    //     for (input_mode, input_mode_keybinds) in deserialized.0 {
4947    //         if let Some(other_input_mode_keybinds) = deserialized_from_serialized.0.get(&input_mode) {
4948    //             for (keybind, action) in input_mode_keybinds {
4949    //                 if let Some(other_action) = other_input_mode_keybinds.get(&keybind) {
4950    //                     assert_eq!(&action, other_action);
4951    //                 } else {
4952    //                     eprintln!("keybind: {:?} not found in other", keybind);
4953    //                 }
4954    //             }
4955    //         }
4956    //     }
4957    assert_eq!(
4958        deserialized, deserialized_from_serialized,
4959        "Deserialized serialized config equals original config"
4960    );
4961    insta::assert_snapshot!(serialized.to_string());
4962}
4963
4964#[test]
4965fn keybinds_to_string_with_shared_modes() {
4966    let fake_config = r#"
4967        keybinds {
4968            normal {
4969                bind "Ctrl n" { NewPane; SwitchToMode "Locked"; }
4970            }
4971            locked {
4972                bind "Ctrl n" { NewPane; SwitchToMode "Locked"; }
4973            }
4974            shared_except "locked" "pane" {
4975                bind "Ctrl f" { TogglePaneEmbedOrFloating; }
4976            }
4977            shared_among "locked" "pane" {
4978                bind "Ctrl p" { WriteChars "foo"; }
4979            }
4980        }"#;
4981    let document: KdlDocument = fake_config.parse().unwrap();
4982    let deserialized = Keybinds::from_kdl(
4983        document.get("keybinds").unwrap(),
4984        Default::default(),
4985        &Default::default(),
4986    )
4987    .unwrap();
4988    let clear_defaults = true;
4989    let serialized = Keybinds::to_kdl(&deserialized, clear_defaults);
4990    let deserialized_from_serialized = Keybinds::from_kdl(
4991        serialized
4992            .to_string()
4993            .parse::<KdlDocument>()
4994            .unwrap()
4995            .get("keybinds")
4996            .unwrap(),
4997        Default::default(),
4998        &Default::default(),
4999    )
5000    .unwrap();
5001    assert_eq!(
5002        deserialized, deserialized_from_serialized,
5003        "Deserialized serialized config equals original config"
5004    );
5005    insta::assert_snapshot!(serialized.to_string());
5006}
5007
5008#[test]
5009fn keybinds_to_string_with_multiple_multiline_actions() {
5010    let fake_config = r#"
5011        keybinds {
5012            shared {
5013                bind "Ctrl n" {
5014                    NewPane
5015                    SwitchToMode "Locked"
5016                    MessagePlugin "zellij:session-manager"{
5017                        name "message_name";
5018                        payload "message_payload";
5019                        cwd "/tmp";
5020                        launch_new true;
5021                        skip_cache true;
5022                        floating true;
5023                        title "plugin_title";
5024                        config_key_1 "config_value_1";
5025                        config_key_2 "config_value_2";
5026                    };
5027                }
5028            }
5029        }"#;
5030    let document: KdlDocument = fake_config.parse().unwrap();
5031    let deserialized = Keybinds::from_kdl(
5032        document.get("keybinds").unwrap(),
5033        Default::default(),
5034        &Default::default(),
5035    )
5036    .unwrap();
5037    let clear_defaults = true;
5038    let serialized = Keybinds::to_kdl(&deserialized, clear_defaults);
5039    let deserialized_from_serialized = Keybinds::from_kdl(
5040        serialized
5041            .to_string()
5042            .parse::<KdlDocument>()
5043            .unwrap()
5044            .get("keybinds")
5045            .unwrap(),
5046        Default::default(),
5047        &Default::default(),
5048    )
5049    .unwrap();
5050    assert_eq!(
5051        deserialized, deserialized_from_serialized,
5052        "Deserialized serialized config equals original config"
5053    );
5054    insta::assert_snapshot!(serialized.to_string());
5055}
5056
5057#[test]
5058fn themes_to_string() {
5059    let fake_config = r#"
5060        themes {
5061           dracula {
5062                fg 248 248 242
5063                bg 40 42 54
5064                black 0 0 0
5065                red 255 85 85
5066                green 80 250 123
5067                yellow 241 250 140
5068                blue 98 114 164
5069                magenta 255 121 198
5070                cyan 139 233 253
5071                white 255 255 255
5072                orange 255 184 108
5073            }
5074        }"#;
5075    let document: KdlDocument = fake_config.parse().unwrap();
5076    let sourced_from_external_file = false;
5077    let deserialized =
5078        Themes::from_kdl(document.get("themes").unwrap(), sourced_from_external_file).unwrap();
5079    let serialized = Themes::to_kdl(&deserialized).unwrap();
5080    let deserialized_from_serialized = Themes::from_kdl(
5081        serialized
5082            .to_string()
5083            .parse::<KdlDocument>()
5084            .unwrap()
5085            .get("themes")
5086            .unwrap(),
5087        sourced_from_external_file,
5088    )
5089    .unwrap();
5090    assert_eq!(
5091        deserialized, deserialized_from_serialized,
5092        "Deserialized serialized config equals original config"
5093    );
5094    insta::assert_snapshot!(serialized.to_string());
5095}
5096
5097#[test]
5098fn themes_to_string_with_hex_definitions() {
5099    let fake_config = r##"
5100        themes {
5101            nord {
5102                fg "#D8DEE9"
5103                bg "#2E3440"
5104                black "#3B4252"
5105                red "#BF616A"
5106                green "#A3BE8C"
5107                yellow "#EBCB8B"
5108                blue "#81A1C1"
5109                magenta "#B48EAD"
5110                cyan "#88C0D0"
5111                white "#E5E9F0"
5112                orange "#D08770"
5113            }
5114        }"##;
5115    let document: KdlDocument = fake_config.parse().unwrap();
5116    let sourced_from_external_file = false;
5117    let deserialized =
5118        Themes::from_kdl(document.get("themes").unwrap(), sourced_from_external_file).unwrap();
5119    let serialized = Themes::to_kdl(&deserialized).unwrap();
5120    let deserialized_from_serialized = Themes::from_kdl(
5121        serialized
5122            .to_string()
5123            .parse::<KdlDocument>()
5124            .unwrap()
5125            .get("themes")
5126            .unwrap(),
5127        sourced_from_external_file,
5128    )
5129    .unwrap();
5130    assert_eq!(
5131        deserialized, deserialized_from_serialized,
5132        "Deserialized serialized config equals original config"
5133    );
5134    insta::assert_snapshot!(serialized.to_string());
5135}
5136
5137#[test]
5138fn themes_to_string_with_eight_bit_definitions() {
5139    let fake_config = r##"
5140        themes {
5141            default {
5142                fg 1
5143                bg 10
5144                black 20
5145                red 30
5146                green 40
5147                yellow 50
5148                blue 60
5149                magenta 70
5150                cyan 80
5151                white 90
5152                orange 254
5153            }
5154        }"##;
5155    let document: KdlDocument = fake_config.parse().unwrap();
5156    let sourced_from_external_file = false;
5157    let deserialized =
5158        Themes::from_kdl(document.get("themes").unwrap(), sourced_from_external_file).unwrap();
5159    let serialized = Themes::to_kdl(&deserialized).unwrap();
5160    let deserialized_from_serialized = Themes::from_kdl(
5161        serialized
5162            .to_string()
5163            .parse::<KdlDocument>()
5164            .unwrap()
5165            .get("themes")
5166            .unwrap(),
5167        sourced_from_external_file,
5168    )
5169    .unwrap();
5170    assert_eq!(
5171        deserialized, deserialized_from_serialized,
5172        "Deserialized serialized config equals original config"
5173    );
5174    insta::assert_snapshot!(serialized.to_string());
5175}
5176
5177#[test]
5178fn themes_to_string_with_combined_definitions() {
5179    let fake_config = r##"
5180        themes {
5181            default {
5182                fg 1
5183                bg 10
5184                black 20
5185                red 30
5186                green 40
5187                yellow 50
5188                blue 60
5189                magenta 70
5190                cyan 80
5191                white 255 255 255
5192                orange "#D08770"
5193            }
5194        }"##;
5195    let document: KdlDocument = fake_config.parse().unwrap();
5196    let sourced_from_external_file = false;
5197    let deserialized =
5198        Themes::from_kdl(document.get("themes").unwrap(), sourced_from_external_file).unwrap();
5199    let serialized = Themes::to_kdl(&deserialized).unwrap();
5200    let deserialized_from_serialized = Themes::from_kdl(
5201        serialized
5202            .to_string()
5203            .parse::<KdlDocument>()
5204            .unwrap()
5205            .get("themes")
5206            .unwrap(),
5207        sourced_from_external_file,
5208    )
5209    .unwrap();
5210    assert_eq!(
5211        deserialized, deserialized_from_serialized,
5212        "Deserialized serialized config equals original config"
5213    );
5214    insta::assert_snapshot!(serialized.to_string());
5215}
5216
5217#[test]
5218fn themes_to_string_with_multiple_theme_definitions() {
5219    let fake_config = r##"
5220        themes {
5221           nord {
5222               fg "#D8DEE9"
5223               bg "#2E3440"
5224               black "#3B4252"
5225               red "#BF616A"
5226               green "#A3BE8C"
5227               yellow "#EBCB8B"
5228               blue "#81A1C1"
5229               magenta "#B48EAD"
5230               cyan "#88C0D0"
5231               white "#E5E9F0"
5232               orange "#D08770"
5233           }
5234           dracula {
5235                fg 248 248 242
5236                bg 40 42 54
5237                black 0 0 0
5238                red 255 85 85
5239                green 80 250 123
5240                yellow 241 250 140
5241                blue 98 114 164
5242                magenta 255 121 198
5243                cyan 139 233 253
5244                white 255 255 255
5245                orange 255 184 108
5246            }
5247        }"##;
5248    let document: KdlDocument = fake_config.parse().unwrap();
5249    let sourced_from_external_file = false;
5250    let deserialized =
5251        Themes::from_kdl(document.get("themes").unwrap(), sourced_from_external_file).unwrap();
5252    let serialized = Themes::to_kdl(&deserialized).unwrap();
5253    let deserialized_from_serialized = Themes::from_kdl(
5254        serialized
5255            .to_string()
5256            .parse::<KdlDocument>()
5257            .unwrap()
5258            .get("themes")
5259            .unwrap(),
5260        sourced_from_external_file,
5261    )
5262    .unwrap();
5263    assert_eq!(
5264        deserialized, deserialized_from_serialized,
5265        "Deserialized serialized config equals original config"
5266    );
5267    insta::assert_snapshot!(serialized.to_string());
5268}
5269
5270#[test]
5271fn plugins_to_string() {
5272    let fake_config = r##"
5273        plugins {
5274            tab-bar location="zellij:tab-bar"
5275            status-bar location="zellij:status-bar"
5276            strider location="zellij:strider"
5277            compact-bar location="zellij:compact-bar"
5278            session-manager location="zellij:session-manager"
5279            welcome-screen location="zellij:session-manager" {
5280                welcome_screen true
5281            }
5282            filepicker location="zellij:strider" {
5283                cwd "/"
5284            }
5285        }"##;
5286    let document: KdlDocument = fake_config.parse().unwrap();
5287    let deserialized = PluginAliases::from_kdl(document.get("plugins").unwrap()).unwrap();
5288    let serialized = PluginAliases::to_kdl(&deserialized, true);
5289    let deserialized_from_serialized = PluginAliases::from_kdl(
5290        serialized
5291            .to_string()
5292            .parse::<KdlDocument>()
5293            .unwrap()
5294            .get("plugins")
5295            .unwrap(),
5296    )
5297    .unwrap();
5298    assert_eq!(
5299        deserialized, deserialized_from_serialized,
5300        "Deserialized serialized config equals original config"
5301    );
5302    insta::assert_snapshot!(serialized.to_string());
5303}
5304
5305#[test]
5306fn plugins_to_string_with_file_and_web() {
5307    let fake_config = r##"
5308        plugins {
5309            tab-bar location="https://foo.com/plugin.wasm"
5310            filepicker location="file:/path/to/my/plugin.wasm" {
5311                cwd "/"
5312            }
5313        }"##;
5314    let document: KdlDocument = fake_config.parse().unwrap();
5315    let deserialized = PluginAliases::from_kdl(document.get("plugins").unwrap()).unwrap();
5316    let serialized = PluginAliases::to_kdl(&deserialized, true);
5317    let deserialized_from_serialized = PluginAliases::from_kdl(
5318        serialized
5319            .to_string()
5320            .parse::<KdlDocument>()
5321            .unwrap()
5322            .get("plugins")
5323            .unwrap(),
5324    )
5325    .unwrap();
5326    assert_eq!(
5327        deserialized, deserialized_from_serialized,
5328        "Deserialized serialized config equals original config"
5329    );
5330    insta::assert_snapshot!(serialized.to_string());
5331}
5332
5333#[test]
5334fn ui_config_to_string() {
5335    let fake_config = r##"
5336        ui {
5337            pane_frames {
5338                rounded_corners true
5339                hide_session_name true
5340            }
5341        }"##;
5342    let document: KdlDocument = fake_config.parse().unwrap();
5343    let deserialized = UiConfig::from_kdl(document.get("ui").unwrap()).unwrap();
5344    let serialized = UiConfig::to_kdl(&deserialized).unwrap();
5345    let deserialized_from_serialized = UiConfig::from_kdl(
5346        serialized
5347            .to_string()
5348            .parse::<KdlDocument>()
5349            .unwrap()
5350            .get("ui")
5351            .unwrap(),
5352    )
5353    .unwrap();
5354    assert_eq!(
5355        deserialized, deserialized_from_serialized,
5356        "Deserialized serialized config equals original config"
5357    );
5358    insta::assert_snapshot!(serialized.to_string());
5359}
5360
5361#[test]
5362fn ui_config_to_string_with_no_ui_config() {
5363    let fake_config = r##"
5364        ui {
5365            pane_frames {
5366            }
5367        }"##;
5368    let document: KdlDocument = fake_config.parse().unwrap();
5369    let deserialized = UiConfig::from_kdl(document.get("ui").unwrap()).unwrap();
5370    assert_eq!(UiConfig::to_kdl(&deserialized), None);
5371}
5372
5373#[test]
5374fn env_vars_to_string() {
5375    let fake_config = r##"
5376        env {
5377            foo "bar"
5378            bar "foo"
5379            thing 1
5380            baz "true"
5381        }"##;
5382    let document: KdlDocument = fake_config.parse().unwrap();
5383    let deserialized = EnvironmentVariables::from_kdl(document.get("env").unwrap()).unwrap();
5384    let serialized = EnvironmentVariables::to_kdl(&deserialized).unwrap();
5385    let deserialized_from_serialized = EnvironmentVariables::from_kdl(
5386        serialized
5387            .to_string()
5388            .parse::<KdlDocument>()
5389            .unwrap()
5390            .get("env")
5391            .unwrap(),
5392    )
5393    .unwrap();
5394    assert_eq!(
5395        deserialized, deserialized_from_serialized,
5396        "Deserialized serialized config equals original config"
5397    );
5398    insta::assert_snapshot!(serialized.to_string());
5399}
5400
5401#[test]
5402fn env_vars_to_string_with_no_env_vars() {
5403    let fake_config = r##"
5404        env {
5405        }"##;
5406    let document: KdlDocument = fake_config.parse().unwrap();
5407    let deserialized = EnvironmentVariables::from_kdl(document.get("env").unwrap()).unwrap();
5408    assert_eq!(EnvironmentVariables::to_kdl(&deserialized), None);
5409}
5410
5411#[test]
5412fn config_options_to_string() {
5413    let fake_config = r##"
5414        simplified_ui true
5415        theme "dracula"
5416        default_mode "locked"
5417        default_shell "fish"
5418        default_cwd "/tmp/foo"
5419        default_layout "compact"
5420        layout_dir "/tmp/layouts"
5421        theme_dir "/tmp/themes"
5422        mouse_mode false
5423        pane_frames false
5424        mirror_session true
5425        on_force_close "quit"
5426        scroll_buffer_size 100
5427        copy_command "pbcopy"
5428        copy_clipboard "system"
5429        copy_on_select false
5430        scrollback_editor "vim"
5431        session_name "my_cool_session"
5432        attach_to_session false
5433        auto_layout false
5434        session_serialization true
5435        serialize_pane_viewport false
5436        scrollback_lines_to_serialize 1000
5437        styled_underlines false
5438        serialization_interval 1
5439        disable_session_metadata true
5440        support_kitty_keyboard_protocol false
5441    "##;
5442    let document: KdlDocument = fake_config.parse().unwrap();
5443    let deserialized = Options::from_kdl(&document).unwrap();
5444    let mut serialized = Options::to_kdl(&deserialized, false);
5445    let mut fake_document = KdlDocument::new();
5446    fake_document.nodes_mut().append(&mut serialized);
5447    let deserialized_from_serialized =
5448        Options::from_kdl(&fake_document.to_string().parse::<KdlDocument>().unwrap()).unwrap();
5449    assert_eq!(
5450        deserialized, deserialized_from_serialized,
5451        "Deserialized serialized config equals original config"
5452    );
5453    insta::assert_snapshot!(fake_document.to_string());
5454}
5455
5456#[test]
5457fn config_options_to_string_with_comments() {
5458    let fake_config = r##"
5459        simplified_ui true
5460        theme "dracula"
5461        default_mode "locked"
5462        default_shell "fish"
5463        default_cwd "/tmp/foo"
5464        default_layout "compact"
5465        layout_dir "/tmp/layouts"
5466        theme_dir "/tmp/themes"
5467        mouse_mode false
5468        pane_frames false
5469        mirror_session true
5470        on_force_close "quit"
5471        scroll_buffer_size 100
5472        copy_command "pbcopy"
5473        copy_clipboard "system"
5474        copy_on_select false
5475        scrollback_editor "vim"
5476        session_name "my_cool_session"
5477        attach_to_session false
5478        auto_layout false
5479        session_serialization true
5480        serialize_pane_viewport false
5481        scrollback_lines_to_serialize 1000
5482        styled_underlines false
5483        serialization_interval 1
5484        disable_session_metadata true
5485        support_kitty_keyboard_protocol false
5486    "##;
5487    let document: KdlDocument = fake_config.parse().unwrap();
5488    let deserialized = Options::from_kdl(&document).unwrap();
5489    let mut serialized = Options::to_kdl(&deserialized, true);
5490    let mut fake_document = KdlDocument::new();
5491    fake_document.nodes_mut().append(&mut serialized);
5492    let deserialized_from_serialized =
5493        Options::from_kdl(&fake_document.to_string().parse::<KdlDocument>().unwrap()).unwrap();
5494    assert_eq!(
5495        deserialized, deserialized_from_serialized,
5496        "Deserialized serialized config equals original config"
5497    );
5498    insta::assert_snapshot!(fake_document.to_string());
5499}
5500
5501#[test]
5502fn config_options_to_string_without_options() {
5503    let fake_config = r##"
5504    "##;
5505    let document: KdlDocument = fake_config.parse().unwrap();
5506    let deserialized = Options::from_kdl(&document).unwrap();
5507    let mut serialized = Options::to_kdl(&deserialized, false);
5508    let mut fake_document = KdlDocument::new();
5509    fake_document.nodes_mut().append(&mut serialized);
5510    let deserialized_from_serialized =
5511        Options::from_kdl(&fake_document.to_string().parse::<KdlDocument>().unwrap()).unwrap();
5512    assert_eq!(
5513        deserialized, deserialized_from_serialized,
5514        "Deserialized serialized config equals original config"
5515    );
5516    insta::assert_snapshot!(fake_document.to_string());
5517}
5518
5519#[test]
5520fn config_options_to_string_with_some_options() {
5521    let fake_config = r##"
5522        default_layout "compact"
5523    "##;
5524    let document: KdlDocument = fake_config.parse().unwrap();
5525    let deserialized = Options::from_kdl(&document).unwrap();
5526    let mut serialized = Options::to_kdl(&deserialized, false);
5527    let mut fake_document = KdlDocument::new();
5528    fake_document.nodes_mut().append(&mut serialized);
5529    let deserialized_from_serialized =
5530        Options::from_kdl(&fake_document.to_string().parse::<KdlDocument>().unwrap()).unwrap();
5531    assert_eq!(
5532        deserialized, deserialized_from_serialized,
5533        "Deserialized serialized config equals original config"
5534    );
5535    insta::assert_snapshot!(fake_document.to_string());
5536}
5537
5538#[test]
5539fn bare_config_from_default_assets_to_string() {
5540    let fake_config = Config::from_default_assets().unwrap();
5541    let fake_config_stringified = fake_config.to_string(false);
5542    let deserialized_from_serialized = Config::from_kdl(&fake_config_stringified, None).unwrap();
5543    assert_eq!(
5544        fake_config, deserialized_from_serialized,
5545        "Deserialized serialized config equals original config"
5546    );
5547    insta::assert_snapshot!(fake_config_stringified);
5548}
5549
5550#[test]
5551fn bare_config_from_default_assets_to_string_with_comments() {
5552    let fake_config = Config::from_default_assets().unwrap();
5553    let fake_config_stringified = fake_config.to_string(true);
5554    let deserialized_from_serialized = Config::from_kdl(&fake_config_stringified, None).unwrap();
5555    assert_eq!(
5556        fake_config, deserialized_from_serialized,
5557        "Deserialized serialized config equals original config"
5558    );
5559    insta::assert_snapshot!(fake_config_stringified);
5560}