aboutsummaryrefslogtreecommitdiff
path: root/src/app
diff options
context:
space:
mode:
authorSeason Saw <season.saw@gmail.com>2026-07-27 21:17:14 +0800
committerGitHub <noreply@github.com>2026-07-27 16:17:14 +0300
commita979916568b0225123711b1aa401d67102e3cf95 (patch)
tree5eca8f1db66b4b03f35c64b532cf10d5072369d4 /src/app
parent6d1bedee3ee164ed0151f8adab1313be5915d652 (diff)
fix: keep focus on the current workspace when another self-closes (#1912)
handle_pane_died only clamped the stale active/selected indices after removing a workspace that closed itself, so it never shifted them down when the removed workspace sat before the focused one. With workspaces [x, y, z] and focus on y, removing x left active pointing at z, silently moving the user into a workspace they did not select. Resolve both indices by workspace id across the removal, as close_selected_workspace already does, keeping the clamp as the fallback for when the removed workspace was the focused one. Also scroll the focused workspace back into view, which the explicit-close path already did and this one did not. refs #1621 Co-authored-by: Can Celik <ogulcancelik@gmail.com>
Diffstat (limited to 'src/app')
-rw-r--r--src/app/actions.rs48
1 files changed, 48 insertions, 0 deletions
diff --git a/src/app/actions.rs b/src/app/actions.rs
index c23c219c..91572765 100644
--- a/src/app/actions.rs
+++ b/src/app/actions.rs
@@ -3326,6 +3326,11 @@ impl AppState {
self.mark_session_dirty();
if should_close_workspace {
+ let active_workspace_id = self
+ .active
+ .and_then(|idx| self.workspaces.get(idx))
+ .map(|ws| ws.id.clone());
+ let selected_workspace_id = self.workspaces.get(self.selected).map(|ws| ws.id.clone());
self.workspaces.remove(ws_idx);
self.remove_unattached_terminal_ids(workspace_terminal_ids);
if self.workspaces.is_empty() {
@@ -3335,14 +3340,29 @@ impl AppState {
self.mode = Mode::Navigate;
}
} else {
+ // Keep focus on the previously focused workspace
+ if let Some(id) = active_workspace_id {
+ if let Some(idx) = self.workspaces.iter().position(|ws| ws.id == id) {
+ self.active = Some(idx);
+ }
+ }
if let Some(active) = self.active {
if active >= self.workspaces.len() {
self.active = Some(self.workspaces.len() - 1);
}
}
+ if let Some(id) = selected_workspace_id {
+ if let Some(idx) = self.workspaces.iter().position(|ws| ws.id == id) {
+ self.selected = idx;
+ }
+ }
if self.selected >= self.workspaces.len() {
self.selected = self.workspaces.len() - 1;
}
+ self.workspace_scroll = self
+ .workspace_scroll
+ .min(self.workspaces.len().saturating_sub(1));
+ self.ensure_workspace_visible(self.selected);
}
} else {
self.remove_unattached_terminal_ids(pane_terminal_id);
@@ -4646,6 +4666,34 @@ mod tests {
}
#[test]
+ fn pane_died_self_closing_earlier_workspace_keeps_focus() {
+ let names = (0..20).map(|i| format!("ws{i:02}")).collect::<Vec<_>>();
+ let name_refs = names.iter().map(String::as_str).collect::<Vec<_>>();
+ let mut state = app_with_workspaces(&name_refs);
+ state.selected = 1;
+ state.active = Some(1);
+ // Constrain the sidebar viewport so the focused workspace starts
+ // off-screen: 20 workspaces cannot all fit in 12 rows, so index 1 is
+ // hidden at maximum scroll regardless of individual card height.
+ state.view.sidebar_rect = ratatui::layout::Rect::new(0, 0, 30, 12);
+ state.workspace_scroll =
+ crate::ui::normalized_workspace_scroll(&state, state.view.sidebar_rect, usize::MAX / 2);
+ let cards = crate::ui::compute_workspace_card_areas(&state, state.view.sidebar_rect);
+ assert!(cards.iter().all(|card| card.ws_idx != 1));
+
+ let pane_id = *state.workspaces[0].panes.keys().next().unwrap();
+ state.handle_pane_died(pane_id);
+
+ assert_eq!(state.workspaces.len(), 19);
+ assert_eq!(state.workspaces[0].display_name(), "ws01");
+ assert_eq!(state.selected, 0);
+ assert_eq!(state.active, Some(0));
+ let cards = crate::ui::compute_workspace_card_areas(&state, state.view.sidebar_rect);
+ assert!(cards.iter().any(|card| card.ws_idx == 0));
+ state.assert_invariants_for_test();
+ }
+
+ #[test]
fn pane_died_last_pane_removes_workspace() {
let mut state = app_with_workspaces(&["a", "b"]);
let pane_id = *state.workspaces[0].panes.keys().next().unwrap();