diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-05-17 01:41:16 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-05-17 01:41:16 +0300 |
| commit | a36da363f14639085bec91d7a378d6c2eeaa6c58 (patch) | |
| tree | 2c86155b115f8c79db2b8826fd8b2199d153f7bf /src/ui | |
| parent | e4de58b37f58d59d146158239ed218a8accd5c3c (diff) | |
refactor: extract terminal state and runtime from panes
Diffstat (limited to 'src/ui')
| -rw-r--r-- | src/ui/mobile.rs | 6 | ||||
| -rw-r--r-- | src/ui/panes.rs | 45 | ||||
| -rw-r--r-- | src/ui/scrollbar.rs | 2 | ||||
| -rw-r--r-- | src/ui/sidebar.rs | 38 |
4 files changed, 56 insertions, 35 deletions
diff --git a/src/ui/mobile.rs b/src/ui/mobile.rs index 9e8c57e9..8bcdf184 100644 --- a/src/ui/mobile.rs +++ b/src/ui/mobile.rs @@ -266,7 +266,7 @@ fn render_header_status(app: &AppState, frame: &mut Frame, area: Rect) { return; }; - let (state, seen) = ws.aggregate_state(); + let (state, seen) = ws.aggregate_state(&app.terminals); let (dot, dot_style) = if matches!(state, AgentState::Working) { ( super::spinner_frame(app.spinner_tick), @@ -430,7 +430,7 @@ fn render_mobile_switcher_content(app: &AppState, frame: &mut Frame, viewport: R let active = Some(idx) == app.active; let selected = idx == app.selected; let bg = mobile_item_bg(selected, active, p); - let (state, seen) = ws.aggregate_state(); + let (state, seen) = ws.aggregate_state(&app.terminals); let (dot, dot_style) = state_dot(state, seen, p); let title = Line::from(vec![ Span::styled(" ", Style::default().bg(bg)), @@ -824,7 +824,7 @@ fn agent_priority_label(app: &AppState) -> String { let mut blocked = 0usize; let mut working = 0usize; let mut done = 0usize; - for detail in ws.pane_details() { + for detail in ws.pane_details(&app.terminals) { match (detail.state, detail.seen) { (AgentState::Blocked, _) => blocked += 1, (AgentState::Working, _) => working += 1, diff --git a/src/ui/panes.rs b/src/ui/panes.rs index 289d5fab..c90183ea 100644 --- a/src/ui/panes.rs +++ b/src/ui/panes.rs @@ -11,9 +11,9 @@ use super::widgets::panel_contrast_fg; use crate::app::state::Palette; use crate::app::{AppState, Mode}; use crate::layout::PaneInfo; -use crate::pane::PaneRuntime; +use crate::terminal::TerminalRuntime; -pub(crate) fn pane_is_scrolled_back(rt: &PaneRuntime) -> bool { +pub(crate) fn pane_is_scrolled_back(rt: &TerminalRuntime) -> bool { rt.scroll_metrics() .is_some_and(|metrics| metrics.offset_from_bottom > 0) } @@ -55,7 +55,20 @@ fn stable_terminal_inner_rect(pane_inner: Rect) -> Rect { ) } -fn stable_scrollbar_gutter(rt: &PaneRuntime, pane_inner: Rect) -> (Rect, Option<Rect>) { +fn runtime_for_tab_pane<'a>( + app: &'a AppState, + tab: &'a crate::workspace::Tab, + pane_id: crate::layout::PaneId, +) -> Option<&'a TerminalRuntime> { + #[cfg(test)] + if let Some(runtime) = tab.runtimes.get(&pane_id) { + return Some(runtime); + } + let terminal_id = tab.terminal_id(pane_id)?; + app.terminal_runtimes.get(terminal_id) +} + +fn stable_scrollbar_gutter(rt: &TerminalRuntime, pane_inner: Rect) -> (Rect, Option<Rect>) { let inner_rect = stable_terminal_inner_rect(pane_inner); if inner_rect == pane_inner { return (inner_rect, None); @@ -76,6 +89,7 @@ fn stable_scrollbar_gutter(rt: &PaneRuntime, pane_inner: Rect) -> (Rect, Option< /// Resize every visible runtime in a tab to the geometry it would receive if the tab were selected. pub(super) fn resize_tab_panes( + app: &AppState, tab: &crate::workspace::Tab, area: Rect, cell_size: crate::kitty_graphics::HostCellSize, @@ -84,7 +98,7 @@ pub(super) fn resize_tab_panes( if tab.zoomed { let focused_id = tab.layout.focused(); - if let Some(rt) = tab.runtimes.get(&focused_id) { + if let Some(rt) = runtime_for_tab_pane(app, tab, focused_id) { let inner_rect = stable_terminal_inner_rect(area); rt.resize( inner_rect.height, @@ -103,7 +117,7 @@ pub(super) fn resize_tab_panes( area }; - if let Some(rt) = tab.runtimes.get(&info.id) { + if let Some(rt) = runtime_for_tab_pane(app, tab, info.id) { let inner_rect = stable_terminal_inner_rect(pane_inner); rt.resize( inner_rect.height, @@ -136,7 +150,7 @@ pub(super) fn compute_pane_infos( let focused_id = ws.layout.focused(); let mut inner_rect = area; let mut scrollbar_rect = None; - if let Some(rt) = ws.runtimes.get(&focused_id) { + if let Some(rt) = app.runtime_for_pane_in_workspace(ws_idx, focused_id) { (inner_rect, scrollbar_rect) = stable_scrollbar_gutter(rt, area); if resize_panes { rt.resize( @@ -175,7 +189,7 @@ pub(super) fn compute_pane_infos( let mut inner_rect = pane_inner; let mut scrollbar_rect = None; - if let Some(rt) = ws.runtimes.get(&info.id) { + if let Some(rt) = app.runtime_for_pane_in_workspace(ws_idx, info.id) { (inner_rect, scrollbar_rect) = stable_scrollbar_gutter(rt, pane_inner); if resize_panes { rt.resize( @@ -208,7 +222,7 @@ pub(super) fn render_panes(app: &AppState, frame: &mut Frame, area: Rect) { let terminal_active = app.mode == Mode::Terminal; for info in &app.view.pane_infos { - if let Some(rt) = ws.runtimes.get(&info.id) { + if let Some(rt) = app.runtime_for_pane_in_workspace(ws_idx, info.id) { if multi_pane { let (border_style, border_set) = if info.is_focused && terminal_active { ( @@ -233,7 +247,10 @@ pub(super) fn render_panes(app: &AppState, frame: &mut Frame, area: Rect) { .border_set(border_set); if let Some(title) = ws .pane_state(info.id) - .and_then(|pane| pane.border_label(app.show_agent_labels_on_pane_borders)) + .and_then(|pane| app.terminals.get(&pane.attached_terminal_id)) + .and_then(|terminal| { + terminal.border_label(app.show_agent_labels_on_pane_borders) + }) .and_then(|label| pane_border_title(label, info.rect.width)) { block = block.title(Line::from(Span::styled(title, border_style))); @@ -360,7 +377,7 @@ fn render_empty(app: &AppState, frame: &mut Frame, area: Rect) { #[cfg(test)] mod tests { use super::*; - use crate::pane::PaneRuntime; + use crate::terminal::TerminalRuntime; use crate::workspace::Workspace; #[test] @@ -381,7 +398,7 @@ mod tests { let root_pane = workspace.tabs[0].root_pane; workspace.tabs[0].runtimes.insert( root_pane, - PaneRuntime::test_with_scrollback_bytes(40, 8, 1024, b"ready\n"), + TerminalRuntime::test_with_scrollback_bytes(40, 8, 1024, b"ready\n"), ); app.workspaces = vec![workspace]; app.active = Some(0); @@ -408,7 +425,7 @@ mod tests { let root_pane = workspace.tabs[0].root_pane; workspace.tabs[0].runtimes.insert( root_pane, - PaneRuntime::test_with_scrollback_bytes(40, 8, 1024, b"ready\n"), + TerminalRuntime::test_with_scrollback_bytes(40, 8, 1024, b"ready\n"), ); app.workspaces = vec![workspace]; app.active = Some(0); @@ -434,7 +451,7 @@ mod tests { let root_pane = workspace.tabs[0].root_pane; workspace.tabs[0].runtimes.insert( root_pane, - PaneRuntime::test_with_scrollback_bytes(4, 8, 1024, b"ready\n"), + TerminalRuntime::test_with_scrollback_bytes(4, 8, 1024, b"ready\n"), ); app.workspaces = vec![workspace]; app.active = Some(0); @@ -460,7 +477,7 @@ mod tests { let root_pane = workspace.tabs[0].root_pane; workspace.tabs[0].runtimes.insert( root_pane, - PaneRuntime::test_with_scrollback_bytes( + TerminalRuntime::test_with_scrollback_bytes( 40, 8, 1024, diff --git a/src/ui/scrollbar.rs b/src/ui/scrollbar.rs index 34e739d0..b73b2c50 100644 --- a/src/ui/scrollbar.rs +++ b/src/ui/scrollbar.rs @@ -165,7 +165,7 @@ pub(super) fn render_pane_scrollbar( app: &AppState, frame: &mut Frame, info: &PaneInfo, - rt: &crate::pane::PaneRuntime, + rt: &crate::terminal::TerminalRuntime, ) { let Some(metrics) = rt.scroll_metrics() else { return; diff --git a/src/ui/sidebar.rs b/src/ui/sidebar.rs index 29f3525d..eb6d6fab 100644 --- a/src/ui/sidebar.rs +++ b/src/ui/sidebar.rs @@ -116,7 +116,7 @@ pub(crate) fn agent_panel_entries(app: &AppState) -> Vec<AgentPanelEntry> { let Some(ws) = app.workspaces.get(ws_idx) else { return Vec::new(); }; - ws.pane_details() + ws.pane_details(&app.terminals) .into_iter() .map(|detail| AgentPanelEntry { ws_idx, @@ -138,7 +138,7 @@ pub(crate) fn agent_panel_entries(app: &AppState) -> Vec<AgentPanelEntry> { .flat_map(|(ws_idx, ws)| { let multi_tab = ws.tabs.len() > 1; let workspace_label = ws.display_name(); - ws.pane_details() + ws.pane_details(&app.terminals) .into_iter() .map(move |detail| AgentPanelEntry { ws_idx, @@ -429,7 +429,7 @@ pub(super) fn render_sidebar_collapsed(app: &AppState, frame: &mut Frame, area: if y >= ws_area.y + ws_area.height { break; } - let (agg_state, agg_seen) = ws.aggregate_state(); + let (agg_state, agg_seen) = ws.aggregate_state(&app.terminals); let (icon, icon_style) = state_dot(agg_state, agg_seen, p); let is_selected = visible_idx == app.selected && is_navigating; let is_active = Some(visible_idx) == app.active; @@ -487,7 +487,7 @@ pub(super) fn render_sidebar_collapsed(app: &AppState, frame: &mut Frame, area: if detail_content_area != Rect::default() { if let Some(ws_idx) = detail_ws_idx { if let Some(ws) = app.workspaces.get(ws_idx) { - for (detail_idx, detail) in ws.pane_details().iter().enumerate() { + for (detail_idx, detail) in ws.pane_details(&app.terminals).iter().enumerate() { let y = detail_content_area.y + detail_idx as u16; if y >= detail_content_area.y + detail_content_area.height { break; @@ -603,7 +603,7 @@ fn render_workspace_list(app: &AppState, frame: &mut Frame, area: Rect, is_navig let is_active = Some(i) == app.active; let is_dragged = dragged_ws_idx == Some(i); let highlighted = selected || is_active || is_dragged; - let (agg_state, agg_seen) = ws.aggregate_state(); + let (agg_state, agg_seen) = ws.aggregate_state(&app.terminals); if highlighted { let bg = if selected { @@ -899,24 +899,28 @@ mod tests { #[test] fn all_workspaces_agent_panel_entries_use_workspace_and_optional_tab_labels() { let mut app = crate::app::state::AppState::test_new(); - let mut first = Workspace::test_new("one"); + let first = Workspace::test_new("one"); let first_pane = first.tabs[0].root_pane; - first.tabs[0] - .panes - .get_mut(&first_pane) - .unwrap() - .detected_agent = Some(Agent::Pi); - let mut second = Workspace::test_new("two"); let second_tab = second.test_add_tab(Some("logs")); let second_pane = second.tabs[second_tab].root_pane; - second.tabs[second_tab] - .panes - .get_mut(&second_pane) - .unwrap() - .detected_agent = Some(Agent::Claude); app.workspaces = vec![first, second]; + app.ensure_test_terminals(); + let first_terminal_id = app.workspaces[0].tabs[0].panes[&first_pane] + .attached_terminal_id + .clone(); + app.terminals + .get_mut(&first_terminal_id) + .unwrap() + .detected_agent = Some(Agent::Pi); + let second_terminal_id = app.workspaces[1].tabs[second_tab].panes[&second_pane] + .attached_terminal_id + .clone(); + app.terminals + .get_mut(&second_terminal_id) + .unwrap() + .detected_agent = Some(Agent::Claude); app.active = Some(0); app.selected = 0; app.agent_panel_scope = AgentPanelScope::AllWorkspaces; |
