feat: restore tabs and splits at launch

The tab layout (tab order and names, pins, the active tab, split trees
with their ratios) is saved whenever it changes — checked once a second,
written only on change, so it survives a crash — and reopened at the
next launch: saved profiles reconnect and local shells start in the
directory they were in (on Linux the live /proc cwd, so it isn't one
command behind). Quick Connect tabs and placeholder panes are skipped.
A General setting turns it off; it never runs when launched with
--connect, --run-local or --open, and screenshot runs never save.

Verified: a local tab left in /usr/share came back there with a
'Restored 1 tab(s)' toast.
This commit is contained in:
Andy Dixon
2026-10-12 18:53:00 +01:00
parent 469acb1203
commit 1990fb5da9
11 changed files with 247 additions and 1 deletions
+20
View File
@@ -6,6 +6,11 @@ impl Manager {
// --- local shell --------------------------------------------------------------------
pub fn connect_local(self: &Arc<Self>, shell: &str) -> String {
self.connect_local_in(shell, None)
}
/// A local shell starting in `cwd` (when it still exists), e.g. a restored tab.
pub fn connect_local_in(self: &Arc<Self>, shell: &str, cwd: Option<PathBuf>) -> String {
let id = new_id();
let info = SessionInfo {
id: id.clone(),
@@ -21,6 +26,9 @@ impl Manager {
error: String::new(),
};
let epoch = self.new_session(info, None, shell.into(), SessionSecrets::default());
if let Some(dir) = cwd.filter(|d| d.is_dir()) {
let _ = self.with(&id, |s| s.local_cwd = Some(dir));
}
self.emit_state(&id, SessionState::Connecting, "");
let me = self.clone();
let id2 = id.clone();
@@ -28,6 +36,18 @@ impl Manager {
id
}
/// A local session's shell and its current directory.
pub fn local_state(&self, id: &str) -> Option<(String, Option<PathBuf>)> {
self.with(id, |s| {
let cwd = match &s.client {
Client::Local(c) => Some(c.working_directory()),
_ => s.local_cwd.clone(),
};
(s.local_shell.clone(), cwd)
})
.ok()
}
/// Starts a fresh shell in an exited local tab, keeping its scrollback and last directory.
pub fn restart_local(self: &Arc<Self>, id: &str, cols: u16, rows: u16) -> Result<()> {
let epoch = self.with(id, |s| {