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