Skip to main content

strop_engine/editor/
dispatch.rs

1//! One typed input owner, computed from state (0028 P2): the replacement
2//! for a hand-ordered condition chain whose branch positions were
3//! load-bearing and invisible. Each owner declares its modal policy as
4//! data; transient overlays forward keys in insert mode after dismissing
5//! themselves (the 0.20.1 swallowed-keystroke fix), modal owners consume.
6
7use super::{Editor, Key, Mode};
8
9/// Who consumes the next key. Priority is fixed by `input_owner`'s
10/// evaluation order: pending fields, pickers, transient cards, document.
11/// A late hover/blame reply never steals a modal field's input or paint.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum InputOwner {
14    /// A live `: / ? |` line.
15    Pending,
16    /// The LSP hover card — a transient overlay.
17    HoverCard,
18    /// The blame card or its in-flight request — a transient overlay.
19    BlameCard,
20    /// An open picker (its fields are their own modal input).
21    Picker,
22    /// The undo-tree browser.
23    UndoBrowser,
24    /// The document itself: mode dispatch (insert/visual/normal).
25    Document,
26}
27
28impl InputOwner {
29    /// Transient overlays dismiss themselves and FORWARD the key in
30    /// insert mode: an async reply can land mid-insert, and the next
31    /// keystroke (Esc most painfully) must still land. Everywhere else
32    /// overlays consume the key that dismisses them.
33    fn forwards_in_insert(self) -> bool {
34        matches!(self, Self::HoverCard | Self::BlameCard)
35    }
36}
37
38impl Editor {
39    /// The typed owner of the next key, computed from state.
40    pub fn input_owner(&self) -> InputOwner {
41        if self.pending.is_active() {
42            return InputOwner::Pending;
43        }
44        if self.picker_open() {
45            return InputOwner::Picker;
46        }
47        if self.hover_card.is_some() {
48            return InputOwner::HoverCard;
49        }
50        if self.blame_card.is_some() || self.card_request.is_some() {
51            return InputOwner::BlameCard;
52        }
53        // The undo browser owns keys only while its buffer is live and
54        // current (the stale-state guard stays in feed_undo_browser).
55        if self.undo_browser.as_ref().is_some_and(|ub| {
56            self.docs
57                .get(ub.browser)
58                .is_some_and(|d| d.buf.name.as_deref() == Some("undo tree"))
59        }) {
60            return InputOwner::UndoBrowser;
61        }
62        InputOwner::Document
63    }
64
65    /// Dispatch one key to its owner. Modal policy is the owner's data:
66    /// transient overlays forward in insert mode; everything else
67    /// consumes. The document owner is the mode machine.
68    pub(super) fn dispatch_owned(&mut self, key: Key) {
69        let owner = self.input_owner();
70        if self.mode == Mode::Insert && owner.forwards_in_insert() {
71            self.dismiss_overlay(owner);
72            self.feed_document(key);
73            return;
74        }
75        match owner {
76            InputOwner::Pending => self.feed_pending(key),
77            InputOwner::HoverCard => {
78                if key == Key::Enter {
79                    self.open_hover_document();
80                } else {
81                    self.hover_card = None;
82                }
83            }
84            InputOwner::BlameCard => {
85                let had_card = self.blame_card.is_some();
86                if let Some(card) = self.dismiss_card_authority() {
87                    if key == Key::Enter {
88                        self.open_log_at(&card.sha);
89                    }
90                }
91                if !had_card {
92                    // only an in-flight request was cancelled above; the
93                    // key still belongs to whatever owns it next
94                    self.dispatch_owned(key);
95                }
96            }
97            InputOwner::Picker => self.feed_picker(key),
98            InputOwner::UndoBrowser => {
99                if !self.feed_undo_browser(key) {
100                    self.feed_document(key);
101                }
102            }
103            InputOwner::Document => self.feed_document(key),
104        }
105    }
106
107    /// Dismiss a transient overlay without consuming the key (insert-mode
108    /// forwarding path).
109    fn dismiss_overlay(&mut self, owner: InputOwner) {
110        match owner {
111            InputOwner::HoverCard => self.hover_card = None,
112            InputOwner::BlameCard => {
113                let _ = self.dismiss_card_authority();
114            }
115            _ => {}
116        }
117    }
118
119    /// The mode machine: insert/visual/normal dispatch.
120    fn feed_document(&mut self, key: Key) {
121        match self.mode {
122            Mode::Insert => self.feed_insert(key),
123            Mode::Visual | Mode::VisualLine | Mode::VisualBlock => self.feed_visual(key),
124            Mode::Normal => self.feed_normal(key),
125        }
126    }
127}
128
129#[cfg(test)]
130mod tests {
131    use super::*;
132    use strop_core::Buffer;
133
134    #[test]
135    fn late_cards_cannot_consume_modal_field_input() {
136        let mut editor = Editor::new(Buffer::from_text("x\n"));
137        editor.open_picker(strop_picker::Kind::RemoteAddress);
138        editor.hover_card = Some("late docs".into());
139        editor.feed_text("host");
140        assert_eq!(editor.picker.as_ref().unwrap().picker.input.text, "host");
141    }
142
143    #[test]
144    fn overlays_forward_in_insert_and_consume_in_normal() {
145        let mut e = Editor::new(Buffer::from_text("x\n"));
146        e.feed_text("i");
147        e.hover_card = Some("late docs".into());
148        e.feed(crate::editor::Key::Esc);
149        assert_eq!(e.mode, Mode::Normal, "esc landed through the overlay");
150        assert!(e.hover_card.is_none());
151        e.hover_card = Some("docs".into());
152        e.feed(crate::editor::Key::Char('j'));
153        assert!(e.hover_card.is_none(), "normal mode: the key dismisses");
154    }
155}