Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 115 additions & 3 deletions src/ui/sidebar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -844,9 +844,22 @@ pub(super) fn render_sidebar_collapsed(app: &AppState, frame: &mut Frame, area:
break;
}
let position = detail_idx + 1;
let position_style = Style::default().fg(p.overlay0);
let is_active = app.is_active_pane(detail.ws_idx, detail.tab_idx, detail.pane_id);
let position_style = if is_active {
Style::default().fg(p.text).bg(p.surface_dim)
} else {
Style::default().fg(p.overlay0)
};
let (icon, icon_style) =
state_icon(detail.state, detail.seen, app.status_indicators, p);

if is_active {
let buf = frame.buffer_mut();
for x in detail_content_area.x..detail_content_area.x + detail_content_area.width {
buf[(x, y)].set_style(Style::default().bg(p.surface_dim));
}
}

frame.render_widget(
Paragraph::new(Line::from(vec![
Span::styled(format!("{position:<2}"), position_style),
Expand Down Expand Up @@ -1575,8 +1588,8 @@ fn render_sidebar_toggle(
#[cfg(test)]
mod tests {
use super::*;
use crate::{detect::Agent, workspace::Workspace};
use ratatui::{backend::TestBackend, Terminal};
use crate::{detect::Agent, layout::PaneId, workspace::Workspace};
use ratatui::{backend::TestBackend, layout::Direction, Terminal};

fn row_text(buffer: &ratatui::buffer::Buffer, row: u16, width: u16) -> String {
(0..width)
Expand Down Expand Up @@ -2162,6 +2175,105 @@ rows = [[{ token = "git_status", fg = "#123456" }]]
assert_eq!(buffer[(detail_area.x, detail_area.y + 1)].symbol(), "2");
}

/// Two agent panes in one workspace plus a second workspace, so the
/// assertions can tell pane-level highlighting apart from workspace-level.
fn collapsed_agent_app() -> (crate::app::state::AppState, PaneId, PaneId) {
let mut app = crate::app::state::AppState::test_new();
let mut first = Workspace::test_new("one");
let second_pane = first.test_split(Direction::Horizontal);
let first_pane = first.tabs[0].root_pane;
app.workspaces = vec![first, Workspace::test_new("two")];
app.ensure_test_terminals();

let terminal_ids: Vec<_> = app
.workspaces
.iter()
.flat_map(|ws| ws.tabs.iter())
.flat_map(|tab| tab.panes.values())
.map(|pane| pane.attached_terminal_id.clone())
.collect();
for terminal_id in terminal_ids {
app.terminals.get_mut(&terminal_id).unwrap().detected_agent = Some(Agent::Claude);
}

(app, first_pane, second_pane)
}

fn collapsed_agent_row_styles(
app: &crate::app::state::AppState,
area: Rect,
detail_area: Rect,
rows: u16,
) -> Vec<Vec<ratatui::style::Style>> {
let mut terminal = Terminal::new(TestBackend::new(area.width, area.height))
.expect("test terminal should initialize");
terminal
.draw(|frame| render_sidebar_collapsed(app, frame, area))
.expect("collapsed sidebar should render");
let buffer = terminal.backend().buffer();
(0..rows)
.map(|row| {
(detail_area.x..detail_area.x + detail_area.width)
.map(|x| buffer[(x, detail_area.y + row)].style())
.collect()
})
.collect()
}

#[test]
fn collapsed_sidebar_highlights_only_the_focused_agent_pane() {
let (mut app, first_pane, second_pane) = collapsed_agent_app();
app.active = Some(0);
app.workspaces[0].tabs[0].layout.focus_pane(second_pane);
assert!(app.is_active_pane(0, 0, second_pane));
assert!(!app.is_active_pane(0, 0, first_pane));

let area = Rect::new(0, 0, 4, 14);
let (_, _, detail_area) = collapsed_sidebar_sections(area);
let rows = collapsed_agent_row_styles(&app, area, detail_area, 3);

let highlighted: Vec<_> = rows
.iter()
.filter(|cells| {
cells
.iter()
.all(|style| style.bg == Some(app.palette.surface_dim))
})
.collect();
assert_eq!(
highlighted.len(),
1,
"only the focused agent pane should be highlighted, across the whole row"
);
assert_eq!(highlighted[0][0].fg, Some(app.palette.text));

let muted = rows
.iter()
.filter(|cells| cells[0].fg == Some(app.palette.overlay0))
.count();
assert_eq!(
muted, 2,
"the sibling pane in the active workspace and the other workspace stay muted"
);
}

#[test]
fn collapsed_sidebar_does_not_highlight_agents_without_active_workspace() {
let (mut app, _, _) = collapsed_agent_app();
app.active = None;

let area = Rect::new(0, 0, 4, 14);
let (_, _, detail_area) = collapsed_sidebar_sections(area);
let rows = collapsed_agent_row_styles(&app, area, detail_area, 3);

for cells in rows {
assert_eq!(cells[0].fg, Some(app.palette.overlay0));
for style in cells {
assert_ne!(style.bg, Some(app.palette.surface_dim));
}
}
}

#[test]
fn collapsed_sidebar_keeps_workspace_status_visible_for_two_digit_positions() {
let mut app = crate::app::state::AppState::test_new();
Expand Down
Loading