reedline/edit_mode/vi/
mod.rs

1mod command;
2mod motion;
3mod parser;
4mod vi_keybindings;
5
6use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers};
7pub use vi_keybindings::{default_vi_insert_keybindings, default_vi_normal_keybindings};
8
9use self::motion::ViCharSearch;
10
11use super::EditMode;
12use crate::{
13    edit_mode::{keybindings::Keybindings, vi::parser::parse},
14    enums::{EditCommand, ReedlineEvent, ReedlineRawEvent},
15    PromptEditMode, PromptViMode,
16};
17
18#[derive(Debug, PartialEq, Eq, Clone, Copy)]
19enum ViMode {
20    Normal,
21    Insert,
22}
23
24/// This parses incoming input `Event`s like a Vi-Style editor
25pub struct Vi {
26    cache: Vec<char>,
27    insert_keybindings: Keybindings,
28    normal_keybindings: Keybindings,
29    mode: ViMode,
30    previous: Option<ReedlineEvent>,
31    // last f, F, t, T motion for ; and ,
32    last_char_search: Option<ViCharSearch>,
33}
34
35impl Default for Vi {
36    fn default() -> Self {
37        Vi {
38            insert_keybindings: default_vi_insert_keybindings(),
39            normal_keybindings: default_vi_normal_keybindings(),
40            cache: Vec::new(),
41            mode: ViMode::Insert,
42            previous: None,
43            last_char_search: None,
44        }
45    }
46}
47
48impl Vi {
49    /// Creates Vi editor using defined keybindings
50    pub fn new(insert_keybindings: Keybindings, normal_keybindings: Keybindings) -> Self {
51        Self {
52            insert_keybindings,
53            normal_keybindings,
54            ..Default::default()
55        }
56    }
57}
58
59impl EditMode for Vi {
60    fn parse_event(&mut self, event: ReedlineRawEvent) -> ReedlineEvent {
61        match event.into() {
62            Event::Key(KeyEvent {
63                code, modifiers, ..
64            }) => match (self.mode, modifiers, code) {
65                (ViMode::Normal, modifier, KeyCode::Char(c)) => {
66                    let c = c.to_ascii_lowercase();
67
68                    if let Some(event) = self
69                        .normal_keybindings
70                        .find_binding(modifiers, KeyCode::Char(c))
71                    {
72                        event
73                    } else if modifier == KeyModifiers::NONE || modifier == KeyModifiers::SHIFT {
74                        self.cache.push(if modifier == KeyModifiers::SHIFT {
75                            c.to_ascii_uppercase()
76                        } else {
77                            c
78                        });
79
80                        let res = parse(&mut self.cache.iter().peekable());
81
82                        if !res.is_valid() {
83                            self.cache.clear();
84                            ReedlineEvent::None
85                        } else if res.is_complete() {
86                            if res.enters_insert_mode() {
87                                self.mode = ViMode::Insert;
88                            }
89
90                            let event = res.to_reedline_event(self);
91                            self.cache.clear();
92                            event
93                        } else {
94                            ReedlineEvent::None
95                        }
96                    } else {
97                        ReedlineEvent::None
98                    }
99                }
100                (ViMode::Insert, modifier, KeyCode::Char(c)) => {
101                    // Note. The modifier can also be a combination of modifiers, for
102                    // example:
103                    //     KeyModifiers::CONTROL | KeyModifiers::ALT
104                    //     KeyModifiers::CONTROL | KeyModifiers::ALT | KeyModifiers::SHIFT
105                    //
106                    // Mixed modifiers are used by non american keyboards that have extra
107                    // keys like 'alt gr'. Keep this in mind if in the future there are
108                    // cases where an event is not being captured
109                    let c = match modifier {
110                        KeyModifiers::NONE => c,
111                        _ => c.to_ascii_lowercase(),
112                    };
113
114                    self.insert_keybindings
115                        .find_binding(modifier, KeyCode::Char(c))
116                        .unwrap_or_else(|| {
117                            if modifier == KeyModifiers::NONE
118                                || modifier == KeyModifiers::SHIFT
119                                || modifier == KeyModifiers::CONTROL | KeyModifiers::ALT
120                                || modifier
121                                    == KeyModifiers::CONTROL
122                                        | KeyModifiers::ALT
123                                        | KeyModifiers::SHIFT
124                            {
125                                ReedlineEvent::Edit(vec![EditCommand::InsertChar(
126                                    if modifier == KeyModifiers::SHIFT {
127                                        c.to_ascii_uppercase()
128                                    } else {
129                                        c
130                                    },
131                                )])
132                            } else {
133                                ReedlineEvent::None
134                            }
135                        })
136                }
137                (_, KeyModifiers::NONE, KeyCode::Esc) => {
138                    self.cache.clear();
139                    self.mode = ViMode::Normal;
140                    ReedlineEvent::Multiple(vec![ReedlineEvent::Esc, ReedlineEvent::Repaint])
141                }
142                (_, KeyModifiers::NONE, KeyCode::Enter) => {
143                    self.mode = ViMode::Insert;
144                    ReedlineEvent::Enter
145                }
146                (ViMode::Normal, _, _) => self
147                    .normal_keybindings
148                    .find_binding(modifiers, code)
149                    .unwrap_or(ReedlineEvent::None),
150                (ViMode::Insert, _, _) => self
151                    .insert_keybindings
152                    .find_binding(modifiers, code)
153                    .unwrap_or(ReedlineEvent::None),
154            },
155
156            Event::Mouse(_) => ReedlineEvent::Mouse,
157            Event::Resize(width, height) => ReedlineEvent::Resize(width, height),
158            Event::FocusGained => ReedlineEvent::None,
159            Event::FocusLost => ReedlineEvent::None,
160            Event::Paste(body) => ReedlineEvent::Edit(vec![EditCommand::InsertString(
161                body.replace("\r\n", "\n").replace('\r', "\n"),
162            )]),
163        }
164    }
165
166    fn edit_mode(&self) -> PromptEditMode {
167        match self.mode {
168            ViMode::Normal => PromptEditMode::Vi(PromptViMode::Normal),
169            ViMode::Insert => PromptEditMode::Vi(PromptViMode::Insert),
170        }
171    }
172}
173
174#[cfg(test)]
175mod test {
176    use super::*;
177    use pretty_assertions::assert_eq;
178
179    #[test]
180    fn esc_leads_to_normal_mode_test() {
181        let mut vi = Vi::default();
182        let esc = ReedlineRawEvent::convert_from(Event::Key(KeyEvent::new(
183            KeyCode::Esc,
184            KeyModifiers::NONE,
185        )))
186        .unwrap();
187        let result = vi.parse_event(esc);
188
189        assert_eq!(
190            result,
191            ReedlineEvent::Multiple(vec![ReedlineEvent::Esc, ReedlineEvent::Repaint])
192        );
193        assert!(matches!(vi.mode, ViMode::Normal));
194    }
195
196    #[test]
197    fn keybinding_without_modifier_test() {
198        let mut keybindings = default_vi_normal_keybindings();
199        keybindings.add_binding(
200            KeyModifiers::NONE,
201            KeyCode::Char('e'),
202            ReedlineEvent::ClearScreen,
203        );
204
205        let mut vi = Vi {
206            insert_keybindings: default_vi_insert_keybindings(),
207            normal_keybindings: keybindings,
208            mode: ViMode::Normal,
209            ..Default::default()
210        };
211
212        let esc = ReedlineRawEvent::convert_from(Event::Key(KeyEvent::new(
213            KeyCode::Char('e'),
214            KeyModifiers::NONE,
215        )))
216        .unwrap();
217        let result = vi.parse_event(esc);
218
219        assert_eq!(result, ReedlineEvent::ClearScreen);
220    }
221
222    #[test]
223    fn keybinding_with_shift_modifier_test() {
224        let mut keybindings = default_vi_normal_keybindings();
225        keybindings.add_binding(
226            KeyModifiers::SHIFT,
227            KeyCode::Char('$'),
228            ReedlineEvent::CtrlD,
229        );
230
231        let mut vi = Vi {
232            insert_keybindings: default_vi_insert_keybindings(),
233            normal_keybindings: keybindings,
234            mode: ViMode::Normal,
235            ..Default::default()
236        };
237
238        let esc = ReedlineRawEvent::convert_from(Event::Key(KeyEvent::new(
239            KeyCode::Char('$'),
240            KeyModifiers::SHIFT,
241        )))
242        .unwrap();
243        let result = vi.parse_event(esc);
244
245        assert_eq!(result, ReedlineEvent::CtrlD);
246    }
247
248    #[test]
249    fn non_register_modifier_test() {
250        let keybindings = default_vi_normal_keybindings();
251        let mut vi = Vi {
252            insert_keybindings: default_vi_insert_keybindings(),
253            normal_keybindings: keybindings,
254            mode: ViMode::Normal,
255            ..Default::default()
256        };
257
258        let esc = ReedlineRawEvent::convert_from(Event::Key(KeyEvent::new(
259            KeyCode::Char('q'),
260            KeyModifiers::NONE,
261        )))
262        .unwrap();
263        let result = vi.parse_event(esc);
264
265        assert_eq!(result, ReedlineEvent::None);
266    }
267}