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