metarepo_core/tui/widgets/
help.rs1use crate::tui::modes::Mode;
4use ratatui::{
5 buffer::Buffer,
6 layout::Rect,
7 style::{Color, Modifier, Style},
8 text::{Line, Span},
9 widgets::{Block, Borders, Paragraph, Widget},
10};
11
12pub struct HelpPanel {
14 pub mode: Mode,
16}
17
18impl HelpPanel {
19 pub fn new(mode: Mode) -> Self {
20 Self { mode }
21 }
22
23 fn get_help_lines(&self) -> Vec<Line<'_>> {
25 let title = Line::from(vec![
26 Span::styled("Keybindings", Style::default().add_modifier(Modifier::BOLD)),
27 Span::raw(" - Press "),
28 Span::styled("?", Style::default().fg(Color::Cyan)),
29 Span::raw(" to close"),
30 ]);
31
32 let mut lines = vec![title, Line::from("")];
33
34 match self.mode {
35 Mode::Normal => {
36 lines.extend(vec![
37 Line::from(vec![Span::styled(
38 "Navigation:",
39 Style::default().fg(Color::Yellow),
40 )]),
41 Line::from(" h/j/k/l or ←/↓/↑/→ Move cursor"),
42 Line::from(" g / G Jump to top / bottom"),
43 Line::from(" Ctrl+u / Ctrl+d Page up / down"),
44 Line::from(""),
45 Line::from(vec![Span::styled(
46 "Tree:",
47 Style::default().fg(Color::Yellow),
48 )]),
49 Line::from(" Enter or o Toggle expand/collapse"),
50 Line::from(" O Expand node"),
51 Line::from(" C Collapse node"),
52 Line::from(""),
53 Line::from(vec![Span::styled(
54 "Editing:",
55 Style::default().fg(Color::Yellow),
56 )]),
57 Line::from(" i Enter insert mode"),
58 Line::from(" v Enter visual mode"),
59 Line::from(" d Delete node"),
60 Line::from(" x Delete character"),
61 Line::from(""),
62 Line::from(vec![Span::styled(
63 "Commands:",
64 Style::default().fg(Color::Yellow),
65 )]),
66 Line::from(" : Enter command mode"),
67 Line::from(" Ctrl+w Save"),
68 Line::from(" Ctrl+q Quit"),
69 Line::from(" ? Show/hide help"),
70 ]);
71 }
72 Mode::Insert => {
73 lines.extend(vec![
74 Line::from(vec![Span::styled(
75 "Insert Mode:",
76 Style::default().fg(Color::Green),
77 )]),
78 Line::from(" Esc Return to normal mode"),
79 Line::from(" Typing Insert text"),
80 Line::from(" Backspace Delete previous char"),
81 Line::from(" Enter New line / confirm"),
82 Line::from(" ←/→/↑/↓ Navigate"),
83 ]);
84 }
85 Mode::Visual => {
86 lines.extend(vec![
87 Line::from(vec![Span::styled(
88 "Visual Mode:",
89 Style::default().fg(Color::Yellow),
90 )]),
91 Line::from(" Esc or v Return to normal mode"),
92 Line::from(" j/k or ↓/↑ Extend selection"),
93 Line::from(" Ctrl+a Select all"),
94 Line::from(" d or x Delete selection"),
95 ]);
96 }
97 Mode::Command => {
98 lines.extend(vec![
99 Line::from(vec![Span::styled(
100 "Command Mode:",
101 Style::default().fg(Color::Magenta),
102 )]),
103 Line::from(" Esc Cancel command"),
104 Line::from(" Enter Execute command"),
105 Line::from(""),
106 Line::from(vec![Span::styled(
107 "Commands:",
108 Style::default().fg(Color::Yellow),
109 )]),
110 Line::from(" :w or :write Save changes"),
111 Line::from(" :q or :quit Quit (fails if modified)"),
112 Line::from(" :q! or :quit! Force quit (discard changes)"),
113 Line::from(" :wq or :x Save and quit"),
114 ]);
115 }
116 }
117
118 lines
119 }
120}
121
122impl Widget for HelpPanel {
123 fn render(self, area: Rect, buf: &mut Buffer) {
124 let lines = self.get_help_lines();
125
126 let block = Block::default()
127 .borders(Borders::ALL)
128 .border_style(Style::default().fg(Color::Cyan))
129 .title(" Help ");
130
131 let paragraph = Paragraph::new(lines).block(block);
132
133 Widget::render(paragraph, area, buf);
134 }
135}
136
137pub struct HelpSection {
139 pub heading: String,
141 pub entries: Vec<(String, String)>,
143}
144
145impl HelpSection {
146 pub fn new(heading: impl Into<String>, entries: Vec<(&str, &str)>) -> Self {
147 Self {
148 heading: heading.into(),
149 entries: entries
150 .into_iter()
151 .map(|(k, d)| (k.to_string(), d.to_string()))
152 .collect(),
153 }
154 }
155}
156
157pub struct KeybindingHelp {
162 title: String,
163 sections: Vec<HelpSection>,
164}
165
166impl KeybindingHelp {
167 pub fn new(title: impl Into<String>, sections: Vec<HelpSection>) -> Self {
168 Self {
169 title: title.into(),
170 sections,
171 }
172 }
173}
174
175impl Widget for KeybindingHelp {
176 fn render(self, area: Rect, buf: &mut Buffer) {
177 let key_width = self
179 .sections
180 .iter()
181 .flat_map(|s| s.entries.iter())
182 .map(|(k, _)| k.chars().count())
183 .max()
184 .unwrap_or(0);
185
186 let mut lines: Vec<Line> = vec![
187 Line::from(vec![
188 Span::raw("Press "),
189 Span::styled("?", Style::default().fg(Color::Cyan)),
190 Span::raw(" or "),
191 Span::styled("Esc", Style::default().fg(Color::Cyan)),
192 Span::raw(" to close"),
193 ]),
194 Line::from(""),
195 ];
196
197 for (i, section) in self.sections.iter().enumerate() {
198 if i > 0 {
199 lines.push(Line::from(""));
200 }
201 lines.push(Line::from(Span::styled(
202 section.heading.clone(),
203 Style::default()
204 .fg(Color::Yellow)
205 .add_modifier(Modifier::BOLD),
206 )));
207 for (keys, desc) in §ion.entries {
208 let pad = " ".repeat(key_width.saturating_sub(keys.chars().count()));
209 lines.push(Line::from(vec![
210 Span::raw(" "),
211 Span::styled(keys.clone(), Style::default().fg(Color::Cyan)),
212 Span::raw(format!("{pad} ")),
213 Span::raw(desc.clone()),
214 ]));
215 }
216 }
217
218 let block = Block::default()
219 .borders(Borders::ALL)
220 .border_style(Style::default().fg(Color::Cyan))
221 .title(format!(" {} ", self.title));
222
223 Widget::render(Paragraph::new(lines).block(block), area, buf);
224 }
225}