use super::{SessionPaneNode, SessionState, SessionTab, SessionWindow};
use crate::app::window_state::WindowState;
use crate::pane::PaneNode;
use par_term_config::snapshot_types::TabSnapshot;
use std::collections::HashMap;
use winit::window::WindowId;
pub fn capture_session(windows: &HashMap<WindowId, WindowState>) -> SessionState {
let mut session_windows = Vec::new();
for window_state in windows.values() {
let Some(window) = &window_state.window else {
continue;
};
let scale = window.scale_factor();
let window_pos = window.outer_position().unwrap_or_default();
let inner_size = window.inner_size();
let visible_tabs = window_state.tab_manager.visible_tabs();
let tabs: Vec<SessionTab> = visible_tabs
.iter()
.map(|tab| SessionTab {
snapshot: capture_tab_snapshot(tab),
})
.collect();
let active_tab_id = window_state.tab_manager.active_tab_id();
let active_tab_index = active_tab_id
.and_then(|id| visible_tabs.iter().position(|t| t.id == id))
.unwrap_or(0);
let (tmux_session_name, mux_session_name) =
window_state.tmux_state.persisted_session_names();
session_windows.push(SessionWindow {
position: (
(window_pos.x as f64 / scale) as i32,
(window_pos.y as f64 / scale) as i32,
),
size: (
(inner_size.width as f64 / scale) as u32,
(inner_size.height as f64 / scale) as u32,
),
tabs,
active_tab_index,
tmux_session_name,
mux_session_name,
});
}
SessionState {
saved_at: chrono::Utc::now().to_rfc3339(),
windows: session_windows,
}
}
pub fn capture_tab_snapshot(tab: &crate::tab::Tab) -> TabSnapshot {
let pane_layout =
tab.pane_manager
.as_ref()
.and_then(|pm| pm.root())
.and_then(|root| match root {
PaneNode::Leaf(_) => None,
PaneNode::Split { .. } => Some(capture_pane_node(root)),
});
TabSnapshot {
cwd: tab.get_cwd(),
title: tab.title.clone(),
custom_color: tab.custom_color,
user_title: if tab.user_named {
Some(tab.title.clone())
} else {
None
},
pane_user_title: tab.sole_pane_user_title(),
custom_icon: tab.custom_icon.clone(),
pane_layout,
}
}
pub fn capture_pane_node(node: &PaneNode) -> SessionPaneNode {
match node {
PaneNode::Leaf(pane) => SessionPaneNode::Leaf {
cwd: pane.get_cwd().or_else(|| pane.working_directory.clone()),
user_title: pane.user_named.then(|| pane.title.clone()),
},
PaneNode::Split {
direction,
ratio,
first,
second,
} => SessionPaneNode::Split {
direction: *direction,
ratio: *ratio,
first: Box::new(capture_pane_node(first)),
second: Box::new(capture_pane_node(second)),
},
}
}
#[cfg(test)]
mod tests {
use super::capture_tab_snapshot;
use crate::pane::{Pane, PaneNode, SplitDirection};
use crate::tab::Tab;
use par_term_config::snapshot_types::SessionPaneNode;
use par_term_terminal::TerminalManager;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use tokio::sync::RwLock;
fn stub(id: crate::pane::PaneId, cwd: &str) -> Pane {
let terminal = TerminalManager::new_with_scrollback(20, 5, 0).expect("stub terminal");
Pane::new_wrapping_terminal(
id,
Arc::new(RwLock::new(terminal)),
Some(cwd.to_string()),
Arc::new(AtomicBool::new(false)),
)
}
fn leaf_cwd(node: &SessionPaneNode) -> Option<&str> {
match node {
SessionPaneNode::Leaf { cwd, .. } => cwd.as_deref(),
SessionPaneNode::Split { .. } => None,
}
}
#[test]
fn a_split_tab_snapshot_keeps_its_pane_tree() {
let mut tab = Tab::new_stub(1, 1);
assert!(
capture_tab_snapshot(&tab).pane_layout.is_none(),
"a single pane restores from the tab cwd"
);
tab.pane_manager_mut()
.expect("pm")
.set_root(PaneNode::split(
SplitDirection::Vertical,
0.3,
PaneNode::leaf(stub(1, "/left")),
PaneNode::split(
SplitDirection::Horizontal,
0.6,
PaneNode::leaf(stub(2, "/top")),
PaneNode::leaf(stub(3, "/bottom")),
),
));
let Some(SessionPaneNode::Split {
direction,
ratio,
first,
second,
}) = capture_tab_snapshot(&tab).pane_layout
else {
panic!("a split root is captured");
};
assert_eq!(direction, SplitDirection::Vertical);
assert!((ratio - 0.3).abs() < f32::EPSILON);
assert_eq!(leaf_cwd(&first), Some("/left"));
let SessionPaneNode::Split {
direction,
first,
second,
..
} = *second
else {
panic!("the nested split is kept");
};
assert_eq!(direction, SplitDirection::Horizontal);
assert_eq!(leaf_cwd(&first), Some("/top"));
assert_eq!(leaf_cwd(&second), Some("/bottom"));
}
}