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/r=row, c=column, a=all, v=cell, q=query, 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 pub fn clear_if_timed_out(&mut self) -> bool {
343 if let Some(start) = self.chord_start {
344 if start.elapsed() > self.chord_timeout {
345 self.cancel_chord();
346 return true;
347 }
348 }
349 false
350 }
351
352 #[must_use]
354 pub fn get_chord_mode_description(&self) -> Option<&str> {
355 self.chord_mode_description.as_deref()
356 }
357
358 pub fn set_timeout(&mut self, millis: u64) {
360 self.chord_timeout = Duration::from_millis(millis);
361 }
362
363 pub fn format_debug_info(&self) -> String {
365 let mut output = String::new();
366
367 output.push_str("========== CHORD STATE ==========\n");
369 if self.current_chord.is_empty() {
370 output.push_str("No active chord\n");
371 } else {
372 output.push_str(&format!(
373 "Current chord: {}\n",
374 self.current_chord
375 .iter()
376 .map(format_key)
377 .collect::<Vec<_>>()
378 .join(" → ")
379 ));
380 if let Some(desc) = &self.chord_mode_description {
381 output.push_str(&format!("Mode: {desc}\n"));
382 }
383 if let Some(start) = self.chord_start {
384 let elapsed = start.elapsed().as_millis();
385 let remaining = self.chord_timeout.as_millis().saturating_sub(elapsed);
386 output.push_str(&format!("Timeout in: {remaining}ms\n"));
387 }
388 }
389
390 output.push_str("\n========== REGISTERED CHORDS ==========\n");
392 let mut chords: Vec<_> = self.chord_map.iter().collect();
393 chords.sort_by_key(|(chord, _)| chord.to_string());
394 for (chord, action) in chords {
395 let action_name = match action {
396 Action::Yank(YankTarget::Row) => "yank_row",
397 Action::Yank(YankTarget::Column) => "yank_column",
398 Action::Yank(YankTarget::All) => "yank_all",
399 Action::Yank(YankTarget::Cell) => "yank_cell",
400 Action::Yank(YankTarget::Query) => "yank_query",
401 _ => "unknown",
402 };
403 output.push_str(&format!("{} → {}\n", chord.to_string(), action_name));
404 }
405
406 output.push_str("\n========== KEY PRESS HISTORY ==========\n");
408 output.push_str("(Most recent at bottom, last 50 keys)\n");
409 for entry in &self.key_history {
410 output.push_str(entry);
411 output.push('\n');
412 }
413
414 output
415 }
416
417 pub fn load_from_config(&mut self, _config: &HashMap<String, String>) {
420 }
427}
428
429fn format_key(key: &KeyEvent) -> String {
431 let mut result = String::new();
432
433 if key.modifiers.contains(KeyModifiers::CONTROL) {
435 result.push_str("Ctrl+");
436 }
437 if key.modifiers.contains(KeyModifiers::ALT) {
438 result.push_str("Alt+");
439 }
440 if key.modifiers.contains(KeyModifiers::SHIFT) {
441 result.push_str("Shift+");
442 }
443
444 match key.code {
446 KeyCode::Char(c) => result.push(c),
447 KeyCode::Enter => result.push_str("Enter"),
448 KeyCode::Esc => result.push_str("Esc"),
449 KeyCode::Backspace => result.push_str("Backspace"),
450 KeyCode::Tab => result.push_str("Tab"),
451 KeyCode::Delete => result.push_str("Del"),
452 KeyCode::Insert => result.push_str("Ins"),
453 KeyCode::F(n) => result.push_str(&format!("F{n}")),
454 KeyCode::Left => result.push('←'),
455 KeyCode::Right => result.push('→'),
456 KeyCode::Up => result.push('↑'),
457 KeyCode::Down => result.push('↓'),
458 KeyCode::Home => result.push_str("Home"),
459 KeyCode::End => result.push_str("End"),
460 KeyCode::PageUp => result.push_str("PgUp"),
461 KeyCode::PageDown => result.push_str("PgDn"),
462 _ => result.push('?'),
463 }
464
465 result
466}
467
468fn format_modifiers(mods: KeyModifiers) -> String {
470 let mut parts = Vec::new();
471 if mods.contains(KeyModifiers::CONTROL) {
472 parts.push("Ctrl");
473 }
474 if mods.contains(KeyModifiers::ALT) {
475 parts.push("Alt");
476 }
477 if mods.contains(KeyModifiers::SHIFT) {
478 parts.push("Shift");
479 }
480 parts.join("+")
481}
482
483#[cfg(test)]
484mod tests {
485 use super::*;
486
487 #[test]
488 fn test_chord_sequence() {
489 let chord = ChordSequence::from_notation("yy").unwrap();
490 assert_eq!(chord.keys.len(), 2);
491 assert_eq!(chord.to_string(), "yy");
492 }
493
494 #[test]
495 fn test_single_key() {
496 let mut handler = KeyChordHandler::new();
497 let key = KeyEvent::new(KeyCode::Char('x'), KeyModifiers::empty());
498 match handler.process_key(key) {
499 ChordResult::SingleKey(_) => {}
500 _ => panic!("Expected single key"),
501 }
502 }
503
504 #[test]
505 fn test_chord_completion() {
506 let mut handler = KeyChordHandler::new();
507
508 let key1 = KeyEvent::new(KeyCode::Char('y'), KeyModifiers::empty());
510 match handler.process_key(key1) {
511 ChordResult::PartialChord(_) => {}
512 _ => panic!("Expected partial chord"),
513 }
514
515 let key2 = KeyEvent::new(KeyCode::Char('y'), KeyModifiers::empty());
517 match handler.process_key(key2) {
518 ChordResult::CompleteChord(action) => {
519 assert_eq!(action, Action::Yank(YankTarget::Row));
520 }
521 _ => panic!("Expected complete chord"),
522 }
523 }
524}