diff options
| author | user <user@clank> | 2026-08-09 00:00:45 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-08-09 00:00:45 +0200 |
| commit | e44292e32cd3fb6d21a34386652c44df872fe797 (patch) | |
| tree | 74a97b9300900c93fc2e5b4297e517c4d191cc57 | |
| parent | sl0p: preview pane reordering live while dragging (diff) | |
| download | sl0ppij-e44292e32cd3fb6d21a34386652c44df872fe797.tar.gz sl0ppij-e44292e32cd3fb6d21a34386652c44df872fe797.tar.xz sl0ppij-e44292e32cd3fb6d21a34386652c44df872fe797.zip | |
sl0p: first keystroke replaces the old tab name when renaming
Entering RenameTab left the existing name in place and appended to it, so
renaming a tab meant clearing it by hand first. The first printable keystroke
now REPLACES it, the way a text field with its contents selected behaves.
Armed when a client enters RenameTab and disarmed when it leaves, so a
keystroke that arrives later cannot wipe a name the user was not editing.
Backspace and delete deliberately do NOT replace: reaching for them means
editing the existing name, not discarding it.
The flag is taken out of the set BEFORE borrowing the active tab, since
get_active_tab_mut borrows self mutably and every branch consumes the flag
anyway.
Applies to renames from any entry point - the keybind and the sl0pstatus
double-click-a-tab both go through Screen::change_mode.
Pane rename is deliberately left alone for now; say the word and it can get
the same treatment.
Baselines held: zellij-server 1441/30.
| -rw-r--r-- | README.md | 5 | ||||
| -rw-r--r-- | zellij-server/src/screen.rs | 26 |
2 files changed, 30 insertions, 1 deletions
@@ -91,6 +91,11 @@ the title is the drag handle for moving the pane, so that path is left alone for Also in the fork, not configurable: +- Renaming a tab REPLACES the existing name on the first keystroke, the way a text + field with its contents selected behaves, instead of appending to it. Backspace + and delete count as editing rather than replacing, so they still work on the old + name. + - A compact scroll indicator (`↑42`) in place of upstream's `SCROLL: 42/1000`, shown only when actually scrolled up rather than whenever scrollback exists. - `sl0p.foo` branding in the default tab-bar plugin. diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs index d4a02cd6..8083de12 100644 --- a/zellij-server/src/screen.rs +++ b/zellij-server/src/screen.rs @@ -1515,6 +1515,11 @@ pub(crate) struct Screen { tab_history: BTreeMap<ClientId, Vec<usize>>, pane_history: BTreeMap<ClientId, Vec<PaneId>>, mode_info: BTreeMap<ClientId, ModeInfo>, + /// Clients that have entered RenameTab mode but not yet typed anything. + /// The first printable character REPLACES the existing name instead of + /// appending to it, the way a text field with its contents selected + /// behaves. Cleared as soon as the client types or leaves the mode. + tab_rename_replaces_on_first_key: HashSet<ClientId>, default_mode_info: ModeInfo, // TODO: restructure ModeInfo to prevent this duplication style: Style, pane_frame_style: PaneFrameStyle, @@ -1702,6 +1707,7 @@ impl Screen { tab_history: BTreeMap::new(), pane_history: BTreeMap::new(), mode_info: BTreeMap::new(), + tab_rename_replaces_on_first_key: HashSet::new(), default_mode_info: mode_info, pane_frame_style, auto_layout, @@ -5225,6 +5231,11 @@ impl Screen { let s = str::from_utf8(&buf) .with_context(|| format!("failed to construct tab name from buf: {buf:?}")) .with_context(err_context)?; + // Taken BEFORE borrowing the tab: every branch below consumes + // it, so it is simply removed here rather than juggled against + // the mutable borrow of self. + let replaces_existing_name = + self.tab_rename_replaces_on_first_key.remove(&client_id); match self.get_active_tab_mut(client_id) { Ok(active_tab) => { match s { @@ -5232,10 +5243,17 @@ impl Screen { active_tab.name = String::new(); }, "\u{007F}" | "\u{0008}" => { - // delete and backspace keys + // delete and backspace count as EDITING the + // existing name rather than replacing it, so + // they intentionally do not clear it active_tab.name.pop(); }, c => { + if replaces_existing_name { + // first keystroke after entering rename + // mode: overwrite rather than append + active_tab.name.clear(); + } active_tab .name .push_str(&clean_string_from_control_and_linebreak(c)); @@ -5491,6 +5509,12 @@ impl Screen { if let Ok(active_tab) = self.get_active_tab_mut(client_id) { active_tab.prev_name = active_tab.name.clone(); } + // arm the replace-on-first-key behaviour + self.tab_rename_replaces_on_first_key.insert(client_id); + } else { + // leaving the mode disarms it, so a later stray keystroke cannot + // wipe a name the user did not mean to edit + self.tab_rename_replaces_on_first_key.remove(&client_id); } if mode_info.mode == InputMode::RenamePane { |
