stynx_code_tui/widgets/
slash_popover.rs1use ratatui::{
2 buffer::Buffer,
3 layout::Rect,
4 style::{Modifier, Style},
5 text::{Line, Span},
6 widgets::{Block, Borders, Clear, Paragraph, Widget},
7};
8
9use crate::state::InputState;
10use crate::theme;
11
12pub struct SlashPopover<'a> {
13 pub state: &'a InputState,
14 pub anchor: Rect,
15}
16
17impl<'a> SlashPopover<'a> {
18 pub fn new(state: &'a InputState, anchor: Rect) -> Self {
19 Self { state, anchor }
20 }
21}
22
23impl<'a> Widget for SlashPopover<'a> {
24 fn render(self, _area: Rect, buf: &mut Buffer) {
25 if self.state.slash_matches.is_empty() { return; }
26 let max_show = 8usize;
27 let count = self.state.slash_matches.len().min(max_show) as u16;
28 let max_cmd = self
29 .state
30 .slash_matches
31 .iter()
32 .map(|(c, _)| c.len())
33 .max()
34 .unwrap_or(8);
35 let max_desc = self
36 .state
37 .slash_matches
38 .iter()
39 .map(|(_, d)| d.len())
40 .max()
41 .unwrap_or(20);
42 let inner_w = (max_cmd + max_desc + 6) as u16;
43 let width = inner_w.min(self.anchor.width.saturating_sub(2)).max(20);
44 let height = count + 2;
45
46 let x = self.anchor.x + 1;
47 let y = self.anchor.y.saturating_sub(height);
48 if y < 1 { return; }
49 let rect = Rect { x, y, width, height };
50 Clear.render(rect, buf);
51
52 let block = Block::default()
53 .borders(Borders::ALL)
54 .border_style(Style::default().fg(theme::BORDER_ACTIVE()))
55 .style(Style::default().bg(theme::BACKGROUND_PANEL()));
56 let inner = block.inner(rect);
57 block.render(rect, buf);
58
59 let mut lines: Vec<Line<'static>> = Vec::new();
60 let selected = self.state.slash_selected.min(self.state.slash_matches.len().saturating_sub(1));
61 for (i, (cmd, desc)) in self.state.slash_matches.iter().take(max_show).enumerate() {
62 let is_sel = i == selected;
63 let bg = if is_sel { theme::BACKGROUND_MENU() } else { theme::BACKGROUND_PANEL() };
64 let cmd_padded = format!("{:width$}", cmd, width = max_cmd);
65 lines.push(Line::from(vec![
66 Span::styled(
67 format!(" {cmd_padded} "),
68 Style::default()
69 .fg(if is_sel { theme::ACCENT() } else { theme::TEXT() })
70 .bg(bg)
71 .add_modifier(Modifier::BOLD),
72 ),
73 Span::styled(
74 desc.clone(),
75 Style::default().fg(theme::TEXT_MUTED()).bg(bg),
76 ),
77 ]));
78 }
79
80 Paragraph::new(lines)
81 .style(Style::default().bg(theme::BACKGROUND_PANEL()))
82 .render(inner, buf);
83 }
84}