diff options
Diffstat (limited to 'src/app/state.rs')
| -rw-r--r-- | src/app/state.rs | 117 |
1 files changed, 117 insertions, 0 deletions
diff --git a/src/app/state.rs b/src/app/state.rs index 0321a6ee..ee9c69be 100644 --- a/src/app/state.rs +++ b/src/app/state.rs @@ -869,6 +869,48 @@ pub(crate) struct NavigatorRow { pub is_tab: bool, pub expanded: bool, pub search_text: String, + /// Whether this row itself matched the active query/state filter, as + /// opposed to being included as ancestor context or cascaded subtree of a + /// matching workspace or tab. Always true when no filter is active. + pub matched: bool, +} + +/// One rendered line in the navigator body. Spacer lines separate workspace +/// groups visually and are not selectable. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum NavigatorDisplayLine { + Spacer, + Row(usize), +} + +pub(crate) fn navigator_display_lines(rows: &[NavigatorRow]) -> Vec<NavigatorDisplayLine> { + let mut lines = Vec::with_capacity(rows.len().saturating_mul(2)); + for (idx, row) in rows.iter().enumerate() { + if row.is_workspace && !lines.is_empty() { + lines.push(NavigatorDisplayLine::Spacer); + } + lines.push(NavigatorDisplayLine::Row(idx)); + } + lines +} + +pub(crate) fn navigator_display_index_of_row( + lines: &[NavigatorDisplayLine], + row_idx: usize, +) -> Option<usize> { + lines + .iter() + .position(|line| *line == NavigatorDisplayLine::Row(row_idx)) +} + +pub(crate) fn navigator_first_row_at_or_after( + lines: &[NavigatorDisplayLine], + line_idx: usize, +) -> Option<usize> { + lines.get(line_idx..)?.iter().find_map(|line| match line { + NavigatorDisplayLine::Row(idx) => Some(*idx), + NavigatorDisplayLine::Spacer => None, + }) } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -2289,6 +2331,81 @@ mod tests { state.assert_invariants_for_test(); } + fn navigator_row_for_display(is_workspace: bool) -> NavigatorRow { + NavigatorRow { + target: NavigatorTarget::Workspace { ws_idx: 0 }, + depth: if is_workspace { 0 } else { 1 }, + label: String::new(), + meta: String::new(), + status: crate::detect::AgentState::Idle, + seen: true, + is_current: false, + is_workspace, + is_tab: false, + expanded: true, + search_text: String::new(), + matched: true, + } + } + + #[test] + fn navigator_display_lines_separate_workspace_groups() { + let rows = vec![ + navigator_row_for_display(true), + navigator_row_for_display(false), + navigator_row_for_display(true), + navigator_row_for_display(false), + ]; + assert_eq!( + navigator_display_lines(&rows), + vec![ + NavigatorDisplayLine::Row(0), + NavigatorDisplayLine::Row(1), + NavigatorDisplayLine::Spacer, + NavigatorDisplayLine::Row(2), + NavigatorDisplayLine::Row(3), + ] + ); + } + + #[test] + fn navigator_display_lines_have_no_leading_spacer() { + let rows = vec![ + navigator_row_for_display(true), + navigator_row_for_display(false), + ]; + assert_eq!( + navigator_display_lines(&rows), + vec![NavigatorDisplayLine::Row(0), NavigatorDisplayLine::Row(1)] + ); + assert!(navigator_display_lines(&[]).is_empty()); + } + + #[test] + fn navigator_display_index_maps_row_to_line() { + let rows = vec![ + navigator_row_for_display(true), + navigator_row_for_display(false), + navigator_row_for_display(true), + ]; + let lines = navigator_display_lines(&rows); + assert_eq!(navigator_display_index_of_row(&lines, 2), Some(3)); + assert_eq!(navigator_display_index_of_row(&lines, 9), None); + } + + #[test] + fn navigator_first_row_skips_spacer_lines() { + let rows = vec![ + navigator_row_for_display(true), + navigator_row_for_display(false), + navigator_row_for_display(true), + ]; + let lines = navigator_display_lines(&rows); + // Line 2 is the spacer before the second workspace. + assert_eq!(navigator_first_row_at_or_after(&lines, 2), Some(2)); + assert_eq!(navigator_first_row_at_or_after(&lines, 4), None); + } + #[test] fn built_in_theme_names_resolve() { for name in THEME_NAMES { |
