1use crate::ui::input::actions::{Action, YankTarget};
2use chrono::Local;
3use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
4use std::collections::HashMap;
5use std::time::{Duration, Instant};
6use tracing::debug;
7
8#[derive(Debug, Clone, PartialEq, Eq, Hash)]
10pub struct ChordSequence {
11 keys: Vec<KeyEvent>,
12}
13
14impl ChordSequence {
15 #[must_use]
16 pub fn new(keys: Vec<KeyEvent>) -> Self {
17 Self { keys }
18 }
19
20 #[must_use]
22 pub fn from_notation(notation: &str) -> Option<Self> {
23 let chars: Vec<char> = notation.chars().collect();
24 if chars.is_empty() {
25 return None;
26 }
27
28 let keys: Vec<KeyEvent> = chars
29 .iter()
30 .map(|&c| KeyEvent::new(KeyCode::Char(c), KeyModifiers::empty()))
31 .collect();
32
33 Some(Self { keys })
34 }
35
36 pub fn to_string(&self) -> String {
38 self.keys.iter().map(format_key).collect::<String>()
39 }
40}
41
42#[derive(Debug, Clone)]
44pub enum ChordResult {
45 SingleKey(KeyEvent),
47 PartialChord(String), CompleteChord(Action),
51 Cancelled,
53}
54
55pub struct KeyChordHandler {
57 chord_map: HashMap<ChordSequence, Action>,
59 current_chord: Vec<KeyEvent>,
61 chord_start: Option<Instant>,
63 chord_timeout: Duration,
65 key_history: Vec<String>,
67 max_history: usize,
69 chord_mode_active: bool,
71 chord_mode_description: Option<String>,
73}
74
75impl Default for KeyChordHandler {
76 fn default() -> Self {
77 Self::new()
78 }
79}
80
81impl KeyChordHandler {
82 #[must_use]
83 pub fn new() -> Self {
84 let mut handler = Self {
85 chord_map: HashMap::new(),
86 current_chord: Vec::new(),
87 chord_start: None,
88 chord_timeout: Duration::from_millis(1000), key_history: Vec::new(),
90 max_history: 50,
91 chord_mode_active: false,
92 chord_mode_description: None,
93 };
94 handler.setup_default_chords();
95 handler
96 }
97
98 fn setup_default_chords(&mut self) {
100 use crate::buffer::AppMode;
101 use crate::ui::input::actions::{CursorPosition, SqlClause};
102
103 self.register_chord_action("yy", Action::Yank(YankTarget::Row));
105 self.register_chord_action("yr", Action::Yank(YankTarget::Row)); self.register_chord_action("yc", Action::Yank(YankTarget::Column));
107 self.register_chord_action("ya", Action::Yank(YankTarget::All));
108 self.register_chord_action("yv", Action::Yank(YankTarget::Cell)); self.register_chord_action("yq", Action::Yank(YankTarget::Query)); self.register_chord_action(
114 "cw",
115 Action::SwitchModeWithCursor(
116 AppMode::Command,
117 CursorPosition::AfterClause(SqlClause::Where),
118 ),
119 );
120 self.register_chord_action(
121 "cs",
122 Action::SwitchModeWithCursor(
123 AppMode::Command,
124 CursorPosition::AfterClause(SqlClause::Select),
125 ),
126 );
127 self.register_chord_action(
128 "cf",
129 Action::SwitchModeWithCursor(
130 AppMode::Command,
131 CursorPosition::AfterClause(SqlClause::From),
132 ),
133 );
134 self.register_chord_action(
135 "co",
136 Action::SwitchModeWithCursor(
137 AppMode::Command,
138 CursorPosition::AfterClause(SqlClause::OrderBy),
139 ),
140 );
141 self.register_chord_action(
142 "cg",
143 Action::SwitchModeWithCursor(
144 AppMode::Command,
145 CursorPosition::AfterClause(SqlClause::GroupBy),
146 ),
147 );
148 self.register_chord_action(
149 "ch",
150 Action::SwitchModeWithCursor(
151 AppMode::Command,
152 CursorPosition::AfterClause(SqlClause::Having),
153 ),
154 );
155 self.register_chord_action(
156 "cl",
157 Action::SwitchModeWithCursor(
158 AppMode::Command,
159 CursorPosition::AfterClause(SqlClause::Limit),
160 ),
161 );
162
163 }
168
169 pub fn register_chord_action(&mut self, notation: &str, action: Action) {
171 if let Some(chord) = ChordSequence::from_notation(notation) {
172 self.chord_map.insert(chord, action);
173 }
174 }
175
176 pub fn process_key(&mut self, key: KeyEvent) -> ChordResult {
178 self.log_key_press(&key);
180
181 if let Some(start) = self.chord_start {
183 if start.elapsed() > self.chord_timeout {
184 self.cancel_chord();
185 return self.process_key_internal(key);
187 }
188 }
189
190 if key.code == KeyCode::Esc && !self.current_chord.is_empty() {
192 self.cancel_chord();
193 return ChordResult::Cancelled;
194 }
195
196 self.process_key_internal(key)
197 }
198
199 fn process_key_internal(&mut self, key: KeyEvent) -> ChordResult {
200 debug!(
201 "process_key_internal: key={:?}, current_chord={:?}",
202 key, self.current_chord
203 );
204
205 self.current_chord.push(key);
207
208 if self.current_chord.len() == 1 {
210 self.chord_start = Some(Instant::now());
211 }
212
213 let current = ChordSequence::new(self.current_chord.clone());
215 debug!("Checking for exact match with chord: {:?}", current);
216 debug!(
217 "Registered chords: {:?}",
218 self.chord_map.keys().collect::<Vec<_>>()
219 );
220 if let Some(action) = self.chord_map.get(¤t) {
221 debug!("Found exact match! Action: {:?}", action);
222 let result = ChordResult::CompleteChord(action.clone());
223 self.reset_chord();
224 return result;
225 }
226
227 debug!("Checking for partial matches...");
229 let has_partial = self.chord_map.keys().any(|chord| {
230 chord.keys.len() > self.current_chord.len()
231 && chord.keys[..self.current_chord.len()] == self.current_chord[..]
232 });
233
234 debug!("has_partial = {}", has_partial);
235 if has_partial {
236 let possible: Vec<String> = self
238 .chord_map
239 .iter()
240 .filter_map(|(chord, action)| {
241 if chord.keys.len() > self.current_chord.len()
242 && chord.keys[..self.current_chord.len()] == self.current_chord[..]
243 {
244 let action_name = match action {
245 Action::Yank(YankTarget::Row) => "yank row",
246 Action::Yank(YankTarget::Column) => "yank column",
247 Action::Yank(YankTarget::All) => "yank all",
248 Action::Yank(YankTarget::Cell) => "yank cell",
249 Action::Yank(YankTarget::Query) => "yank query",
250 _ => "unknown",
251 };
252 Some(format!(
253 "{} → {}",
254 format_key(&chord.keys[self.current_chord.len()]),
255 action_name
256 ))
257 } else {
258 None
259 }
260 })
261 .collect();
262
263 let description = if self.current_chord.len() == 1
264 && self.current_chord[0].code == KeyCode::Char('y')
265 {
266 "Yank mode: y=row, c=column, a=all, ESC=cancel".to_string()
267 } else {
268 format!("Waiting for: {}", possible.join(", "))
269 };
270
271 self.chord_mode_active = true;
272 self.chord_mode_description = Some(description.clone());
273 ChordResult::PartialChord(description)
274 } else {
275 let result = if self.current_chord.len() == 1 {
277 ChordResult::SingleKey(key)
278 } else {
279 ChordResult::SingleKey(self.current_chord[0])
281 };
282 self.reset_chord();
283 result
284 }
285 }
286
287 pub fn cancel_chord(&mut self) {
289 self.reset_chord();
290 }
291
292 fn reset_chord(&mut self) {
294 self.current_chord.clear();
295 self.chord_start = None;
296 self.chord_mode_active = false;
297 self.chord_mode_description = None;
298 }
299
300 pub fn log_key_press(&mut self, key: &KeyEvent) {
302 if self.key_history.len() >= self.max_history {
303 self.key_history.remove(0);
304 }
305
306 let timestamp = Local::now().format("%H:%M:%S.%3f");
307 let key_str = format_key(key);
308 let modifiers = format_modifiers(key.modifiers);
309
310 let entry = if modifiers.is_empty() {
311 format!("[{timestamp}] {key_str}")
312 } else {
313 format!("[{timestamp}] {key_str} ({modifiers})")
314 };
315
316 self.key_history.push(entry);
317 }
318
319 #[must_use]
321 pub fn get_history(&self) -> &[String] {
322 &self.key_history
323 }
324
325 pub fn clear_history(&mut self) {
327 self.key_history.clear();
328 }
329
330 #[must_use]
332 pub fn is_chord_mode_active(&self) -> bool {
333 self.chord_mode_active
334 }
335
336 #[must_use]
338 pub fn get_chord_mode_description(&self) -> Option<&str> {
339 self.chord_mode_description.as_deref()
340 }
341
342 pub fn set_timeout(&mut self, millis: u64) {
344 self.chord_timeout = Duration::from_millis(millis);
345 }
346
347 pub fn format_debug_info(&self) -> String {
349 let mut output = String::new();
350
351 output.push_str("========== CHORD STATE ==========\n");
353 if self.current_chord.is_empty() {
354 output.push_str("No active chord\n");
355 } else {
356 output.push_str(&format!(
357 "Current chord: {}\n",
358 self.current_chord
359 .iter()
360 .map(format_key)
361 .collect::<Vec<_>>()
362 .join(" → ")
363 ));
364 if let Some(desc) = &self.chord_mode_description {
365 output.push_str(&format!("Mode: {desc}\n"));
366 }
367 if let Some(start) = self.chord_start {
368 let elapsed = start.elapsed().as_millis();
369 let remaining = self.chord_timeout.as_millis().saturating_sub(elapsed);
370 output.push_str(&format!("Timeout in: {remaining}ms\n"));
371 }
372 }
373
374 output.push_str("\n========== REGISTERED CHORDS ==========\n");
376 let mut chords: Vec<_> = self.chord_map.iter().collect();
377 chords.sort_by_key(|(chord, _)| chord.to_string());
378 for (chord, action) in chords {
379 let action_name = match action {
380 Action::Yank(YankTarget::Row) => "yank_row",
381 Action::Yank(YankTarget::Column) => "yank_column",
382 Action::Yank(YankTarget::All) => "yank_all",
383 Action::Yank(YankTarget::Cell) => "yank_cell",
384 Action::Yank(YankTarget::Query) => "yank_query",
385 _ => "unknown",
386 };
387 output.push_str(&format!("{} → {}\n", chord.to_string(), action_name));
388 }
389
390 output.push_str("\n========== KEY PRESS HISTORY ==========\n");
392 output.push_str("(Most recent at bottom, last 50 keys)\n");
393 for entry in &self.key_history {
394 output.push_str(entry);
395 output.push('\n');
396 }
397
398 output
399 }
400
401 pub fn load_from_config(&mut self, _config: &HashMap<String, String>) {
404 }
411}
412
413fn format_key(key: &KeyEvent) -> String {
415 let mut result = String::new();
416
417 if key.modifiers.contains(KeyModifiers::CONTROL) {
419 result.push_str("Ctrl+");
420 }
421 if key.modifiers.contains(KeyModifiers::ALT) {
422 result.push_str("Alt+");
423 }
424 if key.modifiers.contains(KeyModifiers::SHIFT) {
425 result.push_str("Shift+");
426 }
427
428 match key.code {
430 KeyCode::Char(c) => result.push(c),
431 KeyCode::Enter => result.push_str("Enter"),
432 KeyCode::Esc => result.push_str("Esc"),
433 KeyCode::Backspace => result.push_str("Backspace"),
434 KeyCode::Tab => result.push_str("Tab"),
435 KeyCode::Delete => result.push_str("Del"),
436 KeyCode::Insert => result.push_str("Ins"),
437 KeyCode::F(n) => result.push_str(&format!("F{n}")),
438 KeyCode::Left => result.push('←'),
439 KeyCode::Right => result.push('→'),
440 KeyCode::Up => result.push('↑'),
441 KeyCode::Down => result.push('↓'),
442 KeyCode::Home => result.push_str("Home"),
443 KeyCode::End => result.push_str("End"),
444 KeyCode::PageUp => result.push_str("PgUp"),
445 KeyCode::PageDown => result.push_str("PgDn"),
446 _ => result.push('?'),
447 }
448
449 result
450}
451
452fn format_modifiers(mods: KeyModifiers) -> String {
454 let mut parts = Vec::new();
455 if mods.contains(KeyModifiers::CONTROL) {
456 parts.push("Ctrl");
457 }
458 if mods.contains(KeyModifiers::ALT) {
459 parts.push("Alt");
460 }
461 if mods.contains(KeyModifiers::SHIFT) {
462 parts.push("Shift");
463 }
464 parts.join("+")
465}
466
467#[cfg(test)]
468mod tests {
469 use super::*;
470
471 #[test]
472 fn test_chord_sequence() {
473 let chord = ChordSequence::from_notation("yy").unwrap();
474 assert_eq!(chord.keys.len(), 2);
475 assert_eq!(chord.to_string(), "yy");
476 }
477
478 #[test]
479 fn test_single_key() {
480 let mut handler = KeyChordHandler::new();
481 let key = KeyEvent::new(KeyCode::Char('x'), KeyModifiers::empty());
482 match handler.process_key(key) {
483 ChordResult::SingleKey(_) => {}
484 _ => panic!("Expected single key"),
485 }
486 }
487
488 #[test]
489 fn test_chord_completion() {
490 let mut handler = KeyChordHandler::new();
491
492 let key1 = KeyEvent::new(KeyCode::Char('y'), KeyModifiers::empty());
494 match handler.process_key(key1) {
495 ChordResult::PartialChord(_) => {}
496 _ => panic!("Expected partial chord"),
497 }
498
499 let key2 = KeyEvent::new(KeyCode::Char('y'), KeyModifiers::empty());
501 match handler.process_key(key2) {
502 ChordResult::CompleteChord(action) => {
503 assert_eq!(action, Action::Yank(YankTarget::Row));
504 }
505 _ => panic!("Expected complete chord"),
506 }
507 }
508}