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 {