aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoruser <user@clank>2026-08-09 00:00:45 +0200
committeruser <user@clank>2026-08-09 00:00:45 +0200
commite44292e32cd3fb6d21a34386652c44df872fe797 (patch)
tree74a97b9300900c93fc2e5b4297e517c4d191cc57
parentsl0p: preview pane reordering live while dragging (diff)
downloadsl0ppij-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.md5
-rw-r--r--zellij-server/src/screen.rs26
2 files changed, 30 insertions, 1 deletions
diff --git a/README.md b/README.md
index cfc1e4d1..33fa9f28 100644
--- a/README.md
+++ b/README.md
@@ -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 {