rustyline/
binding.rs

1/// Custom event handlers
2use crate::{
3    Cmd, EditMode, InputMode, InputState, KeyCode, KeyEvent, Modifiers, Refresher, RepeatCount,
4};
5
6use radix_trie::TrieKey;
7
8/// Input event
9#[derive(Debug, Clone, PartialEq, Eq, Hash)]
10#[cfg_attr(docsrs, doc(cfg(feature = "custom-bindings")))]
11pub enum Event {
12    /// Wildcard.
13    /// Useful if you want to filter out some keys.
14    Any,
15    /// Key sequence
16    KeySeq(Vec<KeyEvent>),
17    /// TODO Mouse event
18    Mouse(),
19}
20
21impl Event {
22    /// See [`KeyEvent::normalize`]
23    pub(crate) fn normalize(mut self) -> Self {
24        if let Self::KeySeq(ref mut keys) = self {
25            for key in keys.iter_mut() {
26                *key = KeyEvent::normalize(*key);
27            }
28        }
29        self
30    }
31
32    /// Return `i`th key event
33    #[must_use]
34    pub fn get(&self, i: usize) -> Option<&KeyEvent> {
35        if let Self::KeySeq(ref ks) = self {
36            ks.get(i)
37        } else {
38            None
39        }
40    }
41}
42
43impl From<KeyEvent> for Event {
44    fn from(k: KeyEvent) -> Self {
45        Self::KeySeq(vec![k])
46    }
47}
48
49const BASE: u32 = 0x0010_ffff + 1;
50const BASE_CONTROL: u32 = 0x0200_0000;
51const BASE_META: u32 = 0x0400_0000;
52const BASE_SHIFT: u32 = 0x0100_0000;
53const ESCAPE: u32 = 27;
54const PAGE_UP: u32 = BASE + 1;
55const PAGE_DOWN: u32 = PAGE_UP + 1;
56const DOWN: u32 = PAGE_DOWN + 1;
57const UP: u32 = DOWN + 1;
58const LEFT: u32 = UP + 1;
59const RIGHT: u32 = LEFT + 1;
60const HOME: u32 = RIGHT + 1;
61const END: u32 = HOME + 1;
62const DELETE: u32 = END + 1;
63const INSERT: u32 = DELETE + 1;
64//const F1: u32 = INSERT + 1;
65const MOUSE: u32 = /* F24 + 1 */ INSERT + 25;
66const PASTE_START: u32 = MOUSE + 1;
67const PASTE_FINISH: u32 = PASTE_START + 1;
68const ANY: u32 = PASTE_FINISH + 1;
69
70impl KeyEvent {
71    fn encode(&self) -> u32 {
72        let mut u = match self.0 {
73            KeyCode::UnknownEscSeq | KeyCode::Null => 0,
74            KeyCode::Backspace => u32::from('\x7f'),
75            KeyCode::BackTab => u32::from('\t') | BASE_SHIFT,
76            KeyCode::BracketedPasteStart => PASTE_START,
77            KeyCode::BracketedPasteEnd => PASTE_FINISH,
78            KeyCode::Char(c) => u32::from(c),
79            KeyCode::Delete => DELETE,
80            KeyCode::Down => DOWN,
81            KeyCode::End => END,
82            KeyCode::Enter => u32::from('\r'),
83            KeyCode::F(i) => INSERT + u32::from(i),
84            KeyCode::Esc => ESCAPE,
85            KeyCode::Home => HOME,
86            KeyCode::Insert => INSERT,
87            KeyCode::Left => LEFT,
88            KeyCode::PageDown => PAGE_DOWN,
89            KeyCode::PageUp => PAGE_UP,
90            KeyCode::Right => RIGHT,
91            KeyCode::Tab => u32::from('\t'),
92            KeyCode::Up => UP,
93        };
94        if self.1.contains(Modifiers::CTRL) {
95            u |= BASE_CONTROL;
96        }
97        if self.1.contains(Modifiers::ALT) {
98            u |= BASE_META;
99        }
100        if self.1.contains(Modifiers::SHIFT) {
101            u |= BASE_SHIFT;
102        }
103        u
104    }
105}
106
107impl TrieKey for Event {
108    fn encode_bytes(&self) -> Vec<u8> {
109        match self {
110            Self::Any => ANY.to_be_bytes().to_vec(),
111            Self::KeySeq(keys) => {
112                let mut dst = Vec::with_capacity(keys.len() * 4);
113                for key in keys {
114                    dst.extend_from_slice(&key.encode().to_be_bytes());
115                }
116                dst
117            }
118            Self::Mouse() => MOUSE.to_be_bytes().to_vec(),
119        }
120    }
121}
122
123/// Event handler
124#[cfg_attr(docsrs, doc(cfg(feature = "custom-bindings")))]
125pub enum EventHandler {
126    /// unconditional command
127    Simple(Cmd),
128    /// handler behaviour depends on input state
129    Conditional(Box<dyn ConditionalEventHandler>),
130    /* invoke multiple actions
131     * TODO Macro(), */
132}
133
134impl From<Cmd> for EventHandler {
135    fn from(c: Cmd) -> Self {
136        Self::Simple(c)
137    }
138}
139
140/// Give access to user input.
141#[cfg_attr(docsrs, doc(cfg(feature = "custom-bindings")))]
142pub struct EventContext<'r> {
143    mode: EditMode,
144    input_mode: InputMode,
145    wrt: &'r dyn Refresher,
146}
147
148impl<'r> EventContext<'r> {
149    pub(crate) fn new(is: &InputState, wrt: &'r dyn Refresher) -> Self {
150        Self {
151            mode: is.mode,
152            input_mode: is.input_mode,
153            wrt,
154        }
155    }
156
157    /// emacs or vi mode
158    #[must_use]
159    pub fn mode(&self) -> EditMode {
160        self.mode
161    }
162
163    /// vi input mode
164    #[must_use]
165    pub fn input_mode(&self) -> InputMode {
166        self.input_mode
167    }
168
169    /// Returns `true` if there is a hint displayed.
170    #[must_use]
171    pub fn has_hint(&self) -> bool {
172        self.wrt.has_hint()
173    }
174
175    /// Returns the hint text that is shown after the current cursor position.
176    #[must_use]
177    pub fn hint_text(&self) -> Option<&str> {
178        self.wrt.hint_text()
179    }
180
181    /// currently edited line
182    #[must_use]
183    pub fn line(&self) -> &str {
184        self.wrt.line()
185    }
186
187    /// Current cursor position (byte position)
188    #[must_use]
189    pub fn pos(&self) -> usize {
190        self.wrt.pos()
191    }
192}
193
194/// May behave differently depending on:
195///  * edit mode (emacs vs vi)
196///  * vi input mode (insert vs replace vs command modes)
197///  * empty line
198///  * cursor position
199///  * repeat count
200///  * original key pressed (when same command is bound to different key)
201///  * hint
202///  * ...
203#[cfg_attr(docsrs, doc(cfg(feature = "custom-bindings")))]
204pub trait ConditionalEventHandler: Send + Sync {
205    /// Takes the current input state and
206    /// returns the command to be performed or `None` to perform the default
207    /// one.
208    fn handle(
209        &self,
210        evt: &Event,
211        n: RepeatCount,
212        positive: bool,
213        ctx: &EventContext,
214    ) -> Option<Cmd>;
215}
216
217#[cfg(test)]
218mod test {
219    use super::{Event, EventHandler};
220    use crate::{Cmd, KeyCode, KeyEvent, Modifiers};
221    use radix_trie::Trie;
222
223    #[test]
224    fn encode() {
225        let mut trie = Trie::new();
226        let evt = Event::KeySeq(vec![KeyEvent::ctrl('X'), KeyEvent::ctrl('E')]);
227        trie.insert(evt.clone(), EventHandler::from(Cmd::Noop));
228        let prefix = Event::from(KeyEvent::ctrl('X'));
229        let subtrie = trie.get_raw_descendant(&prefix);
230        assert!(subtrie.is_some());
231        let subtrie = subtrie.unwrap();
232        let sub_result = subtrie.get(&evt);
233        assert!(sub_result.unwrap().is_some());
234        let prefix = Event::from(KeyEvent::ctrl('O'));
235        let subtrie = trie.get_raw_descendant(&prefix);
236        assert!(subtrie.is_none())
237    }
238
239    #[test]
240    fn no_collision() {
241        use {Event as E, EventHandler as H, KeyCode as C, KeyEvent as K, Modifiers as M};
242        let mut trie = Trie::new();
243        trie.insert(E::from(K(C::Backspace, M::NONE)), H::from(Cmd::Noop));
244        trie.insert(E::from(K(C::Enter, M::NONE)), H::from(Cmd::Noop));
245        trie.insert(E::from(K(C::Tab, M::NONE)), H::from(Cmd::Noop));
246        trie.insert(E::from(K(C::Backspace, M::CTRL)), H::from(Cmd::Noop));
247        trie.insert(E::from(K(C::Enter, M::CTRL)), H::from(Cmd::Noop));
248        trie.insert(E::from(K(C::Tab, M::CTRL)), H::from(Cmd::Noop));
249    }
250
251    #[test]
252    #[ignore]
253    #[cfg(target_arch = "x86_64")]
254    fn size_of_event() {
255        use core::mem::size_of;
256        assert_eq!(size_of::<Event>(), 32);
257    }
258}