mirror of
https://github.com/herdrdev/herdr.git
synced 2026-09-22 00:01:06 +00:00
perf(ui): patch only visible spinner cells
This commit is contained in:
@@ -83,7 +83,7 @@ pub(super) fn render_agent_panel(
|
||||
|row| row.rows.len(),
|
||||
|buffer, rect, row, hits| {
|
||||
hits.agents.push((rect, row.pane_id.clone()));
|
||||
render_agent_row(buffer, rect, row, config, spinner_frame);
|
||||
render_agent_row(buffer, rect, row, config, spinner_frame, hits);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -325,6 +325,7 @@ pub(super) fn render_agent_row(
|
||||
row: &AgentRow,
|
||||
config: &ClientShellConfig,
|
||||
spinner_frame: Option<usize>,
|
||||
hits: &mut ShellHitMap,
|
||||
) {
|
||||
let palette = &config.palette;
|
||||
let row_style = if row.focused {
|
||||
@@ -355,9 +356,11 @@ pub(super) fn render_agent_row(
|
||||
} else {
|
||||
row.rows.clone()
|
||||
};
|
||||
let mut spinner_positions = Vec::new();
|
||||
for (index, tokens) in rows.iter().take(rect.height as usize).enumerate() {
|
||||
let indent = if index == 0 { 1 } else { 3 };
|
||||
let mut spans = vec![ratatui::text::Span::raw(" ".repeat(indent))];
|
||||
let mut state_icon_offsets = Vec::new();
|
||||
spans.extend(crate::ui::resolved_token_spans(
|
||||
tokens,
|
||||
icon,
|
||||
@@ -367,12 +370,32 @@ pub(super) fn render_agent_row(
|
||||
secondary,
|
||||
palette,
|
||||
rect.width.saturating_sub(indent as u16) as usize,
|
||||
Some(&mut state_icon_offsets),
|
||||
));
|
||||
spinner_positions.extend(
|
||||
state_icon_offsets
|
||||
.into_iter()
|
||||
.filter_map(|offset| u16::try_from(offset).ok())
|
||||
.map(|offset| {
|
||||
(
|
||||
rect.x.saturating_add(indent as u16).saturating_add(offset),
|
||||
rect.y + index as u16,
|
||||
)
|
||||
}),
|
||||
);
|
||||
Paragraph::new(Line::from(spans)).style(row_style).render(
|
||||
Rect::new(rect.x, rect.y + index as u16, rect.width, 1),
|
||||
buffer,
|
||||
);
|
||||
}
|
||||
super::render::record_animated_status_cells(
|
||||
buffer,
|
||||
hits,
|
||||
row.status,
|
||||
config.status_indicators,
|
||||
spinner_frame,
|
||||
spinner_positions,
|
||||
);
|
||||
}
|
||||
|
||||
fn put_text(buffer: &mut Buffer, x: u16, y: u16, width: u16, text: &str, style: Style) {
|
||||
|
||||
@@ -42,61 +42,26 @@ impl ClientShellState {
|
||||
}
|
||||
let snapshot = self.snapshot.as_deref()?;
|
||||
let surface = self.pane_surface.as_ref()?;
|
||||
if snapshot.revision != surface.projection_revision {
|
||||
if snapshot.revision != surface.projection_revision
|
||||
|| self.hits.animated_status_cells.is_empty()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let sidebar = self.layout(cols, rows).sidebar;
|
||||
if sidebar.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut buffer = Buffer::empty(sidebar);
|
||||
let mut hits = ShellHitMap::default();
|
||||
let mut render_state = render::ShellRenderState {
|
||||
endpoints: &self.endpoints,
|
||||
active_endpoint_id: &self.active_endpoint_id,
|
||||
collapsed_endpoints: &self.collapsed_endpoints,
|
||||
collapsed_groups: &self.collapsed_groups,
|
||||
remote_collapsed_groups: &self.remote_collapsed_groups,
|
||||
workspace_scroll: &mut self.workspace_scroll,
|
||||
agent_scroll: &mut self.agent_scroll,
|
||||
tab_scroll: &mut self.tab_scroll,
|
||||
reveal_focused_workspace: &mut self.reveal_focused_workspace,
|
||||
reveal_focused_tab: &mut self.reveal_focused_tab,
|
||||
sidebar_collapsed: self.sidebar_collapsed,
|
||||
sidebar_section_split: self.sidebar_section_split,
|
||||
tab_drag_insert_index: None,
|
||||
selected_workspace_id: None,
|
||||
reveal_navigation_workspace: &mut self.reveal_navigation_workspace,
|
||||
dragged_workspace_id: None,
|
||||
workspace_drop_indicator_row: None,
|
||||
spinner_frame: Some(self.spinner_frame),
|
||||
};
|
||||
render::render_shell_sidebar(
|
||||
&mut buffer,
|
||||
sidebar,
|
||||
snapshot,
|
||||
&self.config,
|
||||
&mut render_state,
|
||||
&mut hits,
|
||||
);
|
||||
|
||||
let rows = (sidebar.y..sidebar.bottom())
|
||||
.map(|y| {
|
||||
let cells = (sidebar.x..sidebar.right())
|
||||
.map(|x| {
|
||||
buffer
|
||||
.cell((x, y))
|
||||
.map(crate::protocol::CellData::from_ratatui_cell)
|
||||
})
|
||||
.collect::<Option<Vec<_>>>()?;
|
||||
Some(crate::protocol::PaneSurfacePatchRow {
|
||||
x: sidebar.x,
|
||||
y,
|
||||
cells,
|
||||
})
|
||||
let symbol = STATUS_SPINNER_FRAMES.get(self.spinner_frame)?;
|
||||
let rows = self
|
||||
.hits
|
||||
.animated_status_cells
|
||||
.iter()
|
||||
.map(|spinner| {
|
||||
let mut cell = spinner.cell.clone();
|
||||
cell.symbol = (*symbol).to_owned();
|
||||
crate::protocol::PaneSurfacePatchRow {
|
||||
x: spinner.x,
|
||||
y: spinner.y,
|
||||
cells: vec![cell],
|
||||
}
|
||||
})
|
||||
.collect::<Option<Vec<_>>>()?;
|
||||
.collect();
|
||||
Some(ClientComposedSurfacePatch {
|
||||
rows,
|
||||
cursor: None,
|
||||
|
||||
@@ -16,31 +16,41 @@ pub(super) fn render_collapsed(
|
||||
if row.agent.focused {
|
||||
buffer.set_style(rect, Style::default().bg(config.palette.active_row_bg));
|
||||
}
|
||||
let initial = row.machine_label.chars().next().unwrap_or('?');
|
||||
let initial = row.machine_label.chars().next().unwrap_or('?').to_string();
|
||||
let initial_width = super::render::display_width(&initial)
|
||||
.max(1)
|
||||
.min(rect.width);
|
||||
let style = Style::default()
|
||||
.fg(if row.stale {
|
||||
config.palette.overlay0
|
||||
} else {
|
||||
status_color(row.agent.status, &config.palette)
|
||||
})
|
||||
.add_modifier(if row.stale {
|
||||
Modifier::DIM
|
||||
} else {
|
||||
Modifier::empty()
|
||||
});
|
||||
put_text(buffer, rect.x, rect.y, initial_width, &initial, style);
|
||||
put_text(
|
||||
buffer,
|
||||
rect.x,
|
||||
rect.x.saturating_add(initial_width),
|
||||
rect.y,
|
||||
rect.width,
|
||||
&format!(
|
||||
"{initial}{}",
|
||||
status_icon(
|
||||
row.agent.status,
|
||||
config.status_indicators,
|
||||
spinner_frame.filter(|_| !row.stale),
|
||||
)
|
||||
rect.width.saturating_sub(initial_width),
|
||||
status_icon(
|
||||
row.agent.status,
|
||||
config.status_indicators,
|
||||
spinner_frame.filter(|_| !row.stale),
|
||||
),
|
||||
Style::default()
|
||||
.fg(if row.stale {
|
||||
config.palette.overlay0
|
||||
} else {
|
||||
status_color(row.agent.status, &config.palette)
|
||||
})
|
||||
.add_modifier(if row.stale {
|
||||
Modifier::DIM
|
||||
} else {
|
||||
Modifier::empty()
|
||||
}),
|
||||
style,
|
||||
);
|
||||
super::render::record_animated_status_cells(
|
||||
buffer,
|
||||
hits,
|
||||
row.agent.status,
|
||||
config.status_indicators,
|
||||
spinner_frame.filter(|_| !row.stale),
|
||||
(rect.width > initial_width).then_some((rect.x.saturating_add(initial_width), rect.y)),
|
||||
);
|
||||
hits.endpoint_agents
|
||||
.push((rect, row.endpoint_id, row.agent.pane_id));
|
||||
@@ -84,6 +94,7 @@ pub(super) fn render_expanded(
|
||||
&row.agent,
|
||||
config,
|
||||
spinner_frame.filter(|_| !row.stale),
|
||||
hits,
|
||||
);
|
||||
if row.stale {
|
||||
buffer.set_style(
|
||||
|
||||
@@ -170,6 +170,15 @@ pub(super) fn render_collapsed(
|
||||
})
|
||||
.add_modifier(dim),
|
||||
);
|
||||
super::render::record_animated_status_cells(
|
||||
buffer,
|
||||
hits,
|
||||
workspace.agent_status,
|
||||
config.status_indicators,
|
||||
state.spinner_frame.filter(|_| !stale),
|
||||
(rect.width > number_width)
|
||||
.then_some((rect.x.saturating_add(number_width), rect.y)),
|
||||
);
|
||||
hits.workspaces.push(WorkspaceHit {
|
||||
rect,
|
||||
endpoint_id: endpoint.endpoint_id.clone(),
|
||||
@@ -441,6 +450,7 @@ pub(super) fn render_expanded(
|
||||
let selected = state.selected_workspace_id.is_some_and(|target| {
|
||||
target.matches(&endpoint.endpoint_id, &workspace.workspace_id)
|
||||
});
|
||||
let spinner_start = hits.animated_status_cells.len();
|
||||
super::sidebar::render_workspace_rows(
|
||||
buffer,
|
||||
nested,
|
||||
@@ -451,6 +461,7 @@ pub(super) fn render_expanded(
|
||||
state
|
||||
.spinner_frame
|
||||
.filter(|_| endpoint.status == ClientEndpointStatus::Online),
|
||||
hits,
|
||||
),
|
||||
entry,
|
||||
tokens,
|
||||
@@ -461,6 +472,11 @@ pub(super) fn render_expanded(
|
||||
);
|
||||
if selected && palette.selection_bg == ratatui::style::Color::Reset {
|
||||
buffer.set_style(nested, Style::default().bg(palette.active_row_bg));
|
||||
for spinner in &mut hits.animated_status_cells[spinner_start..] {
|
||||
if let Some(cell) = buffer.cell((spinner.x, spinner.y)) {
|
||||
spinner.cell = crate::protocol::CellData::from_ratatui_cell(cell);
|
||||
}
|
||||
}
|
||||
}
|
||||
if endpoint.status != ClientEndpointStatus::Online {
|
||||
buffer.set_style(
|
||||
|
||||
+21
-60
@@ -288,66 +288,28 @@ pub(super) fn render_shell_sidebar(
|
||||
}
|
||||
}
|
||||
|
||||
fn animated_status_visible(
|
||||
snapshot: &ClientShellSnapshot,
|
||||
state: &ShellRenderState<'_>,
|
||||
hits: &ShellHitMap,
|
||||
) -> bool {
|
||||
let working = crate::api::schema::AgentStatus::Working;
|
||||
let empty_collapsed_groups = HashSet::new();
|
||||
let endpoint_snapshot = |endpoint_id: &ClientEndpointId| {
|
||||
state
|
||||
.endpoints
|
||||
.iter()
|
||||
.find(|endpoint| {
|
||||
&endpoint.endpoint_id == endpoint_id
|
||||
&& endpoint.status == ClientEndpointStatus::Online
|
||||
pub(super) fn record_animated_status_cells(
|
||||
buffer: &Buffer,
|
||||
hits: &mut ShellHitMap,
|
||||
status: crate::api::schema::AgentStatus,
|
||||
indicators: crate::config::StatusIndicatorStyle,
|
||||
spinner_frame: Option<usize>,
|
||||
positions: impl IntoIterator<Item = (u16, u16)>,
|
||||
) {
|
||||
if status != crate::api::schema::AgentStatus::Working
|
||||
|| indicators != crate::config::StatusIndicatorStyle::Animated
|
||||
|| spinner_frame.is_none()
|
||||
{
|
||||
return;
|
||||
}
|
||||
hits.animated_status_cells
|
||||
.extend(positions.into_iter().filter_map(|(x, y)| {
|
||||
buffer.cell((x, y)).map(|cell| ClientAnimatedStatusCell {
|
||||
x,
|
||||
y,
|
||||
cell: crate::protocol::CellData::from_ratatui_cell(cell),
|
||||
})
|
||||
.and_then(|endpoint| endpoint.snapshot.as_deref())
|
||||
};
|
||||
hits.workspaces.iter().any(|hit| {
|
||||
endpoint_snapshot(&hit.endpoint_id).is_some_and(|snapshot| {
|
||||
let collapsed_groups = if hit.endpoint_id.is_local() {
|
||||
state.collapsed_groups
|
||||
} else {
|
||||
state
|
||||
.remote_collapsed_groups
|
||||
.get(&hit.endpoint_id)
|
||||
.unwrap_or(&empty_collapsed_groups)
|
||||
};
|
||||
snapshot
|
||||
.workspaces
|
||||
.iter()
|
||||
.find(|workspace| workspace.workspace_id == hit.workspace_id)
|
||||
.is_some_and(|workspace| {
|
||||
let status = if state.sidebar_collapsed {
|
||||
workspace.agent_status
|
||||
} else {
|
||||
super::sidebar::displayed_workspace_status(
|
||||
snapshot,
|
||||
workspace,
|
||||
collapsed_groups,
|
||||
)
|
||||
};
|
||||
status == working
|
||||
})
|
||||
})
|
||||
}) || hits.agents.iter().any(|(_, pane_id)| {
|
||||
snapshot
|
||||
.agents
|
||||
.iter()
|
||||
.any(|agent| agent.pane_id == *pane_id && agent.agent_status == working)
|
||||
}) || hits
|
||||
.endpoint_agents
|
||||
.iter()
|
||||
.any(|(_, endpoint_id, pane_id)| {
|
||||
endpoint_snapshot(endpoint_id).is_some_and(|snapshot| {
|
||||
snapshot
|
||||
.agents
|
||||
.iter()
|
||||
.any(|agent| agent.pane_id == *pane_id && agent.agent_status == working)
|
||||
})
|
||||
})
|
||||
}));
|
||||
}
|
||||
|
||||
pub(super) fn render_shell(
|
||||
@@ -389,7 +351,6 @@ pub(super) fn render_shell(
|
||||
&mut hits,
|
||||
);
|
||||
}
|
||||
hits.animated_status_visible = animated_status_visible(snapshot, &state, &hits);
|
||||
if !config.mouse_capture {
|
||||
hits.sidebar_divider = Rect::default();
|
||||
hits.sidebar_section_divider = Rect::default();
|
||||
|
||||
@@ -86,6 +86,14 @@ pub(crate) fn render_collapsed_sidebar(
|
||||
status_icon(status, config.status_indicators, spinner_frame),
|
||||
Style::default().fg(status_color(status, palette)),
|
||||
);
|
||||
super::render::record_animated_status_cells(
|
||||
buffer,
|
||||
hits,
|
||||
status,
|
||||
config.status_indicators,
|
||||
spinner_frame,
|
||||
(rect.width > 2).then_some((rect.x + 2, rect.y)),
|
||||
);
|
||||
hits.workspaces.push(WorkspaceHit {
|
||||
rect,
|
||||
endpoint_id: ClientEndpointId::Local,
|
||||
@@ -153,6 +161,14 @@ pub(crate) fn render_collapsed_sidebar(
|
||||
status_icon(agent.agent_status, config.status_indicators, spinner_frame),
|
||||
Style::default().fg(status_color(agent.agent_status, palette)),
|
||||
);
|
||||
super::render::record_animated_status_cells(
|
||||
buffer,
|
||||
hits,
|
||||
agent.agent_status,
|
||||
config.status_indicators,
|
||||
spinner_frame,
|
||||
(rect.width > 2).then_some((rect.x + 2, rect.y)),
|
||||
);
|
||||
hits.agents.push((rect, pane_id));
|
||||
}
|
||||
hits.sidebar_toggle = if area.is_empty() || workspace_area.width == 0 {
|
||||
@@ -311,7 +327,7 @@ pub(crate) fn render_sidebar(
|
||||
rect,
|
||||
workspace,
|
||||
status,
|
||||
(config.status_indicators, state.spinner_frame),
|
||||
(config.status_indicators, state.spinner_frame, hits),
|
||||
entry,
|
||||
rows,
|
||||
true,
|
||||
@@ -643,7 +659,11 @@ pub(in crate::client::shell) fn render_workspace_rows(
|
||||
area: Rect,
|
||||
workspace: &ClientShellWorkspace,
|
||||
status: crate::api::schema::AgentStatus,
|
||||
indicators: (crate::config::StatusIndicatorStyle, Option<usize>),
|
||||
indicators: (
|
||||
crate::config::StatusIndicatorStyle,
|
||||
Option<usize>,
|
||||
&mut ShellHitMap,
|
||||
),
|
||||
entry: &WorkspaceEntry,
|
||||
rows: Vec<Vec<crate::ui::ResolvedToken>>,
|
||||
endpoint_active: bool,
|
||||
@@ -651,6 +671,8 @@ pub(in crate::client::shell) fn render_workspace_rows(
|
||||
dragged: bool,
|
||||
palette: &Palette,
|
||||
) {
|
||||
let (indicator_style, spinner_frame, hits) = indicators;
|
||||
let mut spinner_positions = Vec::new();
|
||||
for (row_index, row) in rows.iter().enumerate() {
|
||||
let y = area.y + row_index as u16;
|
||||
if y >= area.bottom() {
|
||||
@@ -699,10 +721,11 @@ pub(in crate::client::shell) fn render_workspace_rows(
|
||||
} else {
|
||||
palette.overlay0
|
||||
});
|
||||
let mut state_icon_offsets = Vec::new();
|
||||
let spans = crate::ui::resolved_token_spans(
|
||||
row,
|
||||
(
|
||||
status_icon(status, indicators.0, indicators.1),
|
||||
status_icon(status, indicator_style, spinner_frame),
|
||||
Style::default().fg(status_color(status, palette)),
|
||||
),
|
||||
Style::default().fg(status_color(status, palette)),
|
||||
@@ -711,6 +734,13 @@ pub(in crate::client::shell) fn render_workspace_rows(
|
||||
Style::default().fg(palette.overlay1),
|
||||
palette,
|
||||
area.right().saturating_sub(2).saturating_sub(x) as usize,
|
||||
Some(&mut state_icon_offsets),
|
||||
);
|
||||
spinner_positions.extend(
|
||||
state_icon_offsets
|
||||
.into_iter()
|
||||
.filter_map(|offset| u16::try_from(offset).ok())
|
||||
.map(|offset| (x.saturating_add(offset), y)),
|
||||
);
|
||||
Paragraph::new(Line::from(spans)).render(
|
||||
Rect::new(x, y, area.right().saturating_sub(2).saturating_sub(x), 1),
|
||||
@@ -734,4 +764,12 @@ pub(in crate::client::shell) fn render_workspace_rows(
|
||||
}
|
||||
}
|
||||
}
|
||||
super::render::record_animated_status_cells(
|
||||
buffer,
|
||||
hits,
|
||||
status,
|
||||
indicator_style,
|
||||
spinner_frame,
|
||||
spinner_positions,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ pub(super) struct ShellHitMap {
|
||||
pub(super) pane_splits: Vec<PaneSplitHit>,
|
||||
pub(super) agents: Vec<(Rect, String)>,
|
||||
pub(super) endpoint_agents: Vec<(Rect, ClientEndpointId, String)>,
|
||||
pub(super) animated_status_visible: bool,
|
||||
pub(super) animated_status_cells: Vec<ClientAnimatedStatusCell>,
|
||||
pub(super) agent_body: Rect,
|
||||
pub(super) agent_scrollbar: Rect,
|
||||
pub(super) agent_scroll_metrics: Option<crate::pane::ScrollMetrics>,
|
||||
@@ -196,6 +196,12 @@ pub(super) struct ShellHitMap {
|
||||
pub(super) release_notes_max_scroll: usize,
|
||||
}
|
||||
|
||||
pub(super) struct ClientAnimatedStatusCell {
|
||||
pub(super) x: u16,
|
||||
pub(super) y: u16,
|
||||
pub(super) cell: crate::protocol::CellData,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(super) struct PaneHit {
|
||||
pub(super) rect: Rect,
|
||||
@@ -1919,7 +1925,7 @@ impl ClientShellState {
|
||||
&& self
|
||||
.last_composed_size
|
||||
.is_some_and(|(cols, rows)| self.layout(cols, rows).sidebar.width > 0)
|
||||
&& self.hits.animated_status_visible
|
||||
&& !self.hits.animated_status_cells.is_empty()
|
||||
}
|
||||
|
||||
fn sync_spinner_deadline(&mut self, now: std::time::Instant) {
|
||||
|
||||
@@ -638,6 +638,7 @@ fn animated_status_advances_only_on_its_desktop_deadline() {
|
||||
projected.workspaces[0].agent_status = AgentStatus::Working;
|
||||
let mut config = Config::default();
|
||||
config.ui.status_indicators = Animated;
|
||||
config.ui.sidebar.agents.rows = vec![vec![crate::config::AgentSidebarToken::StateIcon; 16]];
|
||||
let mut state = ClientShellState::new(ClientShellConfig::from_config(&config));
|
||||
state.set_snapshot(Box::new(projected.clone()));
|
||||
state.set_pane_surface(surface());
|
||||
@@ -663,6 +664,7 @@ fn animated_status_advances_only_on_its_desktop_deadline() {
|
||||
let patch = state
|
||||
.compose_spinner_patch(106, 30)
|
||||
.expect("animated sidebar patch");
|
||||
assert!(patch.rows.iter().all(|row| row.cells.len() == 1));
|
||||
let patched = apply_composed_surface_patch(&first, patch).expect("applicable sidebar patch");
|
||||
assert_eq!((state.workspace_scroll, state.agent_scroll), scroll_state);
|
||||
let second = full_state.compose(106, 30).expect("advanced full frame");
|
||||
|
||||
@@ -109,6 +109,36 @@ fn state_with_scrollable_agents() -> (ClientShellState, ClientEndpointId) {
|
||||
(state, remote)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collapsed_wide_machine_initial_records_the_spinner_cell() {
|
||||
let (mut state, remote) = state_with_remote();
|
||||
state.config.status_indicators = crate::config::StatusIndicatorStyle::Animated;
|
||||
state.sidebar_collapsed = true;
|
||||
let endpoint = state
|
||||
.endpoints
|
||||
.iter_mut()
|
||||
.find(|endpoint| endpoint.endpoint_id == remote)
|
||||
.expect("remote endpoint");
|
||||
endpoint.label = "東京".into();
|
||||
let snapshot = endpoint.snapshot.as_mut().expect("remote snapshot");
|
||||
snapshot.workspaces[0].agent_status = AgentStatus::Working;
|
||||
snapshot.agents = vec![agent("worker", AgentStatus::Working, 1)];
|
||||
|
||||
state.compose(100, 28).expect("collapsed endpoint sidebar");
|
||||
let rect = state
|
||||
.hits
|
||||
.endpoint_agents
|
||||
.iter()
|
||||
.find(|(_, endpoint_id, _)| endpoint_id == &remote)
|
||||
.expect("remote agent row")
|
||||
.0;
|
||||
assert!(state
|
||||
.hits
|
||||
.animated_status_cells
|
||||
.iter()
|
||||
.any(|spinner| spinner.x == rect.x + 2 && spinner.y == rect.y));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn switching_machines_preserves_aggregate_agent_scroll_and_visible_rows() {
|
||||
let (mut state, remote) = state_with_scrollable_agents();
|
||||
|
||||
+16
-4
@@ -112,6 +112,7 @@ pub(crate) fn resolved_token_spans(
|
||||
custom_style: Style,
|
||||
palette: &Palette,
|
||||
max_width: usize,
|
||||
mut state_icon_offsets: Option<&mut Vec<usize>>,
|
||||
) -> Vec<Span<'static>> {
|
||||
let fixed_widths = resolved
|
||||
.iter()
|
||||
@@ -223,10 +224,21 @@ pub(crate) fn resolved_token_spans(
|
||||
));
|
||||
}
|
||||
match &token.kind {
|
||||
ResolvedTokenKind::StateIcon => spans.push(Span::styled(
|
||||
state_icon.0.to_string(),
|
||||
apply_token_style(state_icon.1, token.style),
|
||||
)),
|
||||
ResolvedTokenKind::StateIcon => {
|
||||
if let Some(offsets) = state_icon_offsets.as_deref_mut() {
|
||||
let offset = spans
|
||||
.iter()
|
||||
.map(|span| display_width(span.content.as_ref()))
|
||||
.sum::<usize>();
|
||||
if offset.saturating_add(fixed_widths[index]) <= max_width {
|
||||
offsets.push(offset);
|
||||
}
|
||||
}
|
||||
spans.push(Span::styled(
|
||||
state_icon.0.to_string(),
|
||||
apply_token_style(state_icon.1, token.style),
|
||||
));
|
||||
}
|
||||
ResolvedTokenKind::StateText(text) => spans.push(Span::styled(
|
||||
truncate_end(text, budgets[index]),
|
||||
apply_token_style(state_text_style, token.style),
|
||||
|
||||
@@ -283,6 +283,7 @@ rows = [[{ token = "workspace", rules = [{ equals = "long-workspace-name", fg =
|
||||
theme,
|
||||
&super::super::Palette::catppuccin(),
|
||||
width,
|
||||
None,
|
||||
);
|
||||
assert_eq!(spans.len(), 1);
|
||||
assert!(super::super::display_width(&spans[0].content) <= width);
|
||||
|
||||
Reference in New Issue
Block a user