Skip to main content

baml_agent_tui/
help.rs

1//! Help overlay widget — shows keybindings and shortcuts.
2
3use ratatui::prelude::*;
4use ratatui::widgets::{Block, Borders, Clear, Paragraph};
5
6/// Keybinding entry for the help overlay.
7pub struct HelpEntry {
8    pub key: &'static str,
9    pub description: &'static str,
10}
11
12/// Help overlay state.
13pub struct HelpOverlay {
14    pub visible: bool,
15    pub entries: Vec<HelpEntry>,
16}
17
18impl Default for HelpOverlay {
19    fn default() -> Self {
20        Self {
21            visible: false,
22            entries: vec![
23                HelpEntry {
24                    key: "Enter",
25                    description: "Send message / toggle expand",
26                },
27                HelpEntry {
28                    key: "Ctrl+P",
29                    description: "Open fuzzy picker",
30                },
31                HelpEntry {
32                    key: "Ctrl+H",
33                    description: "Toggle this help",
34                },
35                HelpEntry {
36                    key: "Ctrl+C/Q",
37                    description: "Quit",
38                },
39                HelpEntry {
40                    key: "PageUp/Down",
41                    description: "Scroll chat",
42                },
43                HelpEntry {
44                    key: "Up/Down",
45                    description: "Input history",
46                },
47                HelpEntry {
48                    key: "Esc",
49                    description: "Close picker / help",
50                },
51                HelpEntry {
52                    key: "Tab",
53                    description: "Switch picker channel",
54                },
55                HelpEntry {
56                    key: "Shift+Tab",
57                    description: "Previous picker channel",
58                },
59            ],
60        }
61    }
62}
63
64impl HelpOverlay {
65    pub fn toggle(&mut self) {
66        self.visible = !self.visible;
67    }
68
69    pub fn close(&mut self) {
70        self.visible = false;
71    }
72
73    /// Render as a centered overlay.
74    pub fn render(&self, area: Rect, buf: &mut Buffer) {
75        if !self.visible {
76            return;
77        }
78
79        let width = area.width.clamp(30, 50);
80        let height = (self.entries.len() as u16 + 4).min(area.height.saturating_sub(2));
81        let x = area.x + (area.width.saturating_sub(width)) / 2;
82        let y = area.y + (area.height.saturating_sub(height)) / 2;
83        let overlay = Rect::new(x, y, width, height);
84
85        Clear.render(overlay, buf);
86
87        let block = Block::default()
88            .borders(Borders::ALL)
89            .title(" Keybindings ")
90            .border_style(Style::default().fg(Color::Yellow))
91            .style(Style::default().bg(Color::Black));
92
93        let inner = block.inner(overlay);
94        block.render(overlay, buf);
95
96        let key_col_width = 14;
97        let lines: Vec<Line> = self
98            .entries
99            .iter()
100            .map(|e| {
101                let key_padded = format!("{:<width$}", e.key, width = key_col_width);
102                Line::from(vec![
103                    Span::styled(
104                        key_padded,
105                        Style::default()
106                            .fg(Color::Cyan)
107                            .add_modifier(Modifier::BOLD),
108                    ),
109                    Span::styled(e.description, Style::default().fg(Color::White)),
110                ])
111            })
112            .collect();
113
114        Paragraph::new(Text::from(lines)).render(inner, buf);
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121
122    #[test]
123    fn help_toggle() {
124        let mut help = HelpOverlay::default();
125        assert!(!help.visible);
126        help.toggle();
127        assert!(help.visible);
128        help.toggle();
129        assert!(!help.visible);
130    }
131
132    #[test]
133    fn help_renders() {
134        let help = HelpOverlay::default();
135        let area = Rect::new(0, 0, 60, 20);
136        let mut buf = Buffer::empty(area);
137        help.render(area, &mut buf);
138    }
139}