aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoruser <user@clank>2026-08-12 18:44:24 +0200
committeruser <user@clank>2026-08-12 18:44:24 +0200
commit2c67579398835d51e625085ce3b22c22bc8f0cba (patch)
tree2b22d137d988f0bbf658ec4d37336f813933bfa1
parentsl0p: document workspace hints for consumers (diff)
downloadsl0ppij-2c67579398835d51e625085ce3b22c22bc8f0cba.tar.gz
sl0ppij-2c67579398835d51e625085ce3b22c22bc8f0cba.tar.xz
sl0ppij-2c67579398835d51e625085ce3b22c22bc8f0cba.zip
sl0p: --purpose flag and tab-level purpose
Completes the two pieces deferred from the first workspace-hints pass. `zellij run --purpose X` / `zellij action new-pane --purpose X` tag a pane at spawn time, which is what an agent wants for panes it creates itself; the OSC wrapper still works for programs that declare their own role. Not supported with --in-place, which replaces another pane rather than being a pane of its own: clap rejects the combination rather than accepting a flag that would do nothing. `tab name="work" purpose="agent-work"` gives a whole tab a role, reported by `list-tabs --json`. It rides the tab's ROOT layout node, reusing the pane field because a tab's root is the tab container and never itself a pane -- asserted by a test for the tightest case (a tab holding exactly one pane), since a leak there would silently mislabel a pane. The interesting part is why the flag first shipped broken. There are TWO protobuf boundaries -- the plugin API and the client->server IPC contract -- and a CLI flag must cross the second one. Defaulting the field in the conversion (what "no protoc" usually forces) compiles cleanly and then silently drops the value in transit, so --purpose parsed, printed a pane id, and did nothing. Fixed by adding the field to both contracts by hand, in the .proto AND the committed generated prost, kept in lockstep so a future regeneration reproduces it: protoc is only needed to regenerate, not to build. Both boundaries now have a round-trip test, and the IPC one was confirmed to fail when the wire read is removed. Sanitising moved to Grid::set_authoritative_purpose, the single choke point every out-of-band source passes through, after the CLI path was observed delivering `bad purpose; rm -rf /` intact into list-panes --json.
-rw-r--r--src/main.rs4
-rw-r--r--zellij-server/src/panes/grid.rs14
-rw-r--r--zellij-server/src/plugins/zellij_exports.rs12
-rw-r--r--zellij-server/src/pty.rs4
-rw-r--r--zellij-server/src/route.rs11
-rw-r--r--zellij-server/src/screen.rs19
-rw-r--r--zellij-server/src/session_layout_metadata.rs6
-rw-r--r--zellij-server/src/tab/mod.rs21
-rw-r--r--zellij-server/src/unit/screen_tests.rs10
-rw-r--r--zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_edit_action_with_default_parameters.snap3
-rw-r--r--zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_edit_action_with_line_number.snap3
-rw-r--r--zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_edit_action_with_split_direction.snap3
-rw-r--r--zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_pane_action_with_command_and_cwd.snap3
-rw-r--r--zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_pane_action_with_default_parameters.snap3
-rw-r--r--zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_pane_action_with_floating_pane_and_coordinates.snap3
-rw-r--r--zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_pane_action_with_split_direction.snap3
-rw-r--r--zellij-utils/assets/prost/api.action.rs6
-rw-r--r--zellij-utils/assets/prost_ipc/client_server_contract.rs8
-rw-r--r--zellij-utils/src/cli.rs12
-rw-r--r--zellij-utils/src/client_server_contract/common_types.proto4
-rw-r--r--zellij-utils/src/data.rs5
-rw-r--r--zellij-utils/src/input/actions.rs118
-rw-r--r--zellij-utils/src/input/unit/layout_test.rs27
-rw-r--r--zellij-utils/src/ipc/protobuf_conversion.rs12
-rw-r--r--zellij-utils/src/ipc/tests/roundtrip_tests.rs11
-rw-r--r--zellij-utils/src/kdl/kdl_layout_parser.rs7
-rw-r--r--zellij-utils/src/kdl/mod.rs7
-rw-r--r--zellij-utils/src/plugin_api/action.proto3
-rw-r--r--zellij-utils/src/plugin_api/action.rs15
-rw-r--r--zellij-utils/src/plugin_api/event.rs5
-rw-r--r--zellij-utils/src/session_serialization.rs9
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap1
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap1
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap1
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap1
35 files changed, 364 insertions, 11 deletions
diff --git a/src/main.rs b/src/main.rs
index 15ed027a..6d633d20 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -50,6 +50,7 @@ fn main() {
block_until_exit_failure,
block_until_exit,
near_current_pane,
+ purpose,
no_focus,
borderless,
tab_id,
@@ -70,6 +71,7 @@ fn main() {
};
let command_cli_action = CliAction::NewPane {
+ purpose,
command,
plugin: None,
direction,
@@ -124,6 +126,8 @@ fn main() {
let blocking = false;
let unblock_condition = None;
let command_cli_action = CliAction::NewPane {
+ // `zellij plugin` panes are zellij UI, and plugin panes carry no purpose
+ purpose: None,
command: vec![],
plugin: Some(url),
direction: None,
diff --git a/zellij-server/src/panes/grid.rs b/zellij-server/src/panes/grid.rs
index 688a454a..1b935557 100644
--- a/zellij-server/src/panes/grid.rs
+++ b/zellij-server/src/panes/grid.rs
@@ -1039,10 +1039,18 @@ impl Grid {
self.pane_purpose = zellij_utils::input::layout::sanitize_pane_purpose(raw);
true
}
- /// Set the purpose from outside the pane (layout application, resurrection).
- /// This wins over anything the pane itself declares, now or later.
+ /// Set the purpose from outside the pane (layout, CLI `--purpose`,
+ /// resurrection). This wins over anything the pane itself declares, now or
+ /// later.
+ ///
+ /// Sanitised here rather than at each caller: this is the one choke point
+ /// every out-of-band source passes through, and a purpose reaching a
+ /// consumer's shell or JSON pipeline unsanitised is the whole risk. A layout
+ /// value has already been sanitised at parse time, which is idempotent.
pub fn set_authoritative_purpose(&mut self, purpose: Option<String>) {
- self.pane_purpose = purpose;
+ self.pane_purpose = purpose
+ .as_deref()
+ .and_then(zellij_utils::input::layout::sanitize_pane_purpose);
self.pane_purpose_is_authoritative = true;
}
pub fn new(
diff --git a/zellij-server/src/plugins/zellij_exports.rs b/zellij-server/src/plugins/zellij_exports.rs
index 318b2acc..34b711de 100644
--- a/zellij-server/src/plugins/zellij_exports.rs
+++ b/zellij-server/src/plugins/zellij_exports.rs
@@ -1622,6 +1622,7 @@ fn open_file_near_plugin(
ClientTabIndexOrPaneId::PaneId(PaneId::Plugin(env.plugin_id)),
Some(NotificationEnd::new(completion_tx)),
false, // set_blocking
+ None, // purpose
);
let _ = env.senders.send_to_pty(pty_instr);
@@ -1665,6 +1666,7 @@ fn open_file_floating_near_plugin(
ClientTabIndexOrPaneId::PaneId(PaneId::Plugin(env.plugin_id)),
Some(NotificationEnd::new(completion_tx)),
false, // set_blocking
+ None, // purpose
);
let _ = env.senders.send_to_pty(pty_instr);
@@ -1735,6 +1737,7 @@ fn open_terminal(env: &PluginEnv, cwd: PathBuf) {
_ => None,
};
let action = Action::NewTiledPane {
+ purpose: None,
direction: None,
command: run_command_action,
pane_name: None,
@@ -1780,6 +1783,7 @@ fn open_terminal_near_plugin(env: &PluginEnv, cwd: PathBuf) {
ClientTabIndexOrPaneId::PaneId(PaneId::Plugin(env.plugin_id)),
Some(NotificationEnd::new(completion_tx)),
false, // set_blocking
+ None, // purpose
));
// Wait for completion
@@ -1813,6 +1817,7 @@ fn open_terminal_floating(
_ => None,
};
let action = Action::NewFloatingPane {
+ purpose: None,
command: run_command_action,
pane_name: None,
coordinates: floating_pane_coordinates,
@@ -1859,6 +1864,7 @@ fn open_terminal_floating_near_plugin(
ClientTabIndexOrPaneId::PaneId(PaneId::Plugin(env.plugin_id)),
Some(NotificationEnd::new(completion_tx)),
false, // set_blocking
+ None, // purpose
));
// Wait for completion
@@ -2175,6 +2181,7 @@ fn open_command_pane(
use_terminal_title,
};
let action = Action::NewTiledPane {
+ purpose: None,
direction,
command: Some(run_command_action),
pane_name: name,
@@ -2240,6 +2247,7 @@ fn open_command_pane_near_plugin(
ClientTabIndexOrPaneId::PaneId(PaneId::Plugin(env.plugin_id)),
Some(NotificationEnd::new(completion_tx)),
false, // set_blocking
+ None, // purpose
));
// Wait for completion
@@ -2285,6 +2293,7 @@ fn open_command_pane_floating(
use_terminal_title,
};
let action = Action::NewFloatingPane {
+ purpose: None,
command: Some(run_command_action),
pane_name: name,
coordinates: floating_pane_coordinates,
@@ -2347,6 +2356,7 @@ fn open_command_pane_floating_near_plugin(
ClientTabIndexOrPaneId::PaneId(PaneId::Plugin(env.plugin_id)),
Some(NotificationEnd::new(completion_tx)),
false, // set_blocking
+ None, // purpose
));
// Wait for completion
@@ -2460,6 +2470,7 @@ fn open_command_pane_background(
ClientTabIndexOrPaneId::ClientId(env.client_id),
Some(NotificationEnd::new(completion_tx)),
false, // set_blocking
+ None, // purpose
));
// Wait for completion
@@ -2880,6 +2891,7 @@ fn new_tiled_pane_in_tab(env: &PluginEnv, tab_position: usize) {
_ => None,
};
let action = Action::NewTiledPane {
+ purpose: None,
direction: None,
command: run_command_action,
pane_name: None,
diff --git a/zellij-server/src/pty.rs b/zellij-server/src/pty.rs
index 68a2371b..5d6b6a05 100644
--- a/zellij-server/src/pty.rs
+++ b/zellij-server/src/pty.rs
@@ -56,6 +56,7 @@ pub enum PtyInstruction {
ClientTabIndexOrPaneId,
Option<NotificationEnd>, // completion signal
bool, // set_blocking
+ Option<String>, // agent-facing purpose (see workspace hints)
), // bool (if Some) is
// should_float, String is an optional pane name
OpenInPlaceEditor(
@@ -224,6 +225,7 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box<Layout>) -> Result<()> {
client_or_tab_index,
completion_tx,
set_blocking,
+ purpose,
) => {
let err_context =
|| format!("failed to spawn terminal for {:?}", client_or_tab_index);
@@ -316,6 +318,7 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box<Layout>) -> Result<()> {
client_or_tab_index,
completion_tx,
set_blocking,
+ purpose.clone(),
))
.with_context(err_context)?;
},
@@ -335,6 +338,7 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box<Layout>) -> Result<()> {
client_or_tab_index,
completion_tx,
set_blocking,
+ purpose.clone(),
))
.with_context(err_context)?;
if let Some(run_command) = run_command {
diff --git a/zellij-server/src/route.rs b/zellij-server/src/route.rs
index 8783af72..abc8f98d 100644
--- a/zellij-server/src/route.rs
+++ b/zellij-server/src/route.rs
@@ -656,10 +656,12 @@ pub(crate) fn route_action(
ClientTabIndexOrPaneId::ClientId(client_id),
Some(NotificationEnd::new(completion_tx)),
false, // set_blocking
+ None, // purpose
))
.with_context(err_context)?;
},
Action::NewBlockingPane {
+ purpose,
placement,
pane_name,
command,
@@ -709,6 +711,7 @@ pub(crate) fn route_action(
client_tab_index_or_paneid,
notification_end,
set_pane_blocking,
+ purpose,
))
.with_context(err_context)?;
wait_forever = true;
@@ -755,6 +758,7 @@ pub(crate) fn route_action(
client_tab_index_or_paneid,
Some(NotificationEnd::new(completion_tx)),
false, // set_blocking
+ None, // purpose
)
};
senders.send_to_pty(pty_instr).with_context(err_context)?;
@@ -775,6 +779,7 @@ pub(crate) fn route_action(
.with_context(err_context)?;
},
Action::NewFloatingPane {
+ purpose,
command: run_command,
pane_name: name,
coordinates: floating_pane_coordinates,
@@ -796,6 +801,7 @@ pub(crate) fn route_action(
client_tab_index_or_paneid,
Some(NotificationEnd::new(completion_tx)),
false, // set_blocking
+ purpose,
))
.with_context(err_context)?;
},
@@ -840,6 +846,7 @@ pub(crate) fn route_action(
.with_context(err_context)?;
},
Action::NewStackedPane {
+ purpose,
command: run_command,
pane_name: name,
near_current_pane,
@@ -893,10 +900,12 @@ pub(crate) fn route_action(
client_tab_index_or_paneid,
Some(NotificationEnd::new(completion_tx)),
false, // set_blocking
+ purpose,
))
.with_context(err_context)?;
},
Action::NewTiledPane {
+ purpose,
direction,
command: run_command,
pane_name: name,
@@ -922,6 +931,7 @@ pub(crate) fn route_action(
client_tab_index_or_paneid,
Some(NotificationEnd::new(completion_tx)),
false, // set_blocking
+ purpose,
))
.with_context(err_context)?;
},
@@ -979,6 +989,7 @@ pub(crate) fn route_action(
client_tab_index_or_paneid,
Some(NotificationEnd::new(completion_tx)),
false, // set_blocking
+ None, // purpose
))
.with_context(err_context)?;
},
diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs
index 35522656..1b63451a 100644
--- a/zellij-server/src/screen.rs
+++ b/zellij-server/src/screen.rs
@@ -343,6 +343,7 @@ pub enum ScreenInstruction {
ClientTabIndexOrPaneId,
Option<NotificationEnd>, // completion signal
bool, // set_blocking
+ Option<String>, // agent-facing purpose (see workspace hints)
),
OpenInPlaceEditor(PaneId, ClientTabIndexOrPaneId),
TogglePaneEmbedOrFloating(ClientId, Option<NotificationEnd>),
@@ -4918,6 +4919,7 @@ impl Screen {
let selectable_tiled_panes_count = tab.get_selectable_tiled_panes_count();
let selectable_floating_panes_count = tab.get_selectable_floating_panes_count();
let tab_info_for_screen = TabInfo {
+ purpose: tab.purpose.clone(),
position: tab.position,
name: tab.name.clone(),
active: self.active_tab_ids.values().any(|i| i == &tab.id),
@@ -4975,6 +4977,7 @@ impl Screen {
let selectable_tiled_panes_count = tab.get_selectable_tiled_panes_count();
let selectable_floating_panes_count = tab.get_selectable_floating_panes_count();
let tab_info_for_plugins = TabInfo {
+ purpose: None,
position: tab.position,
name: tab
.single_pane_tab_name()
@@ -7017,6 +7020,7 @@ impl Screen {
tab.name.clone(),
tab_is_focused,
hide_floating_panes,
+ tab.purpose.clone(),
tiled_panes,
floating_panes,
);
@@ -7123,6 +7127,7 @@ impl Screen {
let selectable_floating_panes_count = tab.get_selectable_floating_panes_count();
TabInfo {
+ purpose: tab.purpose.clone(),
position: tab.position,
name: tab.name.clone(),
active: self.active_tab_ids.values().any(|i| i == &tab.id),
@@ -7788,6 +7793,7 @@ pub(crate) fn screen_thread_main(
client_or_tab_index,
mut completion_tx,
set_blocking,
+ purpose,
) => {
completion_tx.as_mut().map(|c| c.set_affected_pane_id(pid));
@@ -7939,6 +7945,19 @@ pub(crate) fn screen_thread_main(
screen.log_and_report_session_state()?;
screen.render(None)?;
+ // Applied once here rather than in each of the branches
+ // above, all of which end in `tab.new_pane`. Authoritative like a
+ // layout's purpose: whoever can run the CLI can already do
+ // anything, unlike the in-band OSC path which any program in the
+ // pane can drive.
+ if purpose.is_some() {
+ for tab in screen.tabs.values_mut() {
+ if tab.get_pane_with_id_mut(pid).is_some() {
+ tab.set_pane_purpose(pid, purpose.clone());
+ break;
+ }
+ }
+ }
},
ScreenInstruction::OpenInPlaceEditor(pid, client_tab_index_or_pane_id) => {
match client_tab_index_or_pane_id {
diff --git a/zellij-server/src/session_layout_metadata.rs b/zellij-server/src/session_layout_metadata.rs
index 9ecdff98..9e42221e 100644
--- a/zellij-server/src/session_layout_metadata.rs
+++ b/zellij-server/src/session_layout_metadata.rs
@@ -208,10 +208,12 @@ impl SessionLayoutMetadata {
name: String,
is_focused: bool,
hide_floating_panes: bool,
+ purpose: Option<String>,
tiled_panes: Vec<PaneLayoutMetadata>,
floating_panes: Vec<PaneLayoutMetadata>,
) {
self.tabs.push(TabLayoutMetadata {
+ purpose,
name: Some(name),
is_focused,
hide_floating_panes,
@@ -498,6 +500,7 @@ impl Into<TabLayoutManifest> for TabLayoutMetadata {
floating_panes: self.floating_panes.into_iter().map(|t| t.into()).collect(),
is_focused: self.is_focused,
hide_floating_panes: self.hide_floating_panes,
+ purpose: self.purpose.clone(),
}
}
}
@@ -549,6 +552,7 @@ pub struct TabLayoutMetadata {
floating_panes: Vec<PaneLayoutMetadata>,
is_focused: bool,
hide_floating_panes: bool,
+ purpose: Option<String>,
}
#[derive(Debug, Clone)]
@@ -744,7 +748,7 @@ mod tests {
fn session_with_editor(editor: &str, panes: Vec<PaneLayoutMetadata>) -> SessionLayoutMetadata {
let mut meta = SessionLayoutMetadata::default();
meta.default_editor = Some(PathBuf::from(editor));
- meta.add_tab("tab1".to_string(), true, false, panes, vec![]);
+ meta.add_tab("tab1".to_string(), true, false, None, panes, vec![]);
meta
}
diff --git a/zellij-server/src/tab/mod.rs b/zellij-server/src/tab/mod.rs
index 93dc50ea..9ed4ce24 100644
--- a/zellij-server/src/tab/mod.rs
+++ b/zellij-server/src/tab/mod.rs
@@ -298,6 +298,9 @@ pub(crate) struct Tab {
pub id: usize,
pub position: usize,
pub name: String,
+ /// Opaque agent-facing hint for what this tab is for; see
+ /// `sl0p/workspace-hints-proposal.md`.
+ pub purpose: Option<String>,
pub prev_name: String,
default_name: String,
pub size: Size,
@@ -1172,6 +1175,8 @@ impl Tab {
Tab {
id,
position,
+ // set when a layout is applied; a tab created ad hoc has none
+ purpose: None,
tiled_panes,
floating_panes,
suppressed_panes: HashMap::new(),
@@ -2206,6 +2211,14 @@ impl Tab {
client_id: ClientId,
blocking_terminal: Option<(u32, NotificationEnd)>,
) -> Result<()> {
+ // The tab's purpose rides its root layout node (the `tab` node itself),
+ // which is why this is read here and not from any pane.
+ if layout.purpose.is_some() {
+ self.purpose = layout
+ .purpose
+ .as_deref()
+ .and_then(zellij_utils::input::layout::sanitize_pane_purpose);
+ }
self.swap_layouts
.set_base_layout((layout.clone(), floating_panes_layout.clone()));
match LayoutApplier::new(
@@ -2928,6 +2941,7 @@ impl Tab {
client_id_or_tab_index,
completion_tx,
false, // set_blocking
+ None, // purpose
);
self.senders
.send_to_pty(instruction)
@@ -8018,6 +8032,13 @@ impl Tab {
pane.update_rounded_corners(rounded_corners);
}
}
+ /// Set a pane's purpose from outside the pane (CLI spawn, layout). Outranks
+ /// anything the program inside the pane declares via OSC.
+ pub fn set_pane_purpose(&mut self, pane_id: PaneId, purpose: Option<String>) {
+ if let Some(pane) = self.get_pane_with_id_mut(pane_id) {
+ pane.set_purpose(purpose);
+ }
+ }
/// Opaque agent-facing hint for what a pane is for, if it has one.
pub fn pane_purpose(&self, pane_id: PaneId) -> Option<String> {
self.get_pane_with_id(pane_id).and_then(|p| p.purpose())
diff --git a/zellij-server/src/unit/screen_tests.rs b/zellij-server/src/unit/screen_tests.rs
index 90e21f3e..6e872558 100644
--- a/zellij-server/src/unit/screen_tests.rs
+++ b/zellij-server/src/unit/screen_tests.rs
@@ -3319,6 +3319,7 @@ pub fn send_cli_new_pane_action_with_default_parameters() {
pty_receiver
);
let cli_new_pane_action = CliAction::NewPane {
+ purpose: None,
direction: None,
command: vec![],
plugin: None,
@@ -3375,6 +3376,7 @@ pub fn send_cli_new_pane_action_with_split_direction() {
pty_receiver
);
let cli_new_pane_action = CliAction::NewPane {
+ purpose: None,
direction: Some(Direction::Right),
command: vec![],
plugin: None,
@@ -3431,6 +3433,7 @@ pub fn send_cli_new_pane_action_with_command_and_cwd() {
pty_receiver
);
let cli_new_pane_action = CliAction::NewPane {
+ purpose: None,
direction: Some(Direction::Right),
command: vec!["htop".into()],
plugin: None,
@@ -3498,6 +3501,7 @@ pub fn send_cli_new_pane_action_with_floating_pane_and_coordinates() {
pty_receiver
);
let cli_new_pane_action = CliAction::NewPane {
+ purpose: None,
direction: Some(Direction::Right),
command: vec!["htop".into()],
plugin: None,
@@ -5326,6 +5330,7 @@ pub fn send_cli_new_pane_in_place_with_close_replaced_pane() {
pty_receiver
);
let cli_action = CliAction::NewPane {
+ purpose: None,
direction: None,
command: vec!["bash".into()],
plugin: None,
@@ -8066,6 +8071,7 @@ pub fn send_cli_new_pane_action_with_tab_id() {
pty_receiver
);
let cli_new_pane_action = CliAction::NewPane {
+ purpose: None,
direction: Some(Direction::Right),
command: vec![],
plugin: None,
@@ -8129,6 +8135,7 @@ pub fn send_cli_new_floating_pane_action_with_tab_id() {
pty_receiver
);
let cli_new_pane_action = CliAction::NewPane {
+ purpose: None,
direction: None,
command: vec![],
plugin: None,
@@ -8241,6 +8248,7 @@ pub fn send_cli_new_pane_action_with_tab_id_and_direction() {
pty_receiver
);
let cli_new_pane_action = CliAction::NewPane {
+ purpose: None,
direction: Some(Direction::Right),
command: vec![],
plugin: None,
@@ -8303,6 +8311,7 @@ pub fn send_cli_new_pane_action_with_tab_id_and_stacked() {
pty_receiver
);
let cli_new_pane_action = CliAction::NewPane {
+ purpose: None,
direction: None,
command: vec!["ls".into()],
plugin: None,
@@ -8491,6 +8500,7 @@ pub fn pty_bytes_and_hold_pane_buffered_before_new_pane() {
ClientTabIndexOrPaneId::ClientId(client_id),
None, // completion_tx
false, // set_blocking
+ None, // purpose
));
std::thread::sleep(std::time::Duration::from_millis(100));
diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_edit_action_with_default_parameters.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_edit_action_with_default_parameters.snap
index 8996ded8..658ff140 100644
--- a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_edit_action_with_default_parameters.snap
+++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_edit_action_with_default_parameters.snap
@@ -1,5 +1,6 @@
---
source: zellij-server/src/./unit/screen_tests.rs
+assertion_line: 3581
expression: "format!(\"{:?}\", *received_pty_instructions.lock().unwrap())"
---
-[UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), SpawnTerminal(Some(OpenFile(OpenFilePayload { path: "/file/to/edit", line_number: None, cwd: Some("."), originating_plugin: None })), Some("Editing: /file/to/edit"), Tiled { direction: None, borderless: None }, false, ClientId(10), Some(NotificationEnd { channel: None, exit_status: None, unblock_condition: None, affected_pane_id: None, affected_tab_id: None, error_message: None, stdout_message: None }), false), Exit]
+[UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), SpawnTerminal(Some(OpenFile(OpenFilePayload { path: "/file/to/edit", line_number: None, cwd: Some("."), originating_plugin: None })), Some("Editing: /file/to/edit"), Tiled { direction: None, borderless: None }, false, ClientId(10), Some(NotificationEnd { channel: None, exit_status: None, unblock_condition: None, affected_pane_id: None, affected_tab_id: None, error_message: None, stdout_message: None }), false, None), Exit]
diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_edit_action_with_line_number.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_edit_action_with_line_number.snap
index 2866060b..ee35a678 100644
--- a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_edit_action_with_line_number.snap
+++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_edit_action_with_line_number.snap
@@ -1,5 +1,6 @@
---
source: zellij-server/src/./unit/screen_tests.rs
+assertion_line: 3625
expression: "format!(\"{:?}\", *received_pty_instructions.lock().unwrap())"
---
-[UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), SpawnTerminal(Some(OpenFile(OpenFilePayload { path: "/file/to/edit", line_number: Some(100), cwd: Some("."), originating_plugin: None })), Some("Editing: /file/to/edit"), Tiled { direction: None, borderless: None }, false, ClientId(10), Some(NotificationEnd { channel: None, exit_status: None, unblock_condition: None, affected_pane_id: None, affected_tab_id: None, error_message: None, stdout_message: None }), false), Exit]
+[UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), SpawnTerminal(Some(OpenFile(OpenFilePayload { path: "/file/to/edit", line_number: Some(100), cwd: Some("."), originating_plugin: None })), Some("Editing: /file/to/edit"), Tiled { direction: None, borderless: None }, false, ClientId(10), Some(NotificationEnd { channel: None, exit_status: None, unblock_condition: None, affected_pane_id: None, affected_tab_id: None, error_message: None, stdout_message: None }), false, None), Exit]
diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_edit_action_with_split_direction.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_edit_action_with_split_direction.snap
index 036f62bf..48614da5 100644
--- a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_edit_action_with_split_direction.snap
+++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_edit_action_with_split_direction.snap
@@ -1,5 +1,6 @@
---
source: zellij-server/src/./unit/screen_tests.rs
+assertion_line: 3669
expression: "format!(\"{:?}\", *received_pty_instructions.lock().unwrap())"
---
-[UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), SpawnTerminal(Some(OpenFile(OpenFilePayload { path: "/file/to/edit", line_number: None, cwd: Some("."), originating_plugin: None })), Some("Editing: /file/to/edit"), Tiled { direction: Some(Down), borderless: None }, false, ClientId(10), Some(NotificationEnd { channel: None, exit_status: None, unblock_condition: None, affected_pane_id: None, affected_tab_id: None, error_message: None, stdout_message: None }), false), Exit]
+[UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), SpawnTerminal(Some(OpenFile(OpenFilePayload { path: "/file/to/edit", line_number: None, cwd: Some("."), originating_plugin: None })), Some("Editing: /file/to/edit"), Tiled { direction: Some(Down), borderless: None }, false, ClientId(10), Some(NotificationEnd { channel: None, exit_status: None, unblock_condition: None, affected_pane_id: None, affected_tab_id: None, error_message: None, stdout_message: None }), false, None), Exit]
diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_pane_action_with_command_and_cwd.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_pane_action_with_command_and_cwd.snap
index 43dd40d1..e9db2e72 100644
--- a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_pane_action_with_command_and_cwd.snap
+++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_pane_action_with_command_and_cwd.snap
@@ -1,5 +1,6 @@
---
source: zellij-server/src/./unit/screen_tests.rs
+assertion_line: 3480
expression: "format!(\"{:?}\", new_pane_instruction)"
---
-Some(SpawnTerminal(Some(RunCommand(RunCommand { command: "htop", args: [], cwd: Some("/some/folder"), hold_on_close: true, hold_on_start: false, originating_plugin: None, use_terminal_title: false })), None, Tiled { direction: Some(Right), borderless: Some(false) }, false, ClientId(10), Some(NotificationEnd { channel: None, exit_status: None, unblock_condition: None, affected_pane_id: None, affected_tab_id: None, error_message: None, stdout_message: None }), false))
+Some(SpawnTerminal(Some(RunCommand(RunCommand { command: "htop", args: [], cwd: Some("/some/folder"), hold_on_close: true, hold_on_start: false, originating_plugin: None, use_terminal_title: false })), None, Tiled { direction: Some(Right), borderless: Some(false) }, false, ClientId(10), Some(NotificationEnd { channel: None, exit_status: None, unblock_condition: None, affected_pane_id: None, affected_tab_id: None, error_message: None, stdout_message: None }), false, None))
diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_pane_action_with_default_parameters.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_pane_action_with_default_parameters.snap
index 433f4d93..7e3b4f51 100644
--- a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_pane_action_with_default_parameters.snap
+++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_pane_action_with_default_parameters.snap
@@ -1,5 +1,6 @@
---
source: zellij-server/src/./unit/screen_tests.rs
+assertion_line: 3355
expression: "format!(\"{:?}\", *received_pty_instructions.lock().unwrap())"
---
-[UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), SpawnTerminal(None, None, Tiled { direction: None, borderless: Some(false) }, false, ClientId(10), Some(NotificationEnd { channel: None, exit_status: None, unblock_condition: None, affected_pane_id: None, affected_tab_id: None, error_message: None, stdout_message: None }), false), Exit]
+[UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), SpawnTerminal(None, None, Tiled { direction: None, borderless: Some(false) }, false, ClientId(10), Some(NotificationEnd { channel: None, exit_status: None, unblock_condition: None, affected_pane_id: None, affected_tab_id: None, error_message: None, stdout_message: None }), false, None), Exit]
diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_pane_action_with_floating_pane_and_coordinates.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_pane_action_with_floating_pane_and_coordinates.snap
index f33730b2..d2151356 100644
--- a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_pane_action_with_floating_pane_and_coordinates.snap
+++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_pane_action_with_floating_pane_and_coordinates.snap
@@ -1,5 +1,6 @@
---
source: zellij-server/src/./unit/screen_tests.rs
+assertion_line: 3537
expression: "format!(\"{:?}\", *received_pty_instructions.lock().unwrap())"
---
-[UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), SpawnTerminal(Some(RunCommand(RunCommand { command: "htop", args: [], cwd: Some("/some/folder"), hold_on_close: true, hold_on_start: false, originating_plugin: None, use_terminal_title: false })), None, Floating(Some(FloatingPaneCoordinates { x: Some(Fixed(10)), y: None, width: Some(Percent(20)), height: None, pinned: None, borderless: Some(false) })), false, ClientId(10), Some(NotificationEnd { channel: None, exit_status: None, unblock_condition: None, affected_pane_id: None, affected_tab_id: None, error_message: None, stdout_message: None }), false), Exit]
+[UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), SpawnTerminal(Some(RunCommand(RunCommand { command: "htop", args: [], cwd: Some("/some/folder"), hold_on_close: true, hold_on_start: false, originating_plugin: None, use_terminal_title: false })), None, Floating(Some(FloatingPaneCoordinates { x: Some(Fixed(10)), y: None, width: Some(Percent(20)), height: None, pinned: None, borderless: Some(false) })), false, ClientId(10), Some(NotificationEnd { channel: None, exit_status: None, unblock_condition: None, affected_pane_id: None, affected_tab_id: None, error_message: None, stdout_message: None }), false, None), Exit]
diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_pane_action_with_split_direction.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_pane_action_with_split_direction.snap
index 1aebde3e..629e6c76 100644
--- a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_pane_action_with_split_direction.snap
+++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_pane_action_with_split_direction.snap
@@ -1,5 +1,6 @@
---
source: zellij-server/src/./unit/screen_tests.rs
+assertion_line: 3412
expression: "format!(\"{:?}\", *received_pty_instructions.lock().unwrap())"
---
-[UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), SpawnTerminal(None, None, Tiled { direction: Some(Right), borderless: Some(false) }, false, ClientId(10), Some(NotificationEnd { channel: None, exit_status: None, unblock_condition: None, affected_pane_id: None, affected_tab_id: None, error_message: None, stdout_message: None }), false), Exit]
+[UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), SpawnTerminal(None, None, Tiled { direction: Some(Right), borderless: Some(false) }, false, ClientId(10), Some(NotificationEnd { channel: None, exit_status: None, unblock_condition: None, affected_pane_id: None, affected_tab_id: None, error_message: None, stdout_message: None }), false, None), Exit]
diff --git a/zellij-utils/assets/prost/api.action.rs b/zellij-utils/assets/prost/api.action.rs
index cfe201cf..8a7f1ad9 100644
--- a/zellij-utils/assets/prost/api.action.rs
+++ b/zellij-utils/assets/prost/api.action.rs
@@ -530,6 +530,8 @@ pub struct NewFloatingPanePayload {
pub command: ::core::option::Option<RunCommandAction>,
#[prost(bool, tag="2")]
pub near_current_pane: bool,
+ #[prost(string, optional, tag="3")]
+ pub purpose: ::core::option::Option<::prost::alloc::string::String>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
@@ -542,6 +544,8 @@ pub struct NewTiledPanePayload {
pub near_current_pane: bool,
#[prost(bool, optional, tag="4")]
pub borderless: ::core::option::Option<bool>,
+ #[prost(string, optional, tag="5")]
+ pub purpose: ::core::option::Option<::prost::alloc::string::String>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
@@ -592,6 +596,8 @@ pub struct NewBlockingPanePayload {
pub unblock_condition: ::core::option::Option<i32>,
#[prost(bool, tag="5")]
pub near_current_pane: bool,
+ #[prost(string, optional, tag="6")]
+ pub purpose: ::core::option::Option<::prost::alloc::string::String>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
diff --git a/zellij-utils/assets/prost_ipc/client_server_contract.rs b/zellij-utils/assets/prost_ipc/client_server_contract.rs
index 18fdee65..0db61be9 100644
--- a/zellij-utils/assets/prost_ipc/client_server_contract.rs
+++ b/zellij-utils/assets/prost_ipc/client_server_contract.rs
@@ -893,6 +893,8 @@ pub struct NewFloatingPaneAction {
pub tab_id: ::core::option::Option<u32>,
#[prost(bool, tag="9")]
pub no_focus: bool,
+ #[prost(string, optional, tag="10")]
+ pub purpose: ::core::option::Option<::prost::alloc::string::String>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
@@ -911,6 +913,8 @@ pub struct NewTiledPaneAction {
pub tab_id: ::core::option::Option<u32>,
#[prost(bool, tag="10")]
pub no_focus: bool,
+ #[prost(string, optional, tag="11")]
+ pub purpose: ::core::option::Option<::prost::alloc::string::String>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
@@ -943,6 +947,8 @@ pub struct NewStackedPaneAction {
pub tab_id: ::core::option::Option<u32>,
#[prost(bool, tag="5")]
pub no_focus: bool,
+ #[prost(string, optional, tag="6")]
+ pub purpose: ::core::option::Option<::prost::alloc::string::String>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
@@ -961,6 +967,8 @@ pub struct NewBlockingPaneAction {
pub tab_id: ::core::option::Option<u32>,
#[prost(bool, tag="7")]
pub no_focus: bool,
+ #[prost(string, optional, tag="8")]
+ pub purpose: ::core::option::Option<::prost::alloc::string::String>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
diff --git a/zellij-utils/src/cli.rs b/zellij-utils/src/cli.rs
index 38167dd4..c7fd0f35 100644
--- a/zellij-utils/src/cli.rs
+++ b/zellij-utils/src/cli.rs
@@ -508,6 +508,12 @@ pub enum Sessions {
/// if set, will open the pane near the current one rather than following the user's focus
#[clap(long)]
near_current_pane: bool,
+ /// Opaque agent-facing hint for what this pane is for, reported by
+ /// `list-panes --json` (eg. dev-server). Zellij never interprets it.
+ /// Not supported for in-place panes, which temporarily replace another
+ /// pane rather than being a pane of their own.
+ #[clap(long, value_parser, conflicts_with("in_place"))]
+ purpose: Option<String>,
#[clap(
long,
help = "if set, will open the pane without changing the focus of any client, placing it relative to the pane the command was issued from"
@@ -985,6 +991,12 @@ pub enum CliAction {
/// if set, will open the pane near the current one rather than following the user's focus
#[clap(long)]
near_current_pane: bool,
+ /// Opaque agent-facing hint for what this pane is for, reported by
+ /// `list-panes --json` (eg. dev-server). Zellij never interprets it.
+ /// Not supported for in-place panes, which temporarily replace another
+ /// pane rather than being a pane of their own.
+ #[clap(long, value_parser, conflicts_with("in_place"))]
+ purpose: Option<String>,
#[clap(
long,
help = "if set, will open the pane without changing the focus of any client, placing it relative to the pane the command was issued from"
diff --git a/zellij-utils/src/client_server_contract/common_types.proto b/zellij-utils/src/client_server_contract/common_types.proto
index 8f24e92a..6364990a 100644
--- a/zellij-utils/src/client_server_contract/common_types.proto
+++ b/zellij-utils/src/client_server_contract/common_types.proto
@@ -488,6 +488,7 @@ message NewFloatingPaneAction {
bool near_current_pane = 7;
optional uint32 tab_id = 8;
bool no_focus = 9;
+ optional string purpose = 10;
}
message NewTiledPaneAction {
@@ -498,6 +499,7 @@ message NewTiledPaneAction {
optional bool borderless = 8;
optional uint32 tab_id = 9;
bool no_focus = 10;
+ optional string purpose = 11;
}
message NewInPlacePaneAction {
@@ -516,6 +518,7 @@ message NewStackedPaneAction {
bool near_current_pane = 3;
optional uint32 tab_id = 4;
bool no_focus = 5;
+ optional string purpose = 6;
}
message NewBlockingPaneAction {
@@ -526,6 +529,7 @@ message NewBlockingPaneAction {
bool near_current_pane = 5;
optional uint32 tab_id = 6;
bool no_focus = 7;
+ optional string purpose = 8;
}
message PaneNameInputAction {
diff --git a/zellij-utils/src/data.rs b/zellij-utils/src/data.rs
index 14444b2a..a18c540f 100644
--- a/zellij-utils/src/data.rs
+++ b/zellij-utils/src/data.rs
@@ -2340,6 +2340,11 @@ impl SessionInfo {
/// Contains all the information for a currently opened tab.
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
pub struct TabInfo {
+ /// Opaque agent-facing hint for what this TAB is for, from the layout's
+ /// `tab ... purpose=`. Zellij never interprets it. Serde-only: the plugin
+ /// protobuf mirror has no field for it, so it defaults there.
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub purpose: Option<String>,
/// The Tab's 0 indexed position
pub position: usize,
/// The name of the tab as it appears in the UI (if there's enough room for it)
diff --git a/zellij-utils/src/input/actions.rs b/zellij-utils/src/input/actions.rs
index 27dde42e..2fa5be5f 100644
--- a/zellij-utils/src/input/actions.rs
+++ b/zellij-utils/src/input/actions.rs
@@ -238,6 +238,8 @@ pub enum Action {
near_current_pane: bool,
no_focus: bool,
tab_id: Option<usize>,
+ /// Opaque agent-facing hint; zellij never interprets it.
+ purpose: Option<String>,
},
/// Open the file in a new pane using the default editor
/// Returns: Created pane ID (format: terminal_<id>)
@@ -262,6 +264,8 @@ pub enum Action {
near_current_pane: bool,
no_focus: bool,
tab_id: Option<usize>,
+ /// Opaque agent-facing hint; zellij never interprets it.
+ purpose: Option<String>,
},
/// Open a new tiled (embedded, non-floating) pane
/// Returns: Created pane ID (format: terminal_<id> or plugin_<id>)
@@ -273,6 +277,8 @@ pub enum Action {
no_focus: bool,
borderless: Option<bool>,
tab_id: Option<usize>,
+ /// Opaque agent-facing hint; zellij never interprets it.
+ purpose: Option<String>,
},
/// Open a new pane in place of the focused one, suppressing it instead
/// Returns: Created pane ID (format: terminal_<id> or plugin_<id>)
@@ -292,6 +298,8 @@ pub enum Action {
near_current_pane: bool,
no_focus: bool,
tab_id: Option<usize>,
+ /// Opaque agent-facing hint; zellij never interprets it.
+ purpose: Option<String>,
},
/// Embed focused pane in tab if floating or float focused pane if embedded
TogglePaneEmbedOrFloating,
@@ -1079,6 +1087,7 @@ impl Action {
no_focus,
borderless,
tab_id,
+ purpose,
} => {
let pane_id_to_replace = match pane_id {
Some(pane_id_str) => match PaneId::from_str(&pane_id_str) {
@@ -1157,6 +1166,7 @@ impl Action {
};
Ok(vec![Action::NewBlockingPane {
+ purpose: purpose.clone(),
placement,
pane_name: name,
command,
@@ -1241,6 +1251,7 @@ impl Action {
};
if floating {
Ok(vec![Action::NewFloatingPane {
+ purpose: purpose.clone(),
command: Some(run_command_action),
pane_name: name,
coordinates: FloatingPaneCoordinates::new(
@@ -1262,6 +1273,7 @@ impl Action {
}])
} else if stacked {
Ok(vec![Action::NewStackedPane {
+ purpose: purpose.clone(),
command: Some(run_command_action),
pane_name: name,
near_current_pane,
@@ -1270,6 +1282,7 @@ impl Action {
}])
} else {
Ok(vec![Action::NewTiledPane {
+ purpose: purpose.clone(),
direction,
command: Some(run_command_action),
pane_name: name,
@@ -1282,6 +1295,7 @@ impl Action {
} else {
if floating {
Ok(vec![Action::NewFloatingPane {
+ purpose: purpose.clone(),
command: None,
pane_name: name,
coordinates: FloatingPaneCoordinates::new(
@@ -1303,6 +1317,7 @@ impl Action {
}])
} else if stacked {
Ok(vec![Action::NewStackedPane {
+ purpose: purpose.clone(),
command: None,
pane_name: name,
near_current_pane,
@@ -1311,6 +1326,7 @@ impl Action {
}])
} else {
Ok(vec![Action::NewTiledPane {
+ purpose: purpose.clone(),
direction,
command: None,
pane_name: name,
@@ -3669,6 +3685,7 @@ mod tests {
#[test]
fn test_new_pane_tiled_with_tab_id() {
let cli_action = CliAction::NewPane {
+ purpose: None,
direction: Some(Direction::Right),
command: vec![],
plugin: None,
@@ -3713,6 +3730,7 @@ mod tests {
#[test]
fn test_new_pane_tiled_without_tab_id() {
let cli_action = CliAction::NewPane {
+ purpose: None,
direction: None,
command: vec![],
plugin: None,
@@ -3757,6 +3775,7 @@ mod tests {
#[test]
fn test_new_in_place_pane_with_pane_id_to_replace() {
let cli_action = CliAction::NewPane {
+ purpose: None,
direction: None,
command: vec![],
plugin: None,
@@ -3803,6 +3822,7 @@ mod tests {
#[test]
fn test_new_in_place_pane_with_malformed_pane_id() {
let cli_action = CliAction::NewPane {
+ purpose: None,
direction: None,
command: vec![],
plugin: None,
@@ -3840,6 +3860,7 @@ mod tests {
#[test]
fn test_new_pane_floating_with_tab_id() {
let cli_action = CliAction::NewPane {
+ purpose: None,
direction: None,
command: vec![],
plugin: None,
@@ -3884,6 +3905,7 @@ mod tests {
#[test]
fn test_new_pane_stacked_with_tab_id() {
let cli_action = CliAction::NewPane {
+ purpose: None,
direction: None,
command: vec!["ls".into()],
plugin: None,
@@ -3928,6 +3950,7 @@ mod tests {
#[test]
fn test_new_pane_blocking_with_tab_id() {
let cli_action = CliAction::NewPane {
+ purpose: None,
direction: None,
command: vec!["ls".into()],
plugin: None,
@@ -4036,6 +4059,7 @@ mod tests {
#[test]
fn test_new_pane_plugin_tiled_with_tab_id() {
let cli_action = CliAction::NewPane {
+ purpose: None,
direction: None,
command: vec![],
plugin: Some("zellij:strider".into()),
@@ -4080,6 +4104,7 @@ mod tests {
#[test]
fn test_new_pane_plugin_floating_with_tab_id() {
let cli_action = CliAction::NewPane {
+ purpose: None,
direction: None,
command: vec![],
plugin: Some("zellij:strider".into()),
@@ -4121,3 +4146,96 @@ mod tests {
}
}
}
+
+#[cfg(test)]
+mod sl0p_purpose_wire_tests {
+ use super::*;
+
+ /// A CLI flag has to CROSS the client->server protobuf boundary, unlike a
+ /// field that merely exists on both sides. Defaulting it in the conversion
+ /// (which is what "no protoc" usually forces) silently drops it, and the flag
+ /// then parses and does nothing at all -- which is exactly what happened.
+ #[test]
+ fn sl0p_purpose_survives_the_protobuf_round_trip() {
+ use crate::plugin_api::action::ProtobufAction;
+ use std::convert::TryInto;
+
+ let action = Action::NewTiledPane {
+ direction: None,
+ command: Some(RunCommandAction {
+ command: std::path::PathBuf::from("sleep"),
+ ..Default::default()
+ }),
+ pane_name: None,
+ near_current_pane: false,
+ no_focus: false,
+ borderless: None,
+ tab_id: None,
+ purpose: Some("dev-server".to_string()),
+ };
+
+ let proto: ProtobufAction = action.clone().try_into().expect("to protobuf");
+ let back: Action = proto.try_into().expect("from protobuf");
+
+ match back {
+ Action::NewTiledPane { purpose, .. } => assert_eq!(
+ purpose.as_deref(),
+ Some("dev-server"),
+ "purpose must survive the wire, or --purpose is a no-op"
+ ),
+ other => panic!("expected NewTiledPane, got {:?}", other),
+ }
+ }
+}
+
+#[cfg(test)]
+mod sl0p_purpose_ipc_wire_tests {
+ use super::*;
+
+ /// There are TWO protobuf boundaries: the plugin API and the client->server
+ /// IPC contract. `--purpose` parsed fine and did nothing for a whole round of
+ /// testing because only the first was wired, and a field with no wire
+ /// representation is silently dropped in transit rather than failing to
+ /// compile. This is the boundary a CLI flag actually crosses.
+ #[test]
+ fn sl0p_purpose_survives_the_client_server_ipc_round_trip() {
+ use crate::client_server_contract::client_server_contract::Action as ProtoIpcAction;
+ use std::convert::TryInto;
+
+ for action in [
+ Action::NewTiledPane {
+ direction: None,
+ command: None,
+ pane_name: None,
+ near_current_pane: false,
+ no_focus: false,
+ borderless: None,
+ tab_id: None,
+ purpose: Some("dev-server".to_string()),
+ },
+ Action::NewFloatingPane {
+ command: None,
+ pane_name: None,
+ coordinates: None,
+ near_current_pane: false,
+ no_focus: false,
+ tab_id: None,
+ purpose: Some("dev-server".to_string()),
+ },
+ ] {
+ let proto: ProtoIpcAction = action.clone().into();
+ let back: Action = proto.try_into().expect("from ipc protobuf");
+ let purpose = match back {
+ Action::NewTiledPane { purpose, .. } | Action::NewFloatingPane { purpose, .. } => {
+ purpose
+ },
+ other => panic!("unexpected action back from the wire: {:?}", other),
+ };
+ assert_eq!(
+ purpose.as_deref(),
+ Some("dev-server"),
+ "purpose must cross the IPC boundary, or --purpose is a silent no-op"
+ );
+ }
+ }
+}
diff --git a/zellij-utils/src/input/unit/layout_test.rs b/zellij-utils/src/input/unit/layout_test.rs
index 4a6eb854..7154d4ce 100644
--- a/zellij-utils/src/input/unit/layout_test.rs
+++ b/zellij-utils/src/input/unit/layout_test.rs
@@ -3016,3 +3016,30 @@ fn sl0p_unknown_pane_properties_are_still_rejected() {
"an unknown pane property should be a parse error"
);
}
+
+#[test]
+fn sl0p_tab_root_layout_is_never_itself_a_pane() {
+ // Tab-level `purpose` is stored on the tab's root TiledPaneLayout, reusing
+ // the pane field. That is only safe if the root is never applied AS a pane --
+ // otherwise a tab purpose would leak onto a pane. Checked for the tightest
+ // case: a tab holding exactly one pane.
+ let kdl = r#"
+ layout {
+ tab name="solo" purpose="agent-work" {
+ pane name="only"
+ }
+ }
+ "#;
+ let layout = Layout::from_kdl(kdl, Some("w.kdl".into()), None, None).expect("parses");
+ let (_, tiled, _) = layout.tabs().into_iter().next().expect("one tab");
+ assert_eq!(
+ tiled.purpose.as_deref(),
+ Some("agent-work"),
+ "the tab's purpose lands on the tab's root layout"
+ );
+ assert_eq!(tiled.children.len(), 1, "the pane is a CHILD of the root");
+ assert_eq!(
+ tiled.children[0].purpose, None,
+ "the tab purpose must not leak onto the pane inside it"
+ );
+}
diff --git a/zellij-utils/src/ipc/protobuf_conversion.rs b/zellij-utils/src/ipc/protobuf_conversion.rs
index f1deb3fe..3acd196e 100644
--- a/zellij-utils/src/ipc/protobuf_conversion.rs
+++ b/zellij-utils/src/ipc/protobuf_conversion.rs
@@ -1332,8 +1332,10 @@ impl From<crate::input::actions::Action>
near_current_pane,
no_focus,
tab_id,
+ purpose,
..
} => ActionType::NewFloatingPane(NewFloatingPaneAction {
+ purpose,
command: command.map(|c| c.into()),
pane_name,
coordinates: coordinates.map(|c| c.into()),
@@ -1349,8 +1351,10 @@ impl From<crate::input::actions::Action>
no_focus,
borderless,
tab_id,
+ purpose,
..
} => ActionType::NewTiledPane(NewTiledPaneAction {
+ purpose,
direction: direction.map(|d| direction_to_proto_i32(d)),
command: command.map(|c| c.into()),
pane_name,
@@ -1383,8 +1387,10 @@ impl From<crate::input::actions::Action>
near_current_pane,
no_focus,
tab_id,
+ purpose,
..
} => ActionType::NewStackedPane(NewStackedPaneAction {
+ purpose,
command: command.map(|c| c.into()),
pane_name,
near_current_pane,
@@ -1399,8 +1405,10 @@ impl From<crate::input::actions::Action>
near_current_pane,
no_focus,
tab_id,
+ purpose,
..
} => ActionType::NewBlockingPane(NewBlockingPaneAction {
+ purpose,
placement: Some(placement.into()),
pane_name,
command: command.map(|c| c.into()),
@@ -2221,6 +2229,7 @@ impl TryFrom<crate::client_server_contract::client_server_contract::Action>
}),
ActionType::NewFloatingPane(new_floating_action) => {
Ok(crate::input::actions::Action::NewFloatingPane {
+ purpose: new_floating_action.purpose.clone(),
command: new_floating_action
.command
.map(|c| c.try_into())
@@ -2237,6 +2246,7 @@ impl TryFrom<crate::client_server_contract::client_server_contract::Action>
},
ActionType::NewTiledPane(new_tiled_action) => {
Ok(crate::input::actions::Action::NewTiledPane {
+ purpose: new_tiled_action.purpose.clone(),
direction: new_tiled_action
.direction
.map(|d| proto_i32_to_direction(d))
@@ -2267,6 +2277,7 @@ impl TryFrom<crate::client_server_contract::client_server_contract::Action>
},
ActionType::NewStackedPane(new_stacked_action) => {
Ok(crate::input::actions::Action::NewStackedPane {
+ purpose: new_stacked_action.purpose.clone(),
command: new_stacked_action
.command
.map(|c| c.try_into())
@@ -2279,6 +2290,7 @@ impl TryFrom<crate::client_server_contract::client_server_contract::Action>
},
ActionType::NewBlockingPane(new_blocking_action) => {
Ok(crate::input::actions::Action::NewBlockingPane {
+ purpose: new_blocking_action.purpose.clone(),
placement: new_blocking_action
.placement
.ok_or_else(|| anyhow!("NewBlockingPane missing placement"))?
diff --git a/zellij-utils/src/ipc/tests/roundtrip_tests.rs b/zellij-utils/src/ipc/tests/roundtrip_tests.rs
index 5210e788..df1d7209 100644
--- a/zellij-utils/src/ipc/tests/roundtrip_tests.rs
+++ b/zellij-utils/src/ipc/tests/roundtrip_tests.rs
@@ -1356,6 +1356,7 @@ fn test_client_messages() {
});
test_client_roundtrip!(ClientToServerMsg::Action {
action: Action::NewFloatingPane {
+ purpose: None,
command: None,
pane_name: None,
coordinates: None,
@@ -1369,6 +1370,7 @@ fn test_client_messages() {
});
test_client_roundtrip!(ClientToServerMsg::Action {
action: Action::NewFloatingPane {
+ purpose: None,
command: Some(RunCommandAction {
command: PathBuf::from("/path/to/command"),
args: vec![],
@@ -1398,6 +1400,7 @@ fn test_client_messages() {
});
test_client_roundtrip!(ClientToServerMsg::Action {
action: Action::NewFloatingPane {
+ purpose: None,
command: Some(RunCommandAction {
command: PathBuf::from("/path/to/command"),
args: vec!["arg1".to_owned(), "arg2".to_owned()],
@@ -1431,6 +1434,7 @@ fn test_client_messages() {
});
test_client_roundtrip!(ClientToServerMsg::Action {
action: Action::NewTiledPane {
+ purpose: None,
command: None,
direction: None,
pane_name: None,
@@ -1445,6 +1449,7 @@ fn test_client_messages() {
});
test_client_roundtrip!(ClientToServerMsg::Action {
action: Action::NewTiledPane {
+ purpose: None,
command: Some(RunCommandAction {
command: PathBuf::from("/path/to/command"),
args: vec!["arg1".to_owned(), "arg2".to_owned()],
@@ -1513,6 +1518,7 @@ fn test_client_messages() {
});
test_client_roundtrip!(ClientToServerMsg::Action {
action: Action::NewStackedPane {
+ purpose: None,
command: Some(RunCommandAction {
command: PathBuf::from("/path/to/command"),
args: vec!["arg1".to_owned(), "arg2".to_owned()],
@@ -1538,6 +1544,7 @@ fn test_client_messages() {
});
test_client_roundtrip!(ClientToServerMsg::Action {
action: Action::NewStackedPane {
+ purpose: None,
command: None,
pane_name: None,
near_current_pane: false,
@@ -2659,6 +2666,7 @@ fn test_client_messages() {
});
test_client_roundtrip!(ClientToServerMsg::Action {
action: Action::NewTiledPane {
+ purpose: None,
direction: Some(Direction::Right),
command: None,
pane_name: None,
@@ -2673,6 +2681,7 @@ fn test_client_messages() {
});
test_client_roundtrip!(ClientToServerMsg::Action {
action: Action::NewFloatingPane {
+ purpose: None,
command: None,
pane_name: None,
coordinates: None,
@@ -2686,6 +2695,7 @@ fn test_client_messages() {
});
test_client_roundtrip!(ClientToServerMsg::Action {
action: Action::NewStackedPane {
+ purpose: None,
command: None,
pane_name: None,
near_current_pane: false,
@@ -2698,6 +2708,7 @@ fn test_client_messages() {
});
test_client_roundtrip!(ClientToServerMsg::Action {
action: Action::NewBlockingPane {
+ purpose: None,
placement: NewPanePlacement::Tiled {
direction: None,
borderless: None
diff --git a/zellij-utils/src/kdl/kdl_layout_parser.rs b/zellij-utils/src/kdl/kdl_layout_parser.rs
index 9e823849..41b4a430 100644
--- a/zellij-utils/src/kdl/kdl_layout_parser.rs
+++ b/zellij-utils/src/kdl/kdl_layout_parser.rs
@@ -145,6 +145,7 @@ impl<'a> KdlLayoutParser<'a> {
|| property_name == "min_panes"
|| property_name == "exact_panes"
|| property_name == "hide_floating_panes"
+ || property_name == "purpose"
}
pub fn is_a_reserved_plugin_property(property_name: &str) -> bool {
property_name == "location"
@@ -1261,10 +1262,16 @@ impl<'a> KdlLayoutParser<'a> {
},
None => vec![],
};
+ // Tab-level purpose rides the tab's ROOT layout, which is the tab
+ // container and never itself a pane (asserted by
+ // sl0p_tab_root_layout_is_never_itself_a_pane).
+ let tab_purpose = kdl_get_string_property_or_child_value!(kdl_node, "purpose")
+ .and_then(|s| crate::input::layout::sanitize_pane_purpose(s));
let mut pane_layout = TiledPaneLayout {
children_split_direction,
children,
hide_floating_panes,
+ purpose: tab_purpose,
..Default::default()
};
if let Some(cwd_prefix) = &self.cwd_prefix(tab_cwd.as_ref())? {
diff --git a/zellij-utils/src/kdl/mod.rs b/zellij-utils/src/kdl/mod.rs
index 358fa3e2..98743d67 100644
--- a/zellij-utils/src/kdl/mod.rs
+++ b/zellij-utils/src/kdl/mod.rs
@@ -576,6 +576,7 @@ impl Action {
});
} else if string == "stacked" {
return Ok(Action::NewStackedPane {
+ purpose: None,
command: None,
pane_name: None,
near_current_pane: false,
@@ -2067,6 +2068,7 @@ impl TryFrom<(&KdlNode, &Options)> for Action {
.and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "borderless"));
if floating {
Ok(Action::NewFloatingPane {
+ purpose: None,
command: Some(run_command_action),
pane_name: name,
coordinates: FloatingPaneCoordinates::new(
@@ -2088,6 +2090,7 @@ impl TryFrom<(&KdlNode, &Options)> for Action {
})
} else if stacked {
Ok(Action::NewStackedPane {
+ purpose: None,
command: Some(run_command_action),
pane_name: name,
near_current_pane: false,
@@ -2096,6 +2099,7 @@ impl TryFrom<(&KdlNode, &Options)> for Action {
})
} else {
Ok(Action::NewTiledPane {
+ purpose: None,
direction,
command: Some(run_command_action),
pane_name: name,
@@ -6521,6 +6525,7 @@ impl TabInfo {
optional_int_node!("selectable_floating_panes_count", usize).unwrap_or(0);
let tab_id = optional_int_node!("tab_id", usize).unwrap_or(0);
Ok(TabInfo {
+ purpose: None,
position,
name,
active,
@@ -6960,6 +6965,7 @@ fn serialize_and_deserialize_session_info_with_data() {
name: "my session name".to_owned(),
tabs: vec![
TabInfo {
+ purpose: None,
position: 0,
name: "tab 1".to_owned(),
active: true,
@@ -6981,6 +6987,7 @@ fn serialize_and_deserialize_session_info_with_data() {
has_bell_notification: false,
},
TabInfo {
+ purpose: None,
position: 1,
name: "tab 2".to_owned(),
active: true,
diff --git a/zellij-utils/src/plugin_api/action.proto b/zellij-utils/src/plugin_api/action.proto
index 1d896cef..4c6da631 100644
--- a/zellij-utils/src/plugin_api/action.proto
+++ b/zellij-utils/src/plugin_api/action.proto
@@ -328,6 +328,7 @@ message GoToTabNamePayload {
message NewFloatingPanePayload {
optional RunCommandAction command = 1;
bool near_current_pane = 2;
+ optional string purpose = 3;
}
message NewTiledPanePayload {
@@ -335,6 +336,7 @@ message NewTiledPanePayload {
optional resize.ResizeDirection direction = 2;
bool near_current_pane = 3;
optional bool borderless = 4;
+ optional string purpose = 5;
}
message MovePanePayload {
@@ -365,6 +367,7 @@ message NewBlockingPanePayload {
optional RunCommandAction command = 3;
optional UnblockCondition unblock_condition = 4;
bool near_current_pane = 5;
+ optional string purpose = 6;
}
message NewInPlacePanePayload {
diff --git a/zellij-utils/src/plugin_api/action.rs b/zellij-utils/src/plugin_api/action.rs
index e3cd29f8..c7bb144a 100644
--- a/zellij-utils/src/plugin_api/action.rs
+++ b/zellij-utils/src/plugin_api/action.rs
@@ -370,10 +370,12 @@ impl TryFrom<ProtobufAction> for Action {
Some(ProtobufActionName::NewFloatingPane) => match protobuf_action.optional_payload {
Some(OptionalPayload::NewFloatingPanePayload(payload)) => {
let near_current_pane = payload.near_current_pane;
+ let purpose = payload.purpose.clone();
if let Some(payload) = payload.command {
let pane_name = payload.pane_name.clone();
let run_command_action: RunCommandAction = payload.try_into()?;
Ok(Action::NewFloatingPane {
+ purpose: purpose.clone(),
command: Some(run_command_action),
pane_name,
coordinates: None,
@@ -383,6 +385,7 @@ impl TryFrom<ProtobufAction> for Action {
})
} else {
Ok(Action::NewFloatingPane {
+ purpose: purpose.clone(),
command: None,
pane_name: None,
coordinates: None,
@@ -401,11 +404,13 @@ impl TryFrom<ProtobufAction> for Action {
.and_then(|d| ProtobufResizeDirection::from_i32(d))
.and_then(|d| d.try_into().ok());
let near_current_pane = payload.near_current_pane;
+ let purpose = payload.purpose.clone();
let borderless = payload.borderless;
if let Some(payload) = payload.command {
let pane_name = payload.pane_name.clone();
let run_command_action: RunCommandAction = payload.try_into()?;
Ok(Action::NewTiledPane {
+ purpose: purpose.clone(),
direction,
command: Some(run_command_action),
pane_name,
@@ -416,6 +421,7 @@ impl TryFrom<ProtobufAction> for Action {
})
} else {
Ok(Action::NewTiledPane {
+ purpose: purpose.clone(),
direction,
command: None,
pane_name: None,
@@ -1036,6 +1042,7 @@ impl TryFrom<ProtobufAction> for Action {
Some(ProtobufActionName::NewStackedPane) => match protobuf_action.optional_payload {
Some(_) => Err("NewStackedPane should not have a payload"),
None => Ok(Action::NewStackedPane {
+ purpose: None,
command: None,
pane_name: None,
near_current_pane: false,
@@ -1056,7 +1063,9 @@ impl TryFrom<ProtobufAction> for Action {
.and_then(|uc| ProtobufUnblockCondition::from_i32(uc))
.and_then(|uc| uc.try_into().ok());
let near_current_pane = payload.near_current_pane;
+ let purpose = payload.purpose.clone();
Ok(Action::NewBlockingPane {
+ purpose: purpose.clone(),
placement,
pane_name,
command,
@@ -1418,6 +1427,7 @@ impl TryFrom<Action> for ProtobufAction {
pane_name,
coordinates: _coordinates,
near_current_pane,
+ purpose,
..
} => {
let command = run_command_action.and_then(|r| {
@@ -1430,6 +1440,7 @@ impl TryFrom<Action> for ProtobufAction {
name: ProtobufActionName::NewFloatingPane as i32,
optional_payload: Some(OptionalPayload::NewFloatingPanePayload(
NewFloatingPanePayload {
+ purpose: purpose.clone(),
command,
near_current_pane,
},
@@ -1441,6 +1452,7 @@ impl TryFrom<Action> for ProtobufAction {
command: run_command_action,
pane_name,
near_current_pane,
+ purpose,
borderless,
..
} => {
@@ -1459,6 +1471,7 @@ impl TryFrom<Action> for ProtobufAction {
name: ProtobufActionName::NewTiledPane as i32,
optional_payload: Some(OptionalPayload::NewTiledPanePayload(
NewTiledPanePayload {
+ purpose: purpose.clone(),
direction,
command,
near_current_pane,
@@ -1932,6 +1945,7 @@ impl TryFrom<Action> for ProtobufAction {
command,
unblock_condition,
near_current_pane,
+ purpose,
..
} => {
let placement: ProtobufNewPanePlacement = placement.try_into()?;
@@ -1949,6 +1963,7 @@ impl TryFrom<Action> for ProtobufAction {
name: ProtobufActionName::NewBlockingPane as i32,
optional_payload: Some(OptionalPayload::NewBlockingPanePayload(
NewBlockingPanePayload {
+ purpose: purpose.clone(),
placement: Some(placement),
pane_name,
command,
diff --git a/zellij-utils/src/plugin_api/event.rs b/zellij-utils/src/plugin_api/event.rs
index 2e905f22..6be6c6d0 100644
--- a/zellij-utils/src/plugin_api/event.rs
+++ b/zellij-utils/src/plugin_api/event.rs
@@ -1888,6 +1888,7 @@ impl TryFrom<ProtobufTabInfo> for TabInfo {
type Error = &'static str;
fn try_from(protobuf_tab_info: ProtobufTabInfo) -> Result<Self, &'static str> {
Ok(TabInfo {
+ purpose: None, // no protobuf field; plugins do not see tab purposes
position: protobuf_tab_info.position as usize,
name: protobuf_tab_info.name,
active: protobuf_tab_info.active,
@@ -2557,6 +2558,7 @@ fn serialize_tab_update_event_with_non_default_values() {
use prost::Message;
let tab_update_event = Event::TabUpdate(vec![
TabInfo {
+ purpose: None,
position: 0,
name: "First tab".to_owned(),
active: true,
@@ -2578,6 +2580,7 @@ fn serialize_tab_update_event_with_non_default_values() {
is_flashing_bell: false,
},
TabInfo {
+ purpose: None,
position: 1,
name: "Secondtab".to_owned(),
active: false,
@@ -2903,6 +2906,7 @@ fn serialize_session_update_event_with_non_default_values() {
use prost::Message;
let tab_infos = vec![
TabInfo {
+ purpose: None,
position: 0,
name: "First tab".to_owned(),
active: true,
@@ -2924,6 +2928,7 @@ fn serialize_session_update_event_with_non_default_values() {
is_flashing_bell: false,
},
TabInfo {
+ purpose: None,
position: 1,
name: "Secondtab".to_owned(),
active: false,
diff --git a/zellij-utils/src/session_serialization.rs b/zellij-utils/src/session_serialization.rs
index 2b3f651c..75cd4ece 100644
--- a/zellij-utils/src/session_serialization.rs
+++ b/zellij-utils/src/session_serialization.rs
@@ -25,6 +25,8 @@ pub struct TabLayoutManifest {
pub floating_panes: Vec<PaneLayoutManifest>,
pub is_focused: bool,
pub hide_floating_panes: bool,
+ /// Opaque agent-facing hint for what this TAB is for.
+ pub purpose: Option<String>,
}
#[derive(Default, Debug, Clone)]
@@ -87,6 +89,7 @@ pub fn serialize_session_layout(
}
fn serialize_tab(
+ tab_purpose: Option<String>,
tab_name: String,
is_focused: bool,
hide_floating_panes: bool,
@@ -115,6 +118,11 @@ fn serialize_tab(
.entries_mut()
.push(KdlEntry::new_prop("focus", KdlValue::Bool(true)));
}
+ if let Some(purpose) = tab_purpose {
+ serialized_tab
+ .entries_mut()
+ .push(KdlEntry::new_prop("purpose", purpose));
+ }
if hide_floating_panes {
serialized_tab.entries_mut().push(KdlEntry::new_prop(
"hide_floating_panes",
@@ -652,6 +660,7 @@ fn serialize_multiple_tabs(
let floating_panes = tab_layout_manifest.floating_panes;
let hide_floating_panes = tab_layout_manifest.hide_floating_panes;
let serialized = serialize_tab(
+ tab_layout_manifest.purpose.clone(),
tab_name.clone(),
tab_layout_manifest.is_focused,
hide_floating_panes,
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 bdcb9108..16ffd584 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
@@ -1365,6 +1365,7 @@ Config {
near_current_pane: false,
no_focus: false,
tab_id: None,
+ purpose: 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 39eebd82..bfc875dd 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
@@ -1365,6 +1365,7 @@ Config {
near_current_pane: false,
no_focus: false,
tab_id: None,
+ purpose: 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 12909809..63b29457 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
@@ -1365,6 +1365,7 @@ Config {
near_current_pane: false,
no_focus: false,
tab_id: None,
+ purpose: 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 545b5b95..222971bb 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
@@ -1365,6 +1365,7 @@ Config {
near_current_pane: false,
no_focus: false,
tab_id: None,
+ purpose: None,
},
SwitchToMode {
input_mode: Normal,