Skip to main content

tui_pages/input/
pipeline.rs

1use 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}