aboutsummaryrefslogtreecommitdiffstats
path: root/zellij-server/src
diff options
context:
space:
mode:
authoruser <user@clank>2026-08-08 22:19:25 +0200
committeruser <user@clank>2026-08-08 22:19:25 +0200
commit99af0bc5a8de8504d4f268b3c9e301e38ba12e14 (patch)
treeab6c2f76a4e4c0316135d2f73060f757b94249f0 /zellij-server/src
parentsl0p: don't let hover steal focus while typing into a rename prompt (diff)
downloadsl0ppij-99af0bc5a8de8504d4f268b3c9e301e38ba12e14.tar.gz
sl0ppij-99af0bc5a8de8504d4f268b3c9e301e38ba12e14.tar.xz
sl0ppij-99af0bc5a8de8504d4f268b3c9e301e38ba12e14.zip
sl0p: fix getting stuck in rename mode (base_mode wiped)
Double-click-to-rename trapped the client: once in RenamePane, Esc and Enter did nothing and there was no way out without killing the session. Cause: Screen::change_mode() does `mode_info.base_mode = base_mode`, i.e. it OVERWRITES the client's base mode with whatever it is handed. The double-click path passed None, believing that meant 'leave it alone'. It does not - ModeInfo::change_to_default_mode() is: if let Some(base_mode) = self.base_mode { self.mode = base_mode; } so a None base_mode makes leaving ANY temporary mode a silent no-op. The legitimate caller in lib.rs passes Some(base_mode) from the session config; this path had no equivalent. Fix: Tab::client_base_mode() reads the client's existing base_mode (falling back to the tab default) and the rename path passes it through, so entering rename via the mouse leaves the return path exactly as the keybind does. Added a test on ModeInfo pinning the trap itself: change_to_default_mode is a no-op with base_mode None, and returns to the base mode when it is set. That is the property that makes passing None dangerous, and it now fails loudly if anyone assumes otherwise. Immediate escape hatch for a session stuck on an older build: zellij action switch-mode locked (that goes through the server path, which sets base_mode correctly) Baselines held: zellij-server 1429/30, zellij-utils 436/5.
Diffstat (limited to 'zellij-server/src')
-rw-r--r--zellij-server/src/tab/mod.rs14
-rw-r--r--zellij-server/src/tab/mouse_handler.rs6
2 files changed, 19 insertions, 1 deletions
diff --git a/zellij-server/src/tab/mod.rs b/zellij-server/src/tab/mod.rs
index be326868..5792c0bc 100644
--- a/zellij-server/src/tab/mod.rs
+++ b/zellij-server/src/tab/mod.rs
@@ -7499,6 +7499,20 @@ impl Tab {
pub fn update_mouse_hover_effects(&mut self, mouse_hover_effects: bool) {
self.mouse_hover_effects = mouse_hover_effects;
}
+ /// The mode this client returns to when it leaves a temporary mode.
+ ///
+ /// Screen::change_mode OVERWRITES ModeInfo::base_mode with whatever it is
+ /// given, and ModeInfo::change_to_default_mode is a no-op when base_mode is
+ /// None. Passing None therefore does not mean "leave it alone", it means
+ /// "this client can never return to its base mode" - i.e. permanently stuck
+ /// in the mode you just set.
+ pub fn client_base_mode(&self, client_id: ClientId) -> Option<InputMode> {
+ self.mode_info
+ .borrow()
+ .get(&client_id)
+ .and_then(|mode_info| mode_info.base_mode)
+ .or(self.default_mode_info.base_mode)
+ }
/// Is this client currently typing into a modal prompt (renaming a pane or
/// tab, entering a search term)?
///
diff --git a/zellij-server/src/tab/mouse_handler.rs b/zellij-server/src/tab/mouse_handler.rs
index 7a329e4b..fc227bf1 100644
--- a/zellij-server/src/tab/mouse_handler.rs
+++ b/zellij-server/src/tab/mouse_handler.rs
@@ -791,10 +791,14 @@ impl MouseHandler {
clear_hover_for_client(tab, client_id);
tab.focus_pane_with_id(pane_id, false, false, client_id)
.with_context(err_context)?;
+ // base_mode MUST be preserved: Screen::change_mode overwrites it,
+ // and a None base_mode makes Esc/Enter a no-op, trapping the
+ // client in RenamePane with no way out.
+ let base_mode = tab.client_base_mode(client_id);
tab.senders
.send_to_screen(ScreenInstruction::ChangeMode(
InputMode::RenamePane,
- None,
+ base_mode,
client_id,
None,
))