mermaid_cli/render/widgets/
slash_palette.rs1use ratatui::{
11 buffer::Buffer,
12 layout::Rect,
13 style::{Modifier, Style},
14 text::{Line, Span},
15 widgets::{Block, Borders, Paragraph, Widget},
16};
17
18use crate::domain::slash_commands::SlashCommand;
19use crate::render::theme::Theme;
20
21const MAX_VISIBLE_ROWS: usize = 8;
26
27pub struct SlashPaletteWidget<'a> {
28 pub theme: &'a Theme,
29 pub commands: Vec<&'static SlashCommand>,
31 pub selected_index: usize,
34}
35
36impl<'a> Widget for SlashPaletteWidget<'a> {
37 fn render(self, area: Rect, buf: &mut Buffer) {
38 let total = self.commands.len();
44 let scroll_offset = if self.selected_index >= MAX_VISIBLE_ROWS {
45 self.selected_index + 1 - MAX_VISIBLE_ROWS
46 } else {
47 0
48 };
49 let visible_end = (scroll_offset + MAX_VISIBLE_ROWS).min(total);
50
51 let title = if total > MAX_VISIBLE_ROWS {
54 format!(
55 " Commands ({}-{} of {}) ↑↓ navigate · Tab complete · Esc dismiss ",
56 scroll_offset + 1,
57 visible_end,
58 total
59 )
60 } else {
61 format!(
62 " Commands ({}) ↑↓ navigate · Tab complete · Esc dismiss ",
63 total
64 )
65 };
66
67 let block = Block::default()
68 .borders(Borders::ALL)
69 .border_style(Style::new().fg(self.theme.colors.border.to_color()))
70 .title(title);
71
72 if self.commands.is_empty() {
75 let line = Line::from(vec![Span::styled(
76 " No matching commands",
77 Style::new().fg(self.theme.colors.text_disabled.to_color()),
78 )]);
79 Paragraph::new(vec![line]).block(block).render(area, buf);
80 return;
81 }
82
83 let mut lines: Vec<Line> = Vec::with_capacity(MAX_VISIBLE_ROWS);
84 for (offset, cmd) in self.commands[scroll_offset..visible_end].iter().enumerate() {
85 let absolute_index = scroll_offset + offset;
87 let is_selected = absolute_index == self.selected_index;
88
89 let mut name_part = format!("/{}", cmd.name);
92 if let Some(hint) = cmd.arg_hint {
93 name_part.push(' ');
94 name_part.push_str(hint);
95 }
96
97 let name_style = if is_selected {
98 Style::new()
99 .fg(self.theme.colors.text_highlight.to_color())
100 .add_modifier(Modifier::BOLD | Modifier::REVERSED)
101 } else {
102 Style::new()
103 .fg(self.theme.colors.info.to_color())
104 .add_modifier(Modifier::BOLD)
105 };
106 let desc_style = if is_selected {
107 Style::new()
108 .fg(self.theme.colors.text_primary.to_color())
109 .add_modifier(Modifier::REVERSED)
110 } else {
111 Style::new().fg(self.theme.colors.text_secondary.to_color())
112 };
113
114 let padded_name = format!(" {:<22}", name_part);
116 lines.push(Line::from(vec![
117 Span::styled(padded_name, name_style),
118 Span::styled(format!(" {}", cmd.description), desc_style),
119 ]));
120 }
121
122 Paragraph::new(lines).block(block).render(area, buf);
123 }
124}