diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-14 05:30:20 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-14 05:30:20 +0300 |
| commit | 226e87306deaba017d36583f51ae34e42b303aad (patch) | |
| tree | 2a1155689b75007c15d93735aedfa58db7a05edf /src/app | |
| parent | 17544c36ee397233e2f7b3319d86e3b517e3b5d0 (diff) | |
fix: preserve tab identity across restored sessions
Diffstat (limited to 'src/app')
| -rw-r--r-- | src/app/actions.rs | 18 | ||||
| -rw-r--r-- | src/app/api/layouts.rs | 7 | ||||
| -rw-r--r-- | src/app/api/tabs.rs | 12 | ||||
| -rw-r--r-- | src/app/creation.rs | 18 | ||||
| -rw-r--r-- | src/app/ids.rs | 32 | ||||
| -rw-r--r-- | src/app/input/modal.rs | 10 | ||||
| -rw-r--r-- | src/app/mod.rs | 55 |
7 files changed, 106 insertions, 46 deletions
diff --git a/src/app/actions.rs b/src/app/actions.rs index a7d99f35..99bd0ff6 100644 --- a/src/app/actions.rs +++ b/src/app/actions.rs @@ -31,6 +31,13 @@ fn is_completion_transition(change: &EffectiveStateChange) -> bool { ) } +fn public_tab_id_for_index(ws: &crate::workspace::Workspace, tab_idx: usize) -> Option<String> { + let tab_number = ws.public_tab_number(tab_idx)?; + Some(crate::workspace::public_tab_id_for_number( + &ws.id, tab_number, + )) +} + pub fn is_completion_transition_parts( previous_state: AgentState, state: AgentState, @@ -951,7 +958,8 @@ impl AppState { if let Some(ws) = self.workspaces.get_mut(idx) { let active_tab = ws.active_tab; ws.switch_tab(active_tab); - let tab_id = format!("{}:{}", workspace_id, active_tab + 1); + let tab_id = + public_tab_id_for_index(ws, active_tab).unwrap_or_else(|| workspace_id.clone()); crate::logging::tab_focused(&workspace_id, &tab_id); } self.tab_scroll_follow_active = true; @@ -994,7 +1002,8 @@ impl AppState { self.ensure_workspace_visible(ws_idx); if let Some(ws) = self.workspaces.get_mut(ws_idx) { ws.switch_tab(tab_idx); - let tab_id = format!("{}:{}", workspace_id, tab_idx + 1); + let tab_id = + public_tab_id_for_index(ws, tab_idx).unwrap_or_else(|| workspace_id.clone()); crate::logging::tab_focused(&workspace_id, &tab_id); } self.tab_scroll_follow_active = true; @@ -1092,7 +1101,7 @@ impl AppState { }; ws.switch_tab(idx); let workspace_id = ws.id.clone(); - let tab_id = format!("{}:{}", workspace_id, idx + 1); + let tab_id = public_tab_id_for_index(ws, idx).unwrap_or_else(|| workspace_id.clone()); crate::logging::tab_focused(&workspace_id, &tab_id); self.mark_session_dirty(); self.tab_scroll_follow_active = true; @@ -1853,7 +1862,8 @@ impl AppState { return false; }; let workspace_id = ws.id.clone(); - let closing_tab_id = format!("{}:{}", workspace_id, ws.active_tab + 1); + let closing_tab_id = + public_tab_id_for_index(ws, ws.active_tab).unwrap_or_else(|| workspace_id.clone()); ws.close_active_tab(); self.remove_plugin_pane_records(pane_ids); self.remove_unattached_terminal_ids(terminal_ids); diff --git a/src/app/api/layouts.rs b/src/app/api/layouts.rs index e397ccf8..9c492937 100644 --- a/src/app/api/layouts.rs +++ b/src/app/api/layouts.rs @@ -151,10 +151,9 @@ impl App { let closed_tab_id = self .public_tab_id(target_ws_idx, target_tab_idx) .unwrap_or_else(|| { - format!( - "{}:{}", - self.public_workspace_id(target_ws_idx), - target_tab_idx + 1 + crate::workspace::public_tab_id_for_number( + &self.public_workspace_id(target_ws_idx), + target_tab_idx + 1, ) }); let terminal_ids = self diff --git a/src/app/api/tabs.rs b/src/app/api/tabs.rs index c88a00d1..079c2ea4 100644 --- a/src/app/api/tabs.rs +++ b/src/app/api/tabs.rs @@ -105,11 +105,7 @@ impl App { if let Some(label) = label { let workspace_id = self.state.workspaces[ws_idx].id.clone(); let tab_id = self.public_tab_id(ws_idx, tab_idx).unwrap_or_else(|| { - format!( - "{}:t{}", - workspace_id, - crate::workspace::encode_public_number(tab_idx + 1) - ) + crate::workspace::public_tab_id_for_number(&workspace_id, tab_idx + 1) }); if let Some(tab) = self .state @@ -165,9 +161,9 @@ impl App { return tab_not_found(id, ¶ms.tab_id); }; let workspace_id = self.state.workspaces[ws_idx].id.clone(); - let tab_id = self - .public_tab_id(ws_idx, tab_idx) - .unwrap_or_else(|| format!("{}:{}", workspace_id, tab_idx + 1)); + let tab_id = self.public_tab_id(ws_idx, tab_idx).unwrap_or_else(|| { + crate::workspace::public_tab_id_for_number(&workspace_id, tab_idx + 1) + }); let Some(tab) = self .state .workspaces diff --git a/src/app/creation.rs b/src/app/creation.rs index a45dae03..e53bce00 100644 --- a/src/app/creation.rs +++ b/src/app/creation.rs @@ -123,13 +123,9 @@ impl App { self.state.mode = Mode::Terminal; } let workspace_id = self.state.workspaces[ws_idx].id.clone(); - let tab_id = self.public_tab_id(ws_idx, idx).unwrap_or_else(|| { - format!( - "{}:t{}", - workspace_id, - crate::workspace::encode_public_number(idx + 1) - ) - }); + let tab_id = self + .public_tab_id(ws_idx, idx) + .unwrap_or_else(|| crate::workspace::public_tab_id_for_number(&workspace_id, idx + 1)); let root_pane = self.state.workspaces[ws_idx].tabs[idx].root_pane.raw(); crate::logging::tab_created(&workspace_id, &tab_id, root_pane); self.schedule_session_save(); @@ -240,7 +236,7 @@ impl App { Some(crate::api::schema::TabInfo { tab_id: self.public_tab_id(ws_idx, tab_idx)?, workspace_id: self.public_workspace_id(ws_idx), - number: tab_idx + 1, + number: tab.number, label: tab.display_name(), focused: self.state.active == Some(ws_idx) && ws.active_tab == tab_idx, pane_count: tab.panes.len(), @@ -349,9 +345,9 @@ impl App { focused: self.state.active == Some(index), pane_count: ws.public_pane_numbers.len(), tab_count: ws.tabs.len(), - active_tab_id: self - .public_tab_id(index, ws.active_tab) - .unwrap_or_else(|| format!("{}:{}", ws.id, ws.active_tab + 1)), + active_tab_id: self.public_tab_id(index, ws.active_tab).unwrap_or_else(|| { + crate::workspace::public_tab_id_for_number(&ws.id, ws.active_tab + 1) + }), agent_status: pane_agent_status(agg_state, seen), worktree: ws .worktree_space() diff --git a/src/app/ids.rs b/src/app/ids.rs index 42cf47a8..9dc4371e 100644 --- a/src/app/ids.rs +++ b/src/app/ids.rs @@ -19,10 +19,8 @@ impl App { pub(super) fn public_tab_id(&self, ws_idx: usize, tab_idx: usize) -> Option<String> { let ws = self.state.workspaces.get(ws_idx)?; let tab_number = ws.public_tab_number(tab_idx)?; - Some(format!( - "{}:t{}", - ws.id, - crate::workspace::encode_public_number(tab_number) + Some(crate::workspace::public_tab_id_for_number( + &ws.id, tab_number, )) } @@ -33,10 +31,9 @@ impl App { ) -> Option<String> { let ws = self.state.workspaces.get(ws_idx)?; let pane_number = ws.public_pane_number(pane_id)?; - Some(format!( - "{}:p{}", - ws.id, - crate::workspace::encode_public_number(pane_number) + Some(crate::workspace::public_pane_id_for_number( + &ws.id, + pane_number, )) } @@ -80,18 +77,17 @@ impl App { let (ws_raw, tab_raw) = id.rsplit_once(':')?; let ws_idx = self.parse_workspace_id(ws_raw)?; - let tab_number = if let Some(encoded) = tab_raw.strip_prefix('t') { - crate::workspace::decode_public_number(encoded)? + let tab_idx = if let Some(encoded) = tab_raw.strip_prefix('t') { + let tab_number = crate::workspace::decode_public_number(encoded)?; + self.state + .workspaces + .get(ws_idx)? + .tabs + .iter() + .position(|tab| tab.number == tab_number)? } else { - tab_raw.parse::<usize>().ok()? + tab_raw.parse::<usize>().ok()?.checked_sub(1)? }; - let tab_idx = self - .state - .workspaces - .get(ws_idx)? - .tabs - .iter() - .position(|tab| tab.number == tab_number)?; self.state.workspaces.get(ws_idx)?.tabs.get(tab_idx)?; Some((ws_idx, tab_idx)) } diff --git a/src/app/input/modal.rs b/src/app/input/modal.rs index 3d106765..cff760bf 100644 --- a/src/app/input/modal.rs +++ b/src/app/input/modal.rs @@ -458,7 +458,15 @@ pub(super) fn apply_rename_action(state: &mut AppState, action: ModalAction) { tab.is_auto_named() && new_name == tab.number.to_string(); if !new_name.is_empty() && !keep_auto_name { tab.set_custom_name(new_name); - let tab_id = format!("{}:{}", workspace_id, active_tab + 1); + let tab_id = ws + .public_tab_number(active_tab) + .map(|number| { + crate::workspace::public_tab_id_for_number( + &workspace_id, + number, + ) + }) + .unwrap_or_else(|| workspace_id.clone()); crate::logging::tab_renamed(&workspace_id, &tab_id); state.mark_session_dirty(); } diff --git a/src/app/mod.rs b/src/app/mod.rs index 356ecbab..36fedb71 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -2932,6 +2932,61 @@ mod tests { } #[test] + fn tab_info_number_uses_stable_public_tab_number() { + let mut app = test_app(); + let mut workspace = Workspace::test_new("api-tab-public-number"); + let removed_tab = workspace.test_add_tab(None); + let survivor_tab = workspace.test_add_tab(None); + let survivor_pane = workspace.tabs[survivor_tab].root_pane; + assert!(workspace.close_tab(removed_tab)); + app.state.workspaces = vec![workspace]; + app.state.ensure_test_terminals(); + app.state.active = Some(0); + app.state.selected = 0; + let survivor_idx = app.state.workspaces[0] + .find_tab_index_for_pane(survivor_pane) + .unwrap(); + + let tab = app.tab_info(0, survivor_idx).unwrap(); + + assert_eq!(tab.tab_id, format!("{}:t3", app.state.workspaces[0].id)); + assert_eq!(tab.number, 3); + assert_eq!(tab.label, "3"); + } + + #[test] + fn legacy_bare_tab_id_uses_tab_position_not_public_tab_number() { + let mut app = test_app(); + let mut workspace = Workspace::test_new("legacy-tab-id"); + let removed_tab = workspace.test_add_tab(None); + workspace.test_add_tab(None); + let public_four_tab = workspace.test_add_tab(None); + let fourth_position_tab = workspace.test_add_tab(None); + let public_four_pane = workspace.tabs[public_four_tab].root_pane; + let fourth_position_pane = workspace.tabs[fourth_position_tab].root_pane; + assert!(workspace.close_tab(removed_tab)); + app.state.workspaces = vec![workspace]; + + let public_four_idx = app.state.workspaces[0] + .find_tab_index_for_pane(public_four_pane) + .unwrap(); + let fourth_position_idx = app.state.workspaces[0] + .find_tab_index_for_pane(fourth_position_pane) + .unwrap(); + + assert_eq!(app.state.workspaces[0].tabs[public_four_idx].number, 4); + assert_eq!(app.state.workspaces[0].tabs[fourth_position_idx].number, 5); + assert_eq!( + app.parse_tab_id(&format!("{}:t4", app.state.workspaces[0].id)), + Some((0, public_four_idx)) + ); + assert_eq!( + app.parse_tab_id(&format!("{}:4", app.state.workspaces[0].id)), + Some((0, fourth_position_idx)) + ); + } + + #[test] fn workspace_creation_in_navigate_mode_uses_selected_workspace_seed_cwd() { let mut app = test_app(); let mut first = Workspace::test_new("herdr"); |
