tui_pages/input/
pipeline.rs1use crate::input::{ChordSequenceTracker, InputRegistry, KeyChord, PipelineResponse};
2use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
3use tracing::debug;
4
5#[derive(Debug, Clone)]
6pub struct InputPipeline<A> {
7 pub registry: InputRegistry<A>,
8 pub tracker: ChordSequenceTracker,
9}
10
11impl<A> InputPipeline<A> {
12 pub fn new(registry: InputRegistry<A>, timeout_ms: u64) -> Self {
13 Self {
14 registry,
15 tracker: ChordSequenceTracker::new(timeout_ms),
16 }
17 }
18
19 pub fn active(&self) -> bool {
20 !self.tracker.is_empty()
21 }
22}
23
24impl<A: Clone> InputPipeline<A> {
25 pub fn process(
26 &mut self,
27 event: KeyEvent,
28 modes: &[impl AsRef<str>],
29 accepts_text_input: bool,
30 ) -> PipelineResponse<A> {
31 let chord = KeyChord::from_event(&event);
32 let mode_refs: Vec<&str> = modes.iter().map(|mode| mode.as_ref()).collect();
33 let is_plain_char = matches!(chord.code, KeyCode::Char(_))
34 && (chord.modifiers == KeyModifiers::empty() || chord.modifiers == KeyModifiers::SHIFT);
35 let effective_modes: Vec<&str> = if accepts_text_input && is_plain_char {
36 mode_refs
37 .iter()
38 .filter(|mode| **mode != "general")
39 .copied()
40 .collect()
41 } else {
42 mode_refs.clone()
43 };
44
45 let response = if !self.tracker.is_empty() {
46 self.tracker.maybe_expire();
47 if self.tracker.is_empty() {
48 PipelineResponse::Cancel
49 } else {
50 self.tracker.add(chord);
51 let current_sequence = self.tracker.get();
52
53 if let Some(action) = self.registry.match_action(current_sequence, &mode_refs) {
54 self.tracker.reset();
55 PipelineResponse::Execute(action)
56 } else {
57 let hints = self.registry.get_hints(current_sequence, &mode_refs);
58 if hints.is_empty() {
59 self.tracker.reset();
60 PipelineResponse::Cancel
61 } else {
62 PipelineResponse::Wait(hints)
63 }
64 }
65 }
66 } else {
67 let single = [chord];
68 if let Some(action) = self.registry.match_action(&single, &effective_modes) {
69 PipelineResponse::Execute(action)
70 } else if self.registry.starts_sequence(&chord, &effective_modes)
71 && (!accepts_text_input || !is_plain_char)
72 {
73 self.tracker.add(chord);
74 PipelineResponse::Wait(self.registry.get_hints(&single, &effective_modes))
75 } else {
76 PipelineResponse::Type(chord)
77 }
78 };
79
80 debug!(?event, ?mode_refs, "processed input event");
81 response
82 }
83}