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