diff options
| author | divens <divens@users.noreply.github.com> | 2026-04-06 10:34:23 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-04-06 10:34:23 +0200 |
| commit | 2b3ae1e69cbf6a84599415ad80de456970199961 (patch) | |
| tree | 66d13c802e4a1fdbb5af26c1faa7ea15e167d2f2 /zellij-utils/src | |
| parent | docs(changelog): update recent PRs (diff) | |
| download | sl0ppij-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.rs | 1 | ||||
| -rw-r--r-- | zellij-utils/src/input/actions.rs | 43 | ||||
| -rw-r--r-- | zellij-utils/src/ipc/protobuf_conversion.rs | 7 | ||||
| -rw-r--r-- | zellij-utils/src/ipc/tests/roundtrip_tests.rs | 61 |
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); +} |
