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