aboutsummaryrefslogtreecommitdiffstats
path: root/zellij-server/src
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2026-03-30 16:02:09 +0200
committerGitHub <noreply@github.com>2026-03-30 16:02:09 +0200
commit8ce05f0bbd58acbbca95f95ae14fdc4e866f7391 (patch)
tree57c6d9ee2231a556f938df4f2629511a711518df /zellij-server/src
parentdocs(changelog): recent windows fixes (diff)
downloadsl0ppij-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-server/src')
-rw-r--r--zellij-server/src/plugins/wasm_bridge.rs21
-rw-r--r--zellij-server/src/plugins/zellij_exports.rs11
-rw-r--r--zellij-server/src/route.rs176
-rw-r--r--zellij-server/src/screen.rs244
-rw-r--r--zellij-server/src/unit/screen_tests.rs414
-rw-r--r--zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_with_layout_string.snap119
-rw-r--r--zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_with_layout_string_and_name.snap119
7 files changed, 927 insertions, 177 deletions
diff --git a/zellij-server/src/plugins/wasm_bridge.rs b/zellij-server/src/plugins/wasm_bridge.rs
index 47b6e493..31d7e74b 100644
--- a/zellij-server/src/plugins/wasm_bridge.rs
+++ b/zellij-server/src/plugins/wasm_bridge.rs
@@ -287,7 +287,7 @@ impl WasmBridge {
skip_cache: bool,
client_id: Option<ClientId>,
) -> Result<(PluginId, ClientId)> {
- let err_context = move || format!("failed to load plugin");
+ let _err_context = move || format!("failed to load plugin");
let client_id = client_id
.and_then(|client_id| {
@@ -321,9 +321,22 @@ impl WasmBridge {
match run {
Some(run) => {
- let plugin = PluginConfig::from_run_plugin(run)
- .with_context(|| format!("failed to resolve plugin {run:?}"))
- .with_context(err_context)?;
+ let plugin = match PluginConfig::from_run_plugin(run) {
+ Some(plugin) => plugin,
+ None => {
+ self.next_plugin_id += 1;
+ let mut loading_indication =
+ LoadingIndication::new(run.location.to_string());
+ handle_plugin_loading_failure(
+ &self.senders,
+ plugin_id,
+ &mut loading_indication,
+ format!("Failed to resolve plugin: {}", run.location),
+ Some(client_id),
+ );
+ return Ok((plugin_id, client_id));
+ },
+ };
let plugin_name = run.location.to_string();
self.cached_events_for_pending_plugins
diff --git a/zellij-server/src/plugins/zellij_exports.rs b/zellij-server/src/plugins/zellij_exports.rs
index 026be937..24113d0b 100644
--- a/zellij-server/src/plugins/zellij_exports.rs
+++ b/zellij-server/src/plugins/zellij_exports.rs
@@ -1164,6 +1164,7 @@ fn open_plugin_pane_floating(
skip_cache: false,
cwd: Some(env.plugin_cwd.clone()),
coordinates: floating_pane_coordinates,
+ tab_id: None,
};
let error_msg = || format!("Failed to open floating plugin pane");
let result = apply_action!(action, error_msg, env);
@@ -1403,6 +1404,7 @@ fn open_file(env: &PluginEnv, file_to_open: FileToOpen, context: BTreeMap<String
start_suppressed,
coordinates: None,
near_current_pane: false,
+ tab_id: None,
};
let result = apply_action!(action, error_msg, env);
@@ -1505,6 +1507,7 @@ fn open_file_floating(
start_suppressed,
coordinates: floating_pane_coordinates,
near_current_pane: false,
+ tab_id: None,
};
let result = apply_action!(action, error_msg, env);
@@ -1545,6 +1548,7 @@ fn open_file_in_place(
start_suppressed,
coordinates: None,
near_current_pane: false,
+ tab_id: None,
};
let result = apply_action!(action, error_msg, env);
@@ -1705,6 +1709,7 @@ fn open_terminal(env: &PluginEnv, cwd: PathBuf) {
pane_name: None,
near_current_pane: false,
borderless: None,
+ tab_id: None,
};
let result = apply_action!(action, error_msg, env);
@@ -1780,6 +1785,7 @@ fn open_terminal_floating(
pane_name: None,
coordinates: floating_pane_coordinates,
near_current_pane: false,
+ tab_id: None,
};
let result = apply_action!(action, error_msg, env);
@@ -1855,6 +1861,7 @@ fn open_terminal_in_place(env: &PluginEnv, cwd: PathBuf) {
near_current_pane: false,
pane_id_to_replace: None,
close_replaced_pane: false,
+ tab_id: None,
};
let result = apply_action!(action, error_msg, env);
@@ -2139,6 +2146,7 @@ fn open_command_pane(
pane_name: name,
near_current_pane: false,
borderless: None,
+ tab_id: None,
};
let result = apply_action!(action, error_msg, env);
@@ -2246,6 +2254,7 @@ fn open_command_pane_floating(
pane_name: name,
coordinates: floating_pane_coordinates,
near_current_pane: false,
+ tab_id: None,
};
let result = apply_action!(action, error_msg, env);
@@ -2356,6 +2365,7 @@ fn open_command_pane_in_place(
near_current_pane: false,
pane_id_to_replace: None,
close_replaced_pane: false,
+ tab_id: None,
};
let result = apply_action!(action, error_msg, env);
@@ -4423,6 +4433,7 @@ fn try_edit_layout(
start_suppressed: false,
coordinates: None,
near_current_pane: true,
+ tab_id: None,
};
// Route the action - this is fallible
diff --git a/zellij-server/src/route.rs b/zellij-server/src/route.rs
index cb124c65..2ffbd5cb 100644
--- a/zellij-server/src/route.rs
+++ b/zellij-server/src/route.rs
@@ -637,6 +637,7 @@ pub(crate) fn route_action(
command,
unblock_condition,
near_current_pane,
+ tab_id,
} => {
let command = command
.map(|cmd| TerminalAction::RunCommand(cmd.into()))
@@ -668,7 +669,9 @@ pub(crate) fn route_action(
_ => pane_id,
};
- let client_tab_index_or_paneid = if near_current_pane && pane_id.is_some() {
+ let client_tab_index_or_paneid = if let Some(tab_id) = tab_id {
+ ClientTabIndexOrPaneId::TabIndex(tab_id)
+ } else if near_current_pane && pane_id.is_some() {
ClientTabIndexOrPaneId::PaneId(pane_id.unwrap())
} else {
ClientTabIndexOrPaneId::ClientId(client_id)
@@ -695,27 +698,31 @@ pub(crate) fn route_action(
start_suppressed,
coordinates: floating_pane_coordinates,
near_current_pane,
+ tab_id,
} => {
let title = format!("Editing: {}", open_file_payload.path.display());
let open_file = TerminalAction::OpenFile(open_file_payload);
let pty_instr = if should_open_in_place {
- match pane_id {
- Some(pane_id) if near_current_pane => PtyInstruction::SpawnInPlaceTerminal(
- Some(open_file),
- Some(title),
- close_replaced_pane,
- ClientTabIndexOrPaneId::PaneId(pane_id),
- Some(NotificationEnd::new(completion_tx)),
- ),
- _ => PtyInstruction::SpawnInPlaceTerminal(
- Some(open_file),
- Some(title),
- close_replaced_pane,
- ClientTabIndexOrPaneId::ClientId(client_id),
- Some(NotificationEnd::new(completion_tx)),
- ),
- }
+ let client_tab_index_or_paneid = if let Some(tab_id) = tab_id {
+ ClientTabIndexOrPaneId::TabIndex(tab_id)
+ } else if near_current_pane && pane_id.is_some() {
+ ClientTabIndexOrPaneId::PaneId(pane_id.unwrap())
+ } else {
+ ClientTabIndexOrPaneId::ClientId(client_id)
+ };
+ PtyInstruction::SpawnInPlaceTerminal(
+ Some(open_file),
+ Some(title),
+ close_replaced_pane,
+ client_tab_index_or_paneid,
+ Some(NotificationEnd::new(completion_tx)),
+ )
} else {
+ let client_tab_index_or_paneid = if let Some(tab_id) = tab_id {
+ ClientTabIndexOrPaneId::TabIndex(tab_id)
+ } else {
+ ClientTabIndexOrPaneId::ClientId(client_id)
+ };
PtyInstruction::SpawnTerminal(
Some(open_file),
Some(title),
@@ -728,7 +735,7 @@ pub(crate) fn route_action(
}
},
start_suppressed,
- ClientTabIndexOrPaneId::ClientId(client_id),
+ client_tab_index_or_paneid,
Some(NotificationEnd::new(completion_tx)),
false, // set_blocking
)
@@ -761,11 +768,14 @@ pub(crate) fn route_action(
pane_name: name,
coordinates: floating_pane_coordinates,
near_current_pane,
+ tab_id,
} => {
let run_cmd = run_command
.map(|cmd| TerminalAction::RunCommand(cmd.into()))
.or_else(|| default_shell.clone());
- let client_tab_index_or_paneid = if near_current_pane && pane_id.is_some() {
+ let client_tab_index_or_paneid = if let Some(tab_id) = tab_id {
+ ClientTabIndexOrPaneId::TabIndex(tab_id)
+ } else if near_current_pane && pane_id.is_some() {
ClientTabIndexOrPaneId::PaneId(pane_id.unwrap())
} else {
ClientTabIndexOrPaneId::ClientId(client_id)
@@ -788,6 +798,7 @@ pub(crate) fn route_action(
near_current_pane,
pane_id_to_replace,
close_replaced_pane,
+ tab_id,
} => {
let run_cmd = run_command
.map(|cmd| TerminalAction::RunCommand(cmd.into()))
@@ -796,74 +807,70 @@ pub(crate) fn route_action(
Some(pane_id_to_replace) => pane_id_to_replace.try_into().ok(),
None => pane_id,
};
- match pane_id {
- Some(pane_id) if near_current_pane => {
- senders
- .send_to_pty(PtyInstruction::SpawnInPlaceTerminal(
- run_cmd,
- name,
- close_replaced_pane,
- ClientTabIndexOrPaneId::PaneId(pane_id),
- Some(NotificationEnd::new(completion_tx)),
- ))
- .with_context(err_context)?;
- },
- _ => {
- senders
- .send_to_pty(PtyInstruction::SpawnInPlaceTerminal(
- run_cmd,
- name,
- close_replaced_pane,
- ClientTabIndexOrPaneId::ClientId(client_id),
- Some(NotificationEnd::new(completion_tx)),
- ))
- .with_context(err_context)?;
- },
- }
+ let client_tab_index_or_paneid = if let Some(tab_id) = tab_id {
+ ClientTabIndexOrPaneId::TabIndex(tab_id)
+ } else if near_current_pane && pane_id.is_some() {
+ ClientTabIndexOrPaneId::PaneId(pane_id.unwrap())
+ } else {
+ ClientTabIndexOrPaneId::ClientId(client_id)
+ };
+ senders
+ .send_to_pty(PtyInstruction::SpawnInPlaceTerminal(
+ run_cmd,
+ name,
+ close_replaced_pane,
+ client_tab_index_or_paneid,
+ Some(NotificationEnd::new(completion_tx)),
+ ))
+ .with_context(err_context)?;
},
Action::NewStackedPane {
command: run_command,
pane_name: name,
near_current_pane,
+ tab_id,
} => {
let run_cmd = run_command
.map(|cmd| TerminalAction::RunCommand(cmd.into()))
.or_else(|| default_shell.clone());
- match pane_id {
- Some(pane_id) if near_current_pane => {
- senders
- .send_to_pty(PtyInstruction::SpawnTerminal(
- run_cmd,
- name,
- NewPanePlacement::Stacked {
- pane_id_to_stack_under: Some(pane_id.into()),
- borderless: None,
- },
- false,
- ClientTabIndexOrPaneId::PaneId(pane_id),
- Some(NotificationEnd::new(completion_tx)),
- false, // set_blocking
- ))
- .with_context(err_context)?;
- },
- _ => {
- senders
- .send_to_pty(PtyInstruction::SpawnTerminal(
- run_cmd,
- name,
- NewPanePlacement::Stacked {
- pane_id_to_stack_under: None,
- borderless: None,
- },
- false,
- ClientTabIndexOrPaneId::ClientId(client_id),
- Some(NotificationEnd::new(completion_tx)),
- false, // set_blocking
- ))
- .with_context(err_context)?;
- },
- }
+ let (pane_placement, client_tab_index_or_paneid) = if let Some(tab_id) = tab_id {
+ (
+ NewPanePlacement::Stacked {
+ pane_id_to_stack_under: None,
+ borderless: None,
+ },
+ ClientTabIndexOrPaneId::TabIndex(tab_id),
+ )
+ } else if near_current_pane && pane_id.is_some() {
+ let pane_id = pane_id.unwrap();
+ (
+ NewPanePlacement::Stacked {
+ pane_id_to_stack_under: Some(pane_id.into()),
+ borderless: None,
+ },
+ ClientTabIndexOrPaneId::PaneId(pane_id),
+ )
+ } else {
+ (
+ NewPanePlacement::Stacked {
+ pane_id_to_stack_under: None,
+ borderless: None,
+ },
+ ClientTabIndexOrPaneId::ClientId(client_id),
+ )
+ };
+ senders
+ .send_to_pty(PtyInstruction::SpawnTerminal(
+ run_cmd,
+ name,
+ pane_placement,
+ false,
+ client_tab_index_or_paneid,
+ Some(NotificationEnd::new(completion_tx)),
+ false, // set_blocking
+ ))
+ .with_context(err_context)?;
},
Action::NewTiledPane {
direction,
@@ -871,11 +878,14 @@ pub(crate) fn route_action(
pane_name: name,
near_current_pane,
borderless,
+ tab_id,
} => {
let run_cmd = run_command
.map(|cmd| TerminalAction::RunCommand(cmd.into()))
.or_else(|| default_shell.clone());
- let client_tab_index_or_paneid = if near_current_pane && pane_id.is_some() {
+ let client_tab_index_or_paneid = if let Some(tab_id) = tab_id {
+ ClientTabIndexOrPaneId::TabIndex(tab_id)
+ } else if near_current_pane && pane_id.is_some() {
ClientTabIndexOrPaneId::PaneId(pane_id.unwrap())
} else {
ClientTabIndexOrPaneId::ClientId(client_id)
@@ -1296,6 +1306,7 @@ pub(crate) fn route_action(
pane_name: name,
skip_cache,
cwd,
+ tab_id,
} => {
senders
.send_to_screen(ScreenInstruction::NewTiledPluginPane(
@@ -1305,6 +1316,7 @@ pub(crate) fn route_action(
cwd,
client_id,
Some(NotificationEnd::new(completion_tx)),
+ tab_id,
))
.with_context(err_context)?;
},
@@ -1314,6 +1326,7 @@ pub(crate) fn route_action(
skip_cache,
cwd,
coordinates: floating_pane_coordinates,
+ tab_id,
} => {
senders
.send_to_screen(ScreenInstruction::NewFloatingPluginPane(
@@ -1324,6 +1337,7 @@ pub(crate) fn route_action(
floating_pane_coordinates,
client_id,
Some(NotificationEnd::new(completion_tx)),
+ tab_id,
))
.with_context(err_context)?;
},
@@ -1332,6 +1346,7 @@ pub(crate) fn route_action(
pane_name: name,
skip_cache,
close_replaced_pane,
+ tab_id,
} => {
if let Some(pane_id) = pane_id {
senders
@@ -1343,6 +1358,7 @@ pub(crate) fn route_action(
close_replaced_pane,
client_id,
Some(NotificationEnd::new(completion_tx)),
+ tab_id,
))
.with_context(err_context)?;
} else {
@@ -1365,6 +1381,7 @@ pub(crate) fn route_action(
should_open_in_place,
close_replaced_pane,
skip_cache,
+ tab_id,
} => {
senders
.send_to_screen(ScreenInstruction::LaunchOrFocusPlugin(
@@ -1377,6 +1394,7 @@ pub(crate) fn route_action(
skip_cache,
client_id,
Some(NotificationEnd::new(completion_tx)),
+ tab_id,
))
.with_context(err_context)?;
},
@@ -1387,6 +1405,7 @@ pub(crate) fn route_action(
close_replaced_pane,
skip_cache,
cwd,
+ tab_id,
} => {
senders
.send_to_screen(ScreenInstruction::LaunchPlugin(
@@ -1399,6 +1418,7 @@ pub(crate) fn route_action(
cwd,
client_id,
Some(NotificationEnd::new(completion_tx)),
+ tab_id,
))
.with_context(err_context)?;
},
diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs
index 0be349f0..6f1bf70b 100644
--- a/zellij-server/src/screen.rs
+++ b/zellij-server/src/screen.rs
@@ -547,6 +547,7 @@ pub enum ScreenInstruction {
Option<PathBuf>,
ClientId,
Option<NotificationEnd>,
+ Option<usize>, // tab_id
), // Option<String> is
// optional pane title, bool is skip cache, Option<PathBuf> is an optional cwd
NewFloatingPluginPane(
@@ -557,6 +558,7 @@ pub enum ScreenInstruction {
Option<FloatingPaneCoordinates>,
ClientId,
Option<NotificationEnd>,
+ Option<usize>, // tab_id
), // Option<String> is an
// optional pane title, bool
// is skip cache, Option<PathBuf> is an optional cwd
@@ -568,6 +570,7 @@ pub enum ScreenInstruction {
bool,
ClientId,
Option<NotificationEnd>,
+ Option<usize>, // tab_id
), // Option<String> is an
// optional pane title, first bool is skip cache, second bool is close_replaced_pane
StartOrReloadPluginPane(RunPluginOrAlias, Option<String>, Option<NotificationEnd>),
@@ -601,6 +604,7 @@ pub enum ScreenInstruction {
bool,
ClientId,
Option<NotificationEnd>,
+ Option<usize>, // tab_id
), // bools are: should_float, move_to_focused_tab, should_open_in_place, close_replaced_pane, Option<PaneId> is the pane id to replace, bool following it is skip_cache
LaunchPlugin(
RunPluginOrAlias,
@@ -612,6 +616,7 @@ pub enum ScreenInstruction {
Option<PathBuf>,
ClientId,
Option<NotificationEnd>,
+ Option<usize>, // tab_id
), // bools are: should_float, should_open_in_place, close_replaced_pane, Option<PaneId> is the pane id to replace, Option<PathBuf> is an optional cwd, bool after is skip_cache
SuppressPane(PaneId, ClientId),
UnsuppressPane(PaneId, bool), // bool -> should float if hidden
@@ -5136,6 +5141,29 @@ pub(crate) fn screen_thread_main(
}
},
ClientTabIndexOrPaneId::TabIndex(tab_index) => {
+ // Some placements (directional split, stacked without a
+ // target pane) need a client_id to know which pane to
+ // split relative to. Only resolve one when required.
+ let needs_client_id = matches!(
+ new_pane_placement,
+ NewPanePlacement::Tiled {
+ direction: Some(_),
+ ..
+ } | NewPanePlacement::Stacked {
+ pane_id_to_stack_under: None,
+ ..
+ }
+ );
+ let client_id = if needs_client_id {
+ screen
+ .active_tab_ids
+ .iter()
+ .find(|(_, tid)| **tid == tab_index)
+ .map(|(cid, _)| *cid)
+ .or_else(|| screen.active_tab_ids.keys().next().copied())
+ } else {
+ None
+ };
if let Some(active_tab) = screen.tabs.get_mut(&tab_index) {
active_tab.new_pane(
pid,
@@ -5144,7 +5172,7 @@ pub(crate) fn screen_thread_main(
start_suppressed,
true,
new_pane_placement,
- None,
+ client_id,
blocking_notification,
)?;
if let Some(hold_for_command) = hold_for_command {
@@ -7028,8 +7056,10 @@ pub(crate) fn screen_thread_main(
cwd,
client_id,
completion_tx,
+ explicit_tab_id,
) => {
- let tab_index = screen.active_tab_ids.values().next().unwrap_or(&1);
+ let tab_index = explicit_tab_id
+ .unwrap_or_else(|| *screen.active_tab_ids.values().next().unwrap_or(&1));
let size = Size::default();
let should_float = Some(false);
let should_be_opened_in_place = false;
@@ -7042,7 +7072,7 @@ pub(crate) fn screen_thread_main(
false, // close_replaced_pane
pane_title,
run_plugin,
- *tab_index,
+ tab_index,
None,
client_id,
size,
@@ -7061,36 +7091,41 @@ pub(crate) fn screen_thread_main(
floating_pane_coordinates,
client_id,
completion_tx,
- ) => match screen.active_tab_ids.values().next() {
- Some(tab_index) => {
- let size = Size::default();
- let should_float = Some(true);
- let should_be_opened_in_place = false;
- screen
- .bus
- .senders
- .send_to_pty(PtyInstruction::FillPluginCwd(
- should_float,
- should_be_opened_in_place,
- false, // close_replaced_pane
- pane_title,
- run_plugin,
- *tab_index,
- None,
- client_id,
- size,
- skip_cache,
- cwd,
- None,
- floating_pane_coordinates,
- completion_tx,
- ))?;
- },
- None => {
- log::error!(
- "Could not find an active tab - is there at least 1 connected user?"
- );
- },
+ explicit_tab_id,
+ ) => {
+ let resolved_tab_index =
+ explicit_tab_id.or_else(|| screen.active_tab_ids.values().next().copied());
+ match resolved_tab_index {
+ Some(tab_index) => {
+ let size = Size::default();
+ let should_float = Some(true);
+ let should_be_opened_in_place = false;
+ screen
+ .bus
+ .senders
+ .send_to_pty(PtyInstruction::FillPluginCwd(
+ should_float,
+ should_be_opened_in_place,
+ false, // close_replaced_pane
+ pane_title,
+ run_plugin,
+ tab_index,
+ None,
+ client_id,
+ size,
+ skip_cache,
+ cwd,
+ None,
+ floating_pane_coordinates,
+ completion_tx,
+ ))?;
+ },
+ None => {
+ log::error!(
+ "Could not find an active tab - is there at least 1 connected user?"
+ );
+ },
+ }
},
ScreenInstruction::NewInPlacePluginPane(
run_plugin,
@@ -7100,36 +7135,41 @@ pub(crate) fn screen_thread_main(
close_replaced_pane,
client_id,
completion_tx,
- ) => match screen.active_tab_ids.values().next() {
- Some(tab_index) => {
- let size = Size::default();
- let should_float = None;
- let should_be_in_place = true;
- screen
- .bus
- .senders
- .send_to_pty(PtyInstruction::FillPluginCwd(
- should_float,
- should_be_in_place,
- close_replaced_pane,
- pane_title,
- run_plugin,
- *tab_index,
- Some(pane_id_to_replace),
- client_id,
- size,
- skip_cache,
- None,
- None,
- None,
- completion_tx,
- ))?;
- },
- None => {
- log::error!(
- "Could not find an active tab - is there at least 1 connected user?"
- );
- },
+ explicit_tab_id,
+ ) => {
+ let resolved_tab_index =
+ explicit_tab_id.or_else(|| screen.active_tab_ids.values().next().copied());
+ match resolved_tab_index {
+ Some(tab_index) => {
+ let size = Size::default();
+ let should_float = None;
+ let should_be_in_place = true;
+ screen
+ .bus
+ .senders
+ .send_to_pty(PtyInstruction::FillPluginCwd(
+ should_float,
+ should_be_in_place,
+ close_replaced_pane,
+ pane_title,
+ run_plugin,
+ tab_index,
+ Some(pane_id_to_replace),
+ client_id,
+ size,
+ skip_cache,
+ None,
+ None,
+ None,
+ completion_tx,
+ ))?;
+ },
+ None => {
+ log::error!(
+ "Could not find an active tab - is there at least 1 connected user?"
+ );
+ },
+ }
},
ScreenInstruction::StartOrReloadPluginPane(run_plugin, pane_title, completion_tx) => {
let tab_index = screen.active_tab_ids.values().next().unwrap_or(&1);
@@ -7326,9 +7366,12 @@ pub(crate) fn screen_thread_main(
skip_cache,
client_id,
mut completion_tx,
+ explicit_tab_id,
) => match pane_id_to_replace {
Some(pane_id_to_replace) if should_open_in_place => {
- match screen.active_tab_ids.values().next() {
+ let resolved_tab_index =
+ explicit_tab_id.or_else(|| screen.active_tab_ids.values().next().copied());
+ match resolved_tab_index {
Some(tab_index) => {
let size = Size::default();
screen
@@ -7340,7 +7383,7 @@ pub(crate) fn screen_thread_main(
close_replaced_pane,
None,
run_plugin,
- *tab_index,
+ tab_index,
Some(pane_id_to_replace),
client_id,
size,
@@ -7370,7 +7413,10 @@ pub(crate) fn screen_thread_main(
.get(&client_id)
.map(|tab_index| (*tab_index, client_id))
});
- match client_id_and_focused_tab {
+ let resolved_tab_and_client = explicit_tab_id
+ .and_then(|tid| client_id.map(|cid| (tid, cid)))
+ .or(client_id_and_focused_tab);
+ match resolved_tab_and_client {
Some((tab_index, client_id)) => {
if screen.focus_plugin_pane(
&run_plugin,
@@ -7420,35 +7466,40 @@ pub(crate) fn screen_thread_main(
cwd,
client_id,
completion_tx,
+ explicit_tab_id,
) => match pane_id_to_replace {
- Some(pane_id_to_replace) => match screen.active_tab_ids.values().next() {
- Some(tab_index) => {
- let size = Size::default();
- screen
- .bus
- .senders
- .send_to_pty(PtyInstruction::FillPluginCwd(
- Some(should_float),
- should_open_in_place,
- close_replaced_pane,
- None,
- run_plugin,
- *tab_index,
- Some(pane_id_to_replace),
- client_id,
- size,
- skip_cache,
- cwd,
- None,
- None,
- completion_tx,
- ))?;
- },
- None => {
- log::error!(
- "Could not find an active tab - is there at least 1 connected user?"
- );
- },
+ Some(pane_id_to_replace) => {
+ let resolved_tab_index =
+ explicit_tab_id.or_else(|| screen.active_tab_ids.values().next().copied());
+ match resolved_tab_index {
+ Some(tab_index) => {
+ let size = Size::default();
+ screen
+ .bus
+ .senders
+ .send_to_pty(PtyInstruction::FillPluginCwd(
+ Some(should_float),
+ should_open_in_place,
+ close_replaced_pane,
+ None,
+ run_plugin,
+ tab_index,
+ Some(pane_id_to_replace),
+ client_id,
+ size,
+ skip_cache,
+ cwd,
+ None,
+ None,
+ completion_tx,
+ ))?;
+ },
+ None => {
+ log::error!(
+ "Could not find an active tab - is there at least 1 connected user?"
+ );
+ },
+ }
},
None => {
let client_id = if screen.active_tab_ids.contains_key(&client_id) {
@@ -7462,7 +7513,10 @@ pub(crate) fn screen_thread_main(
.get(&client_id)
.map(|tab_index| (*tab_index, client_id))
});
- match client_id_and_focused_tab {
+ let resolved_tab_and_client = explicit_tab_id
+ .and_then(|tid| client_id.map(|cid| (tid, cid)))
+ .or(client_id_and_focused_tab);
+ match resolved_tab_and_client {
Some((tab_index, client_id)) => {
screen
.bus
diff --git a/zellij-server/src/unit/screen_tests.rs b/zellij-server/src/unit/screen_tests.rs
index 491beefb..0897ec49 100644
--- a/zellij-server/src/unit/screen_tests.rs
+++ b/zellij-server/src/unit/screen_tests.rs
@@ -3094,6 +3094,7 @@ pub fn send_cli_new_pane_action_with_default_parameters() {
unblock_condition: None,
near_current_pane: false,
borderless: Some(false),
+ tab_id: None,
};
send_cli_action_to_server(&session_metadata, cli_new_pane_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100)); // give time for actions to be
@@ -3147,6 +3148,7 @@ pub fn send_cli_new_pane_action_with_split_direction() {
unblock_condition: None,
near_current_pane: false,
borderless: Some(false),
+ tab_id: None,
};
send_cli_action_to_server(&session_metadata, cli_new_pane_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100)); // give time for actions to be
@@ -3200,6 +3202,7 @@ pub fn send_cli_new_pane_action_with_command_and_cwd() {
unblock_condition: None,
near_current_pane: false,
borderless: Some(false),
+ tab_id: None,
};
send_cli_action_to_server(&session_metadata, cli_new_pane_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100)); // give time for actions to be
@@ -3264,6 +3267,7 @@ pub fn send_cli_new_pane_action_with_floating_pane_and_coordinates() {
unblock_condition: None,
near_current_pane: false,
borderless: Some(false),
+ tab_id: None,
};
send_cli_action_to_server(&session_metadata, cli_new_pane_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100)); // give time for actions to be
@@ -3306,6 +3310,7 @@ pub fn send_cli_edit_action_with_default_parameters() {
pinned: None,
borderless: Some(false),
near_current_pane: false,
+ tab_id: None,
};
send_cli_action_to_server(&session_metadata, cli_edit_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100)); // give time for actions to be
@@ -3348,6 +3353,7 @@ pub fn send_cli_edit_action_with_line_number() {
pinned: None,
borderless: Some(false),
near_current_pane: false,
+ tab_id: None,
};
send_cli_action_to_server(&session_metadata, cli_edit_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100)); // give time for actions to be
@@ -3390,6 +3396,7 @@ pub fn send_cli_edit_action_with_split_direction() {
pinned: None,
borderless: Some(false),
near_current_pane: false,
+ tab_id: None,
};
send_cli_action_to_server(&session_metadata, cli_edit_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100)); // give time for actions to be
@@ -3580,6 +3587,7 @@ pub fn send_cli_new_tab_action_default_params() {
let new_tab_action = CliAction::NewTab {
name: None,
layout: None,
+ layout_string: None,
layout_dir: None,
cwd: None,
initial_command: vec![],
@@ -3627,6 +3635,7 @@ pub fn send_cli_new_tab_action_with_name_and_layout() {
"{}/src/unit/fixtures/layout-with-three-panes.kdl",
env!("CARGO_MANIFEST_DIR")
))),
+ layout_string: None,
layout_dir: None,
cwd: None,
initial_command: vec![],
@@ -3959,6 +3968,7 @@ pub fn send_cli_launch_or_focus_plugin_action() {
url: "file:/path/to/fake/plugin".to_owned(),
configuration: Default::default(),
skip_plugin_cache: false,
+ tab_id: None,
};
send_cli_action_to_server(&session_metadata, cli_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100)); // give time for actions to be
@@ -4021,6 +4031,7 @@ pub fn send_cli_launch_or_focus_plugin_action_when_plugin_is_already_loaded() {
url: "file:/path/to/fake/plugin".to_owned(),
configuration: Default::default(),
skip_plugin_cache: false,
+ tab_id: None,
};
send_cli_action_to_server(&session_metadata, cli_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100)); // give time for actions to be
@@ -4105,6 +4116,7 @@ pub fn send_cli_launch_or_focus_plugin_action_when_plugin_is_already_loaded_for_
url: "fixture_plugin_for_tests".to_owned(),
configuration: Default::default(),
skip_plugin_cache: false,
+ tab_id: None,
};
send_cli_action_to_server(&session_metadata, cli_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100)); // give time for actions to be
@@ -5097,6 +5109,7 @@ pub fn send_cli_new_pane_in_place_with_close_replaced_pane() {
unblock_condition: None,
near_current_pane: false,
borderless: None,
+ tab_id: None,
};
send_cli_action_to_server(&session_metadata, cli_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100));
@@ -5147,6 +5160,7 @@ pub fn send_cli_edit_in_place_with_close_replaced_pane() {
pinned: None,
near_current_pane: false,
borderless: None,
+ tab_id: None,
};
send_cli_action_to_server(&session_metadata, cli_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100));
@@ -5192,6 +5206,7 @@ pub fn send_cli_launch_or_focus_plugin_in_place_with_close_replaced_pane() {
url: "file:/path/to/fake/plugin".to_owned(),
configuration: Default::default(),
skip_plugin_cache: false,
+ tab_id: None,
};
send_cli_action_to_server(&session_metadata, cli_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100));
@@ -7663,3 +7678,402 @@ pub fn inactive_tab_plugins_get_fresh_state_on_activation() {
active_tab.position
);
}
+
+#[test]
+pub fn send_cli_new_tab_action_with_layout_string() {
+ let size = Size { cols: 80, rows: 10 };
+ let client_id = 10;
+ let mut initial_layout = TiledPaneLayout::default();
+ initial_layout.children_split_direction = SplitDirection::Vertical;
+ initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
+ let mut mock_screen = MockScreen::new(size);
+ let session_metadata = mock_screen.clone_session_metadata();
+ let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
+ let received_plugin_instructions = Arc::new(Mutex::new(vec![]));
+ let plugin_receiver = mock_screen.plugin_receiver.take().unwrap();
+ let plugin_thread = log_actions_in_thread!(
+ received_plugin_instructions,
+ PluginInstruction::Exit,
+ plugin_receiver
+ );
+ // Same layout as layout-with-three-panes.kdl but passed as a string
+ let new_tab_action = CliAction::NewTab {
+ name: None,
+ layout: None,
+ layout_string: Some("layout {\n pane\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,
+ };
+ send_cli_action_to_server(&session_metadata, new_tab_action, client_id);
+ std::thread::sleep(std::time::Duration::from_millis(100));
+ mock_screen.teardown(vec![plugin_thread, screen_thread]);
+ let new_tab_instruction = received_plugin_instructions
+ .lock()
+ .unwrap()
+ .iter()
+ .rev()
+ .find(|i| {
+ if let PluginInstruction::NewTab(..) = i {
+ return true;
+ } else {
+ return false;
+ }
+ })
+ .unwrap()
+ .clone();
+ let output = format!("{:#?}", new_tab_instruction);
+ // Normalize Windows path separators for cross-platform snapshot consistency
+ let output = output.replace("\\\\", "/");
+ assert_snapshot!(output);
+}
+
+#[test]
+pub fn send_cli_new_tab_action_with_layout_string_and_name() {
+ let size = Size { cols: 80, rows: 10 };
+ let client_id = 10;
+ let mut initial_layout = TiledPaneLayout::default();
+ initial_layout.children_split_direction = SplitDirection::Vertical;
+ initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
+ let mut mock_screen = MockScreen::new(size);
+ let session_metadata = mock_screen.clone_session_metadata();
+ let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
+ let received_plugin_instructions = Arc::new(Mutex::new(vec![]));
+ let plugin_receiver = mock_screen.plugin_receiver.take().unwrap();
+ let plugin_thread = log_actions_in_thread!(
+ received_plugin_instructions,
+ PluginInstruction::Exit,
+ plugin_receiver
+ );
+ let new_tab_action = CliAction::NewTab {
+ name: Some("my-string-layout-tab".into()),
+ layout: None,
+ layout_string: Some("layout {\n pane\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,
+ };
+ send_cli_action_to_server(&session_metadata, new_tab_action, client_id);
+ std::thread::sleep(std::time::Duration::from_millis(100));
+ mock_screen.teardown(vec![plugin_thread, screen_thread]);
+ let new_tab_instruction = received_plugin_instructions
+ .lock()
+ .unwrap()
+ .iter()
+ .rev()
+ .find(|i| {
+ if let PluginInstruction::NewTab(..) = i {
+ return true;
+ } else {
+ return false;
+ }
+ })
+ .unwrap()
+ .clone();
+ let output = format!("{:#?}", new_tab_instruction);
+ // Normalize Windows path separators for cross-platform snapshot consistency
+ let output = output.replace("\\\\", "/");
+ assert_snapshot!(output);
+}
+
+#[test]
+pub fn send_cli_new_pane_action_with_tab_id() {
+ let size = Size {
+ cols: 121,
+ rows: 20,
+ };
+ let client_id = 10;
+ let mut mock_screen = MockScreen::new(size);
+ let pty_receiver = mock_screen.pty_receiver.take().unwrap();
+ let session_metadata = mock_screen.clone_session_metadata();
+ let mut initial_layout = TiledPaneLayout::default();
+ initial_layout.children_split_direction = SplitDirection::Vertical;
+ initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
+ let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
+ let received_pty_instructions = Arc::new(Mutex::new(vec![]));
+ let pty_thread = log_actions_in_thread!(
+ received_pty_instructions,
+ PtyInstruction::Exit,
+ pty_receiver
+ );
+ let cli_new_pane_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: Some(false),
+ tab_id: Some(0),
+ };
+ send_cli_action_to_server(&session_metadata, cli_new_pane_action, client_id);
+ std::thread::sleep(std::time::Duration::from_millis(100));
+ mock_screen.teardown(vec![pty_thread, screen_thread]);
+ let pty_instructions = received_pty_instructions.lock().unwrap();
+ // Verify that the PTY instruction uses TabIndex(0) instead of ClientId
+ let pty_debug = format!("{:?}", *pty_instructions);
+ assert!(
+ pty_debug.contains("TabIndex(0)"),
+ "Expected TabIndex(0) in PTY instructions, got: {}",
+ pty_debug
+ );
+}
+
+#[test]
+pub fn send_cli_new_floating_pane_action_with_tab_id() {
+ let size = Size {
+ cols: 121,
+ rows: 20,
+ };
+ let client_id = 10;
+ let mut mock_screen = MockScreen::new(size);
+ let pty_receiver = mock_screen.pty_receiver.take().unwrap();
+ let session_metadata = mock_screen.clone_session_metadata();
+ let mut initial_layout = TiledPaneLayout::default();
+ initial_layout.children_split_direction = SplitDirection::Vertical;
+ initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
+ let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
+ let received_pty_instructions = Arc::new(Mutex::new(vec![]));
+ let pty_thread = log_actions_in_thread!(
+ received_pty_instructions,
+ PtyInstruction::Exit,
+ pty_receiver
+ );
+ let cli_new_pane_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(0),
+ };
+ send_cli_action_to_server(&session_metadata, cli_new_pane_action, client_id);
+ std::thread::sleep(std::time::Duration::from_millis(100));
+ mock_screen.teardown(vec![pty_thread, screen_thread]);
+ let pty_instructions = received_pty_instructions.lock().unwrap();
+ let pty_debug = format!("{:?}", *pty_instructions);
+ assert!(
+ pty_debug.contains("TabIndex(0)"),
+ "Expected TabIndex(0) in PTY instructions for floating pane, got: {}",
+ pty_debug
+ );
+}
+
+#[test]
+pub fn send_cli_edit_action_with_tab_id() {
+ let size = Size {
+ cols: 121,
+ rows: 20,
+ };
+ let client_id = 10;
+ let mut mock_screen = MockScreen::new(size);
+ let pty_receiver = mock_screen.pty_receiver.take().unwrap();
+ let session_metadata = mock_screen.clone_session_metadata();
+ let mut initial_layout = TiledPaneLayout::default();
+ initial_layout.children_split_direction = SplitDirection::Vertical;
+ initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
+ let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
+ let received_pty_instructions = Arc::new(Mutex::new(vec![]));
+ let pty_thread = log_actions_in_thread!(
+ received_pty_instructions,
+ PtyInstruction::Exit,
+ pty_receiver
+ );
+ let cli_edit_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(0),
+ };
+ send_cli_action_to_server(&session_metadata, cli_edit_action, client_id);
+ std::thread::sleep(std::time::Duration::from_millis(100));
+ mock_screen.teardown(vec![pty_thread, screen_thread]);
+ let pty_instructions = received_pty_instructions.lock().unwrap();
+ let pty_debug = format!("{:?}", *pty_instructions);
+ assert!(
+ pty_debug.contains("TabIndex(0)"),
+ "Expected TabIndex(0) in PTY instructions for edit, got: {}",
+ pty_debug
+ );
+}
+
+#[test]
+pub fn send_cli_new_pane_action_with_tab_id_and_direction() {
+ let size = Size {
+ cols: 121,
+ rows: 20,
+ };
+ let client_id = 10;
+ let mut mock_screen = MockScreen::new(size);
+ let pty_receiver = mock_screen.pty_receiver.take().unwrap();
+ let session_metadata = mock_screen.clone_session_metadata();
+ let mut initial_layout = TiledPaneLayout::default();
+ initial_layout.children_split_direction = SplitDirection::Vertical;
+ initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
+ let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
+ let received_pty_instructions = Arc::new(Mutex::new(vec![]));
+ let pty_thread = log_actions_in_thread!(
+ received_pty_instructions,
+ PtyInstruction::Exit,
+ pty_receiver
+ );
+ let cli_new_pane_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: Some(false),
+ tab_id: Some(0),
+ };
+ send_cli_action_to_server(&session_metadata, cli_new_pane_action, client_id);
+ std::thread::sleep(std::time::Duration::from_millis(100));
+ mock_screen.teardown(vec![pty_thread, screen_thread]);
+ let pty_instructions = received_pty_instructions.lock().unwrap();
+ let pty_debug = format!("{:?}", *pty_instructions);
+ assert!(
+ pty_debug.contains("TabIndex(0)"),
+ "Expected TabIndex(0) in PTY instructions with direction, got: {}",
+ pty_debug
+ );
+}
+
+#[test]
+pub fn send_cli_new_pane_action_with_tab_id_and_stacked() {
+ let size = Size {
+ cols: 121,
+ rows: 20,
+ };
+ let client_id = 10;
+ let mut mock_screen = MockScreen::new(size);
+ let pty_receiver = mock_screen.pty_receiver.take().unwrap();
+ let session_metadata = mock_screen.clone_session_metadata();
+ let mut initial_layout = TiledPaneLayout::default();
+ initial_layout.children_split_direction = SplitDirection::Vertical;
+ initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
+ let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
+ let received_pty_instructions = Arc::new(Mutex::new(vec![]));
+ let pty_thread = log_actions_in_thread!(
+ received_pty_instructions,
+ PtyInstruction::Exit,
+ pty_receiver
+ );
+ let cli_new_pane_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(0),
+ };
+ send_cli_action_to_server(&session_metadata, cli_new_pane_action, client_id);
+ std::thread::sleep(std::time::Duration::from_millis(100));
+ mock_screen.teardown(vec![pty_thread, screen_thread]);
+ let pty_instructions = received_pty_instructions.lock().unwrap();
+ let pty_debug = format!("{:?}", *pty_instructions);
+ assert!(
+ pty_debug.contains("TabIndex(0)"),
+ "Expected TabIndex(0) in PTY instructions with stacked, got: {}",
+ pty_debug
+ );
+}
diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_with_layout_string.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_with_layout_string.snap
new file mode 100644
index 00000000..b12db36c
--- /dev/null
+++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_with_layout_string.snap
@@ -0,0 +1,119 @@
+---
+source: zellij-server/src/./unit/screen_tests.rs
+expression: "format!(\"{:#?}\", new_tab_instruction)"
+---
+NewTab(
+ None,
+ None,
+ Some(
+ TiledPaneLayout {
+ children_split_direction: Horizontal,
+ name: None,
+ children: [
+ TiledPaneLayout {
+ children_split_direction: Horizontal,
+ name: None,
+ children: [],
+ split_size: None,
+ run: Some(
+ Cwd(
+ "./.",
+ ),
+ ),
+ borderless: None,
+ focus: None,
+ external_children_index: None,
+ children_are_stacked: false,
+ is_expanded_in_stack: false,
+ exclude_from_sync: None,
+ run_instructions_to_ignore: [],
+ hide_floating_panes: false,
+ pane_initial_contents: None,
+ default_fg: None,
+ default_bg: None,
+ },
+ TiledPaneLayout {
+ children_split_direction: Horizontal,
+ name: None,
+ children: [],
+ split_size: None,
+ run: Some(
+ Cwd(
+ "./.",
+ ),
+ ),
+ borderless: None,
+ focus: None,
+ external_children_index: None,
+ children_are_stacked: false,
+ is_expanded_in_stack: false,
+ exclude_from_sync: None,
+ run_instructions_to_ignore: [],
+ hide_floating_panes: false,
+ pane_initial_contents: None,
+ default_fg: None,
+ default_bg: None,
+ },
+ TiledPaneLayout {
+ children_split_direction: Horizontal,
+ name: None,
+ children: [],
+ split_size: None,
+ run: Some(
+ Cwd(
+ "./.",
+ ),
+ ),
+ borderless: None,
+ focus: None,
+ external_children_index: None,
+ children_are_stacked: false,
+ is_expanded_in_stack: false,
+ exclude_from_sync: None,
+ run_instructions_to_ignore: [],
+ hide_floating_panes: false,
+ pane_initial_contents: None,
+ default_fg: None,
+ default_bg: None,
+ },
+ ],
+ split_size: None,
+ run: Some(
+ Cwd(
+ ".",
+ ),
+ ),
+ borderless: None,
+ focus: None,
+ external_children_index: None,
+ children_are_stacked: false,
+ is_expanded_in_stack: false,
+ exclude_from_sync: None,
+ run_instructions_to_ignore: [],
+ hide_floating_panes: false,
+ pane_initial_contents: None,
+ default_fg: None,
+ default_bg: None,
+ },
+ ),
+ [],
+ 1,
+ None,
+ false,
+ true,
+ (
+ 10,
+ false,
+ ),
+ Some(
+ NotificationEnd {
+ channel: None,
+ exit_status: None,
+ unblock_condition: None,
+ affected_pane_id: None,
+ affected_tab_id: None,
+ error_message: None,
+ stdout_message: None,
+ },
+ ),
+)
diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_with_layout_string_and_name.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_with_layout_string_and_name.snap
new file mode 100644
index 00000000..b12db36c
--- /dev/null
+++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_with_layout_string_and_name.snap
@@ -0,0 +1,119 @@
+---
+source: zellij-server/src/./unit/screen_tests.rs
+expression: "format!(\"{:#?}\", new_tab_instruction)"
+---
+NewTab(
+ None,
+ None,
+ Some(
+ TiledPaneLayout {
+ children_split_direction: Horizontal,
+ name: None,
+ children: [
+ TiledPaneLayout {
+ children_split_direction: Horizontal,
+ name: None,
+ children: [],
+ split_size: None,
+ run: Some(
+ Cwd(
+ "./.",
+ ),
+ ),
+ borderless: None,
+ focus: None,
+ external_children_index: None,
+ children_are_stacked: false,
+ is_expanded_in_stack: false,
+ exclude_from_sync: None,
+ run_instructions_to_ignore: [],
+ hide_floating_panes: false,
+ pane_initial_contents: None,
+ default_fg: None,
+ default_bg: None,
+ },
+ TiledPaneLayout {
+ children_split_direction: Horizontal,
+ name: None,
+ children: [],
+ split_size: None,
+ run: Some(
+ Cwd(
+ "./.",
+ ),
+ ),
+ borderless: None,
+ focus: None,
+ external_children_index: None,
+ children_are_stacked: false,
+ is_expanded_in_stack: false,
+ exclude_from_sync: None,
+ run_instructions_to_ignore: [],
+ hide_floating_panes: false,
+ pane_initial_contents: None,
+ default_fg: None,
+ default_bg: None,
+ },
+ TiledPaneLayout {
+ children_split_direction: Horizontal,
+ name: None,
+ children: [],
+ split_size: None,
+ run: Some(
+ Cwd(
+ "./.",
+ ),
+ ),
+ borderless: None,
+ focus: None,
+ external_children_index: None,
+ children_are_stacked: false,
+ is_expanded_in_stack: false,
+ exclude_from_sync: None,
+ run_instructions_to_ignore: [],
+ hide_floating_panes: false,
+ pane_initial_contents: None,
+ default_fg: None,
+ default_bg: None,
+ },
+ ],
+ split_size: None,
+ run: Some(
+ Cwd(
+ ".",
+ ),
+ ),
+ borderless: None,
+ focus: None,
+ external_children_index: None,
+ children_are_stacked: false,
+ is_expanded_in_stack: false,
+ exclude_from_sync: None,
+ run_instructions_to_ignore: [],
+ hide_floating_panes: false,
+ pane_initial_contents: None,
+ default_fg: None,
+ default_bg: None,
+ },
+ ),
+ [],
+ 1,
+ None,
+ false,
+ true,
+ (
+ 10,
+ false,
+ ),
+ Some(
+ NotificationEnd {
+ channel: None,
+ exit_status: None,
+ unblock_condition: None,
+ affected_pane_id: None,
+ affected_tab_id: None,
+ error_message: None,
+ stdout_message: None,
+ },
+ ),
+)