From 1287018ed91e843921fa822de9e52d144db63496 Mon Sep 17 00:00:00 2001 From: "MVB.Mir" Date: Fri, 17 Apr 2026 03:10:16 +0300 Subject: [PATCH] Split Ctrl+W into close-tab vs close-pane shortcuts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously Ctrl+W closed the whole pane. That's surgery — the common case for most users is closing a single tab. Add a dedicated CloseFocusedTab shortcut and move CloseFocusedPane out of the way. - New ShortcutId / ShortcutCommand: CloseFocusedTab. Default accel: w. Closes the active tab in the focused pane; when the last tab closes, the pane's on_empty callback fires and the pane is removed (and the workspace if that was the last pane). - CloseFocusedPane default accel remapped from w to w. Still closes the whole pane (all its tabs). - Added pane::close_active_tab_in_pane() — the underlying helper that the new shortcut dispatches to. Same code path as the close X on each tab and the middle-click close. - Close pane button tooltip already renders the current keybinding via pane_action_tooltip / shortcuts.tooltip_text, so it automatically reflects the new Ctrl+Alt+W default and any user remaps — no code change needed there. - definitions_cover_current_host_shortcuts test updated (47 → 48). Users with an existing shortcuts.json keeping the old "close_focused_pane" override will continue to see their chosen binding for that action; they'll need to bind close_focused_tab themselves if the default Ctrl+W is not what they want. --- rust/limux-host-linux/src/pane.rs | 33 ++++++++++++++++++++ rust/limux-host-linux/src/shortcut_config.rs | 19 +++++++++-- rust/limux-host-linux/src/window.rs | 17 ++++++++-- 3 files changed, 64 insertions(+), 5 deletions(-) diff --git a/rust/limux-host-linux/src/pane.rs b/rust/limux-host-linux/src/pane.rs index 931350d7..189663b3 100644 --- a/rust/limux-host-linux/src/pane.rs +++ b/rust/limux-host-linux/src/pane.rs @@ -617,6 +617,39 @@ pub fn cycle_tab_in_pane(pane_widget: >k::Widget, delta: i32) { (internals.callbacks.on_state_changed)(); } +/// Close the currently active tab in the given pane. If this was the last +/// tab, the pane is closed via the `on_empty` callback (which in turn closes +/// the workspace if that was the last pane). +pub fn close_active_tab_in_pane(pane_widget: >k::Widget) { + let Some(outer) = pane_widget.downcast_ref::() else { + return; + }; + let internals: Rc = unsafe { + match outer.data::>("limux-pane-internals") { + Some(ptr) => ptr.as_ref().clone(), + None => return, + } + }; + + let active_id = { + let ts = internals.tab_state.borrow(); + ts.active_tab.clone() + }; + let Some(tab_id) = active_id else { + return; + }; + + remove_tab( + &internals.tab_strip, + &internals.content_stack, + &internals.tab_state, + &tab_id, + &internals.callbacks, + outer, + PaneEmptyReason::ClosedLastTab, + ); +} + pub fn focus_active_tab_in_pane(pane_widget: >k::Widget) -> bool { let Some(internals) = find_pane_internals(pane_widget) else { return false; diff --git a/rust/limux-host-linux/src/shortcut_config.rs b/rust/limux-host-linux/src/shortcut_config.rs index d1a9bd6e..d469f33f 100644 --- a/rust/limux-host-linux/src/shortcut_config.rs +++ b/rust/limux-host-linux/src/shortcut_config.rs @@ -28,6 +28,7 @@ pub enum ShortcutId { NewTerminalInFocusedPane, SplitRight, CloseFocusedPane, + CloseFocusedTab, NewTerminal, FocusLeft, FocusRight, @@ -79,6 +80,7 @@ pub enum ShortcutCommand { NewTerminal, SplitRight, CloseFocusedPane, + CloseFocusedTab, FocusLeft, FocusRight, FocusUp, @@ -308,7 +310,7 @@ struct ShortcutConfigFile { shortcuts: HashMap, } -const SHORTCUT_DEFINITIONS: [ShortcutDefinition; 47] = [ +const SHORTCUT_DEFINITIONS: [ShortcutDefinition; 48] = [ ShortcutDefinition { id: ShortcutId::NewWorkspace, config_key: "new_workspace", @@ -463,11 +465,22 @@ const SHORTCUT_DEFINITIONS: [ShortcutDefinition; 47] = [ scope: ShortcutScope::Window, editable_capture_policy: EditableCapturePolicy::BypassInEditable, }, + ShortcutDefinition { + id: ShortcutId::CloseFocusedTab, + config_key: "close_focused_tab", + action_name: "win.close-focused-tab", + default_accel: "w", + label: "Close Focused Tab", + registers_gtk_accel: false, + command: ShortcutCommand::CloseFocusedTab, + scope: ShortcutScope::Window, + editable_capture_policy: EditableCapturePolicy::BypassInEditable, + }, ShortcutDefinition { id: ShortcutId::CloseFocusedPane, config_key: "close_focused_pane", action_name: "win.close-focused-pane", - default_accel: "w", + default_accel: "w", label: "Close Focused Pane", registers_gtk_accel: false, command: ShortcutCommand::CloseFocusedPane, @@ -1594,7 +1607,7 @@ mod tests { #[test] fn definitions_cover_current_host_shortcuts() { - assert_eq!(definitions().len(), 47); + assert_eq!(definitions().len(), 48); } #[test] diff --git a/rust/limux-host-linux/src/window.rs b/rust/limux-host-linux/src/window.rs index 083e7cb0..c4b804a7 100644 --- a/rust/limux-host-linux/src/window.rs +++ b/rust/limux-host-linux/src/window.rs @@ -1719,7 +1719,11 @@ fn dispatch_shortcut_command(state: &State, command: ShortcutCommand) -> bool { true } ShortcutCommand::CloseFocusedPane => { - close_focused_tab(state); + close_focused_pane(state); + true + } + ShortcutCommand::CloseFocusedTab => { + close_focused_active_tab(state); true } ShortcutCommand::FocusLeft => { @@ -4676,7 +4680,7 @@ fn cycle_focused_pane_tab(state: &State, delta: i32) { } } -fn close_focused_tab(state: &State) { +fn close_focused_pane(state: &State) { if let Some((ws_id, pane_widget)) = find_focused_pane(state) { let parent = pane_widget.parent(); // If this is the only pane (parent is Stack), don't close — keep workspace alive @@ -4689,6 +4693,15 @@ fn close_focused_tab(state: &State) { } } +/// Close the active tab inside the focused pane. When the last tab closes, +/// the pane's on_empty callback fires and the pane is removed (and the +/// workspace if it was the last pane). +fn close_focused_active_tab(state: &State) { + if let Some((_ws_id, pane_widget)) = find_focused_pane(state) { + pane::close_active_tab_in_pane(&pane_widget); + } +} + fn add_tab_to_focused_pane(_state: &State, _browser: bool) { if let Some((_ws_id, pane_widget)) = find_focused_pane(_state) { if _browser {