diff options
Diffstat (limited to 'src/ui/keybind_help.rs')
| -rw-r--r-- | src/ui/keybind_help.rs | 152 |
1 files changed, 132 insertions, 20 deletions
diff --git a/src/ui/keybind_help.rs b/src/ui/keybind_help.rs index 8385af36..25c999fa 100644 --- a/src/ui/keybind_help.rs +++ b/src/ui/keybind_help.rs @@ -181,6 +181,26 @@ pub(super) fn keybind_help_groups(app: &AppState) -> Vec<HelpGroup> { groups } +fn filter_keybind_help_groups(groups: Vec<HelpGroup>, query: &str) -> Vec<HelpGroup> { + if query.is_empty() { + return groups; + } + + let query = query.to_lowercase(); + groups + .into_iter() + .filter_map(|(group, entries)| { + let entries = entries + .into_iter() + .filter(|(key, label)| { + key.to_lowercase().contains(&query) || label.to_lowercase().contains(&query) + }) + .collect::<Vec<_>>(); + (!entries.is_empty()).then_some((group, entries)) + }) + .collect() +} + pub(crate) fn keybind_help_lines(app: &AppState) -> Vec<(usize, Line<'static>)> { let heading_style = Style::default() .fg(app.palette.accent) @@ -190,7 +210,7 @@ pub(crate) fn keybind_help_lines(app: &AppState) -> Vec<(usize, Line<'static>)> .add_modifier(Modifier::BOLD); let label_style = Style::default().fg(app.palette.text); - let groups = keybind_help_groups(app); + let groups = filter_keybind_help_groups(keybind_help_groups(app), &app.keybind_help.query); let key_width = groups .iter() .flat_map(|(_, entries)| entries.iter().map(|(key, _)| key.chars().count())) @@ -199,6 +219,17 @@ pub(crate) fn keybind_help_lines(app: &AppState) -> Vec<(usize, Line<'static>)> let mut lines = Vec::new(); + if groups.is_empty() { + let message = " no matching keybinds"; + return vec![( + message.chars().count(), + Line::from(Span::styled( + message, + Style::default().fg(app.palette.overlay1), + )), + )]; + } + for (group, entries) in groups { lines.push(( group.len() + 1, @@ -240,17 +271,38 @@ pub(super) fn render_keybind_help_overlay(app: &AppState, frame: &mut Frame) { frame, release_notes_close_button_rect(header_rows[0]), Some("esc"), - "close", + if app.keybind_help.search_focused { + "back" + } else { + "close" + }, Style::default() .fg(panel_contrast_fg(&app.palette)) .bg(app.palette.accent) .add_modifier(Modifier::BOLD), ); - frame.render_widget( - Paragraph::new(" available commands and configured shortcuts") - .style(Style::default().fg(app.palette.overlay1)), - header_rows[1], - ); + let search_line = if app.keybind_help.search_focused { + Line::from(vec![ + Span::styled( + " / ", + Style::default() + .fg(app.palette.accent) + .add_modifier(Modifier::BOLD), + ), + Span::styled( + app.keybind_help.query.as_str(), + Style::default() + .fg(app.palette.text) + .add_modifier(Modifier::BOLD), + ), + ]) + } else { + Line::from(Span::styled( + " press / to filter by command or shortcut", + Style::default().fg(app.palette.overlay0), + )) + }; + frame.render_widget(Paragraph::new(search_line), header_rows[1]); let body_area = stack.content; let metrics = crate::pane::ScrollMetrics { @@ -292,17 +344,77 @@ pub(super) fn render_keybind_help_overlay(app: &AppState, frame: &mut Frame) { ); } - frame.render_widget( - Paragraph::new(Line::from(vec![ - Span::styled(" scroll ", Style::default().fg(app.palette.overlay0)), - Span::styled("wheel ↑↓", Style::default().fg(app.palette.text)), - Span::styled(" · ", Style::default().fg(app.palette.overlay0)), - Span::styled("jump", Style::default().fg(app.palette.overlay0)), - Span::styled(" pgup / pgdn ", Style::default().fg(app.palette.text)), - Span::styled(" · ", Style::default().fg(app.palette.overlay0)), - Span::styled("close", Style::default().fg(app.palette.overlay0)), - Span::styled(" esc / enter ", Style::default().fg(app.palette.text)), - ])), - stack.footer.unwrap_or_default(), - ); + let footer = if app.keybind_help.search_focused { + Line::from(vec![ + Span::styled(" filter ", Style::default().fg(app.palette.overlay0)), + Span::styled("type/backspace", Style::default().fg(app.palette.text)), + Span::styled(" · ", Style::default().fg(app.palette.overlay0)), + Span::styled("clear ", Style::default().fg(app.palette.overlay0)), + Span::styled("ctrl+u", Style::default().fg(app.palette.text)), + Span::styled(" · ", Style::default().fg(app.palette.overlay0)), + Span::styled("scroll ", Style::default().fg(app.palette.overlay0)), + Span::styled("↑↓/pgup/pgdn", Style::default().fg(app.palette.text)), + Span::styled(" · ", Style::default().fg(app.palette.overlay0)), + Span::styled("back ", Style::default().fg(app.palette.overlay0)), + Span::styled("esc", Style::default().fg(app.palette.text)), + ]) + } else { + Line::from(vec![ + Span::styled(" search ", Style::default().fg(app.palette.overlay0)), + Span::styled("/", Style::default().fg(app.palette.text)), + Span::styled(" · ", Style::default().fg(app.palette.overlay0)), + Span::styled("scroll ", Style::default().fg(app.palette.overlay0)), + Span::styled("j/k/↑↓/pgup/pgdn", Style::default().fg(app.palette.text)), + Span::styled(" · ", Style::default().fg(app.palette.overlay0)), + Span::styled("close ", Style::default().fg(app.palette.overlay0)), + Span::styled("esc/enter", Style::default().fg(app.palette.text)), + ]) + }; + frame.render_widget(Paragraph::new(footer), stack.footer.unwrap_or_default()); +} + +#[cfg(test)] +mod tests { + use super::*; + + fn groups() -> Vec<HelpGroup> { + vec![ + ( + "workspaces / tabs", + vec![ + help_entry("w", "workspace navigation"), + help_entry("c", "new tab"), + ], + ), + ( + "panes", + vec![ + help_entry("v", "split vertical"), + help_entry("x", "close pane"), + ], + ), + ] + } + + #[test] + fn keybind_help_filter_matches_labels_case_insensitively() { + let filtered = filter_keybind_help_groups(groups(), "WoRk"); + + assert_eq!(filtered.len(), 1); + assert_eq!(filtered[0].0, "workspaces / tabs"); + assert_eq!(filtered[0].1.len(), 1); + assert_eq!(filtered[0].1[0].1, "workspace navigation"); + } + + #[test] + fn keybind_help_filter_matches_shortcuts_without_matching_group_headings() { + let filtered = filter_keybind_help_groups(groups(), "x"); + + assert_eq!(filtered.len(), 1); + assert_eq!(filtered[0].0, "panes"); + assert_eq!(filtered[0].1.len(), 1); + assert_eq!(filtered[0].1[0].1, "close pane"); + + assert!(filter_keybind_help_groups(groups(), "panes").is_empty()); + } } |
