Skip to main content

zeph_tui/widgets/
help.rs

1// SPDX-FileCopyrightText: 2026 Andrei G <bug-ops>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use ratatui::Frame;
5use ratatui::layout::{Alignment, Constraint, Rect};
6use ratatui::text::{Line, Span};
7use ratatui::widgets::{Block, Borders, Cell, Clear, Row, Table};
8
9use crate::layout::centered_rect;
10use crate::theme::Theme;
11
12// 29 data rows + 1 header row + 2 border lines
13const POPUP_HEIGHT: u16 = 32;
14
15pub fn render(frame: &mut Frame, area: Rect) {
16    let theme = Theme::default();
17
18    let popup = centered_rect(70, POPUP_HEIGHT, area);
19    frame.render_widget(Clear, popup);
20
21    let rows = vec![
22        Row::new([
23            Cell::from(Span::styled("Normal mode", theme.panel_title)),
24            Cell::from(""),
25        ]),
26        keybind_row("q", "quit"),
27        keybind_row("i", "enter insert mode"),
28        keybind_row("j / k", "scroll down / up"),
29        keybind_row("PgDn / PgUp", "page scroll down / up"),
30        keybind_row("End / Home", "jump to bottom / top"),
31        keybind_row("d", "toggle side panels"),
32        keybind_row("e", "expand tools"),
33        keybind_row("c", "compact tools"),
34        keybind_row(
35            "Tab",
36            "cycle panels (Chat/Skills/Memory/Resources/SubAgents)",
37        ),
38        keybind_row("a", "focus Sub-Agents panel"),
39        keybind_row("?", "toggle this help"),
40        Row::new([Cell::from(""), Cell::from("")]),
41        Row::new([
42            Cell::from(Span::styled(
43                "Sub-Agents panel (focused)",
44                theme.panel_title,
45            )),
46            Cell::from(""),
47        ]),
48        keybind_row("j / k", "navigate agent list"),
49        keybind_row("Enter", "view selected agent transcript"),
50        keybind_row("Esc", "close panel focus"),
51        Row::new([Cell::from(""), Cell::from("")]),
52        Row::new([
53            Cell::from(Span::styled("Subagent transcript view", theme.panel_title)),
54            Cell::from(""),
55        ]),
56        keybind_row("Esc", "return to main conversation"),
57        Row::new([
58            Cell::from(Span::styled("Insert mode", theme.panel_title)),
59            Cell::from(""),
60        ]),
61        keybind_row("Enter", "send message"),
62        keybind_row("Shift+Enter", "insert newline"),
63        keybind_row("Ctrl+J", "insert newline"),
64        keybind_row("Esc", "return to normal mode"),
65        keybind_row("Ctrl+U", "clear input"),
66        keybind_row("Ctrl+K", "clear queue"),
67        keybind_row("Up / Down", "navigate history"),
68        Row::new([Cell::from(""), Cell::from("")]),
69        Row::new([
70            Cell::from(Span::styled("Confirm mode", theme.panel_title)),
71            Cell::from(""),
72        ]),
73        keybind_row("y", "confirm"),
74        keybind_row("n / Esc", "cancel"),
75    ];
76
77    let header = Row::new([
78        Cell::from(Span::styled("Key", theme.highlight)),
79        Cell::from(Span::styled("Action", theme.highlight)),
80    ]);
81
82    let table = Table::new(
83        rows,
84        [Constraint::Percentage(35), Constraint::Percentage(65)],
85    )
86    .header(header)
87    .block(
88        Block::default()
89            .borders(Borders::ALL)
90            .border_style(theme.panel_border)
91            .title(" Help — press ? or Esc to close ")
92            .title_alignment(Alignment::Center),
93    );
94
95    frame.render_widget(table, popup);
96}
97
98fn keybind_row(key: &'static str, action: &'static str) -> Row<'static> {
99    Row::new([Cell::from(Line::from(key)), Cell::from(Line::from(action))])
100}
101
102#[cfg(test)]
103mod tests {
104    use insta::assert_snapshot;
105
106    use crate::test_utils::render_to_string;
107
108    #[test]
109    fn help_default() {
110        let output = render_to_string(80, 30, |frame, area| {
111            super::render(frame, area);
112        });
113        assert_snapshot!(output);
114    }
115}