1pub use super::command::{OpenFilePayload, RunCommandAction};
4use super::layout::{
5 FloatingPaneLayout, Layout, PluginAlias, RunPlugin, RunPluginLocation, RunPluginOrAlias,
6 SwapFloatingLayout, SwapTiledLayout, TabLayoutInfo, TiledPaneLayout,
7};
8use crate::cli::CliAction;
9use crate::data::{
10 CommandOrPlugin, Direction, KeyWithModifier, LayoutInfo, NewPanePlacement, OriginatingPlugin,
11 PaneId, Resize, UnblockCondition,
12};
13use crate::data::{FloatingPaneCoordinates, InputMode};
14use crate::home::{find_default_config_dir, get_layout_dir};
15use crate::input::config::{Config, ConfigError, KdlError};
16use crate::input::mouse::MouseEvent;
17use crate::input::options::{OnForceClose, PaneFrameStyle};
18use miette::{NamedSource, Report};
19use serde::{Deserialize, Serialize};
20use std::collections::BTreeMap;
21use uuid::Uuid;
22
23use std::path::PathBuf;
24use std::str::FromStr;
25
26use crate::position::Position;
27
28pub fn initial_panes_from_cli(
29 initial_command: Vec<String>,
30 initial_plugin: Option<String>,
31 cwd: Option<PathBuf>,
32 caller_cwd: PathBuf,
33 close_on_exit: bool,
34 start_suspended: bool,
35) -> Option<Vec<CommandOrPlugin>> {
36 if let Some(plugin_url) = initial_plugin {
37 let plugin = match RunPluginLocation::parse(&plugin_url, cwd.clone()) {
38 Ok(location) => RunPluginOrAlias::RunPlugin(RunPlugin {
39 _allow_exec_host_cmd: false,
40 location,
41 configuration: Default::default(),
42 initial_cwd: cwd,
43 }),
44 Err(_) => {
45 let mut plugin_alias = PluginAlias::new(&plugin_url, &None, cwd);
46 plugin_alias.set_caller_cwd_if_not_set(Some(caller_cwd));
47 RunPluginOrAlias::Alias(plugin_alias)
48 },
49 };
50 Some(vec![CommandOrPlugin::Plugin(plugin)])
51 } else if !initial_command.is_empty() {
52 let mut initial_command = initial_command;
53 let (command, args) = (
54 PathBuf::from(initial_command.remove(0)),
55 initial_command.into_iter().collect(),
56 );
57 let run_command_action = RunCommandAction {
58 command,
59 args,
60 cwd,
61 direction: None,
62 hold_on_close: !close_on_exit,
63 hold_on_start: start_suspended,
64 ..Default::default()
65 };
66 Some(vec![CommandOrPlugin::Command(run_command_action)])
67 } else {
68 None
69 }
70}
71
72#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
73pub enum ResizeDirection {
74 Left,
75 Right,
76 Up,
77 Down,
78 Increase,
79 Decrease,
80}
81
82impl FromStr for ResizeDirection {
83 type Err = String;
84 fn from_str(s: &str) -> Result<Self, Self::Err> {
85 match s {
86 "Left" | "left" => Ok(ResizeDirection::Left),
87 "Right" | "right" => Ok(ResizeDirection::Right),
88 "Up" | "up" => Ok(ResizeDirection::Up),
89 "Down" | "down" => Ok(ResizeDirection::Down),
90 "Increase" | "increase" | "+" => Ok(ResizeDirection::Increase),
91 "Decrease" | "decrease" | "-" => Ok(ResizeDirection::Decrease),
92 _ => Err(format!(
93 "Failed to parse ResizeDirection. Unknown ResizeDirection: {}",
94 s
95 )),
96 }
97 }
98}
99
100#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]
101pub enum SearchDirection {
102 Down,
103 Up,
104}
105
106impl FromStr for SearchDirection {
107 type Err = String;
108 fn from_str(s: &str) -> Result<Self, Self::Err> {
109 match s {
110 "Down" | "down" => Ok(SearchDirection::Down),
111 "Up" | "up" => Ok(SearchDirection::Up),
112 _ => Err(format!(
113 "Failed to parse SearchDirection. Unknown SearchDirection: {}",
114 s
115 )),
116 }
117 }
118}
119
120#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
121pub enum SearchOption {
122 CaseSensitivity,
123 WholeWord,
124 Wrap,
125}
126
127impl FromStr for SearchOption {
128 type Err = String;
129 fn from_str(s: &str) -> Result<Self, Self::Err> {
130 match s {
131 "CaseSensitivity" | "casesensitivity" | "Casesensitivity" => {
132 Ok(SearchOption::CaseSensitivity)
133 },
134 "WholeWord" | "wholeword" | "Wholeword" => Ok(SearchOption::WholeWord),
135 "Wrap" | "wrap" => Ok(SearchOption::Wrap),
136 _ => Err(format!(
137 "Failed to parse SearchOption. Unknown SearchOption: {}",
138 s
139 )),
140 }
141 }
142}
143
144#[derive(
150 Clone,
151 Debug,
152 PartialEq,
153 Eq,
154 Deserialize,
155 Serialize,
156 strum_macros::Display,
157 strum_macros::EnumString,
158 strum_macros::EnumIter,
159)]
160#[strum(ascii_case_insensitive)]
161pub enum Action {
162 Quit,
164 Write {
166 key_with_modifier: Option<KeyWithModifier>,
167 bytes: Vec<u8>,
168 is_kitty_keyboard_protocol: bool,
169 },
170 WriteChars {
172 chars: String,
173 },
174 WriteToPaneId {
176 bytes: Vec<u8>,
177 pane_id: PaneId,
178 },
179 WriteCharsToPaneId {
181 chars: String,
182 pane_id: PaneId,
183 },
184 Paste {
186 chars: String,
187 pane_id: Option<PaneId>,
188 },
189 SwitchToMode {
191 input_mode: InputMode,
192 },
193 SwitchModeForAllClients {
195 input_mode: InputMode,
196 },
197 Resize {
199 resize: Resize,
200 direction: Option<Direction>,
201 },
202 FocusNextPane,
204 FocusPreviousPane,
205 FocusLastPane,
207 SwitchFocus,
209 MoveFocus {
210 direction: Direction,
211 },
212 MoveFocusOrTab {
215 direction: Direction,
216 },
217 MovePane {
218 direction: Option<Direction>,
219 },
220 MovePaneBackwards,
221 ClearScreen,
223 DumpScreen {
225 file_path: Option<String>,
226 include_scrollback: bool,
227 pane_id: Option<PaneId>,
228 ansi: bool,
229 },
230 DumpLayout,
232 SaveSession,
234 EditScrollback {
235 ansi: bool,
236 },
237 ScrollUp,
239 ScrollUpAt {
241 position: Position,
242 },
243 ScrollDown,
245 ScrollDownAt {
247 position: Position,
248 },
249 ScrollToPreviousPrompt,
250 ScrollToNextPrompt,
251 SelectCommandAtScrollPosition,
252 CopyLastCommandOutput,
253 ScrollToBottom,
255 ScrollToTop,
257 PageScrollUp,
259 PageScrollDown,
261 HalfPageScrollUp,
263 HalfPageScrollDown,
265 ToggleFocusFullscreen,
267 ToggleFocusNoUiFullscreen,
268 TogglePaneFrames,
270 SetPaneFrameStyle(PaneFrameStyle),
271 ToggleActiveSyncTab,
273 NewPane {
276 direction: Option<Direction>,
277 pane_name: Option<String>,
278 start_suppressed: bool,
279 },
280 NewBlockingPane {
282 placement: NewPanePlacement,
283 pane_name: Option<String>,
284 command: Option<RunCommandAction>,
285 unblock_condition: Option<UnblockCondition>,
286 near_current_pane: bool,
287 no_focus: bool,
288 tab_id: Option<usize>,
289 },
290 EditFile {
293 payload: OpenFilePayload,
294 direction: Option<Direction>,
295 floating: bool,
296 in_place: bool,
297 close_replaced_pane: bool,
298 start_suppressed: bool,
299 coordinates: Option<FloatingPaneCoordinates>,
300 near_current_pane: bool,
301 no_focus: bool,
302 tab_id: Option<usize>,
303 },
304 NewFloatingPane {
307 command: Option<RunCommandAction>,
308 pane_name: Option<String>,
309 coordinates: Option<FloatingPaneCoordinates>,
310 near_current_pane: bool,
311 no_focus: bool,
312 tab_id: Option<usize>,
313 },
314 NewTiledPane {
317 direction: Option<Direction>,
318 command: Option<RunCommandAction>,
319 pane_name: Option<String>,
320 near_current_pane: bool,
321 no_focus: bool,
322 borderless: Option<bool>,
323 tab_id: Option<usize>,
324 },
325 NewInPlacePane {
328 command: Option<RunCommandAction>,
329 pane_name: Option<String>,
330 near_current_pane: bool,
331 no_focus: bool,
332 pane_id_to_replace: Option<PaneId>,
333 close_replaced_pane: bool,
334 tab_id: Option<usize>,
335 },
336 NewStackedPane {
338 command: Option<RunCommandAction>,
339 pane_name: Option<String>,
340 near_current_pane: bool,
341 no_focus: bool,
342 tab_id: Option<usize>,
343 },
344 TogglePaneEmbedOrFloating,
346 ToggleFloatingPanes,
348 ShowFloatingPanes {
350 tab_id: Option<usize>,
351 },
352 HideFloatingPanes {
354 tab_id: Option<usize>,
355 },
356 AreFloatingPanesVisible {
358 tab_id: Option<usize>,
359 },
360 CloseFocus,
362 PaneNameInput {
363 input: Vec<u8>,
364 },
365 UndoRenamePane,
366 NewTab {
368 tiled_layout: Option<TiledPaneLayout>,
369 floating_layouts: Vec<FloatingPaneLayout>,
370 swap_tiled_layouts: Option<Vec<SwapTiledLayout>>,
371 swap_floating_layouts: Option<Vec<SwapFloatingLayout>>,
372 tab_name: Option<String>,
373 should_change_focus_to_new_tab: bool,
374 cwd: Option<PathBuf>,
375 initial_panes: Option<Vec<CommandOrPlugin>>,
376 first_pane_unblock_condition: Option<UnblockCondition>,
377 },
378 NoOp,
380 GoToNextTab,
382 GoToPreviousTab,
384 CloseTab,
386 GoToTab {
387 index: u32,
388 },
389 GoToTabName {
390 name: String,
391 create: bool,
392 },
393 ToggleTab,
394 TabNameInput {
395 input: Vec<u8>,
396 },
397 UndoRenameTab,
398 MoveTab {
399 direction: Direction,
400 },
401 Run {
403 command: RunCommandAction,
404 near_current_pane: bool,
405 no_focus: bool,
406 },
407 SetPaneColor {
409 pane_id: PaneId,
410 fg: Option<String>,
411 bg: Option<String>,
412 },
413 Detach,
415 SetDarkTheme,
417 SetLightTheme,
419 ToggleTheme,
421 SwitchSession {
423 name: String,
424 tab_position: Option<usize>,
425 pane_id: Option<(u32, bool)>, layout: Option<LayoutInfo>,
427 cwd: Option<PathBuf>,
428 },
429 LaunchOrFocusPlugin {
431 plugin: RunPluginOrAlias,
432 should_float: bool,
433 move_to_focused_tab: bool,
434 should_open_in_place: bool,
435 close_replaced_pane: bool,
436 skip_cache: bool,
437 tab_id: Option<usize>,
438 },
439 LaunchPlugin {
441 plugin: RunPluginOrAlias,
442 should_float: bool,
443 should_open_in_place: bool,
444 close_replaced_pane: bool,
445 skip_cache: bool,
446 cwd: Option<PathBuf>,
447 no_focus: bool,
448 tab_id: Option<usize>,
449 },
450 MouseEvent {
451 event: MouseEvent,
452 },
453 Copy,
454 Confirm,
456 Deny,
458 SkipConfirm {
460 action: Box<Action>,
461 },
462 SearchInput {
464 input: Vec<u8>,
465 },
466 Search {
468 direction: SearchDirection,
469 },
470 SearchToggleOption {
472 option: SearchOption,
473 },
474 ToggleMouseMode,
475 PreviousSwapLayout,
476 NextSwapLayout,
477 OverrideLayout {
479 tabs: Vec<TabLayoutInfo>,
480 retain_existing_terminal_panes: bool,
481 retain_existing_plugin_panes: bool,
482 apply_only_to_active_tab: bool,
483 },
484 QueryTabNames,
486 NewTiledPluginPane {
489 plugin: RunPluginOrAlias,
490 pane_name: Option<String>,
491 skip_cache: bool,
492 cwd: Option<PathBuf>,
493 no_focus: bool,
494 tab_id: Option<usize>,
495 },
496 NewFloatingPluginPane {
498 plugin: RunPluginOrAlias,
499 pane_name: Option<String>,
500 skip_cache: bool,
501 cwd: Option<PathBuf>,
502 coordinates: Option<FloatingPaneCoordinates>,
503 no_focus: bool,
504 tab_id: Option<usize>,
505 },
506 NewInPlacePluginPane {
508 plugin: RunPluginOrAlias,
509 pane_name: Option<String>,
510 skip_cache: bool,
511 close_replaced_pane: bool,
512 no_focus: bool,
513 tab_id: Option<usize>,
514 },
515 StartOrReloadPlugin {
516 plugin: RunPluginOrAlias,
517 },
518 CloseTerminalPane {
519 pane_id: u32,
520 },
521 ClosePluginPane {
522 pane_id: u32,
523 },
524 FocusTerminalPaneWithId {
525 pane_id: u32,
526 should_float_if_hidden: bool,
527 should_be_in_place_if_hidden: bool,
528 },
529 FocusPluginPaneWithId {
530 pane_id: u32,
531 should_float_if_hidden: bool,
532 should_be_in_place_if_hidden: bool,
533 },
534 RenameTerminalPane {
535 pane_id: u32,
536 name: Vec<u8>,
537 },
538 RenamePluginPane {
539 pane_id: u32,
540 name: Vec<u8>,
541 },
542 RenameTab {
543 tab_index: u32,
544 name: Vec<u8>,
545 },
546 GoToTabById {
547 id: u64,
548 },
549 CloseTabById {
550 id: u64,
551 },
552 RenameTabById {
553 id: u64,
554 name: String,
555 },
556 BreakPane,
557 BreakPaneRight,
558 BreakPaneLeft,
559 FocusHostSession,
560 FocusGuestSession,
561 ToggleHostFullscreen,
562 RenameSession {
563 name: String,
564 },
565 CliPipe {
566 pipe_id: String,
567 name: Option<String>,
568 payload: Option<String>,
569 args: Option<BTreeMap<String, String>>,
570 plugin: Option<String>,
571 configuration: Option<BTreeMap<String, String>>,
572 launch_new: bool,
573 skip_cache: bool,
574 floating: Option<bool>,
575 in_place: Option<bool>,
576 cwd: Option<PathBuf>,
577 pane_title: Option<String>,
578 },
579 KeybindPipe {
580 name: Option<String>,
581 payload: Option<String>,
582 args: Option<BTreeMap<String, String>>,
583 plugin: Option<String>,
584 plugin_id: Option<u32>, configuration: Option<BTreeMap<String, String>>,
586 launch_new: bool,
587 skip_cache: bool,
588 floating: Option<bool>,
589 in_place: Option<bool>,
590 cwd: Option<PathBuf>,
591 pane_title: Option<String>,
592 },
593 ListClients,
594 ListPanes {
595 show_tab: bool,
596 show_command: bool,
597 show_state: bool,
598 show_geometry: bool,
599 show_all: bool,
600 output_json: bool,
601 },
602 ListTabs {
603 show_state: bool,
604 show_dimensions: bool,
605 show_panes: bool,
606 show_layout: bool,
607 show_all: bool,
608 output_json: bool,
609 },
610 CurrentTabInfo {
611 output_json: bool,
612 },
613 TogglePanePinned,
614 StackPanes {
615 pane_ids: Vec<PaneId>,
616 },
617 ChangeFloatingPaneCoordinates {
618 pane_id: PaneId,
619 coordinates: FloatingPaneCoordinates,
620 },
621 TogglePaneBorderless {
622 pane_id: PaneId,
623 },
624 SetPaneBorderless {
625 pane_id: PaneId,
626 borderless: bool,
627 },
628 TogglePaneInGroup,
629 ToggleGroupMarking,
630 ScrollUpByPaneId {
632 pane_id: PaneId,
633 },
634 ScrollDownByPaneId {
635 pane_id: PaneId,
636 },
637 ScrollToTopByPaneId {
638 pane_id: PaneId,
639 },
640 ScrollToBottomByPaneId {
641 pane_id: PaneId,
642 },
643 PageScrollUpByPaneId {
644 pane_id: PaneId,
645 },
646 PageScrollDownByPaneId {
647 pane_id: PaneId,
648 },
649 HalfPageScrollUpByPaneId {
650 pane_id: PaneId,
651 },
652 HalfPageScrollDownByPaneId {
653 pane_id: PaneId,
654 },
655 ResizeByPaneId {
656 pane_id: PaneId,
657 resize: Resize,
658 direction: Option<Direction>,
659 },
660 MovePaneByPaneId {
661 pane_id: PaneId,
662 direction: Option<Direction>,
663 },
664 MovePaneBackwardsByPaneId {
665 pane_id: PaneId,
666 },
667 ClearScreenByPaneId {
668 pane_id: PaneId,
669 },
670 EditScrollbackByPaneId {
671 pane_id: PaneId,
672 ansi: bool,
673 },
674 ToggleFocusFullscreenByPaneId {
675 pane_id: PaneId,
676 },
677 ToggleFocusNoUiFullscreenByPaneId {
678 pane_id: PaneId,
679 },
680 TogglePaneEmbedOrFloatingByPaneId {
681 pane_id: PaneId,
682 },
683 CloseFocusByPaneId {
684 pane_id: PaneId,
685 },
686 RenamePaneByPaneId {
687 pane_id: Option<PaneId>,
688 name: Vec<u8>,
689 },
690 UndoRenamePaneByPaneId {
691 pane_id: PaneId,
692 },
693 TogglePanePinnedByPaneId {
694 pane_id: PaneId,
695 },
696 FocusPaneByPaneId {
697 pane_id: PaneId,
698 },
699 UndoRenameTabByTabId {
701 id: u64,
702 },
703 ToggleActiveSyncTabByTabId {
704 id: u64,
705 },
706 ToggleFloatingPanesByTabId {
707 id: u64,
708 },
709 PreviousSwapLayoutByTabId {
710 id: u64,
711 },
712 NextSwapLayoutByTabId {
713 id: u64,
714 },
715 MoveTabByTabId {
716 id: u64,
717 direction: Direction,
718 },
719}
720
721impl Default for Action {
722 fn default() -> Self {
723 Action::NoOp
724 }
725}
726
727impl Default for SearchDirection {
728 fn default() -> Self {
729 SearchDirection::Down
730 }
731}
732
733impl Default for SearchOption {
734 fn default() -> Self {
735 SearchOption::CaseSensitivity
736 }
737}
738
739impl Action {
740 pub fn shallow_eq(&self, other_action: &Action) -> bool {
742 match (self, other_action) {
743 (Action::NewTab { .. }, Action::NewTab { .. }) => true,
744 (Action::LaunchOrFocusPlugin { .. }, Action::LaunchOrFocusPlugin { .. }) => true,
745 (Action::LaunchPlugin { .. }, Action::LaunchPlugin { .. }) => true,
746 (Action::OverrideLayout { .. }, Action::OverrideLayout { .. }) => true,
747 _ => self == other_action,
748 }
749 }
750
751 pub fn actions_from_cli(
752 cli_action: CliAction,
753 get_current_dir: Box<dyn Fn() -> PathBuf>,
754 config: Option<Config>,
755 ) -> Result<Vec<Action>, String> {
756 match cli_action {
757 CliAction::Write { bytes, pane_id } => match pane_id {
758 Some(pane_id_str) => {
759 let parsed_pane_id = PaneId::from_str(&pane_id_str);
760 match parsed_pane_id {
761 Ok(parsed_pane_id) => {
762 Ok(vec![Action::WriteToPaneId {
763 bytes,
764 pane_id: parsed_pane_id,
765 }])
766 },
767 Err(_e) => {
768 Err(format!(
769 "Malformed pane id: {}, expecting either a bare integer (eg. 1), a terminal pane id (eg. terminal_1) or a plugin pane id (eg. plugin_1)",
770 pane_id_str
771 ))
772 }
773 }
774 },
775 None => Ok(vec![Action::Write {
776 key_with_modifier: None,
777 bytes,
778 is_kitty_keyboard_protocol: false,
779 }]),
780 },
781 CliAction::WriteChars { chars, pane_id } => match pane_id {
782 Some(pane_id_str) => {
783 let parsed_pane_id = PaneId::from_str(&pane_id_str);
784 match parsed_pane_id {
785 Ok(parsed_pane_id) => {
786 Ok(vec![Action::WriteCharsToPaneId {
787 chars,
788 pane_id: parsed_pane_id,
789 }])
790 },
791 Err(_e) => {
792 Err(format!(
793 "Malformed pane id: {}, expecting either a bare integer (eg. 1), a terminal pane id (eg. terminal_1) or a plugin pane id (eg. plugin_1)",
794 pane_id_str
795 ))
796 }
797 }
798 },
799 None => Ok(vec![Action::WriteChars { chars }]),
800 },
801 CliAction::Paste { chars, pane_id } => match pane_id {
802 Some(pane_id_str) => {
803 let parsed_pane_id = PaneId::from_str(&pane_id_str);
804 match parsed_pane_id {
805 Ok(parsed_pane_id) => {
806 Ok(vec![Action::Paste {
807 chars,
808 pane_id: Some(parsed_pane_id),
809 }])
810 },
811 Err(_e) => {
812 Err(format!(
813 "Malformed pane id: {}, expecting either a bare integer (eg. 1), a terminal pane id (eg. terminal_1) or a plugin pane id (eg. plugin_1)",
814 pane_id_str
815 ))
816 }
817 }
818 },
819 None => Ok(vec![Action::Paste {
820 chars,
821 pane_id: None,
822 }]),
823 },
824 CliAction::SendKeys { keys, pane_id } => {
825 let mut actions = Vec::new();
826
827 for (index, key_str) in keys.iter().enumerate() {
828 let key = KeyWithModifier::from_str(key_str).map_err(|e| {
829 let suggestion = suggest_key_fix(key_str);
830 format!(
831 "Invalid key at position {}: \"{}\"\n Error: {}\n{}",
832 index + 1,
833 key_str,
834 e,
835 suggestion
836 )
837 })?;
838
839 #[cfg(not(target_family = "wasm"))]
840 let bytes = key
841 .serialize_kitty()
842 .map(|s| s.into_bytes())
843 .unwrap_or_else(Vec::new);
844
845 #[cfg(target_family = "wasm")]
846 let bytes = vec![];
847
848 match &pane_id {
849 Some(pane_id_str) => {
850 let parsed_pane_id = PaneId::from_str(pane_id_str)
851 .map_err(|_| format!(
852 "Malformed pane id: {}, expecting either a bare integer (eg. 1), a terminal pane id (eg. terminal_1) or a plugin pane id (eg. plugin_1)",
853 pane_id_str
854 ))?;
855 actions.push(Action::WriteToPaneId {
856 bytes,
857 pane_id: parsed_pane_id,
858 });
859 },
860 None => {
861 actions.push(Action::Write {
862 key_with_modifier: Some(key),
863 bytes,
864 is_kitty_keyboard_protocol: true,
865 });
866 },
867 }
868 }
869
870 Ok(actions)
871 },
872 CliAction::Resize {
873 resize,
874 direction,
875 pane_id,
876 } => match pane_id {
877 Some(pane_id_str) => {
878 let pane_id = PaneId::from_str(&pane_id_str)
879 .map_err(|_| format!(
880 "Malformed pane id: {pane_id_str}, expecting either a bare integer (eg. 1), a terminal pane id (eg. terminal_1) or a plugin pane id (eg. plugin_1)"
881 ))?;
882 Ok(vec![Action::ResizeByPaneId {
883 pane_id,
884 resize,
885 direction,
886 }])
887 },
888 None => Ok(vec![Action::Resize { resize, direction }]),
889 },
890 CliAction::FocusNextPane => Ok(vec![Action::FocusNextPane]),
891 CliAction::FocusPreviousPane => Ok(vec![Action::FocusPreviousPane]),
892 CliAction::FocusPaneId { pane_id } => {
893 let pane_id = PaneId::from_str(&pane_id)
894 .map_err(|_| format!(
895 "Malformed pane id: {pane_id}, expecting either a bare integer (eg. 1), a terminal pane id (eg. terminal_1) or a plugin pane id (eg. plugin_1)"
896 ))?;
897 Ok(vec![Action::FocusPaneByPaneId { pane_id }])
898 },
899 CliAction::FocusLastPane => Ok(vec![Action::FocusLastPane]),
900 CliAction::MoveFocus { direction } => Ok(vec![Action::MoveFocus { direction }]),
901 CliAction::MoveFocusOrTab { direction } => {
902 Ok(vec![Action::MoveFocusOrTab { direction }])
903 },
904 CliAction::MovePane { direction, pane_id } => match pane_id {
905 Some(pane_id_str) => {
906 let pane_id = PaneId::from_str(&pane_id_str)
907 .map_err(|_| format!(
908 "Malformed pane id: {pane_id_str}, expecting either a bare integer (eg. 1), a terminal pane id (eg. terminal_1) or a plugin pane id (eg. plugin_1)"
909 ))?;
910 Ok(vec![Action::MovePaneByPaneId { pane_id, direction }])
911 },
912 None => Ok(vec![Action::MovePane { direction }]),
913 },
914 CliAction::MovePaneBackwards { pane_id } => match pane_id {
915 Some(pane_id_str) => {
916 let pane_id = PaneId::from_str(&pane_id_str)
917 .map_err(|_| format!(
918 "Malformed pane id: {pane_id_str}, expecting either a bare integer (eg. 1), a terminal pane id (eg. terminal_1) or a plugin pane id (eg. plugin_1)"
919 ))?;
920 Ok(vec![Action::MovePaneBackwardsByPaneId { pane_id }])
921 },
922 None => Ok(vec![Action::MovePaneBackwards]),
923 },
924 CliAction::MoveTab { direction, tab_id } => match tab_id {
925 Some(id) => Ok(vec![Action::MoveTabByTabId {
926 id: id as u64,
927 direction,
928 }]),
929 None => Ok(vec![Action::MoveTab { direction }]),
930 },
931 CliAction::Clear { pane_id } => match pane_id {
932 Some(pane_id_str) => {
933 let pane_id = PaneId::from_str(&pane_id_str)
934 .map_err(|_| format!(
935 "Malformed pane id: {pane_id_str}, expecting either a bare integer (eg. 1), a terminal pane id (eg. terminal_1) or a plugin pane id (eg. plugin_1)"
936 ))?;
937 Ok(vec![Action::ClearScreenByPaneId { pane_id }])
938 },
939 None => Ok(vec![Action::ClearScreen]),
940 },
941 CliAction::DumpScreen {
942 path,
943 full,
944 pane_id,
945 ansi,
946 } => match pane_id {
947 Some(pane_id_str) => {
948 let parsed_pane_id = PaneId::from_str(&pane_id_str);
949 match parsed_pane_id {
950 Ok(parsed_pane_id) => {
951 Ok(vec![Action::DumpScreen {
952 file_path: path.map(|p| p.as_os_str().to_string_lossy().into()),
953 include_scrollback: full,
954 pane_id: Some(parsed_pane_id),
955 ansi,
956 }])
957 },
958 Err(_e) => {
959 Err(format!(
960 "Malformed pane id: {}, expecting either a bare integer (eg. 1), a terminal pane id (eg. terminal_1) or a plugin pane id (eg. plugin_1)",
961 pane_id_str
962 ))
963 }
964 }
965 },
966 None => Ok(vec![Action::DumpScreen {
967 file_path: path.map(|p| p.as_os_str().to_string_lossy().into()),
968 include_scrollback: full,
969 pane_id: None,
970 ansi,
971 }]),
972 },
973 CliAction::DumpLayout => Ok(vec![Action::DumpLayout]),
974 CliAction::SaveSession => Ok(vec![Action::SaveSession]),
975 CliAction::EditScrollback { pane_id, ansi } => match pane_id {
976 Some(pane_id_str) => {
977 let pane_id = PaneId::from_str(&pane_id_str)
978 .map_err(|_| format!(
979 "Malformed pane id: {pane_id_str}, expecting either a bare integer (eg. 1), a terminal pane id (eg. terminal_1) or a plugin pane id (eg. plugin_1)"
980 ))?;
981 Ok(vec![Action::EditScrollbackByPaneId { pane_id, ansi }])
982 },
983 None => Ok(vec![Action::EditScrollback { ansi }]),
984 },
985 CliAction::ScrollUp { pane_id } => match pane_id {
986 Some(pane_id_str) => {
987 let pane_id = PaneId::from_str(&pane_id_str)
988 .map_err(|_| format!(
989 "Malformed pane id: {pane_id_str}, expecting either a bare integer (eg. 1), a terminal pane id (eg. terminal_1) or a plugin pane id (eg. plugin_1)"
990 ))?;
991 Ok(vec![Action::ScrollUpByPaneId { pane_id }])
992 },
993 None => Ok(vec![Action::ScrollUp]),
994 },
995 CliAction::ScrollDown { pane_id } => match pane_id {
996 Some(pane_id_str) => {
997 let pane_id = PaneId::from_str(&pane_id_str)
998 .map_err(|_| format!(
999 "Malformed pane id: {pane_id_str}, expecting either a bare integer (eg. 1), a terminal pane id (eg. terminal_1) or a plugin pane id (eg. plugin_1)"
1000 ))?;
1001 Ok(vec![Action::ScrollDownByPaneId { pane_id }])
1002 },
1003 None => Ok(vec![Action::ScrollDown]),
1004 },
1005 CliAction::ScrollToBottom { pane_id } => match pane_id {
1006 Some(pane_id_str) => {
1007 let pane_id = PaneId::from_str(&pane_id_str)
1008 .map_err(|_| format!(
1009 "Malformed pane id: {pane_id_str}, expecting either a bare integer (eg. 1), a terminal pane id (eg. terminal_1) or a plugin pane id (eg. plugin_1)"
1010 ))?;
1011 Ok(vec![Action::ScrollToBottomByPaneId { pane_id }])
1012 },
1013 None => Ok(vec![Action::ScrollToBottom]),
1014 },
1015 CliAction::ScrollToTop { pane_id } => match pane_id {
1016 Some(pane_id_str) => {
1017 let pane_id = PaneId::from_str(&pane_id_str)
1018 .map_err(|_| format!(
1019 "Malformed pane id: {pane_id_str}, expecting either a bare integer (eg. 1), a terminal pane id (eg. terminal_1) or a plugin pane id (eg. plugin_1)"
1020 ))?;
1021 Ok(vec![Action::ScrollToTopByPaneId { pane_id }])
1022 },
1023 None => Ok(vec![Action::ScrollToTop]),
1024 },
1025 CliAction::PageScrollUp { pane_id } => match pane_id {
1026 Some(pane_id_str) => {
1027 let pane_id = PaneId::from_str(&pane_id_str)
1028 .map_err(|_| format!(
1029 "Malformed pane id: {pane_id_str}, expecting either a bare integer (eg. 1), a terminal pane id (eg. terminal_1) or a plugin pane id (eg. plugin_1)"
1030 ))?;
1031 Ok(vec![Action::PageScrollUpByPaneId { pane_id }])
1032 },
1033 None => Ok(vec![Action::PageScrollUp]),
1034 },
1035 CliAction::PageScrollDown { pane_id } => match pane_id {
1036 Some(pane_id_str) => {
1037 let pane_id = PaneId::from_str(&pane_id_str)
1038 .map_err(|_| format!(
1039 "Malformed pane id: {pane_id_str}, expecting either a bare integer (eg. 1), a terminal pane id (eg. terminal_1) or a plugin pane id (eg. plugin_1)"
1040 ))?;
1041 Ok(vec![Action::PageScrollDownByPaneId { pane_id }])
1042 },
1043 None => Ok(vec![Action::PageScrollDown]),
1044 },
1045 CliAction::HalfPageScrollUp { pane_id } => match pane_id {
1046 Some(pane_id_str) => {
1047 let pane_id = PaneId::from_str(&pane_id_str)
1048 .map_err(|_| format!(
1049 "Malformed pane id: {pane_id_str}, expecting either a bare integer (eg. 1), a terminal pane id (eg. terminal_1) or a plugin pane id (eg. plugin_1)"
1050 ))?;
1051 Ok(vec![Action::HalfPageScrollUpByPaneId { pane_id }])
1052 },
1053 None => Ok(vec![Action::HalfPageScrollUp]),
1054 },
1055 CliAction::HalfPageScrollDown { pane_id } => match pane_id {
1056 Some(pane_id_str) => {
1057 let pane_id = PaneId::from_str(&pane_id_str)
1058 .map_err(|_| format!(
1059 "Malformed pane id: {pane_id_str}, expecting either a bare integer (eg. 1), a terminal pane id (eg. terminal_1) or a plugin pane id (eg. plugin_1)"
1060 ))?;
1061 Ok(vec![Action::HalfPageScrollDownByPaneId { pane_id }])
1062 },
1063 None => Ok(vec![Action::HalfPageScrollDown]),
1064 },
1065 CliAction::ToggleFullscreen { pane_id } => match pane_id {
1066 Some(pane_id_str) => {
1067 let pane_id = PaneId::from_str(&pane_id_str)
1068 .map_err(|_| format!(
1069 "Malformed pane id: {pane_id_str}, expecting either a bare integer (eg. 1), a terminal pane id (eg. terminal_1) or a plugin pane id (eg. plugin_1)"
1070 ))?;
1071 Ok(vec![Action::ToggleFocusFullscreenByPaneId { pane_id }])
1072 },
1073 None => Ok(vec![Action::ToggleFocusFullscreen]),
1074 },
1075 CliAction::ToggleNoUiFullscreen { pane_id } => match pane_id {
1076 Some(pane_id_str) => {
1077 let pane_id = PaneId::from_str(&pane_id_str)
1078 .map_err(|_| format!(
1079 "Malformed pane id: {pane_id_str}, expecting either a bare integer (eg. 1), a terminal pane id (eg. terminal_1) or a plugin pane id (eg. plugin_1)"
1080 ))?;
1081 Ok(vec![Action::ToggleFocusNoUiFullscreenByPaneId { pane_id }])
1082 },
1083 None => Ok(vec![Action::ToggleFocusNoUiFullscreen]),
1084 },
1085 CliAction::TogglePaneFrames => Ok(vec![Action::TogglePaneFrames]),
1086 CliAction::SetPaneFrameStyle { style } => Ok(vec![Action::SetPaneFrameStyle(style)]),
1087 CliAction::ToggleActiveSyncTab { tab_id } => match tab_id {
1088 Some(id) => Ok(vec![Action::ToggleActiveSyncTabByTabId { id: id as u64 }]),
1089 None => Ok(vec![Action::ToggleActiveSyncTab]),
1090 },
1091 CliAction::NewPane {
1092 direction,
1093 command,
1094 plugin,
1095 cwd,
1096 floating,
1097 in_place,
1098 close_replaced_pane,
1099 pane_id,
1100 name,
1101 close_on_exit,
1102 start_suspended,
1103 configuration,
1104 skip_plugin_cache,
1105 x,
1106 y,
1107 width,
1108 height,
1109 pinned,
1110 stacked,
1111 blocking,
1112 block_until_exit_success,
1113 block_until_exit_failure,
1114 block_until_exit,
1115 unblock_condition,
1116 near_current_pane,
1117 no_focus,
1118 borderless,
1119 tab_id,
1120 } => {
1121 let pane_id_to_replace = match pane_id {
1122 Some(pane_id_str) => match PaneId::from_str(&pane_id_str) {
1123 Ok(parsed_pane_id) => Some(parsed_pane_id),
1124 Err(_e) => {
1125 return Err(format!(
1126 "Malformed pane id: {}, expecting either a bare integer (eg. 1), a terminal pane id (eg. terminal_1) or a plugin pane id (eg. plugin_1)",
1127 pane_id_str
1128 ))
1129 },
1130 },
1131 None => None,
1132 };
1133 let current_dir = get_current_dir();
1134 let alias_cwd = cwd.clone().map(|cwd| current_dir.join(cwd));
1137 let cwd = cwd
1138 .map(|cwd| current_dir.join(cwd))
1139 .or_else(|| Some(current_dir.clone()));
1140 let unblock_condition = unblock_condition.or_else(|| {
1141 if block_until_exit_success {
1142 Some(UnblockCondition::OnExitSuccess)
1143 } else if block_until_exit_failure {
1144 Some(UnblockCondition::OnExitFailure)
1145 } else if block_until_exit {
1146 Some(UnblockCondition::OnAnyExit)
1147 } else {
1148 None
1149 }
1150 });
1151 if blocking || unblock_condition.is_some() {
1152 if plugin.is_some() {
1154 return Err("Blocking panes do not support plugin variants".to_string());
1155 }
1156
1157 let command = if !command.is_empty() {
1158 let mut command = command.clone();
1159 let (command, args) = (PathBuf::from(command.remove(0)), command);
1160 let hold_on_start = start_suspended;
1161 let hold_on_close = !close_on_exit;
1162 Some(RunCommandAction {
1163 command,
1164 args,
1165 cwd,
1166 direction,
1167 hold_on_close,
1168 hold_on_start,
1169 ..Default::default()
1170 })
1171 } else {
1172 None
1173 };
1174
1175 let placement = if floating {
1176 NewPanePlacement::Floating(FloatingPaneCoordinates::new(
1177 x, y, width, height, pinned, borderless,
1178 ))
1179 } else if in_place {
1180 NewPanePlacement::InPlace {
1181 pane_id_to_replace,
1182 close_replaced_pane,
1183 borderless,
1184 }
1185 } else if stacked {
1186 NewPanePlacement::Stacked {
1187 pane_id_to_stack_under: None,
1188 borderless,
1189 }
1190 } else {
1191 NewPanePlacement::Tiled {
1192 direction,
1193 borderless,
1194 }
1195 };
1196
1197 Ok(vec![Action::NewBlockingPane {
1198 placement,
1199 pane_name: name,
1200 command,
1201 unblock_condition,
1202 near_current_pane,
1203 no_focus,
1204 tab_id,
1205 }])
1206 } else if let Some(plugin) = plugin {
1207 let plugin = match RunPluginLocation::parse(&plugin, cwd.clone()) {
1208 Ok(location) => {
1209 let user_configuration = configuration.unwrap_or_default();
1210 RunPluginOrAlias::RunPlugin(RunPlugin {
1211 _allow_exec_host_cmd: false,
1212 location,
1213 configuration: user_configuration,
1214 initial_cwd: cwd.clone(),
1215 })
1216 },
1217 Err(_) => {
1218 let mut plugin_alias = PluginAlias::new(
1219 &plugin,
1220 &configuration.map(|c| c.inner().clone()),
1221 alias_cwd,
1222 );
1223 plugin_alias.set_caller_cwd_if_not_set(Some(current_dir));
1224 RunPluginOrAlias::Alias(plugin_alias)
1225 },
1226 };
1227 if floating {
1228 Ok(vec![Action::NewFloatingPluginPane {
1229 plugin,
1230 pane_name: name,
1231 skip_cache: skip_plugin_cache,
1232 cwd,
1233 coordinates: FloatingPaneCoordinates::new(
1234 x, y, width, height, pinned, borderless,
1235 ),
1236 no_focus,
1237 tab_id,
1238 }])
1239 } else if in_place {
1240 Ok(vec![Action::NewInPlacePluginPane {
1241 plugin,
1242 pane_name: name,
1243 skip_cache: skip_plugin_cache,
1244 close_replaced_pane,
1245 no_focus,
1246 tab_id,
1247 }])
1248 } else {
1249 Ok(vec![Action::NewTiledPluginPane {
1258 plugin,
1259 pane_name: name,
1260 skip_cache: skip_plugin_cache,
1261 cwd,
1262 no_focus,
1263 tab_id,
1264 }])
1265 }
1266 } else if !command.is_empty() {
1267 let mut command = command.clone();
1268 let (command, args) = (PathBuf::from(command.remove(0)), command);
1269 let hold_on_start = start_suspended;
1270 let hold_on_close = !close_on_exit;
1271 let run_command_action = RunCommandAction {
1272 command,
1273 args,
1274 cwd,
1275 direction,
1276 hold_on_close,
1277 hold_on_start,
1278 ..Default::default()
1279 };
1280 if floating {
1281 Ok(vec![Action::NewFloatingPane {
1282 command: Some(run_command_action),
1283 pane_name: name,
1284 coordinates: FloatingPaneCoordinates::new(
1285 x, y, width, height, pinned, borderless,
1286 ),
1287 near_current_pane,
1288 no_focus,
1289 tab_id,
1290 }])
1291 } else if in_place {
1292 Ok(vec![Action::NewInPlacePane {
1293 command: Some(run_command_action),
1294 pane_name: name,
1295 near_current_pane,
1296 no_focus,
1297 pane_id_to_replace,
1298 close_replaced_pane,
1299 tab_id,
1300 }])
1301 } else if stacked {
1302 Ok(vec![Action::NewStackedPane {
1303 command: Some(run_command_action),
1304 pane_name: name,
1305 near_current_pane,
1306 no_focus,
1307 tab_id,
1308 }])
1309 } else {
1310 Ok(vec![Action::NewTiledPane {
1311 direction,
1312 command: Some(run_command_action),
1313 pane_name: name,
1314 near_current_pane,
1315 no_focus,
1316 borderless,
1317 tab_id,
1318 }])
1319 }
1320 } else {
1321 if floating {
1322 Ok(vec![Action::NewFloatingPane {
1323 command: None,
1324 pane_name: name,
1325 coordinates: FloatingPaneCoordinates::new(
1326 x, y, width, height, pinned, borderless,
1327 ),
1328 near_current_pane,
1329 no_focus,
1330 tab_id,
1331 }])
1332 } else if in_place {
1333 Ok(vec![Action::NewInPlacePane {
1334 command: None,
1335 pane_name: name,
1336 near_current_pane,
1337 no_focus,
1338 pane_id_to_replace,
1339 close_replaced_pane,
1340 tab_id,
1341 }])
1342 } else if stacked {
1343 Ok(vec![Action::NewStackedPane {
1344 command: None,
1345 pane_name: name,
1346 near_current_pane,
1347 no_focus,
1348 tab_id,
1349 }])
1350 } else {
1351 Ok(vec![Action::NewTiledPane {
1352 direction,
1353 command: None,
1354 pane_name: name,
1355 near_current_pane,
1356 no_focus,
1357 borderless,
1358 tab_id,
1359 }])
1360 }
1361 }
1362 },
1363 CliAction::Edit {
1364 direction,
1365 file,
1366 line_number,
1367 floating,
1368 in_place,
1369 close_replaced_pane,
1370 cwd,
1371 x,
1372 y,
1373 width,
1374 height,
1375 pinned,
1376 near_current_pane,
1377 no_focus,
1378 borderless,
1379 tab_id,
1380 } => {
1381 let mut file = file;
1382 let current_dir = get_current_dir();
1383 let cwd = cwd
1384 .map(|cwd| current_dir.join(cwd))
1385 .or_else(|| Some(current_dir));
1386 if file.is_relative() {
1387 if let Some(cwd) = cwd.as_ref() {
1388 file = cwd.join(file);
1389 }
1390 }
1391 let start_suppressed = false;
1392 Ok(vec![Action::EditFile {
1393 payload: OpenFilePayload::new(file, line_number, cwd),
1394 direction,
1395 floating,
1396 in_place,
1397 close_replaced_pane,
1398 start_suppressed,
1399 coordinates: FloatingPaneCoordinates::new(
1400 x, y, width, height, pinned, borderless,
1401 ),
1402 near_current_pane,
1403 no_focus,
1404 tab_id,
1405 }])
1406 },
1407 CliAction::SwitchMode { input_mode } => Ok(vec![Action::SwitchToMode { input_mode }]),
1408 CliAction::TogglePaneEmbedOrFloating { pane_id } => match pane_id {
1409 Some(pane_id_str) => {
1410 let pane_id = PaneId::from_str(&pane_id_str)
1411 .map_err(|_| format!(
1412 "Malformed pane id: {pane_id_str}, expecting either a bare integer (eg. 1), a terminal pane id (eg. terminal_1) or a plugin pane id (eg. plugin_1)"
1413 ))?;
1414 Ok(vec![Action::TogglePaneEmbedOrFloatingByPaneId { pane_id }])
1415 },
1416 None => Ok(vec![Action::TogglePaneEmbedOrFloating]),
1417 },
1418 CliAction::ToggleFloatingPanes { tab_id } => match tab_id {
1419 Some(id) => Ok(vec![Action::ToggleFloatingPanesByTabId { id: id as u64 }]),
1420 None => Ok(vec![Action::ToggleFloatingPanes]),
1421 },
1422 CliAction::ShowFloatingPanes { tab_id } => {
1423 Ok(vec![Action::ShowFloatingPanes { tab_id }])
1424 },
1425 CliAction::HideFloatingPanes { tab_id } => {
1426 Ok(vec![Action::HideFloatingPanes { tab_id }])
1427 },
1428 CliAction::AreFloatingPanesVisible { tab_id } => {
1429 Ok(vec![Action::AreFloatingPanesVisible { tab_id }])
1430 },
1431 CliAction::ClosePane { pane_id } => match pane_id {
1432 Some(pane_id_str) => {
1433 let pane_id = PaneId::from_str(&pane_id_str)
1434 .map_err(|_| format!(
1435 "Malformed pane id: {pane_id_str}, expecting either a bare integer (eg. 1), a terminal pane id (eg. terminal_1) or a plugin pane id (eg. plugin_1)"
1436 ))?;
1437 Ok(vec![Action::CloseFocusByPaneId { pane_id }])
1438 },
1439 None => Ok(vec![Action::CloseFocus]),
1440 },
1441 CliAction::RenamePane { name, pane_id } => {
1442 let pane_id = match pane_id {
1443 Some(pane_id_str) => Some(
1444 PaneId::from_str(&pane_id_str).map_err(|_| format!(
1445 "Malformed pane id: {pane_id_str}, expecting either a bare integer (eg. 1), a terminal pane id (eg. terminal_1) or a plugin pane id (eg. plugin_1)"
1446 ))?,
1447 ),
1448 None => None,
1449 };
1450 Ok(vec![Action::RenamePaneByPaneId {
1451 pane_id,
1452 name: name.as_bytes().to_vec(),
1453 }])
1454 },
1455 CliAction::UndoRenamePane { pane_id } => match pane_id {
1456 Some(pane_id_str) => {
1457 let pane_id = PaneId::from_str(&pane_id_str)
1458 .map_err(|_| format!(
1459 "Malformed pane id: {pane_id_str}, expecting either a bare integer (eg. 1), a terminal pane id (eg. terminal_1) or a plugin pane id (eg. plugin_1)"
1460 ))?;
1461 Ok(vec![Action::UndoRenamePaneByPaneId { pane_id }])
1462 },
1463 None => Ok(vec![Action::UndoRenamePane]),
1464 },
1465 CliAction::GoToNextTab => Ok(vec![Action::GoToNextTab]),
1466 CliAction::GoToPreviousTab => Ok(vec![Action::GoToPreviousTab]),
1467 CliAction::CloseTab { tab_id } => match tab_id {
1468 Some(id) => Ok(vec![Action::CloseTabById { id: id as u64 }]),
1469 None => Ok(vec![Action::CloseTab]),
1470 },
1471 CliAction::GoToTab { index } => Ok(vec![Action::GoToTab { index }]),
1472 CliAction::GoToTabName { name, create } => {
1473 Ok(vec![Action::GoToTabName { name, create }])
1474 },
1475 CliAction::RenameTab { name, tab_id } => match tab_id {
1476 Some(id) => Ok(vec![Action::RenameTabById {
1477 id: id as u64,
1478 name,
1479 }]),
1480 None => Ok(vec![
1481 Action::TabNameInput { input: vec![0] },
1482 Action::TabNameInput {
1483 input: name.as_bytes().to_vec(),
1484 },
1485 ]),
1486 },
1487 CliAction::UndoRenameTab { tab_id } => match tab_id {
1488 Some(id) => Ok(vec![Action::UndoRenameTabByTabId { id: id as u64 }]),
1489 None => Ok(vec![Action::UndoRenameTab]),
1490 },
1491 CliAction::GoToTabById { id } => Ok(vec![Action::GoToTabById { id }]),
1492 CliAction::CloseTabById { id } => Ok(vec![Action::CloseTabById { id }]),
1493 CliAction::RenameTabById { id, name } => Ok(vec![Action::RenameTabById { id, name }]),
1494 CliAction::NewTab {
1495 name,
1496 layout,
1497 layout_string,
1498 layout_dir,
1499 cwd,
1500 initial_command,
1501 initial_plugin,
1502 close_on_exit,
1503 start_suspended,
1504 block_until_exit_success,
1505 block_until_exit_failure,
1506 block_until_exit,
1507 no_focus,
1508 } => {
1509 let current_dir = get_current_dir();
1510 let cwd = cwd
1511 .map(|cwd| current_dir.join(cwd))
1512 .or_else(|| Some(current_dir.clone()));
1513
1514 let first_pane_unblock_condition = if block_until_exit_success {
1516 Some(UnblockCondition::OnExitSuccess)
1517 } else if block_until_exit_failure {
1518 Some(UnblockCondition::OnExitFailure)
1519 } else if block_until_exit {
1520 Some(UnblockCondition::OnAnyExit)
1521 } else {
1522 None
1523 };
1524
1525 let initial_panes = initial_panes_from_cli(
1526 initial_command,
1527 initial_plugin,
1528 cwd.clone(),
1529 current_dir.clone(),
1530 close_on_exit,
1531 start_suspended,
1532 );
1533 if let Some(raw_layout) = layout_string {
1534 let layout_source_name = "layout-string".to_owned();
1535 let path_to_raw_layout = layout_source_name.clone();
1536 let swap_layouts: Option<(String, String)> = None;
1537 let should_start_layout_commands_suspended = false;
1538 let raw_layout_for_error = raw_layout.clone();
1539 let mut layout = Layout::from_str(&raw_layout, path_to_raw_layout, swap_layouts.as_ref().map(|(f, p)| (f.as_str(), p.as_str())), cwd).map_err(|e| {
1540 let stringified_error = match e {
1541 ConfigError::KdlError(kdl_error) => {
1542 let error = kdl_error.add_src(layout_source_name.clone(), raw_layout_for_error);
1543 let report: Report = error.into();
1544 format!("{:?}", report)
1545 }
1546 ConfigError::KdlDeserializationError(kdl_error) => {
1547 let error_message = match kdl_error.kind {
1548 kdl::KdlErrorKind::Context("valid node terminator") => {
1549 format!("Failed to deserialize KDL node. \nPossible reasons:\n{}\n{}\n{}\n{}",
1550 "- Missing `;` after a node name, eg. { node; another_node; }",
1551 "- Missing quotations (\") around an argument node eg. { first_node \"argument_node\"; }",
1552 "- Missing an equal sign (=) between node arguments on a title line. eg. argument=\"value\"",
1553 "- Found an extraneous equal sign (=) between node child arguments and their values. eg. { argument=\"value\" }")
1554 },
1555 _ => String::from(kdl_error.help.unwrap_or("Kdl Deserialization Error")),
1556 };
1557 let kdl_error = KdlError {
1558 error_message,
1559 src: Some(NamedSource::new(layout_source_name.clone(), raw_layout_for_error)),
1560 offset: Some(kdl_error.span.offset()),
1561 len: Some(kdl_error.span.len()),
1562 help_message: None,
1563 };
1564 let report: Report = kdl_error.into();
1565 format!("{:?}", report)
1566 },
1567 e => format!("{}", e)
1568 };
1569 stringified_error
1570 })?;
1571 if should_start_layout_commands_suspended {
1572 layout.recursively_add_start_suspended_including_template(Some(true));
1573 }
1574 let mut tabs = layout.tabs();
1575 if !tabs.is_empty() {
1576 let swap_tiled_layouts = Some(layout.swap_tiled_layouts.clone());
1577 let swap_floating_layouts = Some(layout.swap_floating_layouts.clone());
1578 let mut new_tab_actions = vec![];
1579 let mut has_focused_tab = tabs
1580 .iter()
1581 .any(|(_, layout, _)| layout.focus.unwrap_or(false));
1582 for (tab_name, layout, floating_panes_layout) in tabs.drain(..) {
1583 let name = tab_name.or_else(|| name.clone());
1584 let should_change_focus_to_new_tab = !no_focus
1585 && layout.focus.unwrap_or_else(|| {
1586 if !has_focused_tab {
1587 has_focused_tab = true;
1588 true
1589 } else {
1590 false
1591 }
1592 });
1593 new_tab_actions.push(Action::NewTab {
1594 tiled_layout: Some(layout),
1595 floating_layouts: floating_panes_layout,
1596 swap_tiled_layouts: swap_tiled_layouts.clone(),
1597 swap_floating_layouts: swap_floating_layouts.clone(),
1598 tab_name: name,
1599 should_change_focus_to_new_tab,
1600 cwd: None,
1601 initial_panes: initial_panes.clone(),
1602 first_pane_unblock_condition,
1603 });
1604 }
1605 Ok(new_tab_actions)
1606 } else {
1607 let swap_tiled_layouts = Some(layout.swap_tiled_layouts.clone());
1608 let swap_floating_layouts = Some(layout.swap_floating_layouts.clone());
1609 let (layout, floating_panes_layout) = layout.new_tab();
1610 let should_change_focus_to_new_tab = !no_focus;
1611 Ok(vec![Action::NewTab {
1612 tiled_layout: Some(layout),
1613 floating_layouts: floating_panes_layout,
1614 swap_tiled_layouts,
1615 swap_floating_layouts,
1616 tab_name: name,
1617 should_change_focus_to_new_tab,
1618 cwd: None,
1619 initial_panes,
1620 first_pane_unblock_condition,
1621 }])
1622 }
1623 } else if let Some(layout_path) = layout {
1624 let layout_dir = layout_dir
1625 .or_else(|| config.and_then(|c| c.options.layout_dir))
1626 .or_else(|| get_layout_dir(find_default_config_dir()));
1627
1628 let mut should_start_layout_commands_suspended = false;
1629 let layout_source_name;
1630 let (path_to_raw_layout, raw_layout, swap_layouts) = if let Some(layout_url) =
1631 layout_path.to_str().and_then(|l| {
1632 if l.starts_with("http://") || l.starts_with("https://") {
1633 Some(l)
1634 } else {
1635 None
1636 }
1637 }) {
1638 should_start_layout_commands_suspended = true;
1639 layout_source_name = layout_url.to_owned();
1640 (
1641 layout_url.to_owned(),
1642 Layout::stringified_from_url(layout_url)
1643 .map_err(|e| format!("Failed to load layout: {}", e))?,
1644 None,
1645 )
1646 } else {
1647 layout_source_name = layout_path
1648 .as_path()
1649 .as_os_str()
1650 .to_string_lossy()
1651 .to_string();
1652 Layout::stringified_from_path_or_default(Some(&layout_path), layout_dir)
1653 .map_err(|e| format!("Failed to load layout: {}", e))?
1654 };
1655 let mut layout = Layout::from_str(&raw_layout, path_to_raw_layout, swap_layouts.as_ref().map(|(f, p)| (f.as_str(), p.as_str())), cwd).map_err(|e| {
1656 let stringified_error = match e {
1657 ConfigError::KdlError(kdl_error) => {
1658 let error = kdl_error.add_src(layout_source_name.clone(), String::from(raw_layout));
1659 let report: Report = error.into();
1660 format!("{:?}", report)
1661 }
1662 ConfigError::KdlDeserializationError(kdl_error) => {
1663 let error_message = match kdl_error.kind {
1664 kdl::KdlErrorKind::Context("valid node terminator") => {
1665 format!("Failed to deserialize KDL node. \nPossible reasons:\n{}\n{}\n{}\n{}",
1666 "- Missing `;` after a node name, eg. { node; another_node; }",
1667 "- Missing quotations (\") around an argument node eg. { first_node \"argument_node\"; }",
1668 "- Missing an equal sign (=) between node arguments on a title line. eg. argument=\"value\"",
1669 "- Found an extraneous equal sign (=) between node child arguments and their values. eg. { argument=\"value\" }")
1670 },
1671 _ => String::from(kdl_error.help.unwrap_or("Kdl Deserialization Error")),
1672 };
1673 let kdl_error = KdlError {
1674 error_message,
1675 src: Some(NamedSource::new(layout_source_name.clone(), String::from(raw_layout))),
1676 offset: Some(kdl_error.span.offset()),
1677 len: Some(kdl_error.span.len()),
1678 help_message: None,
1679 };
1680 let report: Report = kdl_error.into();
1681 format!("{:?}", report)
1682 },
1683 e => format!("{}", e)
1684 };
1685 stringified_error
1686 })?;
1687 if should_start_layout_commands_suspended {
1688 layout.recursively_add_start_suspended_including_template(Some(true));
1689 }
1690 let mut tabs = layout.tabs();
1691 if !tabs.is_empty() {
1692 let swap_tiled_layouts = Some(layout.swap_tiled_layouts.clone());
1693 let swap_floating_layouts = Some(layout.swap_floating_layouts.clone());
1694 let mut new_tab_actions = vec![];
1695 let mut has_focused_tab = tabs
1696 .iter()
1697 .any(|(_, layout, _)| layout.focus.unwrap_or(false));
1698 for (tab_name, layout, floating_panes_layout) in tabs.drain(..) {
1699 let name = tab_name.or_else(|| name.clone());
1700 let should_change_focus_to_new_tab = !no_focus
1701 && layout.focus.unwrap_or_else(|| {
1702 if !has_focused_tab {
1703 has_focused_tab = true;
1704 true
1705 } else {
1706 false
1707 }
1708 });
1709 new_tab_actions.push(Action::NewTab {
1710 tiled_layout: Some(layout),
1711 floating_layouts: floating_panes_layout,
1712 swap_tiled_layouts: swap_tiled_layouts.clone(),
1713 swap_floating_layouts: swap_floating_layouts.clone(),
1714 tab_name: name,
1715 should_change_focus_to_new_tab,
1716 cwd: None, initial_panes: initial_panes.clone(),
1718 first_pane_unblock_condition,
1719 });
1720 }
1721 Ok(new_tab_actions)
1722 } else {
1723 let swap_tiled_layouts = Some(layout.swap_tiled_layouts.clone());
1724 let swap_floating_layouts = Some(layout.swap_floating_layouts.clone());
1725 let (layout, floating_panes_layout) = layout.new_tab();
1726 let should_change_focus_to_new_tab = !no_focus;
1727 Ok(vec![Action::NewTab {
1728 tiled_layout: Some(layout),
1729 floating_layouts: floating_panes_layout,
1730 swap_tiled_layouts,
1731 swap_floating_layouts,
1732 tab_name: name,
1733 should_change_focus_to_new_tab,
1734 cwd: None, initial_panes,
1736 first_pane_unblock_condition,
1737 }])
1738 }
1739 } else {
1740 let should_change_focus_to_new_tab = !no_focus;
1741 Ok(vec![Action::NewTab {
1742 tiled_layout: None,
1743 floating_layouts: vec![],
1744 swap_tiled_layouts: None,
1745 swap_floating_layouts: None,
1746 tab_name: name,
1747 should_change_focus_to_new_tab,
1748 cwd,
1749 initial_panes,
1750 first_pane_unblock_condition,
1751 }])
1752 }
1753 },
1754 CliAction::PreviousSwapLayout { tab_id } => match tab_id {
1755 Some(id) => Ok(vec![Action::PreviousSwapLayoutByTabId { id: id as u64 }]),
1756 None => Ok(vec![Action::PreviousSwapLayout]),
1757 },
1758 CliAction::NextSwapLayout { tab_id } => match tab_id {
1759 Some(id) => Ok(vec![Action::NextSwapLayoutByTabId { id: id as u64 }]),
1760 None => Ok(vec![Action::NextSwapLayout]),
1761 },
1762 CliAction::OverrideLayout {
1763 layout,
1764 layout_string,
1765 layout_dir,
1766 retain_existing_terminal_panes,
1767 retain_existing_plugin_panes,
1768 apply_only_to_active_tab,
1769 } => {
1770 let layout_dir = layout_dir
1772 .or_else(|| config.and_then(|c| c.options.layout_dir))
1773 .or_else(|| get_layout_dir(find_default_config_dir()));
1774
1775 let layout_source_name;
1777 let (path_to_raw_layout, raw_layout, swap_layouts) = if let Some(raw) =
1778 layout_string
1779 {
1780 layout_source_name = "layout-string".to_owned();
1781 (layout_source_name.clone(), raw, None)
1782 } else if let Some(layout_path) = &layout {
1783 if let Some(layout_url) = layout_path.to_str().and_then(|l| {
1784 if l.starts_with("http://") || l.starts_with("https://") {
1785 Some(l)
1786 } else {
1787 None
1788 }
1789 }) {
1790 layout_source_name = layout_url.to_owned();
1791 (
1792 layout_url.to_owned(),
1793 Layout::stringified_from_url(layout_url)
1794 .map_err(|e| format!("Failed to load layout from URL: {}", e))?,
1795 None,
1796 )
1797 } else {
1798 layout_source_name = layout_path
1799 .as_path()
1800 .as_os_str()
1801 .to_string_lossy()
1802 .to_string();
1803 Layout::stringified_from_path_or_default(Some(layout_path), layout_dir)
1804 .map_err(|e| format!("Failed to load layout: {}", e))?
1805 }
1806 } else {
1807 return Err("Either layout or layout-string must be provided".to_string());
1808 };
1809
1810 let layout = Layout::from_str(
1812 &raw_layout,
1813 path_to_raw_layout,
1814 swap_layouts.as_ref().map(|(f, p)| (f.as_str(), p.as_str())),
1815 None, )
1817 .map_err(|e| {
1818 let stringified_error = match e {
1819 ConfigError::KdlError(kdl_error) => {
1820 let error = kdl_error
1821 .add_src(layout_source_name.clone(), String::from(raw_layout));
1822 let report: Report = error.into();
1823 format!("{:?}", report)
1824 },
1825 ConfigError::KdlDeserializationError(kdl_error) => {
1826 let error_message = kdl_error.to_string();
1827 format!("Failed to deserialize KDL layout: {}", error_message)
1828 },
1829 e => format!("{}", e),
1830 };
1831 stringified_error
1832 })?;
1833
1834 let tabs: Vec<TabLayoutInfo> = layout
1836 .tabs
1837 .iter()
1838 .enumerate()
1839 .map(|(index, (tab_name, tiled, floating))| TabLayoutInfo {
1840 tab_index: index,
1841 tab_name: tab_name.clone(),
1842 tiled_layout: tiled.clone(),
1843 floating_layouts: floating.clone(),
1844 swap_tiled_layouts: Some(layout.swap_tiled_layouts.clone()),
1845 swap_floating_layouts: Some(layout.swap_floating_layouts.clone()),
1846 })
1847 .collect();
1848
1849 let tabs = if tabs.is_empty() {
1851 let (tiled, floating) = layout.new_tab();
1852 vec![TabLayoutInfo {
1853 tab_index: 0,
1854 tab_name: None,
1855 tiled_layout: tiled,
1856 floating_layouts: floating,
1857 swap_tiled_layouts: Some(layout.swap_tiled_layouts),
1858 swap_floating_layouts: Some(layout.swap_floating_layouts),
1859 }]
1860 } else {
1861 tabs
1862 };
1863
1864 Ok(vec![Action::OverrideLayout {
1865 tabs,
1866 retain_existing_terminal_panes,
1867 retain_existing_plugin_panes,
1868 apply_only_to_active_tab,
1869 }])
1870 },
1871 CliAction::QueryTabNames => Ok(vec![Action::QueryTabNames]),
1872 CliAction::StartOrReloadPlugin { url, configuration } => {
1873 let current_dir = get_current_dir();
1874 let run_plugin_or_alias = RunPluginOrAlias::from_url(
1875 &url,
1876 &configuration.map(|c| c.inner().clone()),
1877 None,
1878 Some(current_dir),
1879 )?;
1880 Ok(vec![Action::StartOrReloadPlugin {
1881 plugin: run_plugin_or_alias,
1882 }])
1883 },
1884 CliAction::LaunchOrFocusPlugin {
1885 url,
1886 floating,
1887 in_place,
1888 close_replaced_pane,
1889 move_to_focused_tab,
1890 configuration,
1891 skip_plugin_cache,
1892 tab_id,
1893 } => {
1894 let current_dir = get_current_dir();
1895 let run_plugin_or_alias = RunPluginOrAlias::from_url(
1896 url.as_str(),
1897 &configuration.map(|c| c.inner().clone()),
1898 None,
1899 Some(current_dir),
1900 )?;
1901 Ok(vec![Action::LaunchOrFocusPlugin {
1902 plugin: run_plugin_or_alias,
1903 should_float: floating,
1904 move_to_focused_tab,
1905 should_open_in_place: in_place,
1906 close_replaced_pane,
1907 skip_cache: skip_plugin_cache,
1908 tab_id,
1909 }])
1910 },
1911 CliAction::LaunchPlugin {
1912 url,
1913 floating,
1914 in_place,
1915 close_replaced_pane,
1916 configuration,
1917 skip_plugin_cache,
1918 no_focus,
1919 tab_id,
1920 } => {
1921 let current_dir = get_current_dir();
1922 let run_plugin_or_alias = RunPluginOrAlias::from_url(
1923 &url.as_str(),
1924 &configuration.map(|c| c.inner().clone()),
1925 None,
1926 Some(current_dir.clone()),
1927 )?;
1928 Ok(vec![Action::LaunchPlugin {
1929 plugin: run_plugin_or_alias,
1930 should_float: floating,
1931 should_open_in_place: in_place,
1932 close_replaced_pane,
1933 skip_cache: skip_plugin_cache,
1934 cwd: Some(current_dir),
1935 no_focus,
1936 tab_id,
1937 }])
1938 },
1939 CliAction::RenameSession { name } => Ok(vec![Action::RenameSession { name }]),
1940 CliAction::Pipe {
1941 name,
1942 payload,
1943 args,
1944 plugin,
1945 plugin_configuration,
1946 force_launch_plugin,
1947 skip_plugin_cache,
1948 floating_plugin,
1949 in_place_plugin,
1950 plugin_cwd,
1951 plugin_title,
1952 } => {
1953 let current_dir = get_current_dir();
1954 let cwd = plugin_cwd
1955 .map(|cwd| current_dir.join(cwd))
1956 .or_else(|| Some(current_dir));
1957 let skip_cache = skip_plugin_cache;
1958 let pipe_id = Uuid::new_v4().to_string();
1959 Ok(vec![Action::CliPipe {
1960 pipe_id,
1961 name,
1962 payload,
1963 args: args.map(|a| a.inner().clone()), plugin,
1965 configuration: plugin_configuration.map(|a| a.inner().clone()), launch_new: force_launch_plugin,
1968 floating: floating_plugin,
1969 in_place: in_place_plugin,
1970 cwd,
1971 pane_title: plugin_title,
1972 skip_cache,
1973 }])
1974 },
1975 CliAction::ListClients => Ok(vec![Action::ListClients]),
1976 CliAction::ListPanes {
1977 tab,
1978 command,
1979 state,
1980 geometry,
1981 all,
1982 json,
1983 } => Ok(vec![Action::ListPanes {
1984 show_tab: tab,
1985 show_command: command,
1986 show_state: state,
1987 show_geometry: geometry,
1988 show_all: all,
1989 output_json: json,
1990 }]),
1991 CliAction::ListTabs {
1992 state,
1993 dimensions,
1994 panes,
1995 layout,
1996 all,
1997 json,
1998 } => Ok(vec![Action::ListTabs {
1999 show_state: state,
2000 show_dimensions: dimensions,
2001 show_panes: panes,
2002 show_layout: layout,
2003 show_all: all,
2004 output_json: json,
2005 }]),
2006 CliAction::CurrentTabInfo { json } => {
2007 Ok(vec![Action::CurrentTabInfo { output_json: json }])
2008 },
2009 CliAction::TogglePanePinned { pane_id } => match pane_id {
2010 Some(pane_id_str) => {
2011 let pane_id = PaneId::from_str(&pane_id_str)
2012 .map_err(|_| format!(
2013 "Malformed pane id: {pane_id_str}, expecting either a bare integer (eg. 1), a terminal pane id (eg. terminal_1) or a plugin pane id (eg. plugin_1)"
2014 ))?;
2015 Ok(vec![Action::TogglePanePinnedByPaneId { pane_id }])
2016 },
2017 None => Ok(vec![Action::TogglePanePinned]),
2018 },
2019 CliAction::StackPanes { pane_ids } => {
2020 let mut malformed_ids = vec![];
2021 let pane_ids = pane_ids
2022 .iter()
2023 .filter_map(
2024 |stringified_pane_id| match PaneId::from_str(stringified_pane_id) {
2025 Ok(pane_id) => Some(pane_id),
2026 Err(_e) => {
2027 malformed_ids.push(stringified_pane_id.to_owned());
2028 None
2029 },
2030 },
2031 )
2032 .collect();
2033 if !malformed_ids.is_empty() {
2034 Err(
2035 format!(
2036 "Malformed pane ids: {}, expecting a space separated list of either a bare integer (eg. 1), a terminal pane id (eg. terminal_1) or a plugin pane id (eg. plugin_1)",
2037 malformed_ids.join(", ")
2038 )
2039 )
2040 } else {
2041 Ok(vec![Action::StackPanes { pane_ids }])
2042 }
2043 },
2044 CliAction::ChangeFloatingPaneCoordinates {
2045 pane_id,
2046 x,
2047 y,
2048 width,
2049 height,
2050 pinned,
2051 borderless,
2052 } => {
2053 let Some(coordinates) =
2054 FloatingPaneCoordinates::new(x, y, width, height, pinned, borderless)
2055 else {
2056 return Err(format!("Failed to parse floating pane coordinates"));
2057 };
2058 let parsed_pane_id = PaneId::from_str(&pane_id);
2059 match parsed_pane_id {
2060 Ok(parsed_pane_id) => {
2061 Ok(vec![Action::ChangeFloatingPaneCoordinates {
2062 pane_id: parsed_pane_id,
2063 coordinates,
2064 }])
2065 },
2066 Err(_e) => {
2067 Err(format!(
2068 "Malformed pane id: {}, expecting a space separated list of either a bare integer (eg. 1), a terminal pane id (eg. terminal_1) or a plugin pane id (eg. plugin_1)",
2069 pane_id
2070 ))
2071 }
2072 }
2073 },
2074 CliAction::TogglePaneBorderless { pane_id } => {
2075 let parsed_pane_id = PaneId::from_str(&pane_id);
2076 match parsed_pane_id {
2077 Ok(parsed_pane_id) => {
2078 Ok(vec![Action::TogglePaneBorderless {
2079 pane_id: parsed_pane_id,
2080 }])
2081 },
2082 Err(_e) => {
2083 Err(format!(
2084 "Malformed pane id: {}, expecting either a bare integer (eg. 1), a terminal pane id (eg. terminal_1) or a plugin pane id (eg. plugin_1)",
2085 pane_id
2086 ))
2087 }
2088 }
2089 },
2090 CliAction::SetPaneBorderless {
2091 pane_id,
2092 borderless,
2093 } => {
2094 let parsed_pane_id = PaneId::from_str(&pane_id);
2095 match parsed_pane_id {
2096 Ok(parsed_pane_id) => {
2097 Ok(vec![Action::SetPaneBorderless {
2098 pane_id: parsed_pane_id,
2099 borderless,
2100 }])
2101 },
2102 Err(_e) => {
2103 Err(format!(
2104 "Malformed pane id: {}, expecting either a bare integer (eg. 1), a terminal pane id (eg. terminal_1) or a plugin pane id (eg. plugin_1)",
2105 pane_id
2106 ))
2107 }
2108 }
2109 },
2110 CliAction::SetPaneColor {
2111 pane_id,
2112 fg,
2113 bg,
2114 reset,
2115 } => {
2116 let pane_id_str = match pane_id {
2117 Some(id) => id,
2118 None => std::env::var("ZELLIJ_PANE_ID").map_err(|_| {
2119 "No --pane-id provided and ZELLIJ_PANE_ID is not set".to_string()
2120 })?,
2121 };
2122 let parsed_pane_id = PaneId::from_str(&pane_id_str);
2123 match parsed_pane_id {
2124 Ok(parsed_pane_id) => {
2125 let (fg, bg) = if reset {
2126 (None, None)
2127 } else {
2128 (fg, bg)
2129 };
2130 Ok(vec![Action::SetPaneColor {
2131 pane_id: parsed_pane_id,
2132 fg,
2133 bg,
2134 }])
2135 },
2136 Err(_e) => Err(format!(
2137 "Malformed pane id: {}, expecting either a bare integer (eg. 1), a terminal pane id (eg. terminal_1) or a plugin pane id (eg. plugin_1)",
2138 pane_id_str
2139 )),
2140 }
2141 },
2142 CliAction::Detach => Ok(vec![Action::Detach]),
2143 CliAction::SetDarkTheme => Ok(vec![Action::SetDarkTheme]),
2144 CliAction::SetLightTheme => Ok(vec![Action::SetLightTheme]),
2145 CliAction::ToggleTheme => Ok(vec![Action::ToggleTheme]),
2146 CliAction::SwitchSession {
2147 name,
2148 tab_position,
2149 pane_id,
2150 layout,
2151 layout_string,
2152 layout_dir,
2153 cwd,
2154 } => {
2155 let pane_id = match pane_id {
2156 Some(stringified_pane_id) => match PaneId::from_str(&stringified_pane_id) {
2157 Ok(PaneId::Terminal(id)) => Some((id, false)),
2158 Ok(PaneId::Plugin(id)) => Some((id, true)),
2159 Err(_e) => {
2160 return Err(format!(
2161 "Malformed pane id: {}, expecting either a bare integer (eg. 1), a terminal pane id (eg. terminal_1) or a plugin pane id (eg. plugin_1)",
2162 stringified_pane_id
2163 ));
2164 },
2165 },
2166 None => None,
2167 };
2168
2169 let cwd = cwd.map(|cwd| {
2170 let current_dir = get_current_dir();
2171 current_dir.join(cwd)
2172 });
2173
2174 let layout_dir = layout_dir.map(|layout_dir| {
2175 let current_dir = get_current_dir();
2176 current_dir.join(layout_dir)
2177 });
2178
2179 let layout_info = if let Some(layout_string) = layout_string {
2180 let layout_source_name = "layout-string".to_owned();
2182 let raw_layout_for_error = layout_string.clone();
2183 Layout::from_str(&layout_string, layout_source_name.clone(), None, None)
2184 .map_err(|e| {
2185 match e {
2186 ConfigError::KdlError(kdl_error) => {
2187 let error = kdl_error.add_src(layout_source_name, raw_layout_for_error);
2188 let report: Report = error.into();
2189 format!("{:?}", report)
2190 },
2191 ConfigError::KdlDeserializationError(kdl_error) => {
2192 let error_message = match kdl_error.kind {
2193 kdl::KdlErrorKind::Context("valid node terminator") => {
2194 format!("Failed to deserialize KDL node. \nPossible reasons:\n{}\n{}\n{}\n{}",
2195 "- Missing `;` after a node name, eg. {{ node; another_node; }}",
2196 "- Missing quotations (\") around an argument node eg. {{ first_node \"argument_node\"; }}",
2197 "- Missing an equal sign (=) between node arguments on a title line. eg. argument=\"value\"",
2198 "- Found an extraneous equal sign (=) between node child arguments and their values. eg. {{ argument=\"value\" }}")
2199 },
2200 _ => String::from(kdl_error.help.unwrap_or("Kdl Deserialization Error")),
2201 };
2202 let kdl_error = KdlError {
2203 error_message,
2204 src: Some(NamedSource::new(layout_source_name, raw_layout_for_error)),
2205 offset: Some(kdl_error.span.offset()),
2206 len: Some(kdl_error.span.len()),
2207 help_message: None,
2208 };
2209 let report: Report = kdl_error.into();
2210 format!("{:?}", report)
2211 },
2212 e => format!("{}", e),
2213 }
2214 })?;
2215 Some(LayoutInfo::Stringified(layout_string))
2216 } else if let Some(layout_path) = layout {
2217 let layout_dir = layout_dir
2218 .or_else(|| config.and_then(|c| c.options.layout_dir.clone()))
2219 .or_else(|| get_layout_dir(find_default_config_dir()));
2220 let layout_source_name = layout_path.display().to_string();
2222 Layout::from_path_or_default_without_config(
2223 Some(&layout_path),
2224 layout_dir.clone(),
2225 )
2226 .map_err(|e| {
2227 match e {
2228 ConfigError::KdlError(kdl_error) => {
2229 let report: Report = kdl_error.into();
2230 format!("{:?}", report)
2231 },
2232 ConfigError::KdlDeserializationError(kdl_error) => {
2233 let error_message = match kdl_error.kind {
2234 kdl::KdlErrorKind::Context("valid node terminator") => {
2235 format!("Failed to deserialize KDL node. \nPossible reasons:\n{}\n{}\n{}\n{}",
2236 "- Missing `;` after a node name, eg. {{ node; another_node; }}",
2237 "- Missing quotations (\") around an argument node eg. {{ first_node \"argument_node\"; }}",
2238 "- Missing an equal sign (=) between node arguments on a title line. eg. argument=\"value\"",
2239 "- Found an extraneous equal sign (=) between node child arguments and their values. eg. {{ argument=\"value\" }}")
2240 },
2241 _ => String::from(kdl_error.help.unwrap_or("Kdl Deserialization Error")),
2242 };
2243 let kdl_error = KdlError {
2244 error_message,
2245 src: Some(NamedSource::new(layout_source_name, String::new())),
2246 offset: Some(kdl_error.span.offset()),
2247 len: Some(kdl_error.span.len()),
2248 help_message: None,
2249 };
2250 let report: Report = kdl_error.into();
2251 format!("{:?}", report)
2252 },
2253 e => format!("{}", e),
2254 }
2255 })?;
2256 LayoutInfo::from_config(&layout_dir, &Some(layout_path))
2257 } else {
2258 None
2259 };
2260
2261 Ok(vec![Action::SwitchSession {
2262 name: name.clone(),
2263 tab_position: tab_position.clone(),
2264 pane_id,
2265 layout: layout_info,
2266 cwd,
2267 }])
2268 },
2269 }
2270 }
2271 pub fn populate_originating_plugin(&mut self, originating_plugin: OriginatingPlugin) {
2272 match self {
2273 Action::NewBlockingPane { command, .. }
2274 | Action::NewFloatingPane { command, .. }
2275 | Action::NewTiledPane { command, .. }
2276 | Action::NewInPlacePane { command, .. }
2277 | Action::NewStackedPane { command, .. } => {
2278 command
2279 .as_mut()
2280 .map(|c| c.populate_originating_plugin(originating_plugin));
2281 },
2282 Action::Run { command, .. } => {
2283 command.populate_originating_plugin(originating_plugin);
2284 },
2285 Action::EditFile { payload, .. } => {
2286 payload.originating_plugin = Some(originating_plugin);
2287 },
2288 Action::NewTab { initial_panes, .. } => {
2289 if let Some(initial_panes) = initial_panes.as_mut() {
2290 for pane in initial_panes.iter_mut() {
2291 match pane {
2292 CommandOrPlugin::Command(run_command) => {
2293 run_command.populate_originating_plugin(originating_plugin.clone());
2294 },
2295 _ => {},
2296 }
2297 }
2298 }
2299 },
2300 _ => {},
2301 }
2302 }
2303 pub fn launches_plugin(&self, plugin_url: &str) -> bool {
2304 match self {
2305 Action::LaunchPlugin { plugin, .. } => &plugin.location_string() == plugin_url,
2306 Action::LaunchOrFocusPlugin { plugin, .. } => &plugin.location_string() == plugin_url,
2307 _ => false,
2308 }
2309 }
2310 pub fn is_mouse_action(&self) -> bool {
2311 if let Action::MouseEvent { .. } = self {
2312 return true;
2313 }
2314 false
2315 }
2316}
2317
2318fn suggest_key_fix(key_str: &str) -> String {
2319 if key_str.contains('-') {
2320 return " Hint: Use spaces instead of hyphens (e.g., \"Ctrl a\" not \"Ctrl-a\")"
2321 .to_string();
2322 }
2323
2324 if key_str.trim().is_empty() {
2325 return " Hint: Key string cannot be empty".to_string();
2326 }
2327
2328 let parts: Vec<&str> = key_str.split_whitespace().collect();
2329 if parts.len() > 1 {
2330 for part in &parts[..parts.len() - 1] {
2331 let lower = part.to_ascii_lowercase();
2332 if lower.starts_with("ctr") && lower != "ctrl" {
2333 return format!(" Hint: Did you mean \"Ctrl\" instead of \"{}\"?", part);
2334 }
2335 if !matches!(lower.as_str(), "ctrl" | "alt" | "shift" | "super") {
2336 return " Hint: Valid modifiers are: Ctrl, Alt, Shift, Super".to_string();
2337 }
2338 }
2339 }
2340
2341 " Hint: Use format like \"Ctrl a\", \"Alt Shift F1\", or \"Enter\"".to_string()
2342}
2343
2344impl From<OnForceClose> for Action {
2345 fn from(ofc: OnForceClose) -> Action {
2346 match ofc {
2347 OnForceClose::Quit => Action::Quit,
2348 OnForceClose::Detach => Action::Detach,
2349 }
2350 }
2351}
2352
2353#[cfg(test)]
2354mod tests {
2355 use super::*;
2356 use crate::data::BareKey;
2357 use crate::data::KeyModifier;
2358 use std::path::PathBuf;
2359
2360 #[test]
2361 fn test_send_keys_single_key() {
2362 let cli_action = CliAction::SendKeys {
2363 keys: vec!["Enter".to_string()],
2364 pane_id: None,
2365 };
2366 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
2367 assert!(result.is_ok());
2368 let actions = result.unwrap();
2369 assert_eq!(actions.len(), 1);
2370 match &actions[0] {
2371 Action::Write {
2372 key_with_modifier,
2373 bytes,
2374 is_kitty_keyboard_protocol,
2375 } => {
2376 assert!(key_with_modifier.is_some());
2377 let key = key_with_modifier.as_ref().unwrap();
2378 assert_eq!(key.bare_key, BareKey::Enter);
2379 assert!(key.key_modifiers.is_empty());
2380 assert!(!bytes.is_empty());
2381 assert_eq!(*is_kitty_keyboard_protocol, true);
2382 },
2383 _ => panic!("Expected Write action"),
2384 }
2385 }
2386
2387 #[test]
2388 fn test_send_keys_with_modifier() {
2389 let cli_action = CliAction::SendKeys {
2390 keys: vec!["Ctrl a".to_string()],
2391 pane_id: None,
2392 };
2393 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
2394 assert!(result.is_ok());
2395 let actions = result.unwrap();
2396 assert_eq!(actions.len(), 1);
2397 match &actions[0] {
2398 Action::Write {
2399 key_with_modifier,
2400 is_kitty_keyboard_protocol,
2401 ..
2402 } => {
2403 assert!(key_with_modifier.is_some());
2404 let key = key_with_modifier.as_ref().unwrap();
2405 assert_eq!(key.bare_key, BareKey::Char('a'));
2406 assert!(key.key_modifiers.contains(&KeyModifier::Ctrl));
2407 assert_eq!(*is_kitty_keyboard_protocol, true);
2408 },
2409 _ => panic!("Expected Write action"),
2410 }
2411 }
2412
2413 #[test]
2414 fn test_send_keys_multiple_keys() {
2415 let cli_action = CliAction::SendKeys {
2416 keys: vec!["Ctrl a".to_string(), "F1".to_string(), "Enter".to_string()],
2417 pane_id: None,
2418 };
2419 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
2420 assert!(result.is_ok());
2421 let actions = result.unwrap();
2422 assert_eq!(actions.len(), 3);
2423 for action in &actions {
2424 match action {
2425 Action::Write {
2426 is_kitty_keyboard_protocol,
2427 ..
2428 } => {
2429 assert_eq!(*is_kitty_keyboard_protocol, true);
2430 },
2431 _ => panic!("Expected Write action"),
2432 }
2433 }
2434 }
2435
2436 #[test]
2437 fn test_send_keys_error_hyphen_syntax() {
2438 let cli_action = CliAction::SendKeys {
2439 keys: vec!["Ctrl-a".to_string()],
2440 pane_id: None,
2441 };
2442 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
2443 assert!(result.is_err());
2444 let err = result.unwrap_err();
2445 assert!(err.contains("Use spaces instead of hyphens"));
2446 }
2447
2448 #[test]
2449 fn test_send_keys_error_typo() {
2450 let cli_action = CliAction::SendKeys {
2451 keys: vec!["Ctrll a".to_string()],
2452 pane_id: None,
2453 };
2454 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
2455 assert!(result.is_err());
2456 let err = result.unwrap_err();
2457 assert!(err.contains("Ctrl") || err.contains("modifier"));
2458 }
2459
2460 #[test]
2461 fn test_send_keys_with_pane_id() {
2462 let cli_action = CliAction::SendKeys {
2463 keys: vec!["a".to_string()],
2464 pane_id: Some("terminal_1".to_string()),
2465 };
2466 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
2467 assert!(result.is_ok());
2468 let actions = result.unwrap();
2469 assert_eq!(actions.len(), 1);
2470 match &actions[0] {
2471 Action::WriteToPaneId { pane_id, bytes } => {
2472 assert!(matches!(pane_id, PaneId::Terminal(1)));
2473 assert!(!bytes.is_empty());
2474 },
2475 _ => panic!("Expected WriteToPaneId action"),
2476 }
2477 }
2478
2479 #[test]
2480 fn test_send_keys_error_invalid_pane_id() {
2481 let cli_action = CliAction::SendKeys {
2482 keys: vec!["a".to_string()],
2483 pane_id: Some("invalid_id".to_string()),
2484 };
2485 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
2486 assert!(result.is_err());
2487 let err = result.unwrap_err();
2488 assert!(err.contains("Malformed pane id"));
2489 }
2490
2491 #[test]
2497 fn test_scroll_up_with_pane_id() {
2498 let cli_action = CliAction::ScrollUp {
2499 pane_id: Some("terminal_5".to_string()),
2500 };
2501 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
2502 assert!(result.is_ok());
2503 let actions = result.unwrap();
2504 assert_eq!(actions.len(), 1);
2505 match &actions[0] {
2506 Action::ScrollUpByPaneId { pane_id } => {
2507 assert!(matches!(pane_id, PaneId::Terminal(5)));
2508 },
2509 _ => panic!("Expected ScrollUpByPaneId action"),
2510 }
2511 }
2512
2513 #[test]
2514 fn test_scroll_up_without_pane_id() {
2515 let cli_action = CliAction::ScrollUp { pane_id: None };
2516 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
2517 assert!(result.is_ok());
2518 let actions = result.unwrap();
2519 assert_eq!(actions.len(), 1);
2520 assert!(matches!(actions[0], Action::ScrollUp));
2521 }
2522
2523 #[test]
2525 fn test_scroll_down_with_pane_id() {
2526 let cli_action = CliAction::ScrollDown {
2527 pane_id: Some("terminal_2".to_string()),
2528 };
2529 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
2530 assert!(result.is_ok());
2531 let actions = result.unwrap();
2532 assert_eq!(actions.len(), 1);
2533 match &actions[0] {
2534 Action::ScrollDownByPaneId { pane_id } => {
2535 assert!(matches!(pane_id, PaneId::Terminal(2)));
2536 },
2537 _ => panic!("Expected ScrollDownByPaneId action"),
2538 }
2539 }
2540
2541 #[test]
2542 fn test_scroll_down_without_pane_id() {
2543 let cli_action = CliAction::ScrollDown { pane_id: None };
2544 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
2545 assert!(result.is_ok());
2546 let actions = result.unwrap();
2547 assert_eq!(actions.len(), 1);
2548 assert!(matches!(actions[0], Action::ScrollDown));
2549 }
2550
2551 #[test]
2553 fn test_scroll_to_top_with_pane_id() {
2554 let cli_action = CliAction::ScrollToTop {
2555 pane_id: Some("terminal_1".to_string()),
2556 };
2557 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
2558 assert!(result.is_ok());
2559 let actions = result.unwrap();
2560 assert_eq!(actions.len(), 1);
2561 match &actions[0] {
2562 Action::ScrollToTopByPaneId { pane_id } => {
2563 assert!(matches!(pane_id, PaneId::Terminal(1)));
2564 },
2565 _ => panic!("Expected ScrollToTopByPaneId action"),
2566 }
2567 }
2568
2569 #[test]
2570 fn test_scroll_to_top_without_pane_id() {
2571 let cli_action = CliAction::ScrollToTop { pane_id: None };
2572 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
2573 assert!(result.is_ok());
2574 let actions = result.unwrap();
2575 assert_eq!(actions.len(), 1);
2576 assert!(matches!(actions[0], Action::ScrollToTop));
2577 }
2578
2579 #[test]
2581 fn test_scroll_to_bottom_with_pane_id() {
2582 let cli_action = CliAction::ScrollToBottom {
2583 pane_id: Some("terminal_4".to_string()),
2584 };
2585 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
2586 assert!(result.is_ok());
2587 let actions = result.unwrap();
2588 assert_eq!(actions.len(), 1);
2589 match &actions[0] {
2590 Action::ScrollToBottomByPaneId { pane_id } => {
2591 assert!(matches!(pane_id, PaneId::Terminal(4)));
2592 },
2593 _ => panic!("Expected ScrollToBottomByPaneId action"),
2594 }
2595 }
2596
2597 #[test]
2598 fn test_scroll_to_bottom_without_pane_id() {
2599 let cli_action = CliAction::ScrollToBottom { pane_id: None };
2600 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
2601 assert!(result.is_ok());
2602 let actions = result.unwrap();
2603 assert_eq!(actions.len(), 1);
2604 assert!(matches!(actions[0], Action::ScrollToBottom));
2605 }
2606
2607 #[test]
2609 fn test_page_scroll_up_with_pane_id() {
2610 let cli_action = CliAction::PageScrollUp {
2611 pane_id: Some("terminal_6".to_string()),
2612 };
2613 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
2614 assert!(result.is_ok());
2615 let actions = result.unwrap();
2616 assert_eq!(actions.len(), 1);
2617 match &actions[0] {
2618 Action::PageScrollUpByPaneId { pane_id } => {
2619 assert!(matches!(pane_id, PaneId::Terminal(6)));
2620 },
2621 _ => panic!("Expected PageScrollUpByPaneId action"),
2622 }
2623 }
2624
2625 #[test]
2626 fn test_page_scroll_up_without_pane_id() {
2627 let cli_action = CliAction::PageScrollUp { pane_id: None };
2628 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
2629 assert!(result.is_ok());
2630 let actions = result.unwrap();
2631 assert_eq!(actions.len(), 1);
2632 assert!(matches!(actions[0], Action::PageScrollUp));
2633 }
2634
2635 #[test]
2637 fn test_page_scroll_down_with_pane_id() {
2638 let cli_action = CliAction::PageScrollDown {
2639 pane_id: Some("terminal_8".to_string()),
2640 };
2641 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
2642 assert!(result.is_ok());
2643 let actions = result.unwrap();
2644 assert_eq!(actions.len(), 1);
2645 match &actions[0] {
2646 Action::PageScrollDownByPaneId { pane_id } => {
2647 assert!(matches!(pane_id, PaneId::Terminal(8)));
2648 },
2649 _ => panic!("Expected PageScrollDownByPaneId action"),
2650 }
2651 }
2652
2653 #[test]
2654 fn test_page_scroll_down_without_pane_id() {
2655 let cli_action = CliAction::PageScrollDown { pane_id: None };
2656 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
2657 assert!(result.is_ok());
2658 let actions = result.unwrap();
2659 assert_eq!(actions.len(), 1);
2660 assert!(matches!(actions[0], Action::PageScrollDown));
2661 }
2662
2663 #[test]
2665 fn test_half_page_scroll_up_with_pane_id() {
2666 let cli_action = CliAction::HalfPageScrollUp {
2667 pane_id: Some("terminal_10".to_string()),
2668 };
2669 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
2670 assert!(result.is_ok());
2671 let actions = result.unwrap();
2672 assert_eq!(actions.len(), 1);
2673 match &actions[0] {
2674 Action::HalfPageScrollUpByPaneId { pane_id } => {
2675 assert!(matches!(pane_id, PaneId::Terminal(10)));
2676 },
2677 _ => panic!("Expected HalfPageScrollUpByPaneId action"),
2678 }
2679 }
2680
2681 #[test]
2682 fn test_half_page_scroll_up_without_pane_id() {
2683 let cli_action = CliAction::HalfPageScrollUp { pane_id: None };
2684 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
2685 assert!(result.is_ok());
2686 let actions = result.unwrap();
2687 assert_eq!(actions.len(), 1);
2688 assert!(matches!(actions[0], Action::HalfPageScrollUp));
2689 }
2690
2691 #[test]
2693 fn test_half_page_scroll_down_with_pane_id() {
2694 let cli_action = CliAction::HalfPageScrollDown {
2695 pane_id: Some("terminal_12".to_string()),
2696 };
2697 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
2698 assert!(result.is_ok());
2699 let actions = result.unwrap();
2700 assert_eq!(actions.len(), 1);
2701 match &actions[0] {
2702 Action::HalfPageScrollDownByPaneId { pane_id } => {
2703 assert!(matches!(pane_id, PaneId::Terminal(12)));
2704 },
2705 _ => panic!("Expected HalfPageScrollDownByPaneId action"),
2706 }
2707 }
2708
2709 #[test]
2710 fn test_half_page_scroll_down_without_pane_id() {
2711 let cli_action = CliAction::HalfPageScrollDown { pane_id: None };
2712 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
2713 assert!(result.is_ok());
2714 let actions = result.unwrap();
2715 assert_eq!(actions.len(), 1);
2716 assert!(matches!(actions[0], Action::HalfPageScrollDown));
2717 }
2718
2719 #[test]
2721 fn test_resize_with_pane_id() {
2722 let cli_action = CliAction::Resize {
2723 resize: Resize::Increase,
2724 direction: Some(Direction::Left),
2725 pane_id: Some("terminal_3".to_string()),
2726 };
2727 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
2728 assert!(result.is_ok());
2729 let actions = result.unwrap();
2730 assert_eq!(actions.len(), 1);
2731 match &actions[0] {
2732 Action::ResizeByPaneId {
2733 pane_id,
2734 resize,
2735 direction,
2736 } => {
2737 assert!(matches!(pane_id, PaneId::Terminal(3)));
2738 assert!(matches!(resize, Resize::Increase));
2739 assert!(matches!(direction, Some(Direction::Left)));
2740 },
2741 _ => panic!("Expected ResizeByPaneId action"),
2742 }
2743 }
2744
2745 #[test]
2746 fn test_resize_without_pane_id() {
2747 let cli_action = CliAction::Resize {
2748 resize: Resize::Increase,
2749 direction: Some(Direction::Left),
2750 pane_id: None,
2751 };
2752 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
2753 assert!(result.is_ok());
2754 let actions = result.unwrap();
2755 assert_eq!(actions.len(), 1);
2756 match &actions[0] {
2757 Action::Resize { resize, direction } => {
2758 assert!(matches!(resize, Resize::Increase));
2759 assert!(matches!(direction, Some(Direction::Left)));
2760 },
2761 _ => panic!("Expected Resize action"),
2762 }
2763 }
2764
2765 #[test]
2767 fn test_move_pane_with_pane_id() {
2768 let cli_action = CliAction::MovePane {
2769 direction: Some(Direction::Right),
2770 pane_id: Some("terminal_9".to_string()),
2771 };
2772 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
2773 assert!(result.is_ok());
2774 let actions = result.unwrap();
2775 assert_eq!(actions.len(), 1);
2776 match &actions[0] {
2777 Action::MovePaneByPaneId { pane_id, direction } => {
2778 assert!(matches!(pane_id, PaneId::Terminal(9)));
2779 assert!(matches!(direction, Some(Direction::Right)));
2780 },
2781 _ => panic!("Expected MovePaneByPaneId action"),
2782 }
2783 }
2784
2785 #[test]
2786 fn test_move_pane_without_pane_id() {
2787 let cli_action = CliAction::MovePane {
2788 direction: Some(Direction::Right),
2789 pane_id: None,
2790 };
2791 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
2792 assert!(result.is_ok());
2793 let actions = result.unwrap();
2794 assert_eq!(actions.len(), 1);
2795 match &actions[0] {
2796 Action::MovePane { direction } => {
2797 assert!(matches!(direction, Some(Direction::Right)));
2798 },
2799 _ => panic!("Expected MovePane action"),
2800 }
2801 }
2802
2803 #[test]
2805 fn test_move_pane_backwards_with_pane_id() {
2806 let cli_action = CliAction::MovePaneBackwards {
2807 pane_id: Some("terminal_11".to_string()),
2808 };
2809 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
2810 assert!(result.is_ok());
2811 let actions = result.unwrap();
2812 assert_eq!(actions.len(), 1);
2813 match &actions[0] {
2814 Action::MovePaneBackwardsByPaneId { pane_id } => {
2815 assert!(matches!(pane_id, PaneId::Terminal(11)));
2816 },
2817 _ => panic!("Expected MovePaneBackwardsByPaneId action"),
2818 }
2819 }
2820
2821 #[test]
2822 fn test_move_pane_backwards_without_pane_id() {
2823 let cli_action = CliAction::MovePaneBackwards { pane_id: None };
2824 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
2825 assert!(result.is_ok());
2826 let actions = result.unwrap();
2827 assert_eq!(actions.len(), 1);
2828 assert!(matches!(actions[0], Action::MovePaneBackwards));
2829 }
2830
2831 #[test]
2833 fn test_clear_with_pane_id() {
2834 let cli_action = CliAction::Clear {
2835 pane_id: Some("terminal_14".to_string()),
2836 };
2837 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
2838 assert!(result.is_ok());
2839 let actions = result.unwrap();
2840 assert_eq!(actions.len(), 1);
2841 match &actions[0] {
2842 Action::ClearScreenByPaneId { pane_id } => {
2843 assert!(matches!(pane_id, PaneId::Terminal(14)));
2844 },
2845 _ => panic!("Expected ClearScreenByPaneId action"),
2846 }
2847 }
2848
2849 #[test]
2850 fn test_clear_without_pane_id() {
2851 let cli_action = CliAction::Clear { pane_id: None };
2852 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
2853 assert!(result.is_ok());
2854 let actions = result.unwrap();
2855 assert_eq!(actions.len(), 1);
2856 assert!(matches!(actions[0], Action::ClearScreen));
2857 }
2858
2859 #[test]
2861 fn test_edit_scrollback_with_pane_id() {
2862 let cli_action = CliAction::EditScrollback {
2863 pane_id: Some("terminal_15".to_string()),
2864 ansi: false,
2865 };
2866 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
2867 assert!(result.is_ok());
2868 let actions = result.unwrap();
2869 assert_eq!(actions.len(), 1);
2870 match &actions[0] {
2871 Action::EditScrollbackByPaneId { pane_id, ansi } => {
2872 assert!(matches!(pane_id, PaneId::Terminal(15)));
2873 assert!(!ansi);
2874 },
2875 _ => panic!("Expected EditScrollbackByPaneId action"),
2876 }
2877 }
2878
2879 #[test]
2880 fn test_edit_scrollback_without_pane_id() {
2881 let cli_action = CliAction::EditScrollback {
2882 pane_id: None,
2883 ansi: false,
2884 };
2885 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
2886 assert!(result.is_ok());
2887 let actions = result.unwrap();
2888 assert_eq!(actions.len(), 1);
2889 assert!(matches!(actions[0], Action::EditScrollback { ansi: false }));
2890 }
2891
2892 #[test]
2894 fn test_toggle_fullscreen_with_pane_id() {
2895 let cli_action = CliAction::ToggleFullscreen {
2896 pane_id: Some("terminal_16".to_string()),
2897 };
2898 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
2899 assert!(result.is_ok());
2900 let actions = result.unwrap();
2901 assert_eq!(actions.len(), 1);
2902 match &actions[0] {
2903 Action::ToggleFocusFullscreenByPaneId { pane_id } => {
2904 assert!(matches!(pane_id, PaneId::Terminal(16)));
2905 },
2906 _ => panic!("Expected ToggleFocusFullscreenByPaneId action"),
2907 }
2908 }
2909
2910 #[test]
2911 fn test_toggle_fullscreen_without_pane_id() {
2912 let cli_action = CliAction::ToggleFullscreen { pane_id: None };
2913 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
2914 assert!(result.is_ok());
2915 let actions = result.unwrap();
2916 assert_eq!(actions.len(), 1);
2917 assert!(matches!(actions[0], Action::ToggleFocusFullscreen));
2918 }
2919
2920 #[test]
2921 fn test_toggle_no_ui_fullscreen_with_pane_id() {
2922 let cli_action = CliAction::ToggleNoUiFullscreen {
2923 pane_id: Some("terminal_16".to_string()),
2924 };
2925 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
2926 assert!(result.is_ok());
2927 let actions = result.unwrap();
2928 assert_eq!(actions.len(), 1);
2929 match &actions[0] {
2930 Action::ToggleFocusNoUiFullscreenByPaneId { pane_id } => {
2931 assert!(matches!(pane_id, PaneId::Terminal(16)));
2932 },
2933 _ => panic!("Expected ToggleFocusNoUiFullscreenByPaneId action"),
2934 }
2935 }
2936
2937 #[test]
2938 fn test_toggle_no_ui_fullscreen_without_pane_id() {
2939 let cli_action = CliAction::ToggleNoUiFullscreen { pane_id: None };
2940 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
2941 assert!(result.is_ok());
2942 let actions = result.unwrap();
2943 assert_eq!(actions.len(), 1);
2944 assert!(matches!(actions[0], Action::ToggleFocusNoUiFullscreen));
2945 }
2946
2947 #[test]
2949 fn test_toggle_pane_embed_or_floating_with_pane_id() {
2950 let cli_action = CliAction::TogglePaneEmbedOrFloating {
2951 pane_id: Some("terminal_17".to_string()),
2952 };
2953 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
2954 assert!(result.is_ok());
2955 let actions = result.unwrap();
2956 assert_eq!(actions.len(), 1);
2957 match &actions[0] {
2958 Action::TogglePaneEmbedOrFloatingByPaneId { pane_id } => {
2959 assert!(matches!(pane_id, PaneId::Terminal(17)));
2960 },
2961 _ => panic!("Expected TogglePaneEmbedOrFloatingByPaneId action"),
2962 }
2963 }
2964
2965 #[test]
2966 fn test_toggle_pane_embed_or_floating_without_pane_id() {
2967 let cli_action = CliAction::TogglePaneEmbedOrFloating { pane_id: None };
2968 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
2969 assert!(result.is_ok());
2970 let actions = result.unwrap();
2971 assert_eq!(actions.len(), 1);
2972 assert!(matches!(actions[0], Action::TogglePaneEmbedOrFloating));
2973 }
2974
2975 #[test]
2977 fn test_close_pane_with_pane_id() {
2978 let cli_action = CliAction::ClosePane {
2979 pane_id: Some("terminal_18".to_string()),
2980 };
2981 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
2982 assert!(result.is_ok());
2983 let actions = result.unwrap();
2984 assert_eq!(actions.len(), 1);
2985 match &actions[0] {
2986 Action::CloseFocusByPaneId { pane_id } => {
2987 assert!(matches!(pane_id, PaneId::Terminal(18)));
2988 },
2989 _ => panic!("Expected CloseFocusByPaneId action"),
2990 }
2991 }
2992
2993 #[test]
2994 fn test_close_pane_without_pane_id() {
2995 let cli_action = CliAction::ClosePane { pane_id: None };
2996 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
2997 assert!(result.is_ok());
2998 let actions = result.unwrap();
2999 assert_eq!(actions.len(), 1);
3000 assert!(matches!(actions[0], Action::CloseFocus));
3001 }
3002
3003 #[test]
3005 fn test_rename_pane_with_pane_id() {
3006 let cli_action = CliAction::RenamePane {
3007 name: "my-pane".to_string(),
3008 pane_id: Some("terminal_19".to_string()),
3009 };
3010 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
3011 assert!(result.is_ok());
3012 let actions = result.unwrap();
3013 assert_eq!(actions.len(), 1);
3014 match &actions[0] {
3015 Action::RenamePaneByPaneId { pane_id, name } => {
3016 assert!(matches!(pane_id, Some(PaneId::Terminal(19))));
3017 assert_eq!(name, &"my-pane".as_bytes().to_vec());
3018 },
3019 _ => panic!("Expected RenamePaneByPaneId action"),
3020 }
3021 }
3022
3023 #[test]
3024 fn test_rename_pane_without_pane_id() {
3025 let cli_action = CliAction::RenamePane {
3026 name: "my-pane".to_string(),
3027 pane_id: None,
3028 };
3029 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
3030 assert!(result.is_ok());
3031 let actions = result.unwrap();
3032 assert_eq!(actions.len(), 1);
3033 match &actions[0] {
3034 Action::RenamePaneByPaneId { pane_id, name } => {
3035 assert!(pane_id.is_none());
3036 assert_eq!(name, &"my-pane".as_bytes().to_vec());
3037 },
3038 _ => panic!("Expected RenamePaneByPaneId action"),
3039 }
3040 }
3041
3042 #[test]
3044 fn test_undo_rename_pane_with_pane_id() {
3045 let cli_action = CliAction::UndoRenamePane {
3046 pane_id: Some("terminal_20".to_string()),
3047 };
3048 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
3049 assert!(result.is_ok());
3050 let actions = result.unwrap();
3051 assert_eq!(actions.len(), 1);
3052 match &actions[0] {
3053 Action::UndoRenamePaneByPaneId { pane_id } => {
3054 assert!(matches!(pane_id, PaneId::Terminal(20)));
3055 },
3056 _ => panic!("Expected UndoRenamePaneByPaneId action"),
3057 }
3058 }
3059
3060 #[test]
3061 fn test_undo_rename_pane_without_pane_id() {
3062 let cli_action = CliAction::UndoRenamePane { pane_id: None };
3063 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
3064 assert!(result.is_ok());
3065 let actions = result.unwrap();
3066 assert_eq!(actions.len(), 1);
3067 assert!(matches!(actions[0], Action::UndoRenamePane));
3068 }
3069
3070 #[test]
3072 fn test_toggle_pane_pinned_with_pane_id() {
3073 let cli_action = CliAction::TogglePanePinned {
3074 pane_id: Some("terminal_21".to_string()),
3075 };
3076 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
3077 assert!(result.is_ok());
3078 let actions = result.unwrap();
3079 assert_eq!(actions.len(), 1);
3080 match &actions[0] {
3081 Action::TogglePanePinnedByPaneId { pane_id } => {
3082 assert!(matches!(pane_id, PaneId::Terminal(21)));
3083 },
3084 _ => panic!("Expected TogglePanePinnedByPaneId action"),
3085 }
3086 }
3087
3088 #[test]
3089 fn test_toggle_pane_pinned_without_pane_id() {
3090 let cli_action = CliAction::TogglePanePinned { pane_id: None };
3091 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
3092 assert!(result.is_ok());
3093 let actions = result.unwrap();
3094 assert_eq!(actions.len(), 1);
3095 assert!(matches!(actions[0], Action::TogglePanePinned));
3096 }
3097
3098 #[test]
3100 fn test_scroll_up_with_plugin_pane_id() {
3101 let cli_action = CliAction::ScrollUp {
3102 pane_id: Some("plugin_3".to_string()),
3103 };
3104 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
3105 assert!(result.is_ok());
3106 let actions = result.unwrap();
3107 assert_eq!(actions.len(), 1);
3108 match &actions[0] {
3109 Action::ScrollUpByPaneId { pane_id } => {
3110 assert!(matches!(pane_id, PaneId::Plugin(3)));
3111 },
3112 _ => panic!("Expected ScrollUpByPaneId action with plugin pane id"),
3113 }
3114 }
3115
3116 #[test]
3117 fn test_scroll_up_with_bare_integer_pane_id() {
3118 let cli_action = CliAction::ScrollUp {
3119 pane_id: Some("7".to_string()),
3120 };
3121 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
3122 assert!(result.is_ok());
3123 let actions = result.unwrap();
3124 assert_eq!(actions.len(), 1);
3125 match &actions[0] {
3126 Action::ScrollUpByPaneId { pane_id } => {
3127 assert!(matches!(pane_id, PaneId::Terminal(7)));
3128 },
3129 _ => panic!("Expected ScrollUpByPaneId action with bare integer pane id"),
3130 }
3131 }
3132
3133 #[test]
3134 fn test_scroll_up_with_invalid_pane_id() {
3135 let cli_action = CliAction::ScrollUp {
3136 pane_id: Some("invalid_id".to_string()),
3137 };
3138 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
3139 assert!(result.is_err());
3140 let err = result.unwrap_err();
3141 assert!(err.contains("Malformed pane id"));
3142 }
3143
3144 #[test]
3150 fn test_close_tab_with_tab_id() {
3151 let cli_action = CliAction::CloseTab { tab_id: Some(5) };
3152 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
3153 assert!(result.is_ok());
3154 let actions = result.unwrap();
3155 assert_eq!(actions.len(), 1);
3156 match &actions[0] {
3157 Action::CloseTabById { id } => {
3158 assert_eq!(*id, 5u64);
3159 },
3160 _ => panic!("Expected CloseTabById action"),
3161 }
3162 }
3163
3164 #[test]
3165 fn test_close_tab_without_tab_id() {
3166 let cli_action = CliAction::CloseTab { tab_id: None };
3167 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
3168 assert!(result.is_ok());
3169 let actions = result.unwrap();
3170 assert_eq!(actions.len(), 1);
3171 assert!(matches!(actions[0], Action::CloseTab));
3172 }
3173
3174 #[test]
3175 fn test_set_dark_theme_cli_to_action() {
3176 let result = Action::actions_from_cli(
3177 CliAction::SetDarkTheme,
3178 Box::new(|| PathBuf::from("/tmp")),
3179 None,
3180 );
3181 let actions = result.expect("SetDarkTheme conversion should succeed");
3182 assert_eq!(actions.len(), 1);
3183 assert!(matches!(actions[0], Action::SetDarkTheme));
3184 }
3185
3186 #[test]
3187 fn test_set_light_theme_cli_to_action() {
3188 let result = Action::actions_from_cli(
3189 CliAction::SetLightTheme,
3190 Box::new(|| PathBuf::from("/tmp")),
3191 None,
3192 );
3193 let actions = result.expect("SetLightTheme conversion should succeed");
3194 assert_eq!(actions.len(), 1);
3195 assert!(matches!(actions[0], Action::SetLightTheme));
3196 }
3197
3198 #[test]
3199 fn test_toggle_theme_cli_to_action() {
3200 let result = Action::actions_from_cli(
3201 CliAction::ToggleTheme,
3202 Box::new(|| PathBuf::from("/tmp")),
3203 None,
3204 );
3205 let actions = result.expect("ToggleTheme conversion should succeed");
3206 assert_eq!(actions.len(), 1);
3207 assert!(matches!(actions[0], Action::ToggleTheme));
3208 }
3209
3210 #[test]
3212 fn test_rename_tab_with_tab_id() {
3213 let cli_action = CliAction::RenameTab {
3214 name: "my-tab".to_string(),
3215 tab_id: Some(3),
3216 };
3217 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
3218 assert!(result.is_ok());
3219 let actions = result.unwrap();
3220 assert_eq!(actions.len(), 1);
3221 match &actions[0] {
3222 Action::RenameTabById { id, name } => {
3223 assert_eq!(*id, 3u64);
3224 assert_eq!(name, "my-tab");
3225 },
3226 _ => panic!("Expected RenameTabById action"),
3227 }
3228 }
3229
3230 #[test]
3231 fn test_rename_tab_without_tab_id() {
3232 let cli_action = CliAction::RenameTab {
3233 name: "my-tab".to_string(),
3234 tab_id: None,
3235 };
3236 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
3237 assert!(result.is_ok());
3238 let actions = result.unwrap();
3239 assert_eq!(actions.len(), 2);
3240 assert!(matches!(actions[0], Action::TabNameInput { .. }));
3241 assert!(matches!(actions[1], Action::TabNameInput { .. }));
3242 }
3243
3244 #[test]
3246 fn test_undo_rename_tab_with_tab_id() {
3247 let cli_action = CliAction::UndoRenameTab { tab_id: Some(7) };
3248 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
3249 assert!(result.is_ok());
3250 let actions = result.unwrap();
3251 assert_eq!(actions.len(), 1);
3252 match &actions[0] {
3253 Action::UndoRenameTabByTabId { id } => {
3254 assert_eq!(*id, 7u64);
3255 },
3256 _ => panic!("Expected UndoRenameTabByTabId action"),
3257 }
3258 }
3259
3260 #[test]
3261 fn test_undo_rename_tab_without_tab_id() {
3262 let cli_action = CliAction::UndoRenameTab { tab_id: None };
3263 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
3264 assert!(result.is_ok());
3265 let actions = result.unwrap();
3266 assert_eq!(actions.len(), 1);
3267 assert!(matches!(actions[0], Action::UndoRenameTab));
3268 }
3269
3270 #[test]
3272 fn test_toggle_active_sync_tab_with_tab_id() {
3273 let cli_action = CliAction::ToggleActiveSyncTab { tab_id: Some(2) };
3274 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
3275 assert!(result.is_ok());
3276 let actions = result.unwrap();
3277 assert_eq!(actions.len(), 1);
3278 match &actions[0] {
3279 Action::ToggleActiveSyncTabByTabId { id } => {
3280 assert_eq!(*id, 2u64);
3281 },
3282 _ => panic!("Expected ToggleActiveSyncTabByTabId action"),
3283 }
3284 }
3285
3286 #[test]
3287 fn test_toggle_active_sync_tab_without_tab_id() {
3288 let cli_action = CliAction::ToggleActiveSyncTab { tab_id: None };
3289 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
3290 assert!(result.is_ok());
3291 let actions = result.unwrap();
3292 assert_eq!(actions.len(), 1);
3293 assert!(matches!(actions[0], Action::ToggleActiveSyncTab));
3294 }
3295
3296 #[test]
3298 fn test_toggle_floating_panes_with_tab_id() {
3299 let cli_action = CliAction::ToggleFloatingPanes { tab_id: Some(4) };
3300 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
3301 assert!(result.is_ok());
3302 let actions = result.unwrap();
3303 assert_eq!(actions.len(), 1);
3304 match &actions[0] {
3305 Action::ToggleFloatingPanesByTabId { id } => {
3306 assert_eq!(*id, 4u64);
3307 },
3308 _ => panic!("Expected ToggleFloatingPanesByTabId action"),
3309 }
3310 }
3311
3312 #[test]
3313 fn test_toggle_floating_panes_without_tab_id() {
3314 let cli_action = CliAction::ToggleFloatingPanes { tab_id: None };
3315 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
3316 assert!(result.is_ok());
3317 let actions = result.unwrap();
3318 assert_eq!(actions.len(), 1);
3319 assert!(matches!(actions[0], Action::ToggleFloatingPanes));
3320 }
3321
3322 #[test]
3324 fn test_previous_swap_layout_with_tab_id() {
3325 let cli_action = CliAction::PreviousSwapLayout { tab_id: Some(6) };
3326 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
3327 assert!(result.is_ok());
3328 let actions = result.unwrap();
3329 assert_eq!(actions.len(), 1);
3330 match &actions[0] {
3331 Action::PreviousSwapLayoutByTabId { id } => {
3332 assert_eq!(*id, 6u64);
3333 },
3334 _ => panic!("Expected PreviousSwapLayoutByTabId action"),
3335 }
3336 }
3337
3338 #[test]
3339 fn test_previous_swap_layout_without_tab_id() {
3340 let cli_action = CliAction::PreviousSwapLayout { tab_id: None };
3341 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
3342 assert!(result.is_ok());
3343 let actions = result.unwrap();
3344 assert_eq!(actions.len(), 1);
3345 assert!(matches!(actions[0], Action::PreviousSwapLayout));
3346 }
3347
3348 #[test]
3350 fn test_next_swap_layout_with_tab_id() {
3351 let cli_action = CliAction::NextSwapLayout { tab_id: Some(8) };
3352 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
3353 assert!(result.is_ok());
3354 let actions = result.unwrap();
3355 assert_eq!(actions.len(), 1);
3356 match &actions[0] {
3357 Action::NextSwapLayoutByTabId { id } => {
3358 assert_eq!(*id, 8u64);
3359 },
3360 _ => panic!("Expected NextSwapLayoutByTabId action"),
3361 }
3362 }
3363
3364 #[test]
3365 fn test_next_swap_layout_without_tab_id() {
3366 let cli_action = CliAction::NextSwapLayout { tab_id: None };
3367 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
3368 assert!(result.is_ok());
3369 let actions = result.unwrap();
3370 assert_eq!(actions.len(), 1);
3371 assert!(matches!(actions[0], Action::NextSwapLayout));
3372 }
3373
3374 #[test]
3376 fn test_move_tab_with_tab_id() {
3377 let cli_action = CliAction::MoveTab {
3378 direction: Direction::Right,
3379 tab_id: Some(10),
3380 };
3381 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
3382 assert!(result.is_ok());
3383 let actions = result.unwrap();
3384 assert_eq!(actions.len(), 1);
3385 match &actions[0] {
3386 Action::MoveTabByTabId { id, direction } => {
3387 assert_eq!(*id, 10u64);
3388 assert!(matches!(direction, Direction::Right));
3389 },
3390 _ => panic!("Expected MoveTabByTabId action"),
3391 }
3392 }
3393
3394 #[test]
3395 fn test_move_tab_without_tab_id() {
3396 let cli_action = CliAction::MoveTab {
3397 direction: Direction::Right,
3398 tab_id: None,
3399 };
3400 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
3401 assert!(result.is_ok());
3402 let actions = result.unwrap();
3403 assert_eq!(actions.len(), 1);
3404 match &actions[0] {
3405 Action::MoveTab { direction } => {
3406 assert!(matches!(direction, Direction::Right));
3407 },
3408 _ => panic!("Expected MoveTab action"),
3409 }
3410 }
3411
3412 #[test]
3415 fn test_edit_scrollback_with_ansi_flag() {
3416 let cli_action = CliAction::EditScrollback {
3417 pane_id: None,
3418 ansi: true,
3419 };
3420 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
3421 assert!(result.is_ok());
3422 let actions = result.unwrap();
3423 assert_eq!(actions.len(), 1);
3424 assert!(matches!(actions[0], Action::EditScrollback { ansi: true }));
3425 }
3426
3427 #[test]
3428 fn test_edit_scrollback_with_pane_id_and_ansi() {
3429 let cli_action = CliAction::EditScrollback {
3430 pane_id: Some("terminal_15".to_string()),
3431 ansi: true,
3432 };
3433 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
3434 assert!(result.is_ok());
3435 let actions = result.unwrap();
3436 assert_eq!(actions.len(), 1);
3437 match &actions[0] {
3438 Action::EditScrollbackByPaneId { pane_id, ansi } => {
3439 assert_eq!(*pane_id, PaneId::Terminal(15));
3440 assert!(*ansi);
3441 },
3442 _ => panic!("Expected EditScrollbackByPaneId action"),
3443 }
3444 }
3445
3446 #[test]
3447 fn test_dump_screen_with_ansi_flag() {
3448 let cli_action = CliAction::DumpScreen {
3449 path: Some(PathBuf::from("/tmp/test")),
3450 full: true,
3451 pane_id: None,
3452 ansi: true,
3453 };
3454 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
3455 assert!(result.is_ok());
3456 let actions = result.unwrap();
3457 assert_eq!(actions.len(), 1);
3458 match &actions[0] {
3459 Action::DumpScreen {
3460 ansi,
3461 include_scrollback,
3462 ..
3463 } => {
3464 assert!(*ansi);
3465 assert!(*include_scrollback);
3466 },
3467 _ => panic!("Expected DumpScreen action"),
3468 }
3469 }
3470
3471 #[test]
3472 fn test_dump_screen_with_pane_id_and_ansi() {
3473 let cli_action = CliAction::DumpScreen {
3474 path: None,
3475 full: false,
3476 pane_id: Some("terminal_5".to_string()),
3477 ansi: true,
3478 };
3479 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
3480 assert!(result.is_ok());
3481 let actions = result.unwrap();
3482 assert_eq!(actions.len(), 1);
3483 match &actions[0] {
3484 Action::DumpScreen { pane_id, ansi, .. } => {
3485 assert_eq!(*pane_id, Some(PaneId::Terminal(5)));
3486 assert!(*ansi);
3487 },
3488 _ => panic!("Expected DumpScreen action"),
3489 }
3490 }
3491
3492 #[test]
3493 fn test_focus_pane_id() {
3494 let cli_action = CliAction::FocusPaneId {
3495 pane_id: "terminal_7".to_string(),
3496 };
3497 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
3498 assert!(result.is_ok());
3499 let actions = result.unwrap();
3500 assert_eq!(actions.len(), 1);
3501 match &actions[0] {
3502 Action::FocusPaneByPaneId { pane_id } => {
3503 assert!(matches!(pane_id, PaneId::Terminal(7)));
3504 },
3505 _ => panic!("Expected FocusPaneByPaneId action"),
3506 }
3507 }
3508
3509 #[test]
3510 fn test_focus_pane_id_bare_int() {
3511 let cli_action = CliAction::FocusPaneId {
3512 pane_id: "3".to_string(),
3513 };
3514 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
3515 assert!(result.is_ok());
3516 let actions = result.unwrap();
3517 assert_eq!(actions.len(), 1);
3518 match &actions[0] {
3519 Action::FocusPaneByPaneId { pane_id } => {
3520 assert!(matches!(pane_id, PaneId::Terminal(3)));
3521 },
3522 _ => panic!("Expected FocusPaneByPaneId action"),
3523 }
3524 }
3525
3526 #[test]
3527 fn test_focus_pane_id_plugin() {
3528 let cli_action = CliAction::FocusPaneId {
3529 pane_id: "plugin_2".to_string(),
3530 };
3531 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
3532 assert!(result.is_ok());
3533 let actions = result.unwrap();
3534 assert_eq!(actions.len(), 1);
3535 match &actions[0] {
3536 Action::FocusPaneByPaneId { pane_id } => {
3537 assert!(matches!(pane_id, PaneId::Plugin(2)));
3538 },
3539 _ => panic!("Expected FocusPaneByPaneId action"),
3540 }
3541 }
3542
3543 #[test]
3544 fn test_focus_pane_id_malformed() {
3545 let cli_action = CliAction::FocusPaneId {
3546 pane_id: "invalid_id".to_string(),
3547 };
3548 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
3549 assert!(result.is_err());
3550 }
3551
3552 #[test]
3553 fn test_new_tab_with_layout_string() {
3554 let cli_action = CliAction::NewTab {
3555 name: None,
3556 layout: None,
3557 layout_string: Some("layout {\n pane\n pane\n}\n".into()),
3558 layout_dir: None,
3559 cwd: None,
3560 initial_command: vec![],
3561 initial_plugin: None,
3562 close_on_exit: Default::default(),
3563 start_suspended: Default::default(),
3564 block_until_exit: false,
3565 block_until_exit_success: false,
3566 block_until_exit_failure: false,
3567 no_focus: false,
3568 };
3569 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
3570 assert!(result.is_ok());
3571 let actions = result.unwrap();
3572 assert_eq!(actions.len(), 1);
3573 match &actions[0] {
3574 Action::NewTab {
3575 tiled_layout,
3576 floating_layouts,
3577 ..
3578 } => {
3579 assert!(tiled_layout.is_some());
3580 let layout = tiled_layout.as_ref().unwrap();
3581 assert_eq!(layout.children.len(), 2);
3583 assert!(floating_layouts.is_empty());
3584 },
3585 _ => panic!("Expected NewTab action"),
3586 }
3587 }
3588
3589 #[test]
3590 fn test_new_tab_with_invalid_layout_string() {
3591 let cli_action = CliAction::NewTab {
3592 name: None,
3593 layout: None,
3594 layout_string: Some("invalid { kdl".into()),
3595 layout_dir: None,
3596 cwd: None,
3597 initial_command: vec![],
3598 initial_plugin: None,
3599 close_on_exit: Default::default(),
3600 start_suspended: Default::default(),
3601 block_until_exit: false,
3602 block_until_exit_success: false,
3603 block_until_exit_failure: false,
3604 no_focus: false,
3605 };
3606 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
3607 assert!(result.is_err());
3608 }
3609
3610 #[test]
3611 fn test_override_layout_with_layout_string() {
3612 let cli_action = CliAction::OverrideLayout {
3613 layout: None,
3614 layout_string: Some("layout {\n pane\n pane\n}\n".into()),
3615 layout_dir: None,
3616 retain_existing_terminal_panes: false,
3617 retain_existing_plugin_panes: false,
3618 apply_only_to_active_tab: false,
3619 };
3620 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
3621 assert!(result.is_ok());
3622 let actions = result.unwrap();
3623 assert_eq!(actions.len(), 1);
3624 match &actions[0] {
3625 Action::OverrideLayout { tabs, .. } => {
3626 assert!(!tabs.is_empty());
3627 },
3628 _ => panic!("Expected OverrideLayout action"),
3629 }
3630 }
3631
3632 #[test]
3633 fn test_switch_session_with_layout_string() {
3634 let cli_action = CliAction::SwitchSession {
3635 name: "test-session".into(),
3636 tab_position: None,
3637 pane_id: None,
3638 layout: None,
3639 layout_string: Some("layout {\n pane\n}\n".into()),
3640 layout_dir: None,
3641 cwd: None,
3642 };
3643 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
3644 assert!(result.is_ok());
3645 let actions = result.unwrap();
3646 assert_eq!(actions.len(), 1);
3647 match &actions[0] {
3648 Action::SwitchSession { layout, .. } => {
3649 assert!(matches!(
3650 layout,
3651 Some(crate::data::LayoutInfo::Stringified(_))
3652 ));
3653 },
3654 _ => panic!("Expected SwitchSession action"),
3655 }
3656 }
3657
3658 #[test]
3659 fn test_switch_session_with_invalid_layout_string() {
3660 let cli_action = CliAction::SwitchSession {
3661 name: "test-session".into(),
3662 tab_position: None,
3663 pane_id: None,
3664 layout: None,
3665 layout_string: Some("invalid { kdl".into()),
3666 layout_dir: None,
3667 cwd: None,
3668 };
3669 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
3670 assert!(result.is_err());
3671 }
3672
3673 #[test]
3676 fn test_new_pane_tiled_with_tab_id() {
3677 let cli_action = CliAction::NewPane {
3678 direction: Some(Direction::Right),
3679 command: vec![],
3680 plugin: None,
3681 cwd: None,
3682 floating: false,
3683 in_place: false,
3684 close_replaced_pane: false,
3685 pane_id: None,
3686 name: None,
3687 close_on_exit: false,
3688 start_suspended: false,
3689 configuration: None,
3690 skip_plugin_cache: false,
3691 x: None,
3692 y: None,
3693 width: None,
3694 height: None,
3695 pinned: None,
3696 stacked: false,
3697 blocking: false,
3698 block_until_exit_success: false,
3699 block_until_exit_failure: false,
3700 block_until_exit: false,
3701 unblock_condition: None,
3702 near_current_pane: false,
3703 no_focus: false,
3704 borderless: None,
3705 tab_id: Some(3),
3706 };
3707 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
3708 assert!(result.is_ok());
3709 let actions = result.unwrap();
3710 assert_eq!(actions.len(), 1);
3711 match &actions[0] {
3712 Action::NewTiledPane { tab_id, .. } => {
3713 assert_eq!(*tab_id, Some(3));
3714 },
3715 _ => panic!("Expected NewTiledPane action"),
3716 }
3717 }
3718
3719 #[test]
3720 fn test_new_pane_tiled_without_tab_id() {
3721 let cli_action = CliAction::NewPane {
3722 direction: None,
3723 command: vec![],
3724 plugin: None,
3725 cwd: None,
3726 floating: false,
3727 in_place: false,
3728 close_replaced_pane: false,
3729 pane_id: None,
3730 name: None,
3731 close_on_exit: false,
3732 start_suspended: false,
3733 configuration: None,
3734 skip_plugin_cache: false,
3735 x: None,
3736 y: None,
3737 width: None,
3738 height: None,
3739 pinned: None,
3740 stacked: false,
3741 blocking: false,
3742 block_until_exit_success: false,
3743 block_until_exit_failure: false,
3744 block_until_exit: false,
3745 unblock_condition: None,
3746 near_current_pane: false,
3747 no_focus: false,
3748 borderless: None,
3749 tab_id: None,
3750 };
3751 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
3752 assert!(result.is_ok());
3753 let actions = result.unwrap();
3754 assert_eq!(actions.len(), 1);
3755 match &actions[0] {
3756 Action::NewTiledPane { tab_id, .. } => {
3757 assert_eq!(*tab_id, None);
3758 },
3759 _ => panic!("Expected NewTiledPane action"),
3760 }
3761 }
3762
3763 #[test]
3764 fn test_new_in_place_pane_with_pane_id_to_replace() {
3765 let cli_action = CliAction::NewPane {
3766 direction: None,
3767 command: vec![],
3768 plugin: None,
3769 cwd: None,
3770 floating: false,
3771 in_place: true,
3772 close_replaced_pane: true,
3773 pane_id: Some("terminal_4".to_string()),
3774 name: None,
3775 close_on_exit: false,
3776 start_suspended: false,
3777 configuration: None,
3778 skip_plugin_cache: false,
3779 x: None,
3780 y: None,
3781 width: None,
3782 height: None,
3783 pinned: None,
3784 stacked: false,
3785 blocking: false,
3786 block_until_exit_success: false,
3787 block_until_exit_failure: false,
3788 block_until_exit: false,
3789 unblock_condition: None,
3790 near_current_pane: false,
3791 no_focus: false,
3792 borderless: None,
3793 tab_id: None,
3794 };
3795 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
3796 assert!(result.is_ok());
3797 let actions = result.unwrap();
3798 assert_eq!(actions.len(), 1);
3799 match &actions[0] {
3800 Action::NewInPlacePane {
3801 pane_id_to_replace, ..
3802 } => {
3803 assert_eq!(*pane_id_to_replace, Some(PaneId::Terminal(4)));
3804 },
3805 _ => panic!("Expected NewInPlacePane action"),
3806 }
3807 }
3808
3809 #[test]
3810 fn test_new_in_place_pane_with_malformed_pane_id() {
3811 let cli_action = CliAction::NewPane {
3812 direction: None,
3813 command: vec![],
3814 plugin: None,
3815 cwd: None,
3816 floating: false,
3817 in_place: true,
3818 close_replaced_pane: false,
3819 pane_id: Some("not_a_pane".to_string()),
3820 name: None,
3821 close_on_exit: false,
3822 start_suspended: false,
3823 configuration: None,
3824 skip_plugin_cache: false,
3825 x: None,
3826 y: None,
3827 width: None,
3828 height: None,
3829 pinned: None,
3830 stacked: false,
3831 blocking: false,
3832 block_until_exit_success: false,
3833 block_until_exit_failure: false,
3834 block_until_exit: false,
3835 unblock_condition: None,
3836 near_current_pane: false,
3837 no_focus: false,
3838 borderless: None,
3839 tab_id: None,
3840 };
3841 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
3842 assert!(result.is_err());
3843 assert!(result.unwrap_err().contains("Malformed pane id"));
3844 }
3845
3846 #[test]
3847 fn test_new_pane_floating_with_tab_id() {
3848 let cli_action = CliAction::NewPane {
3849 direction: None,
3850 command: vec![],
3851 plugin: None,
3852 cwd: None,
3853 floating: true,
3854 in_place: false,
3855 close_replaced_pane: false,
3856 pane_id: None,
3857 name: None,
3858 close_on_exit: false,
3859 start_suspended: false,
3860 configuration: None,
3861 skip_plugin_cache: false,
3862 x: None,
3863 y: None,
3864 width: None,
3865 height: None,
3866 pinned: None,
3867 stacked: false,
3868 blocking: false,
3869 block_until_exit_success: false,
3870 block_until_exit_failure: false,
3871 block_until_exit: false,
3872 unblock_condition: None,
3873 near_current_pane: false,
3874 no_focus: false,
3875 borderless: None,
3876 tab_id: Some(5),
3877 };
3878 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
3879 assert!(result.is_ok());
3880 let actions = result.unwrap();
3881 assert_eq!(actions.len(), 1);
3882 match &actions[0] {
3883 Action::NewFloatingPane { tab_id, .. } => {
3884 assert_eq!(*tab_id, Some(5));
3885 },
3886 _ => panic!("Expected NewFloatingPane action"),
3887 }
3888 }
3889
3890 #[test]
3891 fn test_new_pane_stacked_with_tab_id() {
3892 let cli_action = CliAction::NewPane {
3893 direction: None,
3894 command: vec!["ls".into()],
3895 plugin: None,
3896 cwd: None,
3897 floating: false,
3898 in_place: false,
3899 close_replaced_pane: false,
3900 pane_id: None,
3901 name: None,
3902 close_on_exit: false,
3903 start_suspended: false,
3904 configuration: None,
3905 skip_plugin_cache: false,
3906 x: None,
3907 y: None,
3908 width: None,
3909 height: None,
3910 pinned: None,
3911 stacked: true,
3912 blocking: false,
3913 block_until_exit_success: false,
3914 block_until_exit_failure: false,
3915 block_until_exit: false,
3916 unblock_condition: None,
3917 near_current_pane: false,
3918 no_focus: false,
3919 borderless: None,
3920 tab_id: Some(1),
3921 };
3922 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
3923 assert!(result.is_ok());
3924 let actions = result.unwrap();
3925 assert_eq!(actions.len(), 1);
3926 match &actions[0] {
3927 Action::NewStackedPane { tab_id, .. } => {
3928 assert_eq!(*tab_id, Some(1));
3929 },
3930 _ => panic!("Expected NewStackedPane action"),
3931 }
3932 }
3933
3934 #[test]
3935 fn test_new_pane_blocking_with_tab_id() {
3936 let cli_action = CliAction::NewPane {
3937 direction: None,
3938 command: vec!["ls".into()],
3939 plugin: None,
3940 cwd: None,
3941 floating: false,
3942 in_place: false,
3943 close_replaced_pane: false,
3944 pane_id: None,
3945 name: None,
3946 close_on_exit: false,
3947 start_suspended: false,
3948 configuration: None,
3949 skip_plugin_cache: false,
3950 x: None,
3951 y: None,
3952 width: None,
3953 height: None,
3954 pinned: None,
3955 stacked: false,
3956 blocking: true,
3957 block_until_exit_success: false,
3958 block_until_exit_failure: false,
3959 block_until_exit: false,
3960 unblock_condition: None,
3961 near_current_pane: false,
3962 no_focus: false,
3963 borderless: None,
3964 tab_id: Some(2),
3965 };
3966 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
3967 assert!(result.is_ok());
3968 let actions = result.unwrap();
3969 assert_eq!(actions.len(), 1);
3970 match &actions[0] {
3971 Action::NewBlockingPane { tab_id, .. } => {
3972 assert_eq!(*tab_id, Some(2));
3973 },
3974 _ => panic!("Expected NewBlockingPane action"),
3975 }
3976 }
3977
3978 #[test]
3979 fn test_edit_with_tab_id() {
3980 let cli_action = CliAction::Edit {
3981 file: PathBuf::from("/tmp/test.rs"),
3982 direction: None,
3983 line_number: None,
3984 floating: false,
3985 in_place: false,
3986 close_replaced_pane: false,
3987 cwd: None,
3988 x: None,
3989 y: None,
3990 width: None,
3991 height: None,
3992 pinned: None,
3993 near_current_pane: false,
3994 no_focus: false,
3995 borderless: None,
3996 tab_id: Some(4),
3997 };
3998 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
3999 assert!(result.is_ok());
4000 let actions = result.unwrap();
4001 assert_eq!(actions.len(), 1);
4002 match &actions[0] {
4003 Action::EditFile { tab_id, .. } => {
4004 assert_eq!(*tab_id, Some(4));
4005 },
4006 _ => panic!("Expected EditFile action"),
4007 }
4008 }
4009
4010 #[test]
4011 fn test_edit_without_tab_id() {
4012 let cli_action = CliAction::Edit {
4013 file: PathBuf::from("/tmp/test.rs"),
4014 direction: None,
4015 line_number: None,
4016 floating: false,
4017 in_place: false,
4018 close_replaced_pane: false,
4019 cwd: None,
4020 x: None,
4021 y: None,
4022 width: None,
4023 height: None,
4024 pinned: None,
4025 near_current_pane: false,
4026 no_focus: false,
4027 borderless: None,
4028 tab_id: None,
4029 };
4030 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
4031 assert!(result.is_ok());
4032 let actions = result.unwrap();
4033 assert_eq!(actions.len(), 1);
4034 match &actions[0] {
4035 Action::EditFile { tab_id, .. } => {
4036 assert_eq!(*tab_id, None);
4037 },
4038 _ => panic!("Expected EditFile action"),
4039 }
4040 }
4041
4042 #[test]
4043 fn test_new_pane_plugin_tiled_with_tab_id() {
4044 let cli_action = CliAction::NewPane {
4045 direction: None,
4046 command: vec![],
4047 plugin: Some("zellij:strider".into()),
4048 cwd: None,
4049 floating: false,
4050 in_place: false,
4051 close_replaced_pane: false,
4052 pane_id: None,
4053 name: None,
4054 close_on_exit: false,
4055 start_suspended: false,
4056 configuration: None,
4057 skip_plugin_cache: false,
4058 x: None,
4059 y: None,
4060 width: None,
4061 height: None,
4062 pinned: None,
4063 stacked: false,
4064 blocking: false,
4065 block_until_exit_success: false,
4066 block_until_exit_failure: false,
4067 block_until_exit: false,
4068 unblock_condition: None,
4069 near_current_pane: false,
4070 no_focus: false,
4071 borderless: None,
4072 tab_id: Some(2),
4073 };
4074 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
4075 assert!(result.is_ok());
4076 let actions = result.unwrap();
4077 assert_eq!(actions.len(), 1);
4078 match &actions[0] {
4079 Action::NewTiledPluginPane { tab_id, .. } => {
4080 assert_eq!(*tab_id, Some(2));
4081 },
4082 _ => panic!("Expected NewTiledPluginPane action"),
4083 }
4084 }
4085
4086 #[test]
4087 fn test_new_pane_plugin_floating_with_tab_id() {
4088 let cli_action = CliAction::NewPane {
4089 direction: None,
4090 command: vec![],
4091 plugin: Some("zellij:strider".into()),
4092 cwd: None,
4093 floating: true,
4094 in_place: false,
4095 close_replaced_pane: false,
4096 pane_id: None,
4097 name: None,
4098 close_on_exit: false,
4099 start_suspended: false,
4100 configuration: None,
4101 skip_plugin_cache: false,
4102 x: None,
4103 y: None,
4104 width: None,
4105 height: None,
4106 pinned: None,
4107 stacked: false,
4108 blocking: false,
4109 block_until_exit_success: false,
4110 block_until_exit_failure: false,
4111 block_until_exit: false,
4112 unblock_condition: None,
4113 near_current_pane: false,
4114 no_focus: false,
4115 borderless: None,
4116 tab_id: Some(1),
4117 };
4118 let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
4119 assert!(result.is_ok());
4120 let actions = result.unwrap();
4121 assert_eq!(actions.len(), 1);
4122 match &actions[0] {
4123 Action::NewFloatingPluginPane { tab_id, .. } => {
4124 assert_eq!(*tab_id, Some(1));
4125 },
4126 _ => panic!("Expected NewFloatingPluginPane action"),
4127 }
4128 }
4129}