aboutsummaryrefslogtreecommitdiff
path: root/src/persist
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-05-17 01:41:16 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-05-17 01:41:16 +0300
commita36da363f14639085bec91d7a378d6c2eeaa6c58 (patch)
tree2c86155b115f8c79db2b8826fd8b2199d153f7bf /src/persist
parente4de58b37f58d59d146158239ed218a8accd5c3c (diff)
refactor: extract terminal state and runtime from panes
Diffstat (limited to 'src/persist')
-rw-r--r--src/persist/restore.rs149
-rw-r--r--src/persist/snapshot.rs70
2 files changed, 149 insertions, 70 deletions
diff --git a/src/persist/restore.rs b/src/persist/restore.rs
index 1e10df6d..e53a61d9 100644
--- a/src/persist/restore.rs
+++ b/src/persist/restore.rs
@@ -9,7 +9,8 @@ use tracing::{error, warn};
use crate::events::AppEvent;
use crate::layout::{Node, PaneId, TileLayout};
-use crate::pane::{PaneRuntime, PaneState};
+use crate::pane::PaneState;
+use crate::terminal::{TerminalId, TerminalRuntime, TerminalState};
use crate::workspace::Workspace;
use super::{DirectionSnapshot, LayoutSnapshot, SessionSnapshot, TabSnapshot, WorkspaceSnapshot};
@@ -23,22 +24,32 @@ pub fn restore(
events: mpsc::Sender<AppEvent>,
render_notify: Arc<Notify>,
render_dirty: Arc<AtomicBool>,
-) -> Vec<Workspace> {
- snapshot
- .workspaces
- .iter()
- .filter_map(|ws_snap| {
- restore_workspace(
- ws_snap,
- rows,
- cols,
- scrollback_limit_bytes,
- events.clone(),
- render_notify.clone(),
- render_dirty.clone(),
- )
- })
- .collect()
+) -> (
+ Vec<Workspace>,
+ HashMap<TerminalId, TerminalState>,
+ HashMap<TerminalId, TerminalRuntime>,
+) {
+ let mut workspaces = Vec::new();
+ let mut terminals = HashMap::new();
+ let mut terminal_runtimes = HashMap::new();
+ for ws_snap in &snapshot.workspaces {
+ if let Some((workspace, restored_terminals, restored_runtimes)) = restore_workspace(
+ ws_snap,
+ rows,
+ cols,
+ scrollback_limit_bytes,
+ events.clone(),
+ render_notify.clone(),
+ render_dirty.clone(),
+ ) {
+ for terminal in restored_terminals {
+ terminals.insert(terminal.id.clone(), terminal);
+ }
+ terminal_runtimes.extend(restored_runtimes);
+ workspaces.push(workspace);
+ }
+ }
+ (workspaces, terminals, terminal_runtimes)
}
fn restore_workspace(
@@ -49,13 +60,19 @@ fn restore_workspace(
events: mpsc::Sender<AppEvent>,
render_notify: Arc<Notify>,
render_dirty: Arc<AtomicBool>,
-) -> Option<Workspace> {
+) -> Option<(
+ Workspace,
+ Vec<TerminalState>,
+ HashMap<TerminalId, TerminalRuntime>,
+)> {
let mut tabs = Vec::new();
+ let mut terminals = Vec::new();
+ let mut terminal_runtimes = HashMap::new();
let mut public_pane_numbers = HashMap::new();
let mut next_public_pane_number = 1;
for (idx, tab_snap) in snap.tabs.iter().enumerate() {
- let tab = restore_tab(
+ let (tab, restored_terminals, restored_runtimes) = restore_tab(
tab_snap,
idx + 1,
rows,
@@ -69,6 +86,8 @@ fn restore_workspace(
public_pane_numbers.insert(pane_id, next_public_pane_number);
next_public_pane_number += 1;
}
+ terminals.extend(restored_terminals);
+ terminal_runtimes.extend(restored_runtimes);
tabs.push(tab);
}
@@ -76,20 +95,26 @@ fn restore_workspace(
return None;
}
- Some(Workspace {
- id: snap
- .id
- .clone()
- .unwrap_or_else(crate::workspace::generate_workspace_id),
- custom_name: snap.custom_name.clone(),
- identity_cwd: snap.identity_cwd.clone(),
- cached_git_branch: None,
- cached_git_ahead_behind: None,
- public_pane_numbers,
- next_public_pane_number,
- active_tab: snap.active_tab.min(tabs.len().saturating_sub(1)),
- tabs,
- })
+ Some((
+ Workspace {
+ id: snap
+ .id
+ .clone()
+ .unwrap_or_else(crate::workspace::generate_workspace_id),
+ custom_name: snap.custom_name.clone(),
+ identity_cwd: snap.identity_cwd.clone(),
+ cached_git_branch: None,
+ cached_git_ahead_behind: None,
+ public_pane_numbers,
+ next_public_pane_number,
+ active_tab: snap.active_tab.min(tabs.len().saturating_sub(1)),
+ tabs,
+ #[cfg(test)]
+ test_runtimes: HashMap::new(),
+ },
+ terminals,
+ terminal_runtimes,
+ ))
}
fn restore_tab(
@@ -101,7 +126,11 @@ fn restore_tab(
events: mpsc::Sender<AppEvent>,
render_notify: Arc<Notify>,
render_dirty: Arc<AtomicBool>,
-) -> Option<crate::workspace::Tab> {
+) -> Option<(
+ crate::workspace::Tab,
+ Vec<TerminalState>,
+ HashMap<TerminalId, TerminalRuntime>,
+)> {
let (node, id_map) = restore_node_remapped(&snap.layout);
let reverse_id_map: HashMap<PaneId, u32> = id_map
.iter()
@@ -110,9 +139,8 @@ fn restore_tab(
let pane_ids = collect_pane_ids(&node);
let mut panes = HashMap::new();
- let mut pane_cwds = HashMap::new();
- let mut terminal_ids = HashMap::new();
- let mut runtimes = HashMap::new();
+ let mut terminals = Vec::new();
+ let mut terminal_runtimes = HashMap::new();
for id in &pane_ids {
let saved_cwd = reverse_id_map
.get(id)
@@ -142,7 +170,7 @@ fn restore_tab(
.and_then(|old_id| snap.panes.get(old_id))
.and_then(|p| p.label.clone());
- match PaneRuntime::spawn(
+ match TerminalRuntime::spawn(
*id,
rows,
cols,
@@ -154,12 +182,14 @@ fn restore_tab(
render_dirty.clone(),
) {
Ok(runtime) => {
- let mut pane_state = PaneState::new();
- pane_state.manual_label = saved_label;
- panes.insert(*id, pane_state);
- pane_cwds.insert(*id, cwd.clone());
- terminal_ids.insert(*id, crate::terminal::TerminalId::alloc());
- runtimes.insert(*id, runtime);
+ let terminal_id = TerminalId::alloc();
+ let mut terminal = TerminalState::new(terminal_id.clone(), cwd.clone());
+ if let Some(label) = saved_label {
+ terminal.set_manual_label(label);
+ }
+ panes.insert(*id, PaneState::new(terminal_id.clone()));
+ terminal_runtimes.insert(terminal_id, runtime);
+ terminals.push(terminal);
}
Err(e) => {
error!(
@@ -193,20 +223,23 @@ fn restore_tab(
let root_pane = resolve_restored_pane(snap.root_pane, &id_map, &surviving, &pane_ids)?;
let layout = TileLayout::from_saved(node, focus);
- Some(crate::workspace::Tab {
- custom_name: snap.custom_name.clone(),
- number,
- root_pane,
- layout,
- panes,
- pane_cwds,
- terminal_ids,
- runtimes,
- zoomed: snap.zoomed,
- events,
- render_notify,
- render_dirty,
- })
+ Some((
+ crate::workspace::Tab {
+ custom_name: snap.custom_name.clone(),
+ number,
+ root_pane,
+ layout,
+ panes,
+ #[cfg(test)]
+ runtimes: HashMap::new(),
+ zoomed: snap.zoomed,
+ events,
+ render_notify,
+ render_dirty,
+ },
+ terminals,
+ terminal_runtimes,
+ ))
}
pub(super) fn prune_restored_node(node: Node, surviving: &HashSet<PaneId>) -> Option<Node> {
diff --git a/src/persist/snapshot.rs b/src/persist/snapshot.rs
index 76ed96f5..245fbb76 100644
--- a/src/persist/snapshot.rs
+++ b/src/persist/snapshot.rs
@@ -195,6 +195,14 @@ fn first_pane_id_in_layout(layout: &LayoutSnapshot) -> Option<u32> {
/// Capture the current app state into a serializable snapshot.
pub fn capture(
workspaces: &[Workspace],
+ terminals: &std::collections::HashMap<
+ crate::terminal::TerminalId,
+ crate::terminal::TerminalState,
+ >,
+ terminal_runtimes: &std::collections::HashMap<
+ crate::terminal::TerminalId,
+ crate::terminal::TerminalRuntime,
+ >,
active: Option<usize>,
selected: usize,
agent_panel_scope: crate::app::state::AgentPanelScope,
@@ -203,7 +211,10 @@ pub fn capture(
) -> SessionSnapshot {
SessionSnapshot {
version: SNAPSHOT_VERSION,
- workspaces: workspaces.iter().map(capture_workspace).collect(),
+ workspaces: workspaces
+ .iter()
+ .map(|workspace| capture_workspace(workspace, terminals, terminal_runtimes))
+ .collect(),
active,
selected,
agent_panel_scope,
@@ -212,25 +223,53 @@ pub fn capture(
}
}
-fn capture_workspace(ws: &Workspace) -> WorkspaceSnapshot {
+fn capture_workspace(
+ ws: &Workspace,
+ terminals: &std::collections::HashMap<
+ crate::terminal::TerminalId,
+ crate::terminal::TerminalState,
+ >,
+ terminal_runtimes: &std::collections::HashMap<
+ crate::terminal::TerminalId,
+ crate::terminal::TerminalRuntime,
+ >,
+) -> WorkspaceSnapshot {
WorkspaceSnapshot {
id: Some(ws.id.clone()),
custom_name: ws.custom_name.clone(),
identity_cwd: ws
.resolved_identity_cwd()
.unwrap_or_else(|| ws.identity_cwd.clone()),
- tabs: ws.tabs.iter().map(capture_tab).collect(),
+ tabs: ws
+ .tabs
+ .iter()
+ .map(|tab| capture_tab(tab, terminals, terminal_runtimes))
+ .collect(),
active_tab: ws.active_tab,
}
}
-fn capture_tab(tab: &crate::workspace::Tab) -> TabSnapshot {
+fn capture_tab(
+ tab: &crate::workspace::Tab,
+ terminals: &std::collections::HashMap<
+ crate::terminal::TerminalId,
+ crate::terminal::TerminalState,
+ >,
+ terminal_runtimes: &std::collections::HashMap<
+ crate::terminal::TerminalId,
+ crate::terminal::TerminalRuntime,
+ >,
+) -> TabSnapshot {
let mut panes = HashMap::new();
for id in tab.panes.keys() {
let cwd = tab
- .cwd_for_pane(*id)
+ .cwd_for_pane(*id, terminals, terminal_runtimes)
.unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| "/".into()));
- let label = tab.panes.get(id).and_then(|pane| pane.manual_label.clone());
+ let label = tab
+ .panes
+ .get(id)
+ .and_then(|pane| terminals.get(&pane.attached_terminal_id))
+ .and_then(|terminal| terminal.manual_label.clone());
panes.insert(id.raw(), PaneSnapshot { cwd, label });
}
TabSnapshot {
@@ -310,6 +349,7 @@ mod tests {
fn state_with_workspaces(names: &[&str]) -> AppState {
let mut state = AppState::test_new();
state.workspaces = names.iter().map(|name| Workspace::test_new(name)).collect();
+ state.ensure_test_terminals();
if !state.workspaces.is_empty() {
state.active = Some(0);
state.selected = 0;
@@ -321,6 +361,8 @@ mod tests {
fn capture_from_state(state: &AppState) -> SessionSnapshot {
capture(
&state.workspaces,
+ &state.terminals,
+ &state.terminal_runtimes,
state.active,
state.selected,
state.agent_panel_scope,
@@ -657,13 +699,17 @@ mod tests {
fn capture_contract_tracks_workspace_identity_and_pane_cwds() {
let mut state = state_with_workspaces(&["one"]);
let root = state.workspaces[0].tabs[0].root_pane;
- state.workspaces[0].tabs[0]
- .pane_cwds
- .insert(root, PathBuf::from("/tmp/pion"));
+ state.workspaces[0].identity_cwd = PathBuf::from("/tmp/pion");
let second = state.workspaces[0].test_split(Direction::Horizontal);
- state.workspaces[0].tabs[0]
- .pane_cwds
- .insert(second, PathBuf::from("/tmp/herdr"));
+ state.ensure_test_terminals();
+ let root_terminal_id = state.workspaces[0].tabs[0].panes[&root]
+ .attached_terminal_id
+ .clone();
+ state.terminals.get_mut(&root_terminal_id).unwrap().cwd = PathBuf::from("/tmp/pion");
+ let second_terminal_id = state.workspaces[0].tabs[0].panes[&second]
+ .attached_terminal_id
+ .clone();
+ state.terminals.get_mut(&second_terminal_id).unwrap().cwd = PathBuf::from("/tmp/herdr");
let snapshot = capture_from_state(&state);
let workspace = &snapshot.workspaces[0];