Skip to main content

rab/agent/ui/
help.rs

1use crate::agent::ui::theme::RabTheme;
2use crate::tui::Component;
3
4/// Help overlay showing available commands and keybindings.
5pub struct HelpOverlay {
6    theme: RabTheme,
7    commands: Vec<(String, String)>,
8}
9
10impl HelpOverlay {
11    pub fn new(theme: &RabTheme) -> Self {
12        Self {
13            theme: theme.clone(),
14            commands: Vec::new(),
15        }
16    }
17
18    pub fn set_commands(&mut self, commands: Vec<(String, String)>) {
19        self.commands = commands;
20    }
21}
22
23impl Component for HelpOverlay {
24    fn render(&mut self, width: usize) -> Vec<String> {
25        let mut lines = Vec::new();
26
27        // Reusable Text + Spacer for each section
28        let push = |l: &mut Vec<String>, text: &str| {
29            l.push(crate::tui::util::truncate_to_width(text, width, "", true));
30        };
31
32        push(
33            &mut lines,
34            &self.theme.bold(&self.theme.accent("  Keyboard Shortcuts")),
35        );
36        lines.push(String::new());
37
38        let shortcuts = [
39            ("Enter", "Submit message"),
40            ("Ctrl+J", "Newline"),
41            ("Ctrl+C", "Interrupt / clear editor"),
42            ("Ctrl+D", "Quit (empty) / interrupt"),
43            ("Escape", "Clear editor"),
44            ("Ctrl+L", "Open model selector"),
45            ("!<command>", "Run bash inline"),
46            ("!!<command>", "Run bash (excluded from context)"),
47            ("Ctrl+T", "Toggle thinking visibility"),
48            ("Ctrl+O", "Toggle tool output"),
49            ("F1", "Show this help"),
50            ("↑↓", "History (editor empty)"),
51            ("PgUp / PgDn", "Scroll messages"),
52        ];
53
54        for (key, desc) in &shortcuts {
55            let line = format!(
56                "  {:20} {}",
57                self.theme.bold(&self.theme.accent(key)),
58                self.theme.dim(desc)
59            );
60            push(&mut lines, &line);
61        }
62
63        if !self.commands.is_empty() {
64            lines.push(String::new());
65            push(
66                &mut lines,
67                &self.theme.bold(&self.theme.accent("  Slash Commands")),
68            );
69            lines.push(String::new());
70            for (name, desc) in &self.commands {
71                let line = format!(
72                    "  /{:<19} {}",
73                    self.theme.bold(&self.theme.accent(name)),
74                    self.theme.dim(desc)
75                );
76                push(&mut lines, &line);
77            }
78        }
79
80        lines.push(String::new());
81        push(
82            &mut lines,
83            &self.theme.dim("  Press any key to close help."),
84        );
85
86        lines
87    }
88
89    fn handle_input(&mut self, _key: &crossterm::event::KeyEvent) -> bool {
90        // Any key closes help
91        true
92    }
93}