diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-05-28 04:11:56 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-05-28 04:12:53 +0300 |
| commit | bf9cc201244946496b4d950046bf7edcc4892994 (patch) | |
| tree | 920c866e67783cf31d2feff9642457204999c8eb /src/app/api.rs | |
| parent | 789738cada5afaa20bbd77a0d479fb689c455bfe (diff) | |
fix: preserve live handoff terminal state
Diffstat (limited to 'src/app/api.rs')
| -rw-r--r-- | src/app/api.rs | 82 |
1 files changed, 82 insertions, 0 deletions
diff --git a/src/app/api.rs b/src/app/api.rs index a69dccec..562bc449 100644 --- a/src/app/api.rs +++ b/src/app/api.rs @@ -12,6 +12,12 @@ mod worktrees; use super::{api_helpers::pane_agent_status, App, Mode, OverlayPaneState, ToastKind}; use crate::events::AppEvent; +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum RuntimeExitAction { + RespawnShell, + ClosePane, +} + impl App { pub(crate) fn handle_internal_event(&mut self, ev: AppEvent) { if let AppEvent::ClipboardWrite { content } = ev { @@ -43,6 +49,17 @@ impl App { return; } + if let AppEvent::PaneDied { pane_id } = &ev { + if self.runtime_exit_action(*pane_id) == RuntimeExitAction::RespawnShell + && self.respawn_shell_for_launch_pane(*pane_id) + { + self.overlay_panes.remove(pane_id); + self.render_dirty.store(true, Ordering::Release); + self.render_notify.notify_one(); + return; + } + } + let overlay_state = if let AppEvent::PaneDied { pane_id } = &ev { self.overlay_panes.remove(pane_id) } else { @@ -210,6 +227,71 @@ impl App { } } + fn runtime_exit_action(&self, pane_id: crate::layout::PaneId) -> RuntimeExitAction { + let Some((_, pane_state)) = self.find_pane(pane_id) else { + return RuntimeExitAction::ClosePane; + }; + let Some(terminal) = self.state.terminals.get(&pane_state.attached_terminal_id) else { + return RuntimeExitAction::ClosePane; + }; + + if terminal.respawn_shell_on_exit { + RuntimeExitAction::RespawnShell + } else { + RuntimeExitAction::ClosePane + } + } + + fn respawn_shell_for_launch_pane(&mut self, pane_id: crate::layout::PaneId) -> bool { + let Some((ws_idx, pane_state)) = self.find_pane(pane_id) else { + return false; + }; + let terminal_id = pane_state.attached_terminal_id.clone(); + let Some(terminal) = self.state.terminals.get(&terminal_id) else { + return false; + }; + + let cwd = terminal.cwd.clone(); + let (rows, cols) = self + .terminal_runtimes + .get(&terminal_id) + .map(|runtime| runtime.current_size()) + .unwrap_or_else(|| self.state.estimate_pane_size()); + let runtime = match crate::terminal::TerminalRuntime::spawn( + pane_id, + rows, + cols, + cwd, + self.state.pane_scrollback_limit_bytes, + self.state.host_terminal_theme, + &self.state.default_shell, + self.event_tx.clone(), + self.render_notify.clone(), + self.render_dirty.clone(), + ) { + Ok(runtime) => runtime, + Err(err) => { + tracing::warn!( + pane = pane_id.raw(), + terminal = %terminal_id, + err = %err, + "failed to respawn shell after launch command exited" + ); + return false; + } + }; + + self.terminal_runtimes.insert(terminal_id.clone(), runtime); + if let Some(terminal) = self.state.terminals.get_mut(&terminal_id) { + terminal.launch_argv = None; + terminal.respawn_shell_on_exit = false; + terminal.clear_agent_name(); + } + self.state.focus_pane_in_workspace(ws_idx, pane_id); + self.schedule_session_save(); + true + } + pub(crate) fn emit_pane_state_update(&self, update: &crate::app::actions::PaneStateUpdate) { let Some(pane_id) = self.public_pane_id(update.ws_idx, update.pane_id) else { return; |
