aboutsummaryrefslogtreecommitdiffstats
path: root/zellij-utils/src/ipc
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 /zellij-utils/src/ipc
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.
Diffstat (limited to 'zellij-utils/src/ipc')
-rw-r--r--zellij-utils/src/ipc/protobuf_conversion.rs12
-rw-r--r--zellij-utils/src/ipc/tests/roundtrip_tests.rs11
2 files changed, 23 insertions, 0 deletions
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