aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-06-04 23:28:36 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-06-04 23:29:08 +0300
commit6b3f6462810e400fbbfbc21240b677732b0549ec (patch)
tree67596cbf125f56dad39f22d35c4598a5463367da
parent9b2749776a97bf373e50819dd8a66f290e421551 (diff)
fix: use live workspace labels in navigator
refs #377
-rw-r--r--src/app/actions.rs159
-rw-r--r--src/app/input/mod.rs4
-rw-r--r--src/app/input/modal.rs72
-rw-r--r--src/app/input/navigate.rs2
-rw-r--r--src/app/input/overlays.rs48
-rw-r--r--src/app/mod.rs2
-rw-r--r--src/ui.rs2
-rw-r--r--src/ui/navigator.rs89
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.
diff --git a/src/ui.rs b/src/ui.rs
index f6fc54d4..bbdc0b04 100644
--- a/src/ui.rs
+++ b/src/ui.rs
@@ -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)