aboutsummaryrefslogtreecommitdiffstats
path: root/zellij-utils/src
diff options
context:
space:
mode:
authordivens <divens@users.noreply.github.com>2026-04-06 10:34:23 +0200
committerGitHub <noreply@github.com>2026-04-06 10:34:23 +0200
commit2b3ae1e69cbf6a84599415ad80de456970199961 (patch)
tree66d13c802e4a1fdbb5af26c1faa7ea15e167d2f2 /zellij-utils/src
parentdocs(changelog): update recent PRs (diff)
downloadsl0ppij-2b3ae1e69cbf6a84599415ad80de456970199961.tar.gz
sl0ppij-2b3ae1e69cbf6a84599415ad80de456970199961.tar.xz
sl0ppij-2b3ae1e69cbf6a84599415ad80de456970199961.zip
fix: respect --name flag when creating new panes (#4979)
* fix: respect --name flag when creating and renaming panes Three issues fixed: 1. Pane creation: --name was silently discarded for default shell panes because use_terminal_title was true. Now user-provided names take priority. Also pass initial_pane_title as pane_name to TerminalPane constructor (was hardcoded to empty string). 2. CLI rename without --pane-id: was routed through PaneNameInput which appends to existing name. Now uses a dedicated RenameActivePane action that fully replaces the name, working for any name length including single characters. 3. Interactive rename: entering rename mode preserves the existing name for editing (append/backspace). Esc reverts to the name before rename mode was entered. Adds 28 tests covering rename semantics, interactive editing, undo, OSC title interaction, and a wire-level IPC round-trip test. * refactor: route active-pane rename via Option<PaneId> Replace the dedicated Action::RenameActivePane variant with an Option<PaneId> on the existing Action::RenamePaneByPaneId. route.rs dispatches None to the server-internal ScreenInstruction::RenameActivePane and Some(id) to the existing RenamePaneWithPaneId path. Drops all proto/conversion boilerplate for the removed variant: plugin API additions, IPC tag 137, and both directions of the wire conversion. --------- Co-authored-by: divens <divens.dev@gmail.com>
Diffstat (limited to 'zellij-utils/src')
-rw-r--r--zellij-utils/src/errors.rs1
-rw-r--r--zellij-utils/src/input/actions.rs43
-rw-r--r--zellij-utils/src/ipc/protobuf_conversion.rs7
-rw-r--r--zellij-utils/src/ipc/tests/roundtrip_tests.rs61
4 files changed, 85 insertions, 27 deletions
diff --git a/zellij-utils/src/errors.rs b/zellij-utils/src/errors.rs
index 0df851c4..d44a348a 100644
--- a/zellij-utils/src/errors.rs
+++ b/zellij-utils/src/errors.rs
@@ -352,6 +352,7 @@ pub enum ScreenContext {
UnsuppressOrExpandPane,
FocusPaneWithId,
RenamePane,
+ RenameActivePane,
RenameTab,
RequestPluginPermissions,
BreakPane,
diff --git a/zellij-utils/src/input/actions.rs b/zellij-utils/src/input/actions.rs
index 0ce1c507..ab4c9ae3 100644
--- a/zellij-utils/src/input/actions.rs
+++ b/zellij-utils/src/input/actions.rs
@@ -609,7 +609,7 @@ pub enum Action {
pane_id: PaneId,
},
RenamePaneByPaneId {
- pane_id: PaneId,
+ pane_id: Option<PaneId>,
name: Vec<u8>,
},
UndoRenamePaneByPaneId {
@@ -1323,23 +1323,19 @@ impl Action {
},
None => Ok(vec![Action::CloseFocus]),
},
- CliAction::RenamePane { name, pane_id } => match pane_id {
- Some(pane_id_str) => {
- let pane_id = PaneId::from_str(&pane_id_str)
- .map_err(|_| format!(
+ CliAction::RenamePane { name, pane_id } => {
+ let pane_id = match pane_id {
+ Some(pane_id_str) => Some(
+ PaneId::from_str(&pane_id_str).map_err(|_| format!(
"Malformed pane id: {pane_id_str}, expecting either a bare integer (eg. 1), a terminal pane id (eg. terminal_1) or a plugin pane id (eg. plugin_1)"
- ))?;
- Ok(vec![Action::RenamePaneByPaneId {
- pane_id,
- name: name.as_bytes().to_vec(),
- }])
- },
- None => Ok(vec![
- Action::UndoRenamePane,
- Action::PaneNameInput {
- input: name.as_bytes().to_vec(),
- },
- ]),
+ ))?,
+ ),
+ None => None,
+ };
+ Ok(vec![Action::RenamePaneByPaneId {
+ pane_id,
+ name: name.as_bytes().to_vec(),
+ }])
},
CliAction::UndoRenamePane { pane_id } => match pane_id {
Some(pane_id_str) => {
@@ -2899,7 +2895,7 @@ mod tests {
assert_eq!(actions.len(), 1);
match &actions[0] {
Action::RenamePaneByPaneId { pane_id, name } => {
- assert!(matches!(pane_id, PaneId::Terminal(19)));
+ assert!(matches!(pane_id, Some(PaneId::Terminal(19))));
assert_eq!(name, &"my-pane".as_bytes().to_vec());
},
_ => panic!("Expected RenamePaneByPaneId action"),
@@ -2915,9 +2911,14 @@ mod tests {
let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None);
assert!(result.is_ok());
let actions = result.unwrap();
- assert_eq!(actions.len(), 2);
- assert!(matches!(actions[0], Action::UndoRenamePane));
- assert!(matches!(actions[1], Action::PaneNameInput { .. }));
+ assert_eq!(actions.len(), 1);
+ match &actions[0] {
+ Action::RenamePaneByPaneId { pane_id, name } => {
+ assert!(pane_id.is_none());
+ assert_eq!(name, &"my-pane".as_bytes().to_vec());
+ },
+ _ => panic!("Expected RenamePaneByPaneId action"),
+ }
}
// 18. UndoRenamePane
diff --git a/zellij-utils/src/ipc/protobuf_conversion.rs b/zellij-utils/src/ipc/protobuf_conversion.rs
index 189c1370..656ef54c 100644
--- a/zellij-utils/src/ipc/protobuf_conversion.rs
+++ b/zellij-utils/src/ipc/protobuf_conversion.rs
@@ -1725,7 +1725,7 @@ impl From<crate::input::actions::Action>
},
crate::input::actions::Action::RenamePaneByPaneId { pane_id, name } => {
ActionType::RenamePaneByPaneId(RenamePaneByPaneIdAction {
- pane_id: Some(pane_id.into()),
+ pane_id: pane_id.map(|id| id.into()),
name,
})
},
@@ -2641,10 +2641,7 @@ impl TryFrom<crate::client_server_contract::client_server_contract::Action>
},
ActionType::RenamePaneByPaneId(a) => {
Ok(crate::input::actions::Action::RenamePaneByPaneId {
- pane_id: a
- .pane_id
- .ok_or_else(|| anyhow!("RenamePaneByPaneId missing pane_id"))?
- .try_into()?,
+ pane_id: a.pane_id.map(|p| p.try_into()).transpose()?,
name: a.name,
})
},
diff --git a/zellij-utils/src/ipc/tests/roundtrip_tests.rs b/zellij-utils/src/ipc/tests/roundtrip_tests.rs
index 7ef7ac89..23b8c4ac 100644
--- a/zellij-utils/src/ipc/tests/roundtrip_tests.rs
+++ b/zellij-utils/src/ipc/tests/roundtrip_tests.rs
@@ -1481,6 +1481,24 @@ fn test_client_messages() {
is_cli_client: true,
});
test_client_roundtrip!(ClientToServerMsg::Action {
+ action: Action::RenamePaneByPaneId {
+ pane_id: None,
+ name: "spark".as_bytes().to_vec(),
+ },
+ terminal_id: Some(1),
+ client_id: Some(100),
+ is_cli_client: true,
+ });
+ test_client_roundtrip!(ClientToServerMsg::Action {
+ action: Action::RenamePaneByPaneId {
+ pane_id: None,
+ name: "x".as_bytes().to_vec(),
+ },
+ terminal_id: Some(1),
+ client_id: Some(100),
+ is_cli_client: true,
+ });
+ test_client_roundtrip!(ClientToServerMsg::Action {
action: Action::NewTab {
tiled_layout: None,
floating_layouts: vec![],
@@ -3449,7 +3467,7 @@ fn test_client_messages() {
});
test_client_roundtrip!(ClientToServerMsg::Action {
action: Action::RenamePaneByPaneId {
- pane_id: PaneId::Terminal(1),
+ pane_id: Some(PaneId::Terminal(1)),
name: "test-name".as_bytes().to_vec(),
},
terminal_id: Some(1),
@@ -3694,3 +3712,44 @@ fn set_pane_color_wire_roundtrip() {
assert_eq!(original, roundtrip);
}
+
+/// Tests that RenamePaneByPaneId with `pane_id: None` (the active-pane CLI
+/// rename case) survives a full wire-level round-trip
+/// (Rust → proto struct → bytes → proto struct → Rust).
+///
+/// The struct-level round-trip in `test_client_roundtrip!` doesn't catch
+/// issues with prost's `tags` attribute on oneof fields, because prost
+/// only uses the tags list during byte decoding, not struct-to-struct
+/// conversion.
+#[test]
+fn rename_active_pane_wire_roundtrip() {
+ use prost::Message;
+
+ let original = ClientToServerMsg::Action {
+ action: Action::RenamePaneByPaneId {
+ pane_id: None,
+ name: "x".as_bytes().to_vec(),
+ },
+ terminal_id: Some(1),
+ client_id: Some(100),
+ is_cli_client: true,
+ };
+
+ // Rust → proto struct
+ let proto: crate::client_server_contract::client_server_contract::ClientToServerMsg =
+ original.clone().into();
+
+ // proto struct → bytes → proto struct (the actual wire path)
+ let mut buf = Vec::new();
+ proto.encode(&mut buf).expect("Failed to encode protobuf");
+ let decoded =
+ crate::client_server_contract::client_server_contract::ClientToServerMsg::decode(&buf[..])
+ .expect("Failed to decode protobuf bytes");
+
+ // proto struct → Rust
+ let roundtrip: ClientToServerMsg = decoded
+ .try_into()
+ .expect("Failed to convert decoded protobuf back to Rust");
+
+ assert_eq!(original, roundtrip);
+}