1use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
12use datui_cli::keys::{self, Chord, Code, Context, Key};
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum Line {
17 Key(&'static Key),
19 Note(&'static str, &'static str),
21}
22
23#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct Block {
26 pub name: &'static str,
27 pub lines: Vec<Line>,
28}
29
30#[derive(Debug, Default)]
32pub struct Help {
33 context: Option<Context>,
34 pub(crate) filter: String,
36 pub(crate) filtering: bool,
38 pub(crate) selected: usize,
40 pub(crate) scroll: usize,
42 typing: bool,
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub enum HelpKey {
50 Stay,
52 Closed,
54 Press(KeyEvent),
56}
57
58impl Help {
59 pub fn is_open(&self) -> bool {
60 self.context.is_some()
61 }
62
63 pub fn context(&self) -> Option<Context> {
65 self.context
66 }
67
68 pub fn open(&mut self, context: Context, typing: bool) {
70 *self = Help {
71 context: Some(context),
72 typing,
73 ..Help::default()
74 };
75 }
76
77 pub fn runnable(&self, key: &Key) -> Option<KeyEvent> {
80 let event = key_event(key.action()?);
81 let plain_char = matches!(event.code, KeyCode::Char(_))
82 && !event
83 .modifiers
84 .intersects(KeyModifiers::CONTROL | KeyModifiers::ALT);
85 (!(self.typing && plain_char)).then_some(event)
86 }
87
88 pub fn close(&mut self) {
89 *self = Help::default();
90 }
91
92 pub fn title(&self) -> String {
94 self.context
95 .map(|c| format!("{} Help", keys::screen(c).title))
96 .unwrap_or_default()
97 }
98
99 pub fn blocks(&self) -> Vec<Block> {
102 let Some(context) = self.context else {
103 return Vec::new();
104 };
105 let needle = self.filter.to_lowercase();
106 let keeps = |text: String| needle.is_empty() || text.to_lowercase().contains(&needle);
107 let mut blocks: Vec<Block> = Vec::new();
108 let mut push = |name: &'static str, lines: Vec<Line>| {
109 if !lines.is_empty() {
110 blocks.push(Block { name, lines });
111 }
112 };
113 let key_lines = |group: &'static keys::Group| -> Vec<Line> {
114 group
115 .keys
116 .iter()
117 .filter(|k| keeps(format!("{} {} {}", k.keys, k.label, k.line)))
118 .map(Line::Key)
119 .collect()
120 };
121 for group in keys::screen(context).groups {
122 push(group.name, key_lines(group));
123 }
124 if context == Context::Query {
125 let notes = keys::Q_SUMMARY
126 .iter()
127 .filter(|(example, meaning)| keeps(format!("{example} {meaning}")))
128 .map(|&(example, meaning)| Line::Note(example, meaning))
129 .collect();
130 push("q syntax", notes);
131 }
132 push(keys::GLOBAL.name, key_lines(&keys::GLOBAL));
133 blocks
134 }
135
136 pub fn shown_keys(&self) -> Vec<&'static Key> {
138 self.blocks()
139 .into_iter()
140 .flat_map(|b| b.lines)
141 .filter_map(|line| match line {
142 Line::Key(key) => Some(key),
143 Line::Note(..) => None,
144 })
145 .collect()
146 }
147
148 pub fn selected_key(&self) -> Option<&'static Key> {
150 let shown = self.shown_keys();
151 shown
152 .get(self.selected.min(shown.len().saturating_sub(1)))
153 .copied()
154 }
155
156 fn step(&mut self, by: isize) {
157 let count = self.shown_keys().len();
158 if count == 0 {
159 self.selected = 0;
160 return;
161 }
162 self.selected = self
163 .selected
164 .min(count - 1)
165 .saturating_add_signed(by)
166 .min(count - 1);
167 }
168
169 fn run(&mut self) -> HelpKey {
171 let Some(event) = self.selected_key().and_then(|k| self.runnable(k)) else {
172 return HelpKey::Stay;
173 };
174 self.close();
175 HelpKey::Press(event)
176 }
177
178 pub fn key(&mut self, event: &KeyEvent) -> HelpKey {
180 let ctrl = event.modifiers.contains(KeyModifiers::CONTROL);
181 match event.code {
182 KeyCode::Enter => return self.run(),
183 KeyCode::Up => self.step(-1),
184 KeyCode::Down => self.step(1),
185 KeyCode::PageUp => self.step(-10),
186 KeyCode::PageDown => self.step(10),
187 KeyCode::Char('p') if ctrl => self.step(-1),
188 KeyCode::Char('n') if ctrl => self.step(1),
189 KeyCode::Esc => {
190 if self.filtering || !self.filter.is_empty() {
191 self.filter.clear();
192 self.filtering = false;
193 self.selected = 0;
194 } else {
195 self.close();
196 return HelpKey::Closed;
197 }
198 }
199 KeyCode::F(1) => {
200 self.close();
201 return HelpKey::Closed;
202 }
203 _ if self.filtering => match event.code {
204 KeyCode::Backspace => {
205 self.filter.pop();
206 self.selected = 0;
207 }
208 KeyCode::Char('u') if ctrl => {
209 self.filter.clear();
210 self.selected = 0;
211 }
212 KeyCode::Char(c)
213 if !event
214 .modifiers
215 .intersects(KeyModifiers::CONTROL | KeyModifiers::ALT) =>
216 {
217 self.filter.push(c);
218 self.selected = 0;
219 }
220 _ => {}
221 },
222 KeyCode::Char('/') => self.filtering = true,
223 KeyCode::Char('?') => {
224 self.close();
225 return HelpKey::Closed;
226 }
227 KeyCode::Char('k') => self.step(-1),
228 KeyCode::Char('j') => self.step(1),
229 KeyCode::Home | KeyCode::Char('g') => self.selected = 0,
230 KeyCode::End | KeyCode::Char('G') => self.step(isize::MAX),
231 _ => {}
232 }
233 HelpKey::Stay
234 }
235}
236
237pub fn key_event(chord: Chord) -> KeyEvent {
239 let code = match chord.code {
240 Code::Char(c) => KeyCode::Char(c),
241 Code::Enter => KeyCode::Enter,
242 Code::Esc => KeyCode::Esc,
243 Code::Tab => KeyCode::Tab,
244 Code::BackTab => KeyCode::BackTab,
245 Code::Backspace => KeyCode::Backspace,
246 Code::Delete => KeyCode::Delete,
247 Code::Insert => KeyCode::Insert,
248 Code::Up => KeyCode::Up,
249 Code::Down => KeyCode::Down,
250 Code::Left => KeyCode::Left,
251 Code::Right => KeyCode::Right,
252 Code::PageUp => KeyCode::PageUp,
253 Code::PageDown => KeyCode::PageDown,
254 Code::Home => KeyCode::Home,
255 Code::End => KeyCode::End,
256 Code::F(n) => KeyCode::F(n),
257 };
258 let mut modifiers = KeyModifiers::NONE;
259 if chord.ctrl {
260 modifiers |= KeyModifiers::CONTROL;
261 }
262 if chord.alt {
263 modifiers |= KeyModifiers::ALT;
264 }
265 if chord.shift {
266 modifiers |= KeyModifiers::SHIFT;
267 }
268 KeyEvent::new(code, modifiers)
269}
270
271#[cfg(test)]
272mod tests;