reedline/edit_mode/
emacs.rs

1use crate::{
2    edit_mode::{
3        keybindings::{
4            add_common_control_bindings, add_common_edit_bindings, add_common_navigation_bindings,
5            edit_bind, Keybindings,
6        },
7        EditMode,
8    },
9    enums::{EditCommand, ReedlineEvent, ReedlineRawEvent},
10    PromptEditMode,
11};
12use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers};
13
14/// Returns the current default emacs keybindings
15pub fn default_emacs_keybindings() -> Keybindings {
16    use EditCommand as EC;
17    use KeyCode as KC;
18    use KeyModifiers as KM;
19
20    let mut kb = Keybindings::new();
21    add_common_control_bindings(&mut kb);
22    add_common_navigation_bindings(&mut kb);
23    add_common_edit_bindings(&mut kb);
24
25    // This could be in common, but in Vi it also changes the mode
26    kb.add_binding(KM::NONE, KC::Enter, ReedlineEvent::Enter);
27
28    // *** CTRL ***
29    // Moves
30    kb.add_binding(
31        KM::CONTROL,
32        KC::Char('b'),
33        ReedlineEvent::UntilFound(vec![ReedlineEvent::MenuLeft, ReedlineEvent::Left]),
34    );
35    kb.add_binding(
36        KM::CONTROL,
37        KC::Char('f'),
38        ReedlineEvent::UntilFound(vec![
39            ReedlineEvent::HistoryHintComplete,
40            ReedlineEvent::MenuRight,
41            ReedlineEvent::Right,
42        ]),
43    );
44    // Undo/Redo
45    kb.add_binding(KM::CONTROL, KC::Char('g'), edit_bind(EC::Redo));
46    kb.add_binding(KM::CONTROL, KC::Char('z'), edit_bind(EC::Undo));
47    // Cutting
48    kb.add_binding(
49        KM::CONTROL,
50        KC::Char('y'),
51        edit_bind(EC::PasteCutBufferBefore),
52    );
53    kb.add_binding(KM::CONTROL, KC::Char('w'), edit_bind(EC::CutWordLeft));
54    kb.add_binding(KM::CONTROL, KC::Char('k'), edit_bind(EC::CutToEnd));
55    kb.add_binding(KM::CONTROL, KC::Char('u'), edit_bind(EC::CutFromStart));
56    // Edits
57    kb.add_binding(KM::CONTROL, KC::Char('t'), edit_bind(EC::SwapGraphemes));
58
59    // *** ALT ***
60    // Moves
61    kb.add_binding(KM::ALT, KC::Left, edit_bind(EC::MoveWordLeft));
62    kb.add_binding(
63        KM::ALT,
64        KC::Right,
65        ReedlineEvent::UntilFound(vec![
66            ReedlineEvent::HistoryHintWordComplete,
67            edit_bind(EC::MoveWordRight),
68        ]),
69    );
70    kb.add_binding(KM::ALT, KC::Char('b'), edit_bind(EC::MoveWordLeft));
71    kb.add_binding(
72        KM::ALT,
73        KC::Char('f'),
74        ReedlineEvent::UntilFound(vec![
75            ReedlineEvent::HistoryHintWordComplete,
76            edit_bind(EC::MoveWordRight),
77        ]),
78    );
79    // Edits
80    kb.add_binding(KM::ALT, KC::Delete, edit_bind(EC::DeleteWord));
81    kb.add_binding(KM::ALT, KC::Backspace, edit_bind(EC::BackspaceWord));
82    kb.add_binding(
83        KM::ALT,
84        KC::Char('m'),
85        ReedlineEvent::Edit(vec![EditCommand::BackspaceWord]),
86    );
87    // Cutting
88    kb.add_binding(KM::ALT, KC::Char('d'), edit_bind(EC::CutWordRight));
89    // Case changes
90    kb.add_binding(KM::ALT, KC::Char('u'), edit_bind(EC::UppercaseWord));
91    kb.add_binding(KM::ALT, KC::Char('l'), edit_bind(EC::LowercaseWord));
92    kb.add_binding(KM::ALT, KC::Char('c'), edit_bind(EC::CapitalizeChar));
93
94    kb
95}
96
97/// This parses the incoming Events like a emacs style-editor
98pub struct Emacs {
99    keybindings: Keybindings,
100}
101
102impl Default for Emacs {
103    fn default() -> Self {
104        Emacs {
105            keybindings: default_emacs_keybindings(),
106        }
107    }
108}
109
110impl EditMode for Emacs {
111    fn parse_event(&mut self, event: ReedlineRawEvent) -> ReedlineEvent {
112        match event.into() {
113            Event::Key(KeyEvent {
114                code, modifiers, ..
115            }) => match (modifiers, code) {
116                (modifier, KeyCode::Char(c)) => {
117                    // Note. The modifier can also be a combination of modifiers, for
118                    // example:
119                    //     KeyModifiers::CONTROL | KeyModifiers::ALT
120                    //     KeyModifiers::CONTROL | KeyModifiers::ALT | KeyModifiers::SHIFT
121                    //
122                    // Mixed modifiers are used by non american keyboards that have extra
123                    // keys like 'alt gr'. Keep this in mind if in the future there are
124                    // cases where an event is not being captured
125                    let c = match modifier {
126                        KeyModifiers::NONE => c,
127                        _ => c.to_ascii_lowercase(),
128                    };
129
130                    self.keybindings
131                        .find_binding(modifier, KeyCode::Char(c))
132                        .unwrap_or_else(|| {
133                            if modifier == KeyModifiers::NONE
134                                || modifier == KeyModifiers::SHIFT
135                                || modifier == KeyModifiers::CONTROL | KeyModifiers::ALT
136                                || modifier
137                                    == KeyModifiers::CONTROL
138                                        | KeyModifiers::ALT
139                                        | KeyModifiers::SHIFT
140                            {
141                                ReedlineEvent::Edit(vec![EditCommand::InsertChar(
142                                    if modifier == KeyModifiers::SHIFT {
143                                        c.to_ascii_uppercase()
144                                    } else {
145                                        c
146                                    },
147                                )])
148                            } else {
149                                ReedlineEvent::None
150                            }
151                        })
152                }
153                _ => self
154                    .keybindings
155                    .find_binding(modifiers, code)
156                    .unwrap_or(ReedlineEvent::None),
157            },
158
159            Event::Mouse(_) => ReedlineEvent::Mouse,
160            Event::Resize(width, height) => ReedlineEvent::Resize(width, height),
161            Event::FocusGained => ReedlineEvent::None,
162            Event::FocusLost => ReedlineEvent::None,
163            Event::Paste(body) => ReedlineEvent::Edit(vec![EditCommand::InsertString(
164                body.replace("\r\n", "\n").replace('\r', "\n"),
165            )]),
166        }
167    }
168
169    fn edit_mode(&self) -> PromptEditMode {
170        PromptEditMode::Emacs
171    }
172}
173
174impl Emacs {
175    /// Emacs style input parsing constructor if you want to use custom keybindings
176    pub const fn new(keybindings: Keybindings) -> Self {
177        Emacs { keybindings }
178    }
179}
180
181#[cfg(test)]
182mod test {
183    use super::*;
184    use pretty_assertions::assert_eq;
185
186    #[test]
187    fn ctrl_l_leads_to_clear_screen_event() {
188        let mut emacs = Emacs::default();
189        let ctrl_l = ReedlineRawEvent::convert_from(Event::Key(KeyEvent::new(
190            KeyCode::Char('l'),
191            KeyModifiers::CONTROL,
192        )))
193        .unwrap();
194        let result = emacs.parse_event(ctrl_l);
195
196        assert_eq!(result, ReedlineEvent::ClearScreen);
197    }
198
199    #[test]
200    fn overriding_default_keybindings_works() {
201        let mut keybindings = default_emacs_keybindings();
202        keybindings.add_binding(
203            KeyModifiers::CONTROL,
204            KeyCode::Char('l'),
205            ReedlineEvent::HistoryHintComplete,
206        );
207
208        let mut emacs = Emacs::new(keybindings);
209        let ctrl_l = ReedlineRawEvent::convert_from(Event::Key(KeyEvent::new(
210            KeyCode::Char('l'),
211            KeyModifiers::CONTROL,
212        )))
213        .unwrap();
214        let result = emacs.parse_event(ctrl_l);
215
216        assert_eq!(result, ReedlineEvent::HistoryHintComplete);
217    }
218
219    #[test]
220    fn inserting_character_works() {
221        let mut emacs = Emacs::default();
222        let l = ReedlineRawEvent::convert_from(Event::Key(KeyEvent::new(
223            KeyCode::Char('l'),
224            KeyModifiers::NONE,
225        )))
226        .unwrap();
227        let result = emacs.parse_event(l);
228
229        assert_eq!(
230            result,
231            ReedlineEvent::Edit(vec![EditCommand::InsertChar('l')])
232        );
233    }
234
235    #[test]
236    fn inserting_capital_character_works() {
237        let mut emacs = Emacs::default();
238
239        let uppercase_l = ReedlineRawEvent::convert_from(Event::Key(KeyEvent::new(
240            KeyCode::Char('l'),
241            KeyModifiers::SHIFT,
242        )))
243        .unwrap();
244        let result = emacs.parse_event(uppercase_l);
245
246        assert_eq!(
247            result,
248            ReedlineEvent::Edit(vec![EditCommand::InsertChar('L')])
249        );
250    }
251
252    #[test]
253    fn return_none_reedline_event_when_keybinding_is_not_found() {
254        let keybindings = Keybindings::default();
255
256        let mut emacs = Emacs::new(keybindings);
257        let ctrl_l = ReedlineRawEvent::convert_from(Event::Key(KeyEvent::new(
258            KeyCode::Char('l'),
259            KeyModifiers::CONTROL,
260        )))
261        .unwrap();
262        let result = emacs.parse_event(ctrl_l);
263
264        assert_eq!(result, ReedlineEvent::None);
265    }
266
267    #[test]
268    fn inserting_capital_character_for_non_ascii_remains_as_is() {
269        let mut emacs = Emacs::default();
270
271        let uppercase_l = ReedlineRawEvent::convert_from(Event::Key(KeyEvent::new(
272            KeyCode::Char('😀'),
273            KeyModifiers::SHIFT,
274        )))
275        .unwrap();
276        let result = emacs.parse_event(uppercase_l);
277
278        assert_eq!(
279            result,
280            ReedlineEvent::Edit(vec![EditCommand::InsertChar('😀')])
281        );
282    }
283}