diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-12 19:07:07 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-12 19:10:09 +0300 |
| commit | f7a7da03fc27cc6fe4e6a44dabddcd303f939bb9 (patch) | |
| tree | 69a00241e4500703bb723b64dd0060cc83730a0b /src/app | |
| parent | 53429206b736aa0fcfb1c5bfc1c0b11dcca3a059 (diff) | |
fix: stabilize public pane and tab ids
refs #569
Diffstat (limited to 'src/app')
| -rw-r--r-- | src/app/actions.rs | 1 | ||||
| -rw-r--r-- | src/app/agent_resume.rs | 4 | ||||
| -rw-r--r-- | src/app/api.rs | 4 | ||||
| -rw-r--r-- | src/app/api/panes.rs | 14 | ||||
| -rw-r--r-- | src/app/api/tabs.rs | 18 | ||||
| -rw-r--r-- | src/app/api/workspaces.rs | 5 | ||||
| -rw-r--r-- | src/app/creation.rs | 10 | ||||
| -rw-r--r-- | src/app/ids.rs | 38 | ||||
| -rw-r--r-- | src/app/input/modal.rs | 8 | ||||
| -rw-r--r-- | src/app/input/navigate.rs | 4 | ||||
| -rw-r--r-- | src/app/input/sidebar.rs | 5 | ||||
| -rw-r--r-- | src/app/mod.rs | 10 |
12 files changed, 90 insertions, 31 deletions
diff --git a/src/app/actions.rs b/src/app/actions.rs index b218e53c..c2b35dbd 100644 --- a/src/app/actions.rs +++ b/src/app/actions.rs @@ -3162,6 +3162,7 @@ mod tests { events, std::sync::Arc::new(tokio::sync::Notify::new()), std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)), + None, ) .unwrap(); diff --git a/src/app/agent_resume.rs b/src/app/agent_resume.rs index 894b0b37..cf691e33 100644 --- a/src/app/agent_resume.rs +++ b/src/app/agent_resume.rs @@ -223,6 +223,9 @@ impl App { ); return false; }; + let public_pane_id = self + .find_pane(pane_id) + .and_then(|(ws_idx, _)| self.public_pane_id(ws_idx, pane_id)); let runtime = match crate::terminal::TerminalRuntime::spawn( pane_id, @@ -235,6 +238,7 @@ impl App { self.event_tx.clone(), self.render_notify.clone(), self.render_dirty.clone(), + public_pane_id.as_deref(), ) { Ok(runtime) => runtime, Err(err) => { diff --git a/src/app/api.rs b/src/app/api.rs index ea4ea378..32192a07 100644 --- a/src/app/api.rs +++ b/src/app/api.rs @@ -327,6 +327,7 @@ impl App { let Some((ws_idx, pane_state)) = self.find_pane(pane_id) else { return false; }; + let public_pane_id = self.public_pane_id(ws_idx, pane_id); let terminal_id = pane_state.attached_terminal_id.clone(); let Some(terminal) = self.state.terminals.get(&terminal_id) else { return false; @@ -349,6 +350,7 @@ impl App { self.event_tx.clone(), self.render_notify.clone(), self.render_dirty.clone(), + public_pane_id.as_deref(), ) { Ok(runtime) => runtime, Err(err) => { @@ -1236,6 +1238,7 @@ mod tests { events, std::sync::Arc::new(tokio::sync::Notify::new()), std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)), + None, ) .unwrap(); let deadline = std::time::Instant::now() + std::time::Duration::from_secs(2); @@ -1327,6 +1330,7 @@ mod tests { events, std::sync::Arc::new(tokio::sync::Notify::new()), std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)), + None, ) .unwrap(); let deadline = std::time::Instant::now() + std::time::Duration::from_secs(2); diff --git a/src/app/api/panes.rs b/src/app/api/panes.rs index 5afac850..326fbd9f 100644 --- a/src/app/api/panes.rs +++ b/src/app/api/panes.rs @@ -599,6 +599,9 @@ impl App { let Some((ws_idx, pane_id)) = self.parse_pane_id(¶ms.pane_id) else { return pane_not_found(id, ¶ms.pane_id); }; + let Some(public_pane_id) = self.public_pane_id(ws_idx, pane_id) else { + return pane_not_found(id, ¶ms.pane_id); + }; let Some((pane, workspace_id)) = self.lookup_runtime(ws_idx, pane_id) else { return pane_not_found(id, ¶ms.pane_id); }; @@ -630,7 +633,7 @@ impl App { id, ResponseResult::PaneRead { read: PaneReadResult { - pane_id: params.pane_id, + pane_id: public_pane_id, workspace_id, tab_id: self.public_tab_id(ws_idx, tab_idx).unwrap(), source: params.source, @@ -887,6 +890,10 @@ impl App { let Some((ws_idx, pane_id)) = self.parse_pane_id(&target.pane_id) else { return pane_not_found(id, &target.pane_id); }; + let Some(public_pane_id) = self.public_pane_id(ws_idx, pane_id) else { + return pane_not_found(id, &target.pane_id); + }; + let workspace_id = self.public_workspace_id(ws_idx); if self.state.close_pane_would_close_workspace(ws_idx, pane_id) && self.state.confirm_implicit_worktree_group_close(ws_idx) { @@ -896,7 +903,6 @@ impl App { "closing this pane would close a worktree group", ); } - let workspace_id = self.state.workspaces[ws_idx].id.clone(); let terminal_id = self.state.terminal_id_for_pane(ws_idx, pane_id); let should_close_workspace = { let Some(ws) = self.state.workspaces.get_mut(ws_idx) else { @@ -911,7 +917,7 @@ impl App { self.emit_event(EventEnvelope { event: EventKind::PaneClosed, data: EventData::PaneClosed { - pane_id: target.pane_id.clone(), + pane_id: public_pane_id, workspace_id: workspace_id.clone(), }, }); @@ -926,7 +932,7 @@ impl App { self.emit_event(EventEnvelope { event: EventKind::PaneClosed, data: EventData::PaneClosed { - pane_id: target.pane_id, + pane_id: public_pane_id, workspace_id, }, }); diff --git a/src/app/api/tabs.rs b/src/app/api/tabs.rs index 4057eced..7c7ad9d7 100644 --- a/src/app/api/tabs.rs +++ b/src/app/api/tabs.rs @@ -98,9 +98,13 @@ 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!("{}:{}", workspace_id, tab_idx + 1)); + 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) + ) + }); if let Some(tab) = self .state .workspaces @@ -186,6 +190,10 @@ impl App { let Some((ws_idx, tab_idx)) = self.parse_tab_id(&target.tab_id) else { return tab_not_found(id, &target.tab_id); }; + let Some(tab_id) = self.public_tab_id(ws_idx, tab_idx) else { + return tab_not_found(id, &target.tab_id); + }; + let workspace_id = self.public_workspace_id(ws_idx); let terminal_ids = self.state.terminal_ids_for_tab(ws_idx, tab_idx); let Some(ws) = self.state.workspaces.get_mut(ws_idx) else { return tab_not_found(id, &target.tab_id); @@ -210,8 +218,8 @@ impl App { self.emit_event(EventEnvelope { event: EventKind::TabClosed, data: EventData::TabClosed { - tab_id: target.tab_id, - workspace_id: self.public_workspace_id(ws_idx), + tab_id, + workspace_id, }, }); diff --git a/src/app/api/workspaces.rs b/src/app/api/workspaces.rs index 92b41486..e1966adf 100644 --- a/src/app/api/workspaces.rs +++ b/src/app/api/workspaces.rs @@ -146,14 +146,13 @@ impl App { if self.state.workspaces.get(index).is_none() { return workspace_not_found(id, &target.workspace_id); } + let workspace_id = self.public_workspace_id(index); self.state.selected = index; self.state.close_selected_workspace(); self.shutdown_detached_terminal_runtimes(); self.emit_event(EventEnvelope { event: EventKind::WorkspaceClosed, - data: EventData::WorkspaceClosed { - workspace_id: target.workspace_id, - }, + data: EventData::WorkspaceClosed { workspace_id }, }); encode_success(id, ResponseResult::Ok {}) diff --git a/src/app/creation.rs b/src/app/creation.rs index 3f643986..7be324e5 100644 --- a/src/app/creation.rs +++ b/src/app/creation.rs @@ -122,9 +122,13 @@ 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!("{}:{}", workspace_id, idx + 1)); + 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 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(); diff --git a/src/app/ids.rs b/src/app/ids.rs index a18efd2d..0ce02c1f 100644 --- a/src/app/ids.rs +++ b/src/app/ids.rs @@ -18,8 +18,12 @@ 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)?; - ws.tabs.get(tab_idx)?; - Some(format!("{}:{}", ws.id, tab_idx + 1)) + let tab_number = ws.public_tab_number(tab_idx)?; + Some(format!( + "{}:t{}", + ws.id, + crate::workspace::encode_public_number(tab_number) + )) } pub(super) fn public_pane_id( @@ -29,7 +33,11 @@ impl App { ) -> Option<String> { let ws = self.state.workspaces.get(ws_idx)?; let pane_number = ws.public_pane_number(pane_id)?; - Some(format!("{}-{pane_number}", ws.id)) + Some(format!( + "{}:p{}", + ws.id, + crate::workspace::encode_public_number(pane_number) + )) } pub(super) fn parse_workspace_id(&self, id: &str) -> Option<usize> { @@ -52,7 +60,18 @@ impl App { let (ws_raw, tab_raw) = id.rsplit_once(':')?; let ws_idx = self.parse_workspace_id(ws_raw)?; - let tab_idx = tab_raw.parse::<usize>().ok()?.checked_sub(1)?; + let tab_number = if let Some(encoded) = tab_raw.strip_prefix('t') { + crate::workspace::decode_public_number(encoded)? + } else { + tab_raw.parse::<usize>().ok()? + }; + 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)) } @@ -81,6 +100,17 @@ impl App { return self.find_pane(pane_id).map(|(ws_idx, _)| (ws_idx, pane_id)); } + if let Some((ws_raw, pane_number_raw)) = id.rsplit_once(":p") { + let ws_idx = self.parse_workspace_id(ws_raw)?; + let pane_number = crate::workspace::decode_public_number(pane_number_raw)?; + let ws = self.state.workspaces.get(ws_idx)?; + let pane_id = ws + .public_pane_numbers + .iter() + .find_map(|(pane_id, number)| (*number == pane_number).then_some(*pane_id))?; + return Some((ws_idx, pane_id)); + } + let (ws_raw, pane_number_raw) = id.rsplit_once('-')?; let ws_idx = self.parse_workspace_id(ws_raw)?; let pane_number = pane_number_raw.parse::<usize>().ok()?; diff --git a/src/app/input/modal.rs b/src/app/input/modal.rs index 79d066f9..3d106765 100644 --- a/src/app/input/modal.rs +++ b/src/app/input/modal.rs @@ -355,7 +355,7 @@ fn next_new_tab_default_name(state: &AppState) -> String { state .active .and_then(|i| state.workspaces.get(i)) - .map(|ws| (ws.tabs.len() + 1).to_string()) + .map(|ws| ws.next_public_tab_number.to_string()) .unwrap_or_else(|| "1".to_string()) } @@ -1288,7 +1288,7 @@ mod tests { } #[test] - fn closing_first_auto_tab_resets_remaining_auto_tab_and_next_prompt() { + fn closing_first_auto_tab_keeps_remaining_auto_tab_number_and_next_prompt() { let mut state = state_with_workspaces(&["test"]); open_new_tab_dialog(&mut state); handle_rename_key( @@ -1303,11 +1303,11 @@ mod tests { state.workspaces[0].close_tab(0); state.workspaces[0].switch_tab(0); - assert_eq!(state.workspaces[0].tabs[0].display_name(), "1"); + assert_eq!(state.workspaces[0].tabs[0].display_name(), "2"); assert!(state.workspaces[0].tabs[0].custom_name.is_none()); open_new_tab_dialog(&mut state); - assert_eq!(state.name_input, "2"); + assert_eq!(state.name_input, "3"); } #[test] diff --git a/src/app/input/navigate.rs b/src/app/input/navigate.rs index 9d861f66..b27b5cb9 100644 --- a/src/app/input/navigate.rs +++ b/src/app/input/navigate.rs @@ -1943,8 +1943,8 @@ last_pane = "prefix+tab" let lines: Vec<&str> = content.lines().collect(); assert_eq!(lines.len(), 3); assert_eq!(lines[0], app.state.workspaces[0].id); - assert_eq!(lines[1], format!("{}:1", app.state.workspaces[0].id)); - assert_eq!(lines[2], format!("{}-1", app.state.workspaces[0].id)); + assert_eq!(lines[1], format!("{}:t1", app.state.workspaces[0].id)); + assert_eq!(lines[2], format!("{}:p1", app.state.workspaces[0].id)); assert_eq!(app.state.mode, Mode::Terminal); let _ = std::fs::remove_file(output_path); diff --git a/src/app/input/sidebar.rs b/src/app/input/sidebar.rs index 43093a82..c4bd52d9 100644 --- a/src/app/input/sidebar.rs +++ b/src/app/input/sidebar.rs @@ -1300,13 +1300,16 @@ mod tests { .iter() .map(|tab| tab.display_name()) .collect(); - assert_eq!(labels, vec!["foo", "2", "3"]); + assert_eq!(labels, vec!["foo", "3", "1"]); assert_eq!( app.state.workspaces[0].tabs[0].custom_name.as_deref(), Some("foo") ); assert!(app.state.workspaces[0].tabs[1].custom_name.is_none()); assert!(app.state.workspaces[0].tabs[2].custom_name.is_none()); + assert_eq!(app.state.workspaces[0].tabs[0].number, 2); + assert_eq!(app.state.workspaces[0].tabs[1].number, 3); + assert_eq!(app.state.workspaces[0].tabs[2].number, 1); assert_eq!(app.state.workspaces[0].tabs[2].root_pane, moved_root); assert_eq!(app.state.workspaces[0].active_tab, 2); } diff --git a/src/app/mod.rs b/src/app/mod.rs index 99f4e461..5d09800b 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -2799,14 +2799,14 @@ mod tests { id: "req_2".into(), method: crate::api::schema::Method::WorkspaceFocus( crate::api::schema::WorkspaceTarget { - workspace_id: "w_1".into(), + workspace_id: "w1".into(), }, ), }; let pane_rename = crate::api::schema::Request { id: "req_3".into(), method: crate::api::schema::Method::PaneRename(crate::api::schema::PaneRenameParams { - pane_id: "w_1-1".into(), + pane_id: "w1:p1".into(), label: Some("logs".into()), }), }; @@ -2825,7 +2825,7 @@ mod tests { let pane_swap = crate::api::schema::Request { id: "req_6".into(), method: crate::api::schema::Method::PaneSwap(crate::api::schema::PaneSwapParams { - pane_id: Some("w_1-1".into()), + pane_id: Some("w1:p1".into()), direction: Some(crate::api::schema::PaneDirection::Right), ..crate::api::schema::PaneSwapParams::default() }), @@ -2834,7 +2834,7 @@ mod tests { id: "req_7".into(), method: crate::api::schema::Method::PaneFocusDirection( crate::api::schema::PaneFocusDirectionParams { - pane_id: Some("w_1-1".into()), + pane_id: Some("w1:p1".into()), direction: crate::api::schema::PaneDirection::Right, }, ), @@ -2842,7 +2842,7 @@ mod tests { let pane_resize = crate::api::schema::Request { id: "req_8".into(), method: crate::api::schema::Method::PaneResize(crate::api::schema::PaneResizeParams { - pane_id: Some("w_1-1".into()), + pane_id: Some("w1:p1".into()), direction: crate::api::schema::PaneDirection::Right, amount: Some(0.05), }), |
