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