sql_cli/ui/key_handling/
sequence_renderer.rs1use std::collections::VecDeque;
2use std::time::Instant;
3
4#[derive(Debug, Clone)]
6struct KeySequence {
7 key: String,
8 count: usize,
9 first_press: Instant,
10 last_press: Instant,
11}
12
13pub struct KeySequenceRenderer {
19 sequences: VecDeque<KeySequence>,
21 max_display: usize,
23 collapse_window_ms: u64,
25 chord_mode: Option<String>,
27 fade_duration_ms: u64,
29 enabled: bool,
31}
32
33impl KeySequenceRenderer {
34 pub fn new() -> Self {
35 Self {
36 sequences: VecDeque::with_capacity(10),
37 max_display: 5,
38 collapse_window_ms: 500, chord_mode: None,
40 fade_duration_ms: 2000,
41 enabled: true, }
43 }
44
45 pub fn set_enabled(&mut self, enabled: bool) {
47 self.enabled = enabled;
48 if !enabled {
49 self.sequences.clear();
50 self.chord_mode = None;
51 }
52 }
53
54 pub fn record_key(&mut self, key: String) {
56 if !self.enabled {
57 return;
58 }
59
60 let now = Instant::now();
61
62 if let Some(last) = self.sequences.back_mut() {
64 if last.key == key
65 && last.last_press.elapsed().as_millis() < self.collapse_window_ms as u128
66 {
67 last.count += 1;
69 last.last_press = now;
70 return;
71 }
72 }
73
74 self.sequences.push_back(KeySequence {
76 key,
77 count: 1,
78 first_press: now,
79 last_press: now,
80 });
81
82 self.cleanup_sequences();
84 }
85
86 pub fn set_chord_mode(&mut self, description: Option<String>) {
88 self.chord_mode = description;
89 }
90
91 pub fn clear_chord_mode(&mut self) {
93 self.chord_mode = None;
94 }
95
96 pub fn get_display(&self) -> String {
98 if !self.enabled {
99 return String::new();
100 }
101
102 if let Some(ref chord_desc) = self.chord_mode {
104 return self.format_chord_display(chord_desc);
105 }
106
107 self.format_sequence_display()
109 }
110
111 fn format_chord_display(&self, description: &str) -> String {
113 if description.starts_with("Yank mode:") {
115 if let Some(options) = description.strip_prefix("Yank mode: ") {
117 let keys: Vec<&str> = options
119 .split(", ")
120 .filter_map(|part| {
121 let key = part.split('=').next()?;
122 if key == "ESC" {
123 None } else {
125 Some(key)
126 }
127 })
128 .collect();
129
130 if !keys.is_empty() {
131 return format!("y({})", keys.join(","));
132 }
133 }
134 }
135
136 if description.contains("Waiting for:") {
138 if let Some(waiting) = description.strip_prefix("Waiting for: ") {
140 let parts: Vec<&str> = waiting
141 .split(", ")
142 .map(|p| p.split(" → ").next().unwrap_or(p))
143 .collect();
144 if !parts.is_empty() && self.sequences.back().is_some() {
145 if let Some(last) = self.sequences.back() {
146 return format!("{}({})", last.key, parts.join(","));
147 }
148 }
149 }
150 }
151
152 if description.len() > 20 {
154 format!("{}...", &description[..17])
155 } else {
156 description.to_string()
157 }
158 }
159
160 fn format_sequence_display(&self) -> String {
162 let now = Instant::now();
163 let mut display_sequences = Vec::new();
164
165 for seq in self.sequences.iter().rev().take(self.max_display) {
167 let age_ms = now.duration_since(seq.last_press).as_millis() as u64;
168
169 if age_ms > self.fade_duration_ms {
171 continue;
172 }
173
174 let formatted = if seq.count > 1 {
176 format!("{}{}", seq.count, seq.key)
178 } else {
179 seq.key.clone()
180 };
181
182 display_sequences.push(formatted);
183 }
184
185 display_sequences.reverse();
187
188 display_sequences.join(" ")
190 }
191
192 fn cleanup_sequences(&mut self) {
194 let now = Instant::now();
195
196 self.sequences.retain(|seq| {
198 now.duration_since(seq.last_press).as_millis() < self.fade_duration_ms as u128
199 });
200
201 while self.sequences.len() > self.max_display * 2 {
203 self.sequences.pop_front();
204 }
205 }
206
207 pub fn has_content(&self) -> bool {
209 self.enabled && (!self.sequences.is_empty() || self.chord_mode.is_some())
210 }
211
212 pub fn clear(&mut self) {
214 self.sequences.clear();
215 self.chord_mode = None;
216 }
217
218 pub fn configure(
220 &mut self,
221 max_display: Option<usize>,
222 collapse_window_ms: Option<u64>,
223 fade_duration_ms: Option<u64>,
224 ) {
225 if let Some(max) = max_display {
226 self.max_display = max;
227 }
228 if let Some(window) = collapse_window_ms {
229 self.collapse_window_ms = window;
230 }
231 if let Some(fade) = fade_duration_ms {
232 self.fade_duration_ms = fade;
233 }
234 }
235
236 pub fn is_enabled(&self) -> bool {
238 self.enabled
239 }
240
241 pub fn get_chord_mode(&self) -> &Option<String> {
242 &self.chord_mode
243 }
244
245 pub fn sequence_count(&self) -> usize {
246 self.sequences.len()
247 }
248
249 pub fn get_sequences(&self) -> Vec<(String, usize)> {
250 self.sequences
251 .iter()
252 .map(|seq| (seq.key.clone(), seq.count))
253 .collect()
254 }
255}
256
257impl Default for KeySequenceRenderer {
258 fn default() -> Self {
259 Self::new()
260 }
261}
262
263#[cfg(test)]
264mod tests {
265 use super::*;
266 use std::thread::sleep;
267 use std::time::Duration;
268
269 #[test]
270 fn test_collapse_repeated_keys() {
271 let mut renderer = KeySequenceRenderer::new();
272 renderer.set_enabled(true);
273
274 renderer.record_key("j".to_string());
276 sleep(Duration::from_millis(50));
277 renderer.record_key("j".to_string());
278 sleep(Duration::from_millis(50));
279 renderer.record_key("j".to_string());
280
281 let display = renderer.get_display();
282 assert_eq!(display, "3j");
283 }
284
285 #[test]
286 fn test_separate_sequences() {
287 let mut renderer = KeySequenceRenderer::new();
288 renderer.set_enabled(true);
289
290 renderer.record_key("j".to_string());
292 sleep(Duration::from_millis(600)); renderer.record_key("k".to_string());
294 sleep(Duration::from_millis(600));
295 renderer.record_key("h".to_string());
296
297 let display = renderer.get_display();
298 assert_eq!(display, "j k h");
299 }
300
301 #[test]
302 fn test_chord_mode_display() {
303 let mut renderer = KeySequenceRenderer::new();
304 renderer.set_enabled(true);
305
306 renderer.record_key("y".to_string());
307 renderer.set_chord_mode(Some(
308 "Yank mode: y=row, c=column, a=all, ESC=cancel".to_string(),
309 ));
310
311 let display = renderer.get_display();
312 assert_eq!(display, "y(y,c,a)");
313 }
314
315 #[test]
316 fn test_max_display_limit() {
317 let mut renderer = KeySequenceRenderer::new();
318 renderer.set_enabled(true);
319 renderer.configure(Some(3), None, None); for i in 1..=10 {
323 renderer.record_key(format!("{}", i));
324 sleep(Duration::from_millis(600));
325 }
326
327 let display = renderer.get_display();
328 let parts: Vec<&str> = display.split(' ').collect();
329 assert!(parts.len() <= 3);
330 }
331
332 #[test]
333 fn test_mixed_repeated_and_single() {
334 let mut renderer = KeySequenceRenderer::new();
335 renderer.set_enabled(true);
336
337 renderer.record_key("j".to_string());
339 sleep(Duration::from_millis(50));
340 renderer.record_key("j".to_string());
341 sleep(Duration::from_millis(50));
342 renderer.record_key("j".to_string());
343 sleep(Duration::from_millis(600)); renderer.record_key("g".to_string());
345 sleep(Duration::from_millis(600));
346 renderer.record_key("k".to_string());
347 sleep(Duration::from_millis(50));
348 renderer.record_key("k".to_string());
349
350 let display = renderer.get_display();
351 assert_eq!(display, "3j g 2k");
352 }
353}