diff options
| author | Aram Drevekenin <aram@poor.dev> | 2026-03-30 16:02:09 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-03-30 16:02:09 +0200 |
| commit | 8ce05f0bbd58acbbca95f95ae14fdc4e866f7391 (patch) | |
| tree | 57c6d9ee2231a556f938df4f2629511a711518df /zellij-utils/src | |
| parent | docs(changelog): recent windows fixes (diff) | |
| download | sl0ppij-8ce05f0bbd58acbbca95f95ae14fdc4e866f7391.tar.gz sl0ppij-8ce05f0bbd58acbbca95f95ae14fdc4e866f7391.tar.xz sl0ppij-8ce05f0bbd58acbbca95f95ae14fdc4e866f7391.zip | |
Layout string flag (#4956)
* allow sending a stringified layout with --layout-string
* add --tab-id to all the things
* fix windows tests
* add pr
Diffstat (limited to 'zellij-utils/src')
12 files changed, 1102 insertions, 27 deletions
diff --git a/zellij-utils/src/cli.rs b/zellij-utils/src/cli.rs index 12117265..f02ace98 100644 --- a/zellij-utils/src/cli.rs +++ b/zellij-utils/src/cli.rs @@ -59,6 +59,12 @@ pub struct CliArgs { #[clap(short, long, value_parser, overrides_with = "layout")] pub layout: Option<PathBuf>, + /// Raw KDL layout string to use directly (instead of a file path) + /// if inside a session (or using the --session flag) will be added to the session as a new tab + /// or tabs, otherwise will start a new session + #[clap(long, value_parser, conflicts_with_all = &["layout", "new-session-with-layout"])] + pub layout_string: Option<String>, + /// Name of a predefined layout inside the layout directory or the path to a layout file /// Will always start a new session, even if inside an existing session #[clap(short, long, value_parser, overrides_with = "new_session_with_layout")] @@ -515,6 +521,14 @@ pub enum Sessions { /// mouse) #[clap(short, long, value_parser)] borderless: Option<bool>, + /// Target a specific tab by ID + #[clap( + long, + value_parser, + conflicts_with("near-current-pane"), + conflicts_with("in-place") + )] + tab_id: Option<usize>, }, /// Load a plugin /// Returns: Created pane ID (format: plugin_<id>) @@ -575,6 +589,9 @@ pub enum Sessions { /// mouse) #[clap(short, long, value_parser)] borderless: Option<bool>, + /// Target a specific tab by ID + #[clap(long, value_parser, conflicts_with("in-place"))] + tab_id: Option<usize>, }, /// Edit file with default $EDITOR / $VISUAL /// Returns: Created pane ID (format: terminal_<id>) @@ -641,6 +658,14 @@ pub enum Sessions { /// mouse) #[clap(short, long, value_parser)] borderless: Option<bool>, + /// Target a specific tab by ID + #[clap( + long, + value_parser, + conflicts_with("near-current-pane"), + conflicts_with("in-place") + )] + tab_id: Option<usize>, }, ConvertConfig { old_config_file: PathBuf, @@ -1010,6 +1035,14 @@ pub enum CliAction { /// mouse) #[clap(long, value_parser)] borderless: Option<bool>, + /// Target a specific tab by ID + #[clap( + long, + value_parser, + conflicts_with("near-current-pane"), + conflicts_with("in-place") + )] + tab_id: Option<usize>, }, /// Open the specified file in a new zellij pane with your default EDITOR /// Returns: Created pane ID (format: terminal_<id>) @@ -1075,6 +1108,14 @@ pub enum CliAction { /// mouse) #[clap(short, long, value_parser)] borderless: Option<bool>, + /// Target a specific tab by ID + #[clap( + long, + value_parser, + conflicts_with("near-current-pane"), + conflicts_with("in-place") + )] + tab_id: Option<usize>, }, /// Switch input mode of all connected clients [locked|pane|tab|resize|move|search|session] SwitchMode { @@ -1187,9 +1228,13 @@ pub enum CliAction { /// Returns: The created tab's ID as a single number on stdout NewTab { /// Layout to use for the new tab - #[clap(short, long, value_parser)] + #[clap(short, long, value_parser, conflicts_with = "layout-string")] layout: Option<PathBuf>, + /// Raw KDL layout string to use directly (instead of a layout file path) + #[clap(long, value_parser, conflicts_with = "layout")] + layout_string: Option<String>, + /// Default folder to look for layouts #[clap(long, value_parser, requires("layout"))] layout_dir: Option<PathBuf>, @@ -1292,8 +1337,16 @@ pub enum CliAction { /// Override the layout of the active tab OverrideLayout { /// Path to the layout file - #[clap(value_parser)] - layout: PathBuf, + #[clap( + value_parser, + required_unless_present = "layout-string", + conflicts_with = "layout-string" + )] + layout: Option<PathBuf>, + + /// Raw KDL layout string to use directly (instead of a layout file path) + #[clap(long, value_parser, conflicts_with = "layout")] + layout_string: Option<String>, /// Default folder to look for layouts #[clap(long, value_parser)] @@ -1341,6 +1394,9 @@ pub enum CliAction { configuration: Option<PluginUserConfiguration>, #[clap(short, long, value_parser)] skip_plugin_cache: bool, + /// Target a specific tab by ID + #[clap(long, value_parser, conflicts_with("in-place"))] + tab_id: Option<usize>, }, /// Returns: Plugin pane ID (format: plugin_<id>) LaunchPlugin { @@ -1362,6 +1418,9 @@ pub enum CliAction { configuration: Option<PluginUserConfiguration>, #[clap(short, long, value_parser)] skip_plugin_cache: bool, + /// Target a specific tab by ID + #[clap(long, value_parser, conflicts_with("in-place"))] + tab_id: Option<usize>, }, RenameSession { name: String, @@ -1573,8 +1632,11 @@ tail -f /tmp/my-live-logfile | zellij action pipe --name logs --plugin https://e #[clap(long)] pane_id: Option<String>, /// Layout to apply when switching to the session (relative paths start at layout-dir) - #[clap(short, long, value_parser)] + #[clap(short, long, value_parser, conflicts_with = "layout-string")] layout: Option<PathBuf>, + /// Raw KDL layout string to use directly + #[clap(long, value_parser, conflicts_with = "layout")] + layout_string: Option<String>, /// Default folder to look for layouts #[clap(long, value_parser, requires("layout"))] layout_dir: Option<PathBuf>, diff --git a/zellij-utils/src/client_server_contract/common_types.proto b/zellij-utils/src/client_server_contract/common_types.proto index 6a6f211c..e610eb2a 100644 --- a/zellij-utils/src/client_server_contract/common_types.proto +++ b/zellij-utils/src/client_server_contract/common_types.proto @@ -454,6 +454,7 @@ message EditFileAction { optional FloatingPaneCoordinates coordinates = 6; bool near_current_pane = 7; bool close_replaced_pane = 8; + optional uint32 tab_id = 9; } message NewFloatingPaneAction { @@ -461,6 +462,7 @@ message NewFloatingPaneAction { optional string pane_name = 2; optional FloatingPaneCoordinates coordinates = 3; bool near_current_pane = 7; + optional uint32 tab_id = 8; } message NewTiledPaneAction { @@ -469,6 +471,7 @@ message NewTiledPaneAction { optional string pane_name = 3; bool near_current_pane = 7; optional bool borderless = 8; + optional uint32 tab_id = 9; } message NewInPlacePaneAction { @@ -477,12 +480,14 @@ message NewInPlacePaneAction { bool near_current_pane = 3; optional PaneId pane_id_to_replace = 4; bool close_replaced_pane = 5; + optional uint32 tab_id = 6; } message NewStackedPaneAction { optional RunCommandAction command = 1; optional string pane_name = 2; bool near_current_pane = 3; + optional uint32 tab_id = 4; } message NewBlockingPaneAction { @@ -491,6 +496,7 @@ message NewBlockingPaneAction { optional RunCommandAction command = 3; optional UnblockCondition unblock_condition = 4; bool near_current_pane = 5; + optional uint32 tab_id = 6; } message PaneNameInputAction { @@ -538,6 +544,7 @@ message LaunchOrFocusPluginAction { bool should_open_in_place = 4; bool skip_cache = 5; bool close_replaced_pane = 6; + optional uint32 tab_id = 7; } message LaunchPluginAction { @@ -547,6 +554,7 @@ message LaunchPluginAction { bool skip_cache = 4; optional string cwd = 5; bool close_replaced_pane = 6; + optional uint32 tab_id = 7; } message MouseEventAction { @@ -574,6 +582,7 @@ message NewTiledPluginPaneAction { optional string pane_name = 2; bool skip_cache = 3; optional string cwd = 4; + optional uint32 tab_id = 5; } message NewFloatingPluginPaneAction { @@ -582,6 +591,7 @@ message NewFloatingPluginPaneAction { bool skip_cache = 3; optional string cwd = 4; optional FloatingPaneCoordinates coordinates = 5; + optional uint32 tab_id = 6; } message NewInPlacePluginPaneAction { @@ -589,6 +599,7 @@ message NewInPlacePluginPaneAction { optional string pane_name = 2; bool skip_cache = 3; bool close_replaced_pane = 4; + optional uint32 tab_id = 5; } message StartOrReloadPluginAction { diff --git a/zellij-utils/src/input/actions.rs b/zellij-utils/src/input/actions.rs index 6dda2b7e..0ce1c507 100644 --- a/zellij-utils/src/input/actions.rs +++ b/zellij-utils/src/input/actions.rs @@ -232,6 +232,7 @@ pub enum Action { command: Option<RunCommandAction>, unblock_condition: Option<UnblockCondition>, near_current_pane: bool, + tab_id: Option<usize>, }, /// Open the file in a new pane using the default editor /// Returns: Created pane ID (format: terminal_<id>) @@ -244,6 +245,7 @@ pub enum Action { start_suppressed: bool, coordinates: Option<FloatingPaneCoordinates>, near_current_pane: bool, + tab_id: Option<usize>, }, /// Open a new floating pane /// Returns: Created pane ID (format: terminal_<id> or plugin_<id>) @@ -252,6 +254,7 @@ pub enum Action { pane_name: Option<String>, coordinates: Option<FloatingPaneCoordinates>, near_current_pane: bool, + tab_id: Option<usize>, }, /// Open a new tiled (embedded, non-floating) pane /// Returns: Created pane ID (format: terminal_<id> or plugin_<id>) @@ -261,6 +264,7 @@ pub enum Action { pane_name: Option<String>, near_current_pane: bool, borderless: Option<bool>, + tab_id: Option<usize>, }, /// Open a new pane in place of the focused one, suppressing it instead /// Returns: Created pane ID (format: terminal_<id> or plugin_<id>) @@ -270,12 +274,14 @@ pub enum Action { near_current_pane: bool, pane_id_to_replace: Option<PaneId>, close_replaced_pane: bool, + tab_id: Option<usize>, }, /// Returns: Created pane ID (format: terminal_<id> or plugin_<id>) NewStackedPane { command: Option<RunCommandAction>, pane_name: Option<String>, near_current_pane: bool, + tab_id: Option<usize>, }, /// Embed focused pane in tab if floating or float focused pane if embedded TogglePaneEmbedOrFloating, @@ -363,6 +369,7 @@ pub enum Action { should_open_in_place: bool, close_replaced_pane: bool, skip_cache: bool, + tab_id: Option<usize>, }, /// Returns: Plugin pane ID (format: plugin_<id>) LaunchPlugin { @@ -372,6 +379,7 @@ pub enum Action { close_replaced_pane: bool, skip_cache: bool, cwd: Option<PathBuf>, + tab_id: Option<usize>, }, MouseEvent { event: MouseEvent, @@ -416,6 +424,7 @@ pub enum Action { pane_name: Option<String>, skip_cache: bool, cwd: Option<PathBuf>, + tab_id: Option<usize>, }, /// Returns: Created pane ID (format: plugin_<id>) NewFloatingPluginPane { @@ -424,6 +433,7 @@ pub enum Action { skip_cache: bool, cwd: Option<PathBuf>, coordinates: Option<FloatingPaneCoordinates>, + tab_id: Option<usize>, }, /// Returns: Created pane ID (format: plugin_<id>) NewInPlacePluginPane { @@ -431,6 +441,7 @@ pub enum Action { pane_name: Option<String>, skip_cache: bool, close_replaced_pane: bool, + tab_id: Option<usize>, }, StartOrReloadPlugin { plugin: RunPluginOrAlias, @@ -1016,6 +1027,7 @@ impl Action { unblock_condition, near_current_pane, borderless, + tab_id, } => { let current_dir = get_current_dir(); // cwd should only be specified in a plugin alias if it was explicitly given to us, @@ -1087,6 +1099,7 @@ impl Action { command, unblock_condition, near_current_pane, + tab_id, }]) } else if let Some(plugin) = plugin { let plugin = match RunPluginLocation::parse(&plugin, cwd.clone()) { @@ -1118,6 +1131,7 @@ impl Action { coordinates: FloatingPaneCoordinates::new( x, y, width, height, pinned, borderless, ), + tab_id, }]) } else if in_place { Ok(vec![Action::NewInPlacePluginPane { @@ -1125,6 +1139,7 @@ impl Action { pane_name: name, skip_cache: skip_plugin_cache, close_replaced_pane, + tab_id, }]) } else { // it is intentional that a new tiled plugin pane cannot include a @@ -1140,6 +1155,7 @@ impl Action { pane_name: name, skip_cache: skip_plugin_cache, cwd, + tab_id, }]) } } else if !command.is_empty() { @@ -1164,6 +1180,7 @@ impl Action { x, y, width, height, pinned, borderless, ), near_current_pane, + tab_id, }]) } else if in_place { Ok(vec![Action::NewInPlacePane { @@ -1172,12 +1189,14 @@ impl Action { near_current_pane, pane_id_to_replace: None, // TODO: support this close_replaced_pane, + tab_id, }]) } else if stacked { Ok(vec![Action::NewStackedPane { command: Some(run_command_action), pane_name: name, near_current_pane, + tab_id, }]) } else { Ok(vec![Action::NewTiledPane { @@ -1186,6 +1205,7 @@ impl Action { pane_name: name, near_current_pane, borderless, + tab_id, }]) } } else { @@ -1197,6 +1217,7 @@ impl Action { x, y, width, height, pinned, borderless, ), near_current_pane, + tab_id, }]) } else if in_place { Ok(vec![Action::NewInPlacePane { @@ -1205,12 +1226,14 @@ impl Action { near_current_pane, pane_id_to_replace: None, // TODO: support this close_replaced_pane, + tab_id, }]) } else if stacked { Ok(vec![Action::NewStackedPane { command: None, pane_name: name, near_current_pane, + tab_id, }]) } else { Ok(vec![Action::NewTiledPane { @@ -1219,6 +1242,7 @@ impl Action { pane_name: name, near_current_pane, borderless, + tab_id, }]) } } @@ -1238,6 +1262,7 @@ impl Action { pinned, near_current_pane, borderless, + tab_id, } => { let mut file = file; let current_dir = get_current_dir(); @@ -1261,6 +1286,7 @@ impl Action { x, y, width, height, pinned, borderless, ), near_current_pane, + tab_id, }]) }, CliAction::SwitchMode { input_mode } => Ok(vec![Action::SwitchToMode { input_mode }]), @@ -1357,6 +1383,7 @@ impl Action { CliAction::NewTab { name, layout, + layout_string, layout_dir, cwd, initial_command, @@ -1421,12 +1448,103 @@ impl Action { } else { None }; - if let Some(layout_path) = layout { + if let Some(raw_layout) = layout_string { + let layout_source_name = "layout-string".to_owned(); + let path_to_raw_layout = layout_source_name.clone(); + let swap_layouts: Option<(String, String)> = None; + let should_start_layout_commands_suspended = false; + let raw_layout_for_error = raw_layout.clone(); + 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| { + let stringified_error = match e { + ConfigError::KdlError(kdl_error) => { + let error = kdl_error.add_src(layout_source_name.clone(), raw_layout_for_error); + let report: Report = error.into(); + format!("{:?}", report) + } + ConfigError::KdlDeserializationError(kdl_error) => { + let error_message = match kdl_error.kind { + kdl::KdlErrorKind::Context("valid node terminator") => { + format!("Failed to deserialize KDL node. \nPossible reasons:\n{}\n{}\n{}\n{}", + "- Missing `;` after a node name, eg. { node; another_node; }", + "- Missing quotations (\") around an argument node eg. { first_node \"argument_node\"; }", + "- Missing an equal sign (=) between node arguments on a title line. eg. argument=\"value\"", + "- Found an extraneous equal sign (=) between node child arguments and their values. eg. { argument=\"value\" }") + }, + _ => String::from(kdl_error.help.unwrap_or("Kdl Deserialization Error")), + }; + let kdl_error = KdlError { + error_message, + src: Some(NamedSource::new(layout_source_name.clone(), raw_layout_for_error)), + offset: Some(kdl_error.span.offset()), + len: Some(kdl_error.span.len()), + help_message: None, + }; + let report: Report = kdl_error.into(); + format!("{:?}", report) + }, + e => format!("{}", e) + }; + stringified_error + })?; + if should_start_layout_commands_suspended { + layout.recursively_add_start_suspended_including_template(Some(true)); + } + let mut tabs = layout.tabs(); + if !tabs.is_empty() { + let swap_tiled_layouts = Some(layout.swap_tiled_layouts.clone()); + let swap_floating_layouts = Some(layout.swap_floating_layouts.clone()); + let mut new_tab_actions = vec![]; + let mut has_focused_tab = tabs + .iter() + .any(|(_, layout, _)| layout.focus.unwrap_or(false)); + for (tab_name, layout, floating_panes_layout) in tabs.drain(..) { + let name = tab_name.or_else(|| name.clone()); + let should_change_focus_to_new_tab = + layout.focus.unwrap_or_else(|| { + if !has_focused_tab { + has_focused_tab = true; + true + } else { + false + } + }); + new_tab_actions.push(Action::NewTab { + tiled_layout: Some(layout), + floating_layouts: floating_panes_layout, + swap_tiled_layouts: swap_tiled_layouts.clone(), + swap_floating_layouts: swap_floating_layouts.clone(), + tab_name: name, + should_change_focus_to_new_tab, + cwd: None, + initial_panes: initial_panes.clone(), + first_pane_unblock_condition, + }); + } + Ok(new_tab_actions) + } else { + let swap_tiled_layouts = Some(layout.swap_tiled_layouts.clone()); + let swap_floating_layouts = Some(layout.swap_floating_layouts.clone()); + let (layout, floating_panes_layout) = layout.new_tab(); + let should_change_focus_to_new_tab = true; + Ok(vec![Action::NewTab { + tiled_layout: Some(layout), + floating_layouts: floating_panes_layout, + swap_tiled_layouts, + swap_floating_layouts, + tab_name: name, + should_change_focus_to_new_tab, + cwd: None, + initial_panes, + first_pane_unblock_condition, + }]) + } + } else if let Some(layout_path) = layout { let layout_dir = layout_dir .or_else(|| config.and_then(|c| c.options.layout_dir)) .or_else(|| get_layout_dir(find_default_config_dir())); let mut should_start_layout_commands_suspended = false; + let layout_source_name; let (path_to_raw_layout, raw_layout, swap_layouts) = if let Some(layout_url) = layout_path.to_str().and_then(|l| { if l.starts_with("http://") || l.starts_with("https://") { @@ -1436,6 +1554,7 @@ impl Action { } }) { should_start_layout_commands_suspended = true; + layout_source_name = layout_url.to_owned(); ( layout_url.to_owned(), Layout::stringified_from_url(layout_url) @@ -1443,13 +1562,18 @@ impl Action { None, ) } else { + layout_source_name = layout_path + .as_path() + .as_os_str() + .to_string_lossy() + .to_string(); Layout::stringified_from_path_or_default(Some(&layout_path), layout_dir) .map_err(|e| format!("Failed to load layout: {}", e))? }; 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| { let stringified_error = match e { ConfigError::KdlError(kdl_error) => { - let error = kdl_error.add_src(layout_path.as_path().as_os_str().to_string_lossy().to_string(), String::from(raw_layout)); + let error = kdl_error.add_src(layout_source_name.clone(), String::from(raw_layout)); let report: Report = error.into(); format!("{:?}", report) } @@ -1466,7 +1590,7 @@ impl Action { }; let kdl_error = KdlError { error_message, - src: Some(NamedSource::new(layout_path.as_path().as_os_str().to_string_lossy().to_string(), String::from(raw_layout))), + src: Some(NamedSource::new(layout_source_name.clone(), String::from(raw_layout))), offset: Some(kdl_error.span.offset()), len: Some(kdl_error.span.len()), help_message: None, @@ -1555,6 +1679,7 @@ impl Action { }, CliAction::OverrideLayout { layout, + layout_string, layout_dir, retain_existing_terminal_panes, retain_existing_plugin_panes, @@ -1565,24 +1690,39 @@ impl Action { .or_else(|| config.and_then(|c| c.options.layout_dir)) .or_else(|| get_layout_dir(find_default_config_dir())); - // Load layout from URL or file path - let (path_to_raw_layout, raw_layout, swap_layouts) = if let Some(layout_url) = - layout.to_str().and_then(|l| { + // Load layout from string, URL, or file path + let layout_source_name; + let (path_to_raw_layout, raw_layout, swap_layouts) = if let Some(raw) = + layout_string + { + layout_source_name = "layout-string".to_owned(); + (layout_source_name.clone(), raw, None) + } else if let Some(layout_path) = &layout { + if let Some(layout_url) = layout_path.to_str().and_then(|l| { if l.starts_with("http://") || l.starts_with("https://") { Some(l) } else { None } }) { - ( - layout_url.to_owned(), - Layout::stringified_from_url(layout_url) - .map_err(|e| format!("Failed to load layout from URL: {}", e))?, - None, - ) + layout_source_name = layout_url.to_owned(); + ( + layout_url.to_owned(), + Layout::stringified_from_url(layout_url) + .map_err(|e| format!("Failed to load layout from URL: {}", e))?, + None, + ) + } else { + layout_source_name = layout_path + .as_path() + .as_os_str() + .to_string_lossy() + .to_string(); + Layout::stringified_from_path_or_default(Some(layout_path), layout_dir) + .map_err(|e| format!("Failed to load layout: {}", e))? + } } else { - Layout::stringified_from_path_or_default(Some(&layout), layout_dir) - .map_err(|e| format!("Failed to load layout: {}", e))? + return Err("Either layout or layout-string must be provided".to_string()); }; // Parse KDL layout @@ -1595,10 +1735,8 @@ impl Action { .map_err(|e| { let stringified_error = match e { ConfigError::KdlError(kdl_error) => { - let error = kdl_error.add_src( - layout.as_path().as_os_str().to_string_lossy().to_string(), - String::from(raw_layout), - ); + let error = kdl_error + .add_src(layout_source_name.clone(), String::from(raw_layout)); let report: Report = error.into(); format!("{:?}", report) }, @@ -1669,6 +1807,7 @@ impl Action { move_to_focused_tab, configuration, skip_plugin_cache, + tab_id, } => { let current_dir = get_current_dir(); let run_plugin_or_alias = RunPluginOrAlias::from_url( @@ -1684,6 +1823,7 @@ impl Action { should_open_in_place: in_place, close_replaced_pane, skip_cache: skip_plugin_cache, + tab_id, }]) }, CliAction::LaunchPlugin { @@ -1693,6 +1833,7 @@ impl Action { close_replaced_pane, configuration, skip_plugin_cache, + tab_id, } => { let current_dir = get_current_dir(); let run_plugin_or_alias = RunPluginOrAlias::from_url( @@ -1708,6 +1849,7 @@ impl Action { close_replaced_pane, skip_cache: skip_plugin_cache, cwd: Some(current_dir), + tab_id, }]) }, CliAction::RenameSession { name } => Ok(vec![Action::RenameSession { name }]), @@ -1919,6 +2061,7 @@ impl Action { tab_position, pane_id, layout, + layout_string, layout_dir, cwd, } => { @@ -1946,10 +2089,83 @@ impl Action { current_dir.join(layout_dir) }); - let layout_info = if let Some(layout_path) = layout { + let layout_info = if let Some(layout_string) = layout_string { + // validate the layout string before sending it to the target session + let layout_source_name = "layout-string".to_owned(); + let raw_layout_for_error = layout_string.clone(); + Layout::from_str(&layout_string, layout_source_name.clone(), None, None) + .map_err(|e| { + match e { + ConfigError::KdlError(kdl_error) => { + let error = kdl_error.add_src(layout_source_name, raw_layout_for_error); + let report: Report = error.into(); + format!("{:?}", report) + }, + ConfigError::KdlDeserializationError(kdl_error) => { + let error_message = match kdl_error.kind { + kdl::KdlErrorKind::Context("valid node terminator") => { + format!("Failed to deserialize KDL node. \nPossible reasons:\n{}\n{}\n{}\n{}", + "- Missing `;` after a node name, eg. {{ node; another_node; }}", + "- Missing quotations (\") around an argument node eg. {{ first_node \"argument_node\"; }}", + "- Missing an equal sign (=) between node arguments on a title line. eg. argument=\"value\"", + "- Found an extraneous equal sign (=) between node child arguments and their values. eg. {{ argument=\"value\" }}") + }, + _ => String::from(kdl_error.help.unwrap_or("Kdl Deserialization Error")), + }; + let kdl_error = KdlError { + error_message, + src: Some(NamedSource::new(layout_source_name, raw_layout_for_error)), + offset: Some(kdl_error.span.offset()), + len: Some(kdl_error.span.len()), + help_message: None, + }; + let report: Report = kdl_error.into(); + format!("{:?}", report) + }, + e => format!("{}", e), + } + })?; + Some(LayoutInfo::Stringified(layout_string)) + } else if let Some(layout_path) = layout { let layout_dir = layout_dir .or_else(|| config.and_then(|c| c.options.layout_dir.clone())) .or_else(|| get_layout_dir(find_default_config_dir())); + // validate the layout file before sending it to the target session + let layout_source_name = layout_path.display().to_string(); + Layout::from_path_or_default_without_config( + Some(&layout_path), + layout_dir.clone(), + ) + .map_err(|e| { + match e { + ConfigError::KdlError(kdl_error) => { + let report: Report = kdl_error.into(); + format!("{:?}", report) + }, + ConfigError::KdlDeserializationError(kdl_error) => { + let error_message = match kdl_error.kind { + kdl::KdlErrorKind::Context("valid node terminator") => { + format!("Failed to deserialize KDL node. \nPossible reasons:\n{}\n{}\n{}\n{}", + "- Missing `;` after a node name, eg. {{ node; another_node; }}", + "- Missing quotations (\") around an argument node eg. {{ first_node \"argument_node\"; }}", + "- Missing an equal sign (=) between node arguments on a title line. eg. argument=\"value\"", + "- Found an extraneous equal sign (=) between node child arguments and their values. eg. {{ argument=\"value\" }}") + }, + _ => String::from(kdl_error.help.unwrap_or("Kdl Deserialization Error")), + }; + let kdl_error = KdlError { + error_message, + src: Some(NamedSource::new(layout_source_name, String::new())), + offset: Some(kdl_error.span.offset()), + len: Some(kdl_error.span.len()), + help_message: None, + }; + let report: Report = kdl_error.into(); + format!("{:?}", report) + }, + e => format!("{}", e), + } + })?; LayoutInfo::from_config(&layout_dir, &Some(layout_path)) } else { None @@ -3177,4 +3393,481 @@ mod tests { let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None); assert!(result.is_err()); } + + #[test] + fn test_new_tab_with_layout_string() { + let cli_action = CliAction::NewTab { + name: None, + layout: None, + layout_string: Some("layout {\n pane\n pane\n}\n".into()), + layout_dir: None, + cwd: None, + initial_command: vec![], + initial_plugin: None, + close_on_exit: Default::default(), + start_suspended: Default::default(), + block_until_exit: false, + block_until_exit_success: false, + block_until_exit_failure: false, + }; + let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None); + assert!(result.is_ok()); + let actions = result.unwrap(); + assert_eq!(actions.len(), 1); + match &actions[0] { + Action::NewTab { + tiled_layout, + floating_layouts, + .. + } => { + assert!(tiled_layout.is_some()); + let layout = tiled_layout.as_ref().unwrap(); + // layout { pane; pane } produces a layout with 2 children + assert_eq!(layout.children.len(), 2); + assert!(floating_layouts.is_empty()); + }, + _ => panic!("Expected NewTab action"), + } + } + + #[test] + fn test_new_tab_with_invalid_layout_string() { + let cli_action = CliAction::NewTab { + name: None, + layout: None, + layout_string: Some("invalid { kdl".into()), + layout_dir: None, + cwd: None, + initial_command: vec![], + initial_plugin: None, + close_on_exit: Default::default(), + start_suspended: Default::default(), + block_until_exit: false, + block_until_exit_success: false, + block_until_exit_failure: false, + }; + let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None); + assert!(result.is_err()); + } + + #[test] + fn test_override_layout_with_layout_string() { + let cli_action = CliAction::OverrideLayout { + layout: None, + layout_string: Some("layout {\n pane\n pane\n}\n".into()), + layout_dir: None, + retain_existing_terminal_panes: false, + retain_existing_plugin_panes: false, + apply_only_to_active_tab: false, + }; + let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None); + assert!(result.is_ok()); + let actions = result.unwrap(); + assert_eq!(actions.len(), 1); + match &actions[0] { + Action::OverrideLayout { tabs, .. } => { + assert!(!tabs.is_empty()); + }, + _ => panic!("Expected OverrideLayout action"), + } + } + + #[test] + fn test_switch_session_with_layout_string() { + let cli_action = CliAction::SwitchSession { + name: "test-session".into(), + tab_position: None, + pane_id: None, + layout: None, + layout_string: Some("layout {\n pane\n}\n".into()), + layout_dir: None, + cwd: None, + }; + let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None); + assert!(result.is_ok()); + let actions = result.unwrap(); + assert_eq!(actions.len(), 1); + match &actions[0] { + Action::SwitchSession { layout, .. } => { + assert!(matches!( + layout, + Some(crate::data::LayoutInfo::Stringified(_)) + )); + }, + _ => panic!("Expected SwitchSession action"), + } + } + + #[test] + fn test_switch_session_with_invalid_layout_string() { + let cli_action = CliAction::SwitchSession { + name: "test-session".into(), + tab_position: None, + pane_id: None, + layout: None, + layout_string: Some("invalid { kdl".into()), + layout_dir: None, + cwd: None, + }; + let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None); + assert!(result.is_err()); + } + + // Tab-targeting for pane creation commands + + #[test] + fn test_new_pane_tiled_with_tab_id() { + let cli_action = CliAction::NewPane { + direction: Some(Direction::Right), + command: vec![], + plugin: None, + cwd: None, + floating: false, + in_place: false, + close_replaced_pane: false, + name: None, + close_on_exit: false, + start_suspended: false, + configuration: None, + skip_plugin_cache: false, + x: None, + y: None, + width: None, + height: None, + pinned: None, + stacked: false, + blocking: false, + block_until_exit_success: false, + block_until_exit_failure: false, + block_until_exit: false, + unblock_condition: None, + near_current_pane: false, + borderless: None, + tab_id: Some(3), + }; + let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None); + assert!(result.is_ok()); + let actions = result.unwrap(); + assert_eq!(actions.len(), 1); + match &actions[0] { + Action::NewTiledPane { tab_id, .. } => { + assert_eq!(*tab_id, Some(3)); + }, + _ => panic!("Expected NewTiledPane action"), + } + } + + #[test] + fn test_new_pane_tiled_without_tab_id() { + let cli_action = CliAction::NewPane { + direction: None, + command: vec![], + plugin: None, + cwd: None, + floating: false, + in_place: false, + close_replaced_pane: false, + name: None, + close_on_exit: false, + start_suspended: false, + configuration: None, + skip_plugin_cache: false, + x: None, + y: None, + width: None, + height: None, + pinned: None, + stacked: false, + blocking: false, + block_until_exit_success: false, + block_until_exit_failure: false, + block_until_exit: false, + unblock_condition: None, + near_current_pane: false, + borderless: None, + tab_id: None, + }; + let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None); + assert!(result.is_ok()); + let actions = result.unwrap(); + assert_eq!(actions.len(), 1); + match &actions[0] { + Action::NewTiledPane { tab_id, .. } => { + assert_eq!(*tab_id, None); + }, + _ => panic!("Expected NewTiledPane action"), + } + } + + #[test] + fn test_new_pane_floating_with_tab_id() { + let cli_action = CliAction::NewPane { + direction: None, + command: vec![], + plugin: None, + cwd: None, + floating: true, + in_place: false, + close_replaced_pane: false, + name: None, + close_on_exit: false, + start_suspended: false, + configuration: None, + skip_plugin_cache: false, + x: None, + y: None, + width: None, + height: None, + pinned: None, + stacked: false, + blocking: false, + block_until_exit_success: false, + block_until_exit_failure: false, + block_until_exit: false, + unblock_condition: None, + near_current_pane: false, + borderless: None, + tab_id: Some(5), + }; + let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None); + assert!(result.is_ok()); + let actions = result.unwrap(); + assert_eq!(actions.len(), 1); + match &actions[0] { + Action::NewFloatingPane { tab_id, .. } => { + assert_eq!(*tab_id, Some(5)); + }, + _ => panic!("Expected NewFloatingPane action"), + } + } + + #[test] + fn test_new_pane_stacked_with_tab_id() { + let cli_action = CliAction::NewPane { + direction: None, + command: vec!["ls".into()], + plugin: None, + cwd: None, + floating: false, + in_place: false, + close_replaced_pane: false, + name: None, + close_on_exit: false, + start_suspended: false, + configuration: None, + skip_plugin_cache: false, + x: None, + y: None, + width: None, + height: None, + pinned: None, + stacked: true, + blocking: false, + block_until_exit_success: false, + block_until_exit_failure: false, + block_until_exit: false, + unblock_condition: None, + near_current_pane: false, + borderless: None, + tab_id: Some(1), + }; + let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None); + assert!(result.is_ok()); + let actions = result.unwrap(); + assert_eq!(actions.len(), 1); + match &actions[0] { + Action::NewStackedPane { tab_id, .. } => { + assert_eq!(*tab_id, Some(1)); + }, + _ => panic!("Expected NewStackedPane action"), + } + } + + #[test] + fn test_new_pane_blocking_with_tab_id() { + let cli_action = CliAction::NewPane { + direction: None, + command: vec!["ls".into()], + plugin: None, + cwd: None, + floating: false, + in_place: false, + close_replaced_pane: false, + name: None, + close_on_exit: false, + start_suspended: false, + configuration: None, + skip_plugin_cache: false, + x: None, + y: None, + width: None, + height: None, + pinned: None, + stacked: false, + blocking: true, + block_until_exit_success: false, + block_until_exit_failure: false, + block_until_exit: false, + unblock_condition: None, + near_current_pane: false, + borderless: None, + tab_id: Some(2), + }; + let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None); + assert!(result.is_ok()); + let actions = result.unwrap(); + assert_eq!(actions.len(), 1); + match &actions[0] { + Action::NewBlockingPane { tab_id, .. } => { + assert_eq!(*tab_id, Some(2)); + }, + _ => panic!("Expected NewBlockingPane action"), + } + } + + #[test] + fn test_edit_with_tab_id() { + let cli_action = CliAction::Edit { + file: PathBuf::from("/tmp/test.rs"), + direction: None, + line_number: None, + floating: false, + in_place: false, + close_replaced_pane: false, + cwd: None, + x: None, + y: None, + width: None, + height: None, + pinned: None, + near_current_pane: false, + borderless: None, + tab_id: Some(4), + }; + let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None); + assert!(result.is_ok()); + let actions = result.unwrap(); + assert_eq!(actions.len(), 1); + match &actions[0] { + Action::EditFile { tab_id, .. } => { + assert_eq!(*tab_id, Some(4)); + }, + _ => panic!("Expected EditFile action"), + } + } + + #[test] + fn test_edit_without_tab_id() { + let cli_action = CliAction::Edit { + file: PathBuf::from("/tmp/test.rs"), + direction: None, + line_number: None, + floating: false, + in_place: false, + close_replaced_pane: false, + cwd: None, + x: None, + y: None, + width: None, + height: None, + pinned: None, + near_current_pane: false, + borderless: None, + tab_id: None, + }; + let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None); + assert!(result.is_ok()); + let actions = result.unwrap(); + assert_eq!(actions.len(), 1); + match &actions[0] { + Action::EditFile { tab_id, .. } => { + assert_eq!(*tab_id, None); + }, + _ => panic!("Expected EditFile action"), + } + } + + #[test] + fn test_new_pane_plugin_tiled_with_tab_id() { + let cli_action = CliAction::NewPane { + direction: None, + command: vec![], + plugin: Some("zellij:strider".into()), + cwd: None, + floating: false, + in_place: false, + close_replaced_pane: false, + name: None, + close_on_exit: false, + start_suspended: false, + configuration: None, + skip_plugin_cache: false, + x: None, + y: None, + width: None, + height: None, + pinned: None, + stacked: false, + blocking: false, + block_until_exit_success: false, + block_until_exit_failure: false, + block_until_exit: false, + unblock_condition: None, + near_current_pane: false, + borderless: None, + tab_id: Some(2), + }; + let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None); + assert!(result.is_ok()); + let actions = result.unwrap(); + assert_eq!(actions.len(), 1); + match &actions[0] { + Action::NewTiledPluginPane { tab_id, .. } => { + assert_eq!(*tab_id, Some(2)); + }, + _ => panic!("Expected NewTiledPluginPane action"), + } + } + + #[test] + fn test_new_pane_plugin_floating_with_tab_id() { + let cli_action = CliAction::NewPane { + direction: None, + command: vec![], + plugin: Some("zellij:strider".into()), + cwd: None, + floating: true, + in_place: false, + close_replaced_pane: false, + name: None, + close_on_exit: false, + start_suspended: false, + configuration: None, + skip_plugin_cache: false, + x: None, + y: None, + width: None, + height: None, + pinned: None, + stacked: false, + blocking: false, + block_until_exit_success: false, + block_until_exit_failure: false, + block_until_exit: false, + unblock_condition: None, + near_current_pane: false, + borderless: None, + tab_id: Some(1), + }; + let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None); + assert!(result.is_ok()); + let actions = result.unwrap(); + assert_eq!(actions.len(), 1); + match &actions[0] { + Action::NewFloatingPluginPane { tab_id, .. } => { + assert_eq!(*tab_id, Some(1)); + }, + _ => panic!("Expected NewFloatingPluginPane action"), + } + } } diff --git a/zellij-utils/src/ipc/protobuf_conversion.rs b/zellij-utils/src/ipc/protobuf_conversion.rs index 5c78a445..189c1370 100644 --- a/zellij-utils/src/ipc/protobuf_conversion.rs +++ b/zellij-utils/src/ipc/protobuf_conversion.rs @@ -1095,6 +1095,8 @@ impl From<crate::input::actions::Action> start_suppressed, coordinates, near_current_pane, + tab_id, + .. } => ActionType::EditFile(EditFileAction { payload: Some(payload.into()), direction: direction.map(|d| direction_to_proto_i32(d)), @@ -1104,17 +1106,21 @@ impl From<crate::input::actions::Action> start_suppressed, coordinates: coordinates.map(|c| c.into()), near_current_pane, + tab_id: tab_id.map(|t| t as u32), }), crate::input::actions::Action::NewFloatingPane { command, pane_name, coordinates, near_current_pane, + tab_id, + .. } => ActionType::NewFloatingPane(NewFloatingPaneAction { command: command.map(|c| c.into()), pane_name, coordinates: coordinates.map(|c| c.into()), near_current_pane, + tab_id: tab_id.map(|t| t as u32), }), crate::input::actions::Action::NewTiledPane { direction, @@ -1122,12 +1128,15 @@ impl From<crate::input::actions::Action> pane_name, near_current_pane, borderless, + tab_id, + .. } => ActionType::NewTiledPane(NewTiledPaneAction { direction: direction.map(|d| direction_to_proto_i32(d)), command: command.map(|c| c.into()), pane_name, near_current_pane, borderless, + tab_id: tab_id.map(|t| t as u32), }), crate::input::actions::Action::NewInPlacePane { command, @@ -1135,21 +1144,27 @@ impl From<crate::input::actions::Action> near_current_pane, pane_id_to_replace, close_replaced_pane, + tab_id, + .. } => ActionType::NewInPlacePane(NewInPlacePaneAction { command: command.map(|c| c.into()), pane_name, near_current_pane, pane_id_to_replace: pane_id_to_replace.and_then(|p| p.try_into().ok()), close_replaced_pane, + tab_id: tab_id.map(|t| t as u32), }), crate::input::actions::Action::NewStackedPane { command, pane_name, near_current_pane, + tab_id, + .. } => ActionType::NewStackedPane(NewStackedPaneAction { command: command.map(|c| c.into()), pane_name, near_current_pane, + tab_id: tab_id.map(|t| t as u32), }), crate::input::actions::Action::NewBlockingPane { placement, @@ -1157,12 +1172,15 @@ impl From<crate::input::actions::Action> command, unblock_condition, near_current_pane, + tab_id, + .. } => ActionType::NewBlockingPane(NewBlockingPaneAction { placement: Some(placement.into()), pane_name, command: command.map(|c| c.into()), unblock_condition: unblock_condition.map(|c| unblock_condition_to_proto_i32(c)), near_current_pane, + tab_id: tab_id.map(|t| t as u32), }), crate::input::actions::Action::TogglePaneEmbedOrFloating => { ActionType::TogglePaneEmbedOrFloating(TogglePaneEmbedOrFloatingAction {}) @@ -1283,6 +1301,8 @@ impl From<crate::input::actions::Action> should_open_in_place, close_replaced_pane, skip_cache, + tab_id, + .. } => ActionType::LaunchOrFocusPlugin(LaunchOrFocusPluginAction { plugin: Some(plugin.into()), should_float, @@ -1290,6 +1310,7 @@ impl From<crate::input::actions::Action> should_open_in_place, close_replaced_pane, skip_cache, + tab_id: tab_id.map(|t| t as u32), }), crate::input::actions::Action::LaunchPlugin { plugin, @@ -1298,6 +1319,8 @@ impl From<crate::input::actions::Action> close_replaced_pane, skip_cache, cwd, + tab_id, + .. } => ActionType::LaunchPlugin(LaunchPluginAction { plugin: Some(plugin.into()), should_float, @@ -1305,6 +1328,7 @@ impl From<crate::input::actions::Action> close_replaced_pane, skip_cache, cwd: cwd.map(|p| p.to_string_lossy().to_string()), + tab_id: tab_id.map(|t| t as u32), }), crate::input::actions::Action::MouseEvent { event } => { ActionType::MouseEvent(MouseEventAction { @@ -1362,11 +1386,14 @@ impl From<crate::input::actions::Action> pane_name, skip_cache, cwd, + tab_id, + .. } => ActionType::NewTiledPluginPane(NewTiledPluginPaneAction { plugin: Some(plugin.into()), pane_name, skip_cache, cwd: cwd.map(|p| p.to_string_lossy().to_string()), + tab_id: tab_id.map(|t| t as u32), }), crate::input::actions::Action::NewFloatingPluginPane { plugin, @@ -1374,23 +1401,29 @@ impl From<crate::input::actions::Action> skip_cache, cwd, coordinates, + tab_id, + .. } => ActionType::NewFloatingPluginPane(NewFloatingPluginPaneAction { plugin: Some(plugin.into()), pane_name, skip_cache, cwd: cwd.map(|p| p.to_string_lossy().to_string()), coordinates: coordinates.map(|c| c.into()), + tab_id: tab_id.map(|t| t as u32), }), crate::input::actions::Action::NewInPlacePluginPane { plugin, pane_name, skip_cache, close_replaced_pane, + tab_id, + .. } => ActionType::NewInPlacePluginPane(NewInPlacePluginPaneAction { plugin: Some(plugin.into()), pane_name, skip_cache, close_replaced_pane, + tab_id: tab_id.map(|t| t as u32), }), crate::input::actions::Action::StartOrReloadPlugin { plugin } => { ActionType::StartOrReloadPlugin(StartOrReloadPluginAction { @@ -1916,6 +1949,7 @@ impl TryFrom<crate::client_server_contract::client_server_contract::Action> .map(|c| c.try_into()) .transpose()?, near_current_pane: edit_file_action.near_current_pane, + tab_id: edit_file_action.tab_id.map(|t| t as usize), }), ActionType::NewFloatingPane(new_floating_action) => { Ok(crate::input::actions::Action::NewFloatingPane { @@ -1929,6 +1963,7 @@ impl TryFrom<crate::client_server_contract::client_server_contract::Action> .map(|c| c.try_into()) .transpose()?, near_current_pane: new_floating_action.near_current_pane, + tab_id: new_floating_action.tab_id.map(|t| t as usize), }) }, ActionType::NewTiledPane(new_tiled_action) => { @@ -1941,6 +1976,7 @@ impl TryFrom<crate::client_server_contract::client_server_contract::Action> pane_name: new_tiled_action.pane_name, near_current_pane: new_tiled_action.near_current_pane, borderless: new_tiled_action.borderless, + tab_id: new_tiled_action.tab_id.map(|t| t as usize), }) }, ActionType::NewInPlacePane(new_in_place_action) => { @@ -1955,6 +1991,7 @@ impl TryFrom<crate::client_server_contract::client_server_contract::Action> .pane_id_to_replace .and_then(|p| p.try_into().ok()), close_replaced_pane: new_in_place_action.close_replaced_pane, + tab_id: new_in_place_action.tab_id.map(|t| t as usize), }) }, ActionType::NewStackedPane(new_stacked_action) => { @@ -1965,6 +2002,7 @@ impl TryFrom<crate::client_server_contract::client_server_contract::Action> .transpose()?, pane_name: new_stacked_action.pane_name, near_current_pane: new_stacked_action.near_current_pane, + tab_id: new_stacked_action.tab_id.map(|t| t as usize), }) }, ActionType::NewBlockingPane(new_blocking_action) => { @@ -1983,6 +2021,7 @@ impl TryFrom<crate::client_server_contract::client_server_contract::Action> .map(|c| proto_i32_to_unblock_condition(c)) .transpose()?, near_current_pane: new_blocking_action.near_current_pane, + tab_id: new_blocking_action.tab_id.map(|t| t as usize), }) }, ActionType::TogglePaneEmbedOrFloating(_) => { @@ -2126,6 +2165,7 @@ impl TryFrom<crate::client_server_contract::client_server_contract::Action> should_open_in_place: launch_plugin_action.should_open_in_place, close_replaced_pane: launch_plugin_action.close_replaced_pane, skip_cache: launch_plugin_action.skip_cache, + tab_id: launch_plugin_action.tab_id.map(|t| t as usize), }) }, ActionType::LaunchPlugin(launch_plugin_action) => { @@ -2139,6 +2179,7 @@ impl TryFrom<crate::client_server_contract::client_server_contract::Action> close_replaced_pane: launch_plugin_action.close_replaced_pane, skip_cache: launch_plugin_action.skip_cache, cwd: launch_plugin_action.cwd.map(PathBuf::from), + tab_id: launch_plugin_action.tab_id.map(|t| t as usize), }) }, ActionType::MouseEvent(mouse_event_action) => { @@ -2210,6 +2251,7 @@ impl TryFrom<crate::client_server_contract::client_server_contract::Action> pane_name: new_tiled_plugin_action.pane_name, skip_cache: new_tiled_plugin_action.skip_cache, cwd: new_tiled_plugin_action.cwd.map(PathBuf::from), + tab_id: new_tiled_plugin_action.tab_id.map(|t| t as usize), }) }, ActionType::NewFloatingPluginPane(new_floating_plugin_action) => { @@ -2225,6 +2267,7 @@ impl TryFrom<crate::client_server_contract::client_server_contract::Action> .coordinates .map(|c| c.try_into()) .transpose()?, + tab_id: new_floating_plugin_action.tab_id.map(|t| t as usize), }) }, ActionType::NewInPlacePluginPane(new_in_place_plugin_action) => { @@ -2236,6 +2279,7 @@ impl TryFrom<crate::client_server_contract::client_server_contract::Action> pane_name: new_in_place_plugin_action.pane_name, skip_cache: new_in_place_plugin_action.skip_cache, close_replaced_pane: new_in_place_plugin_action.close_replaced_pane, + tab_id: new_in_place_plugin_action.tab_id.map(|t| t as usize), }) }, ActionType::StartOrReloadPlugin(start_plugin_action) => { diff --git a/zellij-utils/src/ipc/tests/roundtrip_tests.rs b/zellij-utils/src/ipc/tests/roundtrip_tests.rs index cfe882a1..7ef7ac89 100644 --- a/zellij-utils/src/ipc/tests/roundtrip_tests.rs +++ b/zellij-utils/src/ipc/tests/roundtrip_tests.rs @@ -1,8 +1,8 @@ use super::test_framework::*; use crate::data::{ BareKey, CommandOrPlugin, ConnectToSession, Direction, FloatingPaneCoordinates, InputMode, - KeyModifier, KeyWithModifier, LayoutInfo, LayoutMetadata, OriginatingPlugin, PaneId, PluginTag, - Resize, WebSharing, + KeyModifier, KeyWithModifier, LayoutInfo, LayoutMetadata, NewPanePlacement, OriginatingPlugin, + PaneId, PluginTag, Resize, WebSharing, }; use crate::input::actions::{Action, SearchDirection, SearchOption}; use crate::input::cli_assets::CliAssets; @@ -1054,6 +1054,7 @@ fn test_client_messages() { start_suppressed: false, coordinates: None, near_current_pane: false, + tab_id: None, }, terminal_id: Some(1), client_id: Some(100), @@ -1078,6 +1079,7 @@ fn test_client_messages() { start_suppressed: false, coordinates: None, near_current_pane: false, + tab_id: None, }, terminal_id: Some(1), client_id: Some(100), @@ -1102,6 +1104,7 @@ fn test_client_messages() { start_suppressed: false, coordinates: None, near_current_pane: false, + tab_id: None, }, terminal_id: Some(1), client_id: Some(100), @@ -1126,6 +1129,7 @@ fn test_client_messages() { start_suppressed: true, coordinates: FloatingPaneCoordinates::new(None, None, None, None, None, Some(false)), near_current_pane: false, + tab_id: None, }, terminal_id: Some(1), client_id: Some(100), @@ -1157,6 +1161,7 @@ fn test_client_messages() { Some(false), ), near_current_pane: false, + tab_id: None, }, terminal_id: Some(1), client_id: Some(100), @@ -1188,6 +1193,7 @@ fn test_client_messages() { Some(false), ), near_current_pane: false, + tab_id: None, }, terminal_id: Some(1), client_id: Some(100), @@ -1219,6 +1225,7 @@ fn test_client_messages() { Some(false), ), near_current_pane: false, + tab_id: None, }, terminal_id: Some(1), client_id: Some(100), @@ -1250,6 +1257,7 @@ fn test_client_messages() { Some(false) ), near_current_pane: false, + tab_id: None, }, terminal_id: Some(1), client_id: Some(100), @@ -1261,6 +1269,7 @@ fn test_client_messages() { pane_name: None, coordinates: None, near_current_pane: false, + tab_id: None, }, terminal_id: Some(1), client_id: Some(100), @@ -1288,6 +1297,7 @@ fn test_client_messages() { Some(false), ), near_current_pane: false, + tab_id: None, }, terminal_id: Some(1), client_id: Some(100), @@ -1319,6 +1329,7 @@ fn test_client_messages() { Some(false), ), near_current_pane: false, + tab_id: None, }, terminal_id: Some(1), client_id: Some(100), @@ -1331,6 +1342,7 @@ fn test_client_messages() { pane_name: None, near_current_pane: false, borderless: None, + tab_id: None, }, terminal_id: Some(1), client_id: Some(100), @@ -1356,6 +1368,7 @@ fn test_client_messages() { pane_name: Some("my_pane_name".to_owned()), near_current_pane: false, borderless: Some(true), + tab_id: None, }, terminal_id: Some(1), client_id: Some(100), @@ -1381,6 +1394,7 @@ fn test_client_messages() { near_current_pane: false, pane_id_to_replace: None, close_replaced_pane: false, + tab_id: None, }, terminal_id: Some(1), client_id: Some(100), @@ -1393,6 +1407,7 @@ fn test_client_messages() { near_current_pane: false, pane_id_to_replace: None, close_replaced_pane: false, + tab_id: None, }, terminal_id: Some(1), client_id: Some(100), @@ -1416,6 +1431,7 @@ fn test_client_messages() { }), pane_name: Some("my_pane_name".to_owned()), near_current_pane: false, + tab_id: None, }, terminal_id: Some(1), client_id: Some(100), @@ -1426,6 +1442,7 @@ fn test_client_messages() { command: None, pane_name: None, near_current_pane: false, + tab_id: None, }, terminal_id: Some(1), client_id: Some(100), @@ -2230,6 +2247,7 @@ fn test_client_messages() { should_open_in_place: true, close_replaced_pane: false, skip_cache: true, + tab_id: None, }, terminal_id: Some(1), client_id: Some(100), @@ -2244,6 +2262,7 @@ fn test_client_messages() { should_open_in_place: false, close_replaced_pane: false, skip_cache: false, + tab_id: None, }, terminal_id: Some(1), client_id: Some(100), @@ -2258,6 +2277,7 @@ fn test_client_messages() { close_replaced_pane: false, skip_cache: true, cwd: None, + tab_id: None, }, terminal_id: Some(1), client_id: Some(100), @@ -2272,6 +2292,7 @@ fn test_client_messages() { close_replaced_pane: false, skip_cache: false, cwd: Some(PathBuf::from("/path/to/cwd")), + tab_id: None, }, terminal_id: Some(1), client_id: Some(100), @@ -2438,6 +2459,7 @@ fn test_client_messages() { pane_name: Some("my_pane_name".to_owned()), skip_cache: false, cwd: Some(PathBuf::from("relative/path/to/cwd")), + tab_id: None, }, terminal_id: Some(1), client_id: Some(100), @@ -2450,6 +2472,7 @@ fn test_client_messages() { skip_cache: true, cwd: Some(PathBuf::from("relative/path/to/cwd")), coordinates: None, + tab_id: None, }, terminal_id: Some(1), client_id: Some(100), @@ -2469,6 +2492,7 @@ fn test_client_messages() { Some(true), Some(false), ), + tab_id: None, }, terminal_id: Some(1), client_id: Some(100), @@ -2480,6 +2504,159 @@ fn test_client_messages() { pane_name: Some("my_pane_name".to_owned()), skip_cache: true, close_replaced_pane: false, + tab_id: None, + }, + terminal_id: Some(1), + client_id: Some(100), + is_cli_client: true, + }); + // tab_id roundtrip tests - verify tab_id survives serialization + test_client_roundtrip!(ClientToServerMsg::Action { + action: Action::NewTiledPane { + direction: Some(Direction::Right), + command: None, + pane_name: None, + near_current_pane: false, + borderless: None, + tab_id: Some(3), + }, + terminal_id: Some(1), + client_id: Some(100), + is_cli_client: true, + }); + test_client_roundtrip!(ClientToServerMsg::Action { + action: Action::NewFloatingPane { + command: None, + pane_name: None, + coordinates: None, + near_current_pane: false, + tab_id: Some(5), + }, + terminal_id: Some(1), + client_id: Some(100), + is_cli_client: true, + }); + test_client_roundtrip!(ClientToServerMsg::Action { + action: Action::NewStackedPane { + command: None, + pane_name: None, + near_current_pane: false, + tab_id: Some(2), + }, + terminal_id: Some(1), + client_id: Some(100), + is_cli_client: true, + }); + test_client_roundtrip!(ClientToServerMsg::Action { + action: Action::NewBlockingPane { + placement: NewPanePlacement::Tiled { + direction: None, + borderless: None + }, + pane_name: None, + command: None, + unblock_condition: None, + near_current_pane: false, + tab_id: Some(1), + }, + terminal_id: Some(1), + client_id: Some(100), + is_cli_client: true, + }); + test_client_roundtrip!(ClientToServerMsg::Action { + action: Action::EditFile { + payload: OpenFilePayload { + path: PathBuf::from("/file/path"), + line_number: None, + cwd: None, + originating_plugin: None, + }, + direction: None, + floating: false, + in_place: false, + close_replaced_pane: false, + start_suppressed: false, + coordinates: None, + near_current_pane: false, + tab_id: Some(4), + }, + terminal_id: Some(1), + client_id: Some(100), + is_cli_client: true, + }); + test_client_roundtrip!(ClientToServerMsg::Action { + action: Action::NewInPlacePane { + command: None, + pane_name: None, + near_current_pane: false, + pane_id_to_replace: None, + close_replaced_pane: false, + tab_id: Some(7), + }, + terminal_id: Some(1), + client_id: Some(100), + is_cli_client: true, + }); + test_client_roundtrip!(ClientToServerMsg::Action { + action: Action::LaunchOrFocusPlugin { + plugin: RunPluginOrAlias::RunPlugin(RunPlugin::default()), + should_float: true, + move_to_focused_tab: false, + should_open_in_place: false, + close_replaced_pane: false, + skip_cache: false, + tab_id: Some(2), + }, + terminal_id: Some(1), + client_id: Some(100), + is_cli_client: true, + }); + test_client_roundtrip!(ClientToServerMsg::Action { + action: Action::LaunchPlugin { + plugin: RunPluginOrAlias::RunPlugin(RunPlugin::default()), + should_float: false, + should_open_in_place: false, + close_replaced_pane: false, + skip_cache: false, + cwd: None, + tab_id: Some(6), + }, + terminal_id: Some(1), + client_id: Some(100), + is_cli_client: true, + }); + test_client_roundtrip!(ClientToServerMsg::Action { + action: Action::NewTiledPluginPane { + plugin: RunPluginOrAlias::RunPlugin(RunPlugin::default()), + pane_name: None, + skip_cache: false, + cwd: None, + tab_id: Some(1), + }, + terminal_id: Some(1), + client_id: Some(100), + is_cli_client: true, + }); + test_client_roundtrip!(ClientToServerMsg::Action { + action: Action::NewFloatingPluginPane { + plugin: RunPluginOrAlias::RunPlugin(RunPlugin::default()), + pane_name: None, + skip_cache: false, + cwd: None, + coordinates: None, + tab_id: Some(3), + }, + terminal_id: Some(1), + client_id: Some(100), + is_cli_client: true, + }); + test_client_roundtrip!(ClientToServerMsg::Action { + action: Action::NewInPlacePluginPane { + plugin: RunPluginOrAlias::RunPlugin(RunPlugin::default()), + pane_name: None, + skip_cache: false, + close_replaced_pane: false, + tab_id: Some(2), }, terminal_id: Some(1), client_id: Some(100), diff --git a/zellij-utils/src/kdl/mod.rs b/zellij-utils/src/kdl/mod.rs index 919c865f..2b009e1a 100644 --- a/zellij-utils/src/kdl/mod.rs +++ b/zellij-utils/src/kdl/mod.rs @@ -558,6 +558,7 @@ impl Action { command: None, pane_name: None, near_current_pane: false, + tab_id: None, }); } else { let direction = Direction::from_str(string.as_str()).map_err(|_| { @@ -840,6 +841,7 @@ impl Action { pane_name: name, near_current_pane: false, borderless: _, + .. } => { let mut node = KdlNode::new("Run"); let mut node_children = KdlDocument::new(); @@ -890,6 +892,7 @@ impl Action { pane_name: name, coordinates: floating_pane_coordinates, near_current_pane: false, + .. } => { let mut node = KdlNode::new("Run"); let mut node_children = KdlDocument::new(); @@ -983,6 +986,7 @@ impl Action { near_current_pane: false, pane_id_to_replace: None, close_replaced_pane, + .. } => { let mut node = KdlNode::new("Run"); let mut node_children = KdlDocument::new(); @@ -1029,6 +1033,7 @@ impl Action { command: run_command_action, pane_name: name, near_current_pane: _, + .. } => match run_command_action { Some(run_command_action) => { let mut node = KdlNode::new("Run"); @@ -1108,6 +1113,7 @@ impl Action { should_open_in_place, close_replaced_pane, skip_cache: skip_plugin_cache, + .. } => { let mut node = KdlNode::new("LaunchOrFocusPlugin"); let mut node_children = KdlDocument::new(); @@ -1157,6 +1163,7 @@ impl Action { close_replaced_pane, skip_cache: skip_plugin_cache, cwd, + .. } => { let mut node = KdlNode::new("LaunchPlugin"); let mut node_children = KdlDocument::new(); @@ -1993,6 +2000,7 @@ impl TryFrom<(&KdlNode, &Options)> for Action { x, y, width, height, pinned, borderless, ), near_current_pane: false, + tab_id: None, }) } else if in_place { Ok(Action::NewInPlacePane { @@ -2001,12 +2009,14 @@ impl TryFrom<(&KdlNode, &Options)> for Action { near_current_pane: false, pane_id_to_replace: None, close_replaced_pane, + tab_id: None, }) } else if stacked { Ok(Action::NewStackedPane { command: Some(run_command_action), pane_name: name, near_current_pane: false, + tab_id: None, }) } else { Ok(Action::NewTiledPane { @@ -2015,6 +2025,7 @@ impl TryFrom<(&KdlNode, &Options)> for Action { pane_name: name, near_current_pane: false, borderless: None, + tab_id: None, }) } }, @@ -2071,6 +2082,7 @@ impl TryFrom<(&KdlNode, &Options)> for Action { should_open_in_place, close_replaced_pane, skip_cache: skip_plugin_cache, + tab_id: None, }) }, "LaunchPlugin" => { @@ -2120,7 +2132,8 @@ impl TryFrom<(&KdlNode, &Options)> for Action { close_replaced_pane, skip_cache: skip_plugin_cache, cwd: None, // we explicitly do not send the current dir here so that it will be - // filled from the active pane == better UX + // filled from the active pane == better UX + tab_id: None, }) }, "PreviousSwapLayout" => Ok(Action::PreviousSwapLayout), diff --git a/zellij-utils/src/plugin_api/action.rs b/zellij-utils/src/plugin_api/action.rs index 4384eb68..cbd62e5b 100644 --- a/zellij-utils/src/plugin_api/action.rs +++ b/zellij-utils/src/plugin_api/action.rs @@ -338,6 +338,7 @@ impl TryFrom<ProtobufAction> for Action { start_suppressed: false, coordinates: None, near_current_pane, + tab_id: None, }) }, _ => Err("Wrong payload for Action::NewPane"), @@ -353,6 +354,7 @@ impl TryFrom<ProtobufAction> for Action { pane_name, coordinates: None, near_current_pane, + tab_id: None, }) } else { Ok(Action::NewFloatingPane { @@ -360,6 +362,7 @@ impl TryFrom<ProtobufAction> for Action { pane_name: None, coordinates: None, near_current_pane, + tab_id: None, }) } }, @@ -382,6 +385,7 @@ impl TryFrom<ProtobufAction> for Action { pane_name, near_current_pane, borderless, + tab_id: None, }) } else { Ok(Action::NewTiledPane { @@ -390,6 +394,7 @@ impl TryFrom<ProtobufAction> for Action { pane_name: None, near_current_pane, borderless, + tab_id: None, }) } }, @@ -658,6 +663,7 @@ impl TryFrom<ProtobufAction> for Action { should_open_in_place, close_replaced_pane: false, skip_cache: skip_plugin_cache, + tab_id: None, }) }, _ => Err("Wrong payload for Action::LaunchOrFocusPlugin"), @@ -688,6 +694,7 @@ impl TryFrom<ProtobufAction> for Action { close_replaced_pane: false, skip_cache: skip_plugin_cache, cwd: None, + tab_id: None, }) }, _ => Err("Wrong payload for Action::LaunchOrFocusPlugin"), @@ -807,6 +814,7 @@ impl TryFrom<ProtobufAction> for Action { pane_name, skip_cache: skip_plugin_cache, cwd: None, + tab_id: None, }) }, _ => Err("Wrong payload for Action::NewTiledPluginPane"), @@ -832,6 +840,7 @@ impl TryFrom<ProtobufAction> for Action { skip_cache: skip_plugin_cache, cwd: None, coordinates: None, + tab_id: None, }) }, _ => Err("Wrong payload for Action::MiddleClick"), @@ -984,6 +993,7 @@ impl TryFrom<ProtobufAction> for Action { command: None, pane_name: None, near_current_pane: false, + tab_id: None, }), }, Some(ProtobufActionName::NewBlockingPane) => match protobuf_action.optional_payload { @@ -1005,6 +1015,7 @@ impl TryFrom<ProtobufAction> for Action { command, unblock_condition, near_current_pane, + tab_id: None, }) }, _ => Err("Wrong payload for Action::NewBlockingPane"), @@ -1024,6 +1035,7 @@ impl TryFrom<ProtobufAction> for Action { near_current_pane, pane_id_to_replace, close_replaced_pane, + tab_id: None, }) } else { Ok(Action::NewInPlacePane { @@ -1032,6 +1044,7 @@ impl TryFrom<ProtobufAction> for Action { near_current_pane, pane_id_to_replace, close_replaced_pane, + tab_id: None, }) } }, @@ -1298,6 +1311,7 @@ impl TryFrom<Action> for ProtobufAction { start_suppressed: _start_suppressed, coordinates: _floating_pane_coordinates, near_current_pane, + .. } => { let file_to_edit = open_file_payload.path.display().to_string(); let cwd = open_file_payload.cwd.map(|cwd| cwd.display().to_string()); @@ -1322,6 +1336,7 @@ impl TryFrom<Action> for ProtobufAction { pane_name, coordinates: _coordinates, near_current_pane, + .. } => { let command = run_command_action.and_then(|r| { let mut protobuf_run_command_action: ProtobufRunCommandAction = @@ -1345,6 +1360,7 @@ impl TryFrom<Action> for ProtobufAction { pane_name, near_current_pane, borderless, + .. } => { let direction = direction.and_then(|direction| { let protobuf_direction: ProtobufResizeDirection = direction.try_into().ok()?; @@ -1558,6 +1574,7 @@ impl TryFrom<Action> for ProtobufAction { should_open_in_place, close_replaced_pane: _close_replaced_pane, skip_cache: skip_plugin_cache, + .. } => { let configuration = run_plugin_or_alias.get_configuration().unwrap_or_default(); Ok(ProtobufAction { @@ -1581,6 +1598,7 @@ impl TryFrom<Action> for ProtobufAction { close_replaced_pane: _close_replaced_pane, skip_cache: skip_plugin_cache, cwd: _cwd, + .. } => { let configuration = run_plugin_or_alias.get_configuration().unwrap_or_default(); Ok(ProtobufAction { @@ -1668,6 +1686,7 @@ impl TryFrom<Action> for ProtobufAction { pane_name, skip_cache: skip_plugin_cache, cwd: _cwd, + .. } => Ok(ProtobufAction { name: ProtobufActionName::NewTiledPluginPane as i32, optional_payload: Some(OptionalPayload::NewTiledPluginPanePayload( @@ -1684,6 +1703,7 @@ impl TryFrom<Action> for ProtobufAction { skip_cache: skip_plugin_cache, cwd: _cwd, coordinates: _coordinates, + .. } => Ok(ProtobufAction { name: ProtobufActionName::NewFloatingPluginPane as i32, optional_payload: Some(OptionalPayload::NewFloatingPluginPanePayload( @@ -1806,6 +1826,7 @@ impl TryFrom<Action> for ProtobufAction { command: _, pane_name: _, near_current_pane: _, + .. } => Ok(ProtobufAction { name: ProtobufActionName::NewStackedPane as i32, optional_payload: None, @@ -1816,6 +1837,7 @@ impl TryFrom<Action> for ProtobufAction { command, unblock_condition, near_current_pane, + .. } => { let placement: ProtobufNewPanePlacement = placement.try_into()?; let command = command.and_then(|c| { @@ -1847,6 +1869,7 @@ impl TryFrom<Action> for ProtobufAction { near_current_pane, pane_id_to_replace, close_replaced_pane, + .. } => { let command = run_command_action.and_then(|r| { let mut protobuf_run_command_action: ProtobufRunCommandAction = @@ -1876,6 +1899,7 @@ impl TryFrom<Action> for ProtobufAction { pane_name: _, skip_cache: _, close_replaced_pane: _, + .. } | Action::Deny | Action::Copy diff --git a/zellij-utils/src/setup.rs b/zellij-utils/src/setup.rs index 6d74187d..d2eafe7e 100644 --- a/zellij-utils/src/setup.rs +++ b/zellij-utils/src/setup.rs @@ -612,7 +612,9 @@ impl Setup { // the chosen layout can either be a path relative to the layout_dir or a name of one // of our assets, this distinction is made when parsing the layout - TODO: ideally, this // logic should not be split up and all the decisions should happen here - let (layout_info, chosen_layout) = if let Some(chosen_layout) = cli_args.layout.clone() { + let (layout_info, chosen_layout) = if let Some(ref layout_string) = cli_args.layout_string { + (Some(LayoutInfo::Stringified(layout_string.clone())), None) + } else if let Some(chosen_layout) = cli_args.layout.clone() { let layout_info = LayoutInfo::from_cli( &layout_dir, &Some(chosen_layout.clone()), @@ -631,6 +633,10 @@ impl Setup { Some(LayoutInfo::Url(ref layout_url)) => { Layout::from_url(layout_url, config).map(|(_layout, config)| (layout_info, config)) }, + Some(LayoutInfo::Stringified(ref raw_layout)) => { + Layout::from_stringified_layout(raw_layout, config) + .map(|(_layout, config)| (layout_info, config)) + }, _ => Layout::from_path_or_default(chosen_layout.as_ref(), layout_dir.clone(), config) .map(|(_layout, config)| (layout_info, config)), } @@ -879,4 +885,21 @@ mod setup_test { let expected = cwd.join("assets/layouts/compact"); assert_eq!(layout_path, expected.display().to_string()); } + + #[test] + fn layout_string_cli_argument() { + let layout_kdl = "layout {\n pane\n pane\n}\n".to_string(); + let cli_args = CliArgs { + layout_string: Some(layout_kdl.clone()), + ..Default::default() + }; + let (_, layout_info, _, _, _) = Setup::from_cli_args(&cli_args).unwrap(); + let Some(LayoutInfo::Stringified(content)) = layout_info else { + panic!( + "layout info should be Stringified variant, got: {:#?}", + layout_info + ); + }; + assert_eq!(content, layout_kdl); + } } diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap index ff0a3ae4..243ea7ba 100644 --- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap +++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap @@ -1349,6 +1349,7 @@ Config { command: None, pane_name: None, near_current_pane: false, + tab_id: None, }, SwitchToMode { input_mode: Normal, @@ -4190,6 +4191,7 @@ Config { should_open_in_place: false, close_replaced_pane: false, skip_cache: false, + tab_id: None, }, SwitchToMode { input_mode: Normal, @@ -4231,6 +4233,7 @@ Config { should_open_in_place: false, close_replaced_pane: false, skip_cache: false, + tab_id: None, }, SwitchToMode { input_mode: Normal, @@ -4352,6 +4355,7 @@ Config { should_open_in_place: false, close_replaced_pane: false, skip_cache: false, + tab_id: None, }, SwitchToMode { input_mode: Normal, @@ -4443,6 +4447,7 @@ Config { should_open_in_place: false, close_replaced_pane: false, skip_cache: false, + tab_id: None, }, SwitchToMode { input_mode: Normal, @@ -4517,6 +4522,7 @@ Config { should_open_in_place: false, close_replaced_pane: false, skip_cache: false, + tab_id: None, }, SwitchToMode { input_mode: Normal, @@ -4570,6 +4576,7 @@ Config { should_open_in_place: false, close_replaced_pane: false, skip_cache: false, + tab_id: None, }, SwitchToMode { input_mode: Normal, diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap index 04e30a09..6c352256 100644 --- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap +++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap @@ -1349,6 +1349,7 @@ Config { command: None, pane_name: None, near_current_pane: false, + tab_id: None, }, SwitchToMode { input_mode: Normal, @@ -4190,6 +4191,7 @@ Config { should_open_in_place: false, close_replaced_pane: false, skip_cache: false, + tab_id: None, }, SwitchToMode { input_mode: Normal, @@ -4231,6 +4233,7 @@ Config { should_open_in_place: false, close_replaced_pane: false, skip_cache: false, + tab_id: None, }, SwitchToMode { input_mode: Normal, @@ -4352,6 +4355,7 @@ Config { should_open_in_place: false, close_replaced_pane: false, skip_cache: false, + tab_id: None, }, SwitchToMode { input_mode: Normal, @@ -4443,6 +4447,7 @@ Config { should_open_in_place: false, close_replaced_pane: false, skip_cache: false, + tab_id: None, }, SwitchToMode { input_mode: Normal, @@ -4517,6 +4522,7 @@ Config { should_open_in_place: false, close_replaced_pane: false, skip_cache: false, + tab_id: None, }, SwitchToMode { input_mode: Normal, @@ -4570,6 +4576,7 @@ Config { should_open_in_place: false, close_replaced_pane: false, skip_cache: false, + tab_id: None, }, SwitchToMode { input_mode: Normal, diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap index 9cb4295d..027cee69 100644 --- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap +++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap @@ -1349,6 +1349,7 @@ Config { command: None, pane_name: None, near_current_pane: false, + tab_id: None, }, SwitchToMode { input_mode: Normal, @@ -4190,6 +4191,7 @@ Config { should_open_in_place: false, close_replaced_pane: false, skip_cache: false, + tab_id: None, }, SwitchToMode { input_mode: Normal, @@ -4231,6 +4233,7 @@ Config { should_open_in_place: false, close_replaced_pane: false, skip_cache: false, + tab_id: None, }, SwitchToMode { input_mode: Normal, @@ -4352,6 +4355,7 @@ Config { should_open_in_place: false, close_replaced_pane: false, skip_cache: false, + tab_id: None, }, SwitchToMode { input_mode: Normal, @@ -4443,6 +4447,7 @@ Config { should_open_in_place: false, close_replaced_pane: false, skip_cache: false, + tab_id: None, }, SwitchToMode { input_mode: Normal, @@ -4517,6 +4522,7 @@ Config { should_open_in_place: false, close_replaced_pane: false, skip_cache: false, + tab_id: None, }, SwitchToMode { input_mode: Normal, @@ -4570,6 +4576,7 @@ Config { should_open_in_place: false, close_replaced_pane: false, skip_cache: false, + tab_id: None, }, SwitchToMode { input_mode: Normal, diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap index eb72f108..70b23c18 100644 --- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap +++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap @@ -1349,6 +1349,7 @@ Config { command: None, pane_name: None, near_current_pane: false, + tab_id: None, }, SwitchToMode { input_mode: Normal, @@ -4190,6 +4191,7 @@ Config { should_open_in_place: false, close_replaced_pane: false, skip_cache: false, + tab_id: None, }, SwitchToMode { input_mode: Normal, @@ -4231,6 +4233,7 @@ Config { should_open_in_place: false, close_replaced_pane: false, skip_cache: false, + tab_id: None, }, SwitchToMode { input_mode: Normal, @@ -4352,6 +4355,7 @@ Config { should_open_in_place: false, close_replaced_pane: false, skip_cache: false, + tab_id: None, }, SwitchToMode { input_mode: Normal, @@ -4443,6 +4447,7 @@ Config { should_open_in_place: false, close_replaced_pane: false, skip_cache: false, + tab_id: None, }, SwitchToMode { input_mode: Normal, @@ -4517,6 +4522,7 @@ Config { should_open_in_place: false, close_replaced_pane: false, skip_cache: false, + tab_id: None, }, SwitchToMode { input_mode: Normal, @@ -4570,6 +4576,7 @@ Config { should_open_in_place: false, close_replaced_pane: false, skip_cache: false, + tab_id: None, }, SwitchToMode { input_mode: Normal, |
