diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/app/actions.rs | 159 | ||||
| -rw-r--r-- | src/app/input/mod.rs | 4 | ||||
| -rw-r--r-- | src/app/input/modal.rs | 72 | ||||
| -rw-r--r-- | src/app/input/navigate.rs | 2 | ||||
| -rw-r--r-- | src/app/input/overlays.rs | 48 | ||||
| -rw-r--r-- | src/app/mod.rs | 2 | ||||
| -rw-r--r-- | src/ui.rs | 2 | ||||
| -rw-r--r-- | src/ui/navigator.rs | 89 |
8 files changed, 284 insertions, 94 deletions
diff --git a/src/app/actions.rs b/src/app/actions.rs index 95e634be..9bce43ed 100644 --- a/src/app/actions.rs +++ b/src/app/actions.rs @@ -182,7 +182,16 @@ impl AppState { false } + #[cfg(test)] pub(crate) fn open_navigator(&mut self) { + let terminal_runtimes = crate::terminal::TerminalRuntimeRegistry::new(); + self.open_navigator_from(&terminal_runtimes); + } + + pub(crate) fn open_navigator_from( + &mut self, + terminal_runtimes: &crate::terminal::TerminalRuntimeRegistry, + ) { self.navigator.query.clear(); self.navigator.search_focused = false; self.navigator.state_filter = None; @@ -194,17 +203,27 @@ impl AppState { } self.mode = Mode::Navigator; - self.navigator.selected = self.current_navigator_row_index().unwrap_or(0); - self.ensure_navigator_selection_visible(); + self.navigator.selected = self + .current_navigator_row_index_from(terminal_runtimes) + .unwrap_or(0); + self.ensure_navigator_selection_visible_from(terminal_runtimes); } + #[cfg(test)] pub(crate) fn navigator_rows(&self) -> Vec<NavigatorRow> { + let terminal_runtimes = crate::terminal::TerminalRuntimeRegistry::new(); + self.navigator_rows_from(&terminal_runtimes) + } + + pub(crate) fn navigator_rows_from( + &self, + terminal_runtimes: &crate::terminal::TerminalRuntimeRegistry, + ) -> Vec<NavigatorRow> { let query = self.navigator.query.trim().to_lowercase(); let query_kind = navigator_query_kind(&query, self.navigator.state_filter); - let terminal_runtimes = crate::terminal::TerminalRuntimeRegistry::new(); let mut rows = Vec::new(); for (ws_idx, ws) in self.workspaces.iter().enumerate() { - let workspace_label = ws.display_name_from(&self.terminals, &terminal_runtimes); + let workspace_label = ws.display_name_from(&self.terminals, terminal_runtimes); let activity = workspace_activity_summary(ws, &self.terminals); let workspace_search_text = format!("{workspace_label} {activity}").to_lowercase(); let workspace_matches = match query_kind { @@ -397,21 +416,27 @@ impl AppState { rows } - fn current_navigator_row_index(&self) -> Option<usize> { - let rows = self.navigator_rows(); + fn current_navigator_row_index_from( + &self, + terminal_runtimes: &crate::terminal::TerminalRuntimeRegistry, + ) -> Option<usize> { + let rows = self.navigator_rows_from(terminal_runtimes); rows.iter() .position(|row| matches!(row.target, NavigatorTarget::Pane { .. }) && row.is_current) .or_else(|| rows.iter().position(|row| row.is_current)) } - pub(crate) fn ensure_navigator_selection_visible(&mut self) { + pub(crate) fn ensure_navigator_selection_visible_from( + &mut self, + terminal_runtimes: &crate::terminal::TerminalRuntimeRegistry, + ) { let body = self.navigator_body_rect(); let viewport = body.height as usize; if viewport == 0 { self.navigator.scroll = 0; return; } - let max_scroll = self.navigator_max_scroll(viewport); + let max_scroll = self.navigator_max_scroll_from(terminal_runtimes, viewport); if self.navigator.selected < self.navigator.scroll { self.navigator.scroll = self.navigator.selected; } else if self.navigator.selected >= self.navigator.scroll.saturating_add(viewport) { @@ -424,15 +449,25 @@ impl AppState { self.navigator.scroll = self.navigator.scroll.min(max_scroll); } - pub(crate) fn navigator_max_scroll(&self, viewport: usize) -> usize { + pub(crate) fn navigator_max_scroll_from( + &self, + terminal_runtimes: &crate::terminal::TerminalRuntimeRegistry, + viewport: usize, + ) -> usize { if viewport == 0 { return 0; } - self.navigator_rows().len().saturating_sub(viewport) + self.navigator_rows_from(terminal_runtimes) + .len() + .saturating_sub(viewport) } - pub(crate) fn move_navigator_selection(&mut self, delta: isize) { - let count = self.navigator_rows().len(); + pub(crate) fn move_navigator_selection_from( + &mut self, + terminal_runtimes: &crate::terminal::TerminalRuntimeRegistry, + delta: isize, + ) { + let count = self.navigator_rows_from(terminal_runtimes).len(); if count == 0 { self.navigator.selected = 0; self.navigator.scroll = 0; @@ -440,17 +475,27 @@ impl AppState { } let current = self.navigator.selected.min(count - 1) as isize; self.navigator.selected = (current + delta).clamp(0, count as isize - 1) as usize; - self.ensure_navigator_selection_visible(); + self.ensure_navigator_selection_visible_from(terminal_runtimes); } - pub(crate) fn clamp_navigator_selection(&mut self) { - let count = self.navigator_rows().len(); + pub(crate) fn clamp_navigator_selection_from( + &mut self, + terminal_runtimes: &crate::terminal::TerminalRuntimeRegistry, + ) { + let count = self.navigator_rows_from(terminal_runtimes).len(); self.navigator.selected = self.navigator.selected.min(count.saturating_sub(1)); - self.ensure_navigator_selection_visible(); + self.ensure_navigator_selection_visible_from(terminal_runtimes); } - pub(crate) fn toggle_selected_navigator_workspace(&mut self) { - let Some(row) = self.navigator_rows().get(self.navigator.selected).cloned() else { + pub(crate) fn toggle_selected_navigator_workspace_from( + &mut self, + terminal_runtimes: &crate::terminal::TerminalRuntimeRegistry, + ) { + let Some(row) = self + .navigator_rows_from(terminal_runtimes) + .get(self.navigator.selected) + .cloned() + else { return; }; let NavigatorTarget::Workspace { ws_idx } = row.target else { @@ -464,11 +509,24 @@ impl AppState { } else { self.navigator.expanded_workspaces.insert(workspace_id); } - self.clamp_navigator_selection(); + self.clamp_navigator_selection_from(terminal_runtimes); } + #[cfg(test)] pub(crate) fn accept_navigator_selection(&mut self) -> bool { - let Some(row) = self.navigator_rows().get(self.navigator.selected).cloned() else { + let terminal_runtimes = crate::terminal::TerminalRuntimeRegistry::new(); + self.accept_navigator_selection_from(&terminal_runtimes) + } + + pub(crate) fn accept_navigator_selection_from( + &mut self, + terminal_runtimes: &crate::terminal::TerminalRuntimeRegistry, + ) -> bool { + let Some(row) = self + .navigator_rows_from(terminal_runtimes) + .get(self.navigator.selected) + .cloned() + else { return false; }; self.focus_navigator_target(row.target) @@ -2596,6 +2654,67 @@ mod tests { ))); } + #[tokio::test] + async fn navigator_rows_match_live_root_runtime_cwd_workspace_label() { + let unique = format!( + "herdr-navigator-runtime-cwd-{}-{}", + std::process::id(), + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_nanos() + ); + let root = std::env::temp_dir().join(unique); + let stale_cwd = root.join("issue-264-nix-support"); + let live_cwd = root.join("herdr"); + std::fs::create_dir_all(stale_cwd.join(".git")).unwrap(); + std::fs::create_dir_all(live_cwd.join(".git")).unwrap(); + + let mut state = AppState::test_new(); + let mut workspace = Workspace::test_new("stale-name"); + workspace.custom_name = None; + workspace.identity_cwd = stale_cwd.clone(); + let pane = workspace.tabs[0].root_pane; + state.workspaces = vec![workspace]; + state.ensure_test_terminals(); + let terminal_id = state.workspaces[0].terminal_id(pane).cloned().unwrap(); + state.terminals.get_mut(&terminal_id).unwrap().cwd = stale_cwd; + + let (events, _) = tokio::sync::mpsc::channel(4); + let runtime = crate::terminal::TerminalRuntime::spawn( + pane, + 24, + 80, + live_cwd.clone(), + 0, + crate::terminal_theme::TerminalTheme::default(), + crate::pane::PaneShellConfig::new("/bin/sh", crate::config::ShellModeConfig::NonLogin), + events, + std::sync::Arc::new(tokio::sync::Notify::new()), + std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)), + ) + .unwrap(); + + let deadline = std::time::Instant::now() + std::time::Duration::from_secs(2); + while runtime.cwd() != Some(live_cwd.clone()) && std::time::Instant::now() < deadline { + tokio::time::sleep(std::time::Duration::from_millis(10)).await; + } + + let mut runtime_registry = crate::terminal::TerminalRuntimeRegistry::new(); + runtime_registry.insert(terminal_id, runtime); + state.open_navigator_from(&runtime_registry); + state.navigator.query = "herdr".into(); + let rows = state.navigator_rows_from(&runtime_registry); + + for (_, runtime) in runtime_registry.drain() { + runtime.shutdown(); + } + let _ = std::fs::remove_dir_all(root); + + assert_eq!(rows.len(), 1); + assert_eq!(rows[0].label, "herdr (1)"); + } + #[test] fn navigator_rows_include_shell_and_agent_panes() { let mut state = app_with_workspaces(&["one"]); diff --git a/src/app/input/mod.rs b/src/app/input/mod.rs index 1da647e8..e8761a35 100644 --- a/src/app/input/mod.rs +++ b/src/app/input/mod.rs @@ -86,7 +86,9 @@ impl App { Mode::Settings => self.handle_settings_key(key_event), Mode::GlobalMenu => handle_global_menu_key(&mut self.state, key_event), Mode::KeybindHelp => handle_keybind_help_key(&mut self.state, key_event), - Mode::Navigator => handle_navigator_key(&mut self.state, key_event), + Mode::Navigator => { + handle_navigator_key(&mut self.state, &self.terminal_runtimes, key_event) + } Mode::Terminal => unreachable!(), } } diff --git a/src/app/input/modal.rs b/src/app/input/modal.rs index aedc3adb..334b008e 100644 --- a/src/app/input/modal.rs +++ b/src/app/input/modal.rs @@ -152,7 +152,11 @@ pub(crate) fn handle_global_menu_key(state: &mut AppState, key: KeyEvent) { } } -pub(crate) fn handle_navigator_key(state: &mut AppState, key: KeyEvent) { +pub(crate) fn handle_navigator_key( + state: &mut AppState, + terminal_runtimes: &crate::terminal::TerminalRuntimeRegistry, + key: KeyEvent, +) { if state.navigator.search_focused { match key.code { KeyCode::Esc => { @@ -163,36 +167,36 @@ pub(crate) fn handle_navigator_key(state: &mut AppState, key: KeyEvent) { state.navigator.query.clear(); state.navigator.state_filter = None; state.navigator.search_focused = false; - state.clamp_navigator_selection(); + state.clamp_navigator_selection_from(terminal_runtimes); } } KeyCode::Enter => { - state.accept_navigator_selection(); + state.accept_navigator_selection_from(terminal_runtimes); } KeyCode::Backspace => { state.navigator.state_filter = None; state.navigator.query.pop(); - state.clamp_navigator_selection(); + state.clamp_navigator_selection_from(terminal_runtimes); } - KeyCode::Up => state.move_navigator_selection(-1), - KeyCode::Down => state.move_navigator_selection(1), + KeyCode::Up => state.move_navigator_selection_from(terminal_runtimes, -1), + KeyCode::Down => state.move_navigator_selection_from(terminal_runtimes, 1), KeyCode::Char('n') if key.modifiers == KeyModifiers::CONTROL => { - state.move_navigator_selection(1) + state.move_navigator_selection_from(terminal_runtimes, 1) } KeyCode::Char('p') if key.modifiers == KeyModifiers::CONTROL => { - state.move_navigator_selection(-1) + state.move_navigator_selection_from(terminal_runtimes, -1) } KeyCode::Char('u') if key.modifiers == KeyModifiers::CONTROL => { state.navigator.query.clear(); state.navigator.state_filter = None; - state.clamp_navigator_selection(); + state.clamp_navigator_selection_from(terminal_runtimes); } KeyCode::Char(c) if key.modifiers.is_empty() || key.modifiers == KeyModifiers::SHIFT => { state.navigator.state_filter = None; state.navigator.query.push(c); - state.clamp_navigator_selection(); + state.clamp_navigator_selection_from(terminal_runtimes); } _ => {} } @@ -206,62 +210,74 @@ pub(crate) fn handle_navigator_key(state: &mut AppState, key: KeyEvent) { } else { state.navigator.query.clear(); state.navigator.state_filter = None; - state.clamp_navigator_selection(); + state.clamp_navigator_selection_from(terminal_runtimes); } } KeyCode::Enter => { - state.accept_navigator_selection(); + state.accept_navigator_selection_from(terminal_runtimes); } KeyCode::Char('/') => { state.navigator.query.clear(); state.navigator.state_filter = None; state.navigator.search_focused = true; - state.clamp_navigator_selection(); + state.clamp_navigator_selection_from(terminal_runtimes); } KeyCode::Backspace if state.navigator.state_filter.is_some() => { state.navigator.state_filter = None; - state.clamp_navigator_selection(); + state.clamp_navigator_selection_from(terminal_runtimes); } KeyCode::Char('a') if key.modifiers.is_empty() => { state.navigator.query.clear(); state.navigator.state_filter = None; - state.clamp_navigator_selection(); + state.clamp_navigator_selection_from(terminal_runtimes); } KeyCode::Char('b') if key.modifiers.is_empty() => { state.navigator.query.clear(); state.navigator.state_filter = Some(NavigatorStateFilter::Blocked); - state.clamp_navigator_selection(); + state.clamp_navigator_selection_from(terminal_runtimes); } KeyCode::Char('w') if key.modifiers.is_empty() => { state.navigator.query.clear(); state.navigator.state_filter = Some(NavigatorStateFilter::Working); - state.clamp_navigator_selection(); + state.clamp_navigator_selection_from(terminal_runtimes); } KeyCode::Char('i') if key.modifiers.is_empty() => { state.navigator.query.clear(); state.navigator.state_filter = Some(NavigatorStateFilter::Idle); - state.clamp_navigator_selection(); + state.clamp_navigator_selection_from(terminal_runtimes); } KeyCode::Char('d') if key.modifiers.is_empty() => { state.navigator.query.clear(); state.navigator.state_filter = Some(NavigatorStateFilter::Done); - state.clamp_navigator_selection(); + state.clamp_navigator_selection_from(terminal_runtimes); } - KeyCode::Char('j') | KeyCode::Down => state.move_navigator_selection(1), - KeyCode::Char('k') | KeyCode::Up => state.move_navigator_selection(-1), - KeyCode::Char('d') if key.modifiers == KeyModifiers::CONTROL => { - state.move_navigator_selection((state.navigator_body_rect().height / 2).max(1) as isize) + KeyCode::Char('j') | KeyCode::Down => { + state.move_navigator_selection_from(terminal_runtimes, 1) } + KeyCode::Char('k') | KeyCode::Up => { + state.move_navigator_selection_from(terminal_runtimes, -1) + } + KeyCode::Char('d') if key.modifiers == KeyModifiers::CONTROL => state + .move_navigator_selection_from( + terminal_runtimes, + (state.navigator_body_rect().height / 2).max(1) as isize, + ), KeyCode::Char('u') if key.modifiers == KeyModifiers::CONTROL => state - .move_navigator_selection(-((state.navigator_body_rect().height / 2).max(1) as isize)), - KeyCode::Char(' ') => state.toggle_selected_navigator_workspace(), + .move_navigator_selection_from( + terminal_runtimes, + -((state.navigator_body_rect().height / 2).max(1) as isize), + ), + KeyCode::Char(' ') => state.toggle_selected_navigator_workspace_from(terminal_runtimes), KeyCode::Home => { state.navigator.selected = 0; - state.ensure_navigator_selection_visible(); + state.ensure_navigator_selection_visible_from(terminal_runtimes); } KeyCode::End | KeyCode::Char('G') => { - state.navigator.selected = state.navigator_rows().len().saturating_sub(1); - state.ensure_navigator_selection_visible(); + state.navigator.selected = state + .navigator_rows_from(terminal_runtimes) + .len() + .saturating_sub(1); + state.ensure_navigator_selection_visible_from(terminal_runtimes); } _ => {} } diff --git a/src/app/input/navigate.rs b/src/app/input/navigate.rs index 4bd69f1c..2ac86aeb 100644 --- a/src/app/input/navigate.rs +++ b/src/app/input/navigate.rs @@ -830,7 +830,7 @@ pub(super) fn execute_navigate_action_in_context( super::modal::request_detach(state); leave_navigate_mode(state); } - NavigateAction::OpenNavigator => state.open_navigator(), + NavigateAction::OpenNavigator => state.open_navigator_from(terminal_runtimes), } finish_action_context(state, context, previous_mode); diff --git a/src/app/input/overlays.rs b/src/app/input/overlays.rs index 423334b9..20944080 100644 --- a/src/app/input/overlays.rs +++ b/src/app/input/overlays.rs @@ -126,9 +126,14 @@ impl App { if self.state.mode == Mode::Navigator { match mouse.kind { MouseEventKind::Moved => { - if let Some(idx) = self.state.navigator_row_index_at(mouse.column, mouse.row) { + if let Some(idx) = self.state.navigator_row_index_at_from( + &self.terminal_runtimes, + mouse.column, + mouse.row, + ) { self.state.navigator.selected = idx; - self.state.ensure_navigator_selection_visible(); + self.state + .ensure_navigator_selection_visible_from(&self.terminal_runtimes); } } MouseEventKind::Down(MouseButton::Left) => { @@ -137,23 +142,29 @@ impl App { .navigator_search_contains(mouse.column, mouse.row) { self.state.navigator.search_focused = true; - } else if let Some(idx) = - self.state.navigator_row_index_at(mouse.column, mouse.row) - { + } else if let Some(idx) = self.state.navigator_row_index_at_from( + &self.terminal_runtimes, + mouse.column, + mouse.row, + ) { self.state.navigator.selected = idx; let target = self .state - .navigator_rows() + .navigator_rows_from(&self.terminal_runtimes) .get(idx) .map(|row| (row.target.clone(), row.is_workspace)); if let Some((NavigatorTarget::Workspace { .. }, true)) = target { if self.state.navigator_row_caret_at(mouse.column) { - self.state.toggle_selected_navigator_workspace(); + self.state.toggle_selected_navigator_workspace_from( + &self.terminal_runtimes, + ); } else { - self.state.accept_navigator_selection(); + self.state + .accept_navigator_selection_from(&self.terminal_runtimes); } } else { - self.state.accept_navigator_selection(); + self.state + .accept_navigator_selection_from(&self.terminal_runtimes); } } else if !self.state.navigator_popup_contains(mouse.column, mouse.row) { leave_modal(&mut self.state); @@ -162,15 +173,19 @@ impl App { MouseEventKind::ScrollUp => { self.state.navigator.scroll = self.state.navigator.scroll.saturating_sub(3); self.state.navigator.selected = self.state.navigator.scroll; - self.state.clamp_navigator_selection(); + self.state + .clamp_navigator_selection_from(&self.terminal_runtimes); } MouseEventKind::ScrollDown => { let viewport = self.state.navigator_body_rect().height as usize; - let max = self.state.navigator_max_scroll(viewport); + let max = self + .state + .navigator_max_scroll_from(&self.terminal_runtimes, viewport); self.state.navigator.scroll = self.state.navigator.scroll.saturating_add(3).min(max); self.state.navigator.selected = self.state.navigator.scroll; - self.state.clamp_navigator_selection(); + self.state + .clamp_navigator_selection_from(&self.terminal_runtimes); } _ => {} } @@ -312,7 +327,12 @@ impl AppState { rect_contains(self.navigator_search_rect(), col, row) } - pub(crate) fn navigator_row_index_at(&self, col: u16, row: u16) -> Option<usize> { + pub(crate) fn navigator_row_index_at_from( + &self, + terminal_runtimes: &crate::terminal::TerminalRuntimeRegistry, + col: u16, + row: u16, + ) -> Option<usize> { let body = self.navigator_body_rect(); if !rect_contains(body, col, row) { return None; @@ -321,7 +341,7 @@ impl AppState { .navigator .scroll .saturating_add(row.saturating_sub(body.y) as usize); - (idx < self.navigator_rows().len()).then_some(idx) + (idx < self.navigator_rows_from(terminal_runtimes).len()).then_some(idx) } pub(crate) fn navigator_row_caret_at(&self, col: u16) -> bool { diff --git a/src/app/mod.rs b/src/app/mod.rs index 18d52bfc..0c2feb67 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -1415,7 +1415,7 @@ impl App { self.handle_settings_key(key_event); } Mode::Navigator => { - input::handle_navigator_key(&mut self.state, key_event); + input::handle_navigator_key(&mut self.state, &self.terminal_runtimes, key_event); } Mode::Terminal => { // Should not be called in terminal mode. @@ -401,7 +401,7 @@ pub fn render_with_runtime_registry( Mode::ConfirmRemoveWorktree => render_remove_worktree_overlay(app, frame, frame.area()), Mode::GlobalMenu => render_global_launcher_menu(app, frame), Mode::KeybindHelp => render_keybind_help_overlay(app, frame), - Mode::Navigator => render_navigator_overlay(app, frame), + Mode::Navigator => render_navigator_overlay(app, terminal_runtimes, frame), Mode::Terminal => {} } } diff --git a/src/ui/navigator.rs b/src/ui/navigator.rs index b6bc1f19..b0414f98 100644 --- a/src/ui/navigator.rs +++ b/src/ui/navigator.rs @@ -12,8 +12,13 @@ use super::{ widgets::{panel_contrast_fg, render_panel_shell}, }; use crate::app::state::{AppState, NavigatorRow, NavigatorStateFilter, NavigatorTarget}; +use crate::terminal::TerminalRuntimeRegistry; -pub(super) fn render_navigator_overlay(app: &AppState, frame: &mut Frame) { +pub(super) fn render_navigator_overlay( + app: &AppState, + terminal_runtimes: &TerminalRuntimeRegistry, + frame: &mut Frame, +) { let popup = app.navigator_popup_rect(); let Some(inner) = render_panel_shell(frame, popup, app.palette.accent, app.palette.panel_bg) else { @@ -28,10 +33,10 @@ pub(super) fn render_navigator_overlay(app: &AppState, frame: &mut Frame) { if body.height > 0 { render_separator(frame, Rect::new(inner.x, search.y + 1, inner.width, 1), app); - render_rows(app, frame, body); - render_navigator_scrollbar(app, frame, body); + render_rows(app, terminal_runtimes, frame, body); + render_navigator_scrollbar(app, terminal_runtimes, frame, body); } - render_detail(app, frame, detail); + render_detail(app, terminal_runtimes, frame, detail); render_footer(app, frame, footer); } @@ -129,8 +134,13 @@ fn render_separator(frame: &mut Frame, area: Rect, app: &AppState) { ); } -fn render_rows(app: &AppState, frame: &mut Frame, body: Rect) { - let rows = app.navigator_rows(); +fn render_rows( + app: &AppState, + terminal_runtimes: &TerminalRuntimeRegistry, + frame: &mut Frame, + body: Rect, +) { + let rows = app.navigator_rows_from(terminal_runtimes); let start = app.navigator.scroll.min(rows.len()); let end = rows.len().min(start.saturating_add(body.height as usize)); for (visible_idx, row) in rows[start..end].iter().enumerate() { @@ -227,11 +237,16 @@ fn render_row(app: &AppState, frame: &mut Frame, rect: Rect, row: &NavigatorRow, } } -fn render_navigator_scrollbar(app: &AppState, frame: &mut Frame, body: Rect) { +fn render_navigator_scrollbar( + app: &AppState, + terminal_runtimes: &TerminalRuntimeRegistry, + frame: &mut Frame, + body: Rect, +) { if body.width <= 1 || body.height == 0 { return; } - let rows = app.navigator_rows().len(); + let rows = app.navigator_rows_from(terminal_runtimes).len(); let viewport = body.height as usize; if rows <= viewport { return; @@ -269,12 +284,17 @@ fn metadata_width(width: u16) -> u16 { } } -fn render_detail(app: &AppState, frame: &mut Frame, area: Rect) { +fn render_detail( + app: &AppState, + terminal_runtimes: &TerminalRuntimeRegistry, + frame: &mut Frame, + area: Rect, +) { if area.height == 0 || area.width == 0 { return; } render_separator(frame, area, app); - let detail = selected_detail(app); + let detail = selected_detail(app, terminal_runtimes); if detail.is_empty() { return; } @@ -285,50 +305,59 @@ fn render_detail(app: &AppState, frame: &mut Frame, area: Rect) { ); } -fn selected_detail(app: &AppState) -> String { - let rows = app.navigator_rows(); +fn selected_detail(app: &AppState, terminal_runtimes: &TerminalRuntimeRegistry) -> String { + let rows = app.navigator_rows_from(terminal_runtimes); let Some(row) = rows.get(app.navigator.selected) else { return String::new(); }; match row.target { - NavigatorTarget::Workspace { ws_idx } => workspace_detail(app, ws_idx), - NavigatorTarget::Tab { ws_idx, tab_idx } => tab_detail(app, ws_idx, tab_idx), + NavigatorTarget::Workspace { ws_idx } => workspace_detail(app, terminal_runtimes, ws_idx), + NavigatorTarget::Tab { ws_idx, tab_idx } => { + tab_detail(app, terminal_runtimes, ws_idx, tab_idx) + } NavigatorTarget::Pane { ws_idx, tab_idx, pane_id, - } => pane_detail(app, ws_idx, tab_idx, pane_id), + } => pane_detail(app, terminal_runtimes, ws_idx, tab_idx, pane_id), } } -fn workspace_detail(app: &AppState, ws_idx: usize) -> String { +fn workspace_detail( + app: &AppState, + terminal_runtimes: &TerminalRuntimeRegistry, + ws_idx: usize, +) -> String { let Some(ws) = app.workspaces.get(ws_idx) else { return String::new(); }; - let terminal_runtimes = crate::terminal::TerminalRuntimeRegistry::new(); - let label = ws.display_name_from(&app.terminals, &terminal_runtimes); + let label = ws.display_name_from(&app.terminals, terminal_runtimes); let pane_count = ws.tabs.iter().map(|tab| tab.panes.len()).sum::<usize>(); let mut parts = vec![label, format!("{pane_count} panes")]; - if !rowless_workspace_activity(app, ws_idx).is_empty() { - parts.push(rowless_workspace_activity(app, ws_idx)); + if !rowless_workspace_activity(app, terminal_runtimes, ws_idx).is_empty() { + parts.push(rowless_workspace_activity(app, terminal_runtimes, ws_idx)); } parts.join(" · ") } -fn tab_detail(app: &AppState, ws_idx: usize, tab_idx: usize) -> String { +fn tab_detail( + app: &AppState, + terminal_runtimes: &TerminalRuntimeRegistry, + ws_idx: usize, + tab_idx: usize, +) -> String { let Some(ws) = app.workspaces.get(ws_idx) else { return String::new(); }; let Some(tab) = ws.tabs.get(tab_idx) else { return String::new(); }; - let terminal_runtimes = crate::terminal::TerminalRuntimeRegistry::new(); let mut parts = vec![ - ws.display_name_from(&app.terminals, &terminal_runtimes), + ws.display_name_from(&app.terminals, terminal_runtimes), format!("tab: {}", tab.display_name()), format!("{} panes", tab.panes.len()), ]; - let rows = app.navigator_rows(); + let rows = app.navigator_rows_from(terminal_runtimes); if let Some(meta) = rows .into_iter() .find(|row| matches!(row.target, NavigatorTarget::Tab { ws_idx: row_ws_idx, tab_idx: row_tab_idx } if row_ws_idx == ws_idx && row_tab_idx == tab_idx)) @@ -342,6 +371,7 @@ fn tab_detail(app: &AppState, ws_idx: usize, tab_idx: usize) -> String { fn pane_detail( app: &AppState, + terminal_runtimes: &TerminalRuntimeRegistry, ws_idx: usize, tab_idx: usize, pane_id: crate::layout::PaneId, @@ -352,8 +382,7 @@ fn pane_detail( let Some(tab) = ws.tabs.get(tab_idx) else { return String::new(); }; - let terminal_runtimes = crate::terminal::TerminalRuntimeRegistry::new(); - let mut parts = vec![ws.display_name_from(&app.terminals, &terminal_runtimes)]; + let mut parts = vec![ws.display_name_from(&app.terminals, terminal_runtimes)]; if ws.tabs.len() > 1 { parts.push(format!("tab: {}", tab.display_name())); } @@ -397,8 +426,12 @@ fn pane_detail( parts.join(" · ") } -fn rowless_workspace_activity(app: &AppState, ws_idx: usize) -> String { - app.navigator_rows() +fn rowless_workspace_activity( + app: &AppState, + terminal_runtimes: &TerminalRuntimeRegistry, + ws_idx: usize, +) -> String { + app.navigator_rows_from(terminal_runtimes) .into_iter() .find(|row| matches!(row.target, NavigatorTarget::Workspace { ws_idx: row_ws_idx } if row_ws_idx == ws_idx)) .map(|row| row.meta) |
