Skip to main content

graphrag_cli/ui/components/
help_overlay.rs

1//! Help overlay component showing keybindings
2
3use crate::theme::Theme;
4use ratatui::{
5    layout::{Alignment, Constraint, Direction, Layout, Rect},
6    text::{Line, Span},
7    widgets::{Block, BorderType, Borders, Clear, Paragraph},
8    Frame,
9};
10
11/// Help overlay with keybindings
12pub struct HelpOverlay {
13    /// Is help visible?
14    visible: bool,
15    /// Theme
16    theme: Theme,
17}
18
19impl HelpOverlay {
20    pub fn new() -> Self {
21        Self {
22            visible: false,
23            theme: Theme::default(),
24        }
25    }
26
27    /// Toggle visibility
28    pub fn toggle(&mut self) {
29        self.visible = !self.visible;
30    }
31
32    /// Show help
33    #[allow(dead_code)]
34    pub fn show(&mut self) {
35        self.visible = true;
36    }
37
38    /// Hide help
39    #[allow(dead_code)]
40    pub fn hide(&mut self) {
41        self.visible = false;
42    }
43
44    /// Check if visible
45    pub fn is_visible(&self) -> bool {
46        self.visible
47    }
48}
49
50impl super::Component for HelpOverlay {
51    fn handle_action(&mut self, action: &crate::action::Action) -> Option<crate::action::Action> {
52        match action {
53            crate::action::Action::ToggleHelp => {
54                self.toggle();
55                None
56            },
57            _ => None,
58        }
59    }
60
61    fn render(&mut self, f: &mut Frame, _area: Rect) {
62        if !self.visible {
63            return;
64        }
65
66        // Center the popup (60% x 60%)
67        let area = centered_rect(60, 70, f.area());
68
69        // Clear background
70        f.render_widget(Clear, area);
71
72        let block = Block::default()
73            .title(" GraphRAG CLI - Help ")
74            .borders(Borders::ALL)
75            .border_type(BorderType::Rounded)
76            .border_style(self.theme.border_focused());
77
78        let help_text = vec![
79            Line::from(""),
80            Line::from(vec![Span::styled("Global Shortcuts", self.theme.title())]),
81            Line::from("━".repeat(55)),
82            keybinding_line("?", "Toggle this help overlay", &self.theme),
83            keybinding_line("Ctrl+C / q", "Quit application", &self.theme),
84            keybinding_line(
85                "Ctrl+N / Ctrl+P",
86                "Cycle focus: Input→Results→Raw→Info",
87                &self.theme,
88            ),
89            keybinding_line(
90                "Ctrl+1/2/3/4",
91                "Direct focus: Input/Results/Raw/Info",
92                &self.theme,
93            ),
94            keybinding_line("Esc", "Return focus to Input", &self.theme),
95            keybinding_line(
96                "Ctrl+N (Info Panel)",
97                "Cycle tabs: Stats → Sources → History",
98                &self.theme,
99            ),
100            Line::from(""),
101            Line::from(vec![Span::styled("Input Box", self.theme.title())]),
102            Line::from("━".repeat(55)),
103            keybinding_line("Enter", "Submit query or /command", &self.theme),
104            keybinding_line("Ctrl+D", "Clear input", &self.theme),
105            keybinding_line("Backspace", "Delete character", &self.theme),
106            Line::from(vec![
107                Span::styled("  Tip: ", self.theme.dimmed()),
108                Span::styled("Type queries directly or use /commands", self.theme.text()),
109            ]),
110            Line::from(""),
111            Line::from(vec![Span::styled(
112                "Results Viewer Navigation",
113                self.theme.title(),
114            )]),
115            Line::from("━".repeat(55)),
116            keybinding_line("j/k or ↑/↓", "Scroll focused panel", &self.theme),
117            keybinding_line("Alt+↑/↓", "Scroll focused panel from anywhere", &self.theme),
118            keybinding_line("Ctrl+D", "Scroll down one page", &self.theme),
119            keybinding_line("Ctrl+U", "Scroll up one page", &self.theme),
120            keybinding_line("Home", "Jump to top", &self.theme),
121            keybinding_line("End", "Jump to bottom", &self.theme),
122            Line::from(""),
123            Line::from(vec![Span::styled("Slash Commands", self.theme.title())]),
124            Line::from("━".repeat(55)),
125            keybinding_line("/config <file>", "Load configuration", &self.theme),
126            keybinding_line("/config show", "Show current config", &self.theme),
127            keybinding_line("/load <file>", "Load document", &self.theme),
128            keybinding_line("/stats", "Show graph statistics", &self.theme),
129            keybinding_line("/entities [filter]", "List entities", &self.theme),
130            keybinding_line("/reason <query>", "One-shot reasoning query", &self.theme),
131            keybinding_line("/mode ask|explain|reason", "Switch query mode", &self.theme),
132            keybinding_line(
133                "/export <file.md>",
134                "Export history to Markdown",
135                &self.theme,
136            ),
137            keybinding_line("/workspace <name>", "Switch workspace", &self.theme),
138            keybinding_line("/help", "Show command help", &self.theme),
139            Line::from(""),
140            Line::from(vec![Span::styled("Status Indicators", self.theme.title())]),
141            Line::from("━".repeat(55)),
142            Line::from(vec![
143                Span::styled("ℹ  ", self.theme.info()),
144                Span::styled("Info  ", self.theme.text()),
145                Span::styled("✓  ", self.theme.success()),
146                Span::styled("Success  ", self.theme.text()),
147                Span::styled("⚠  ", self.theme.warning()),
148                Span::styled("Warning", self.theme.text()),
149            ]),
150            Line::from(vec![
151                Span::styled("✗  ", self.theme.error()),
152                Span::styled("Error  ", self.theme.text()),
153                Span::styled("⟳  ", self.theme.progress()),
154                Span::styled("Progress", self.theme.text()),
155            ]),
156            Line::from(""),
157            Line::from(vec![Span::styled(
158                "Press ? to close this help",
159                self.theme.dimmed(),
160            )]),
161        ];
162
163        let paragraph = Paragraph::new(help_text)
164            .block(block)
165            .alignment(Alignment::Left);
166
167        f.render_widget(paragraph, area);
168    }
169}
170
171/// Helper to create a keybinding line
172fn keybinding_line<'a>(key: &'a str, description: &'a str, theme: &'a Theme) -> Line<'a> {
173    Line::from(vec![
174        Span::styled(format!("  {:15} ", key), theme.highlight()),
175        Span::styled(description.to_string(), theme.text()),
176    ])
177}
178
179/// Helper to center a rect
180fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {
181    let popup_layout = Layout::default()
182        .direction(Direction::Vertical)
183        .constraints([
184            Constraint::Percentage((100 - percent_y) / 2),
185            Constraint::Percentage(percent_y),
186            Constraint::Percentage((100 - percent_y) / 2),
187        ])
188        .split(r);
189
190    Layout::default()
191        .direction(Direction::Horizontal)
192        .constraints([
193            Constraint::Percentage((100 - percent_x) / 2),
194            Constraint::Percentage(percent_x),
195            Constraint::Percentage((100 - percent_x) / 2),
196        ])
197        .split(popup_layout[1])[1]
198}
199
200impl Default for HelpOverlay {
201    fn default() -> Self {
202        Self::new()
203    }
204}