reedline/edit_mode/
keybindings.rs

1use {
2    crate::{enums::ReedlineEvent, EditCommand},
3    crossterm::event::{KeyCode, KeyModifiers},
4    serde::{Deserialize, Serialize},
5    std::collections::HashMap,
6};
7
8#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Hash, Debug)]
9pub struct KeyCombination {
10    pub modifier: KeyModifiers,
11    pub key_code: KeyCode,
12}
13
14/// Main definition of editor keybindings
15#[derive(Serialize, Deserialize, Clone, Debug)]
16pub struct Keybindings {
17    /// Defines a keybinding for a reedline event
18    pub bindings: HashMap<KeyCombination, ReedlineEvent>,
19}
20
21impl Default for Keybindings {
22    fn default() -> Self {
23        Self::new()
24    }
25}
26
27impl Keybindings {
28    /// New keybining
29    pub fn new() -> Self {
30        Self {
31            bindings: HashMap::new(),
32        }
33    }
34
35    /// Defines an empty keybinding object
36    pub fn empty() -> Self {
37        Self::new()
38    }
39
40    /// Adds a keybinding
41    ///
42    /// # Panics
43    ///
44    /// If `command` is an empty [`ReedlineEvent::UntilFound`]
45    pub fn add_binding(
46        &mut self,
47        modifier: KeyModifiers,
48        key_code: KeyCode,
49        command: ReedlineEvent,
50    ) {
51        if let ReedlineEvent::UntilFound(subcommands) = &command {
52            assert!(
53                !subcommands.is_empty(),
54                "UntilFound should contain a series of potential events to handle"
55            );
56        }
57
58        let key_combo = KeyCombination { modifier, key_code };
59        self.bindings.insert(key_combo, command);
60    }
61
62    /// Find a keybinding based on the modifier and keycode
63    pub fn find_binding(&self, modifier: KeyModifiers, key_code: KeyCode) -> Option<ReedlineEvent> {
64        let key_combo = KeyCombination { modifier, key_code };
65        self.bindings.get(&key_combo).cloned()
66    }
67
68    /// Remove a keybinding
69    ///
70    /// Returns `Some(ReedlineEvent)` if the keycombination was previously bound to a particular [`ReedlineEvent`]
71    pub fn remove_binding(
72        &mut self,
73        modifier: KeyModifiers,
74        key_code: KeyCode,
75    ) -> Option<ReedlineEvent> {
76        let key_combo = KeyCombination { modifier, key_code };
77        self.bindings.remove(&key_combo)
78    }
79
80    /// Get assigned keybindings
81    pub fn get_keybindings(&self) -> &HashMap<KeyCombination, ReedlineEvent> {
82        &self.bindings
83    }
84}
85
86pub fn edit_bind(command: EditCommand) -> ReedlineEvent {
87    ReedlineEvent::Edit(vec![command])
88}
89
90/// Add the basic special keybindings
91///
92/// `Ctrl-C`, `Ctrl-D`, `Ctrl-O`, `Ctrl-R`
93/// + `Esc`
94/// + `Ctrl-O` to open the external editor
95pub fn add_common_control_bindings(kb: &mut Keybindings) {
96    use KeyCode as KC;
97    use KeyModifiers as KM;
98
99    kb.add_binding(KM::NONE, KC::Esc, ReedlineEvent::Esc);
100    kb.add_binding(KM::CONTROL, KC::Char('c'), ReedlineEvent::CtrlC);
101    kb.add_binding(KM::CONTROL, KC::Char('d'), ReedlineEvent::CtrlD);
102    kb.add_binding(KM::CONTROL, KC::Char('l'), ReedlineEvent::ClearScreen);
103    kb.add_binding(KM::CONTROL, KC::Char('r'), ReedlineEvent::SearchHistory);
104    kb.add_binding(KM::CONTROL, KC::Char('o'), ReedlineEvent::OpenEditor);
105}
106/// Add the arrow navigation and its `Ctrl` variants
107pub fn add_common_navigation_bindings(kb: &mut Keybindings) {
108    use EditCommand as EC;
109    use KeyCode as KC;
110    use KeyModifiers as KM;
111
112    // Arrow keys without modifier
113    kb.add_binding(
114        KM::NONE,
115        KC::Up,
116        ReedlineEvent::UntilFound(vec![ReedlineEvent::MenuUp, ReedlineEvent::Up]),
117    );
118    kb.add_binding(
119        KM::NONE,
120        KC::Down,
121        ReedlineEvent::UntilFound(vec![ReedlineEvent::MenuDown, ReedlineEvent::Down]),
122    );
123    kb.add_binding(
124        KM::NONE,
125        KC::Left,
126        ReedlineEvent::UntilFound(vec![ReedlineEvent::MenuLeft, ReedlineEvent::Left]),
127    );
128    kb.add_binding(
129        KM::NONE,
130        KC::Right,
131        ReedlineEvent::UntilFound(vec![
132            ReedlineEvent::HistoryHintComplete,
133            ReedlineEvent::MenuRight,
134            ReedlineEvent::Right,
135        ]),
136    );
137
138    // Ctrl Left and Right
139    kb.add_binding(KM::CONTROL, KC::Left, edit_bind(EC::MoveWordLeft));
140    kb.add_binding(
141        KM::CONTROL,
142        KC::Right,
143        ReedlineEvent::UntilFound(vec![
144            ReedlineEvent::HistoryHintWordComplete,
145            edit_bind(EC::MoveWordRight),
146        ]),
147    );
148    // Home/End & ctrl+a/ctrl+e
149    kb.add_binding(KM::NONE, KC::Home, edit_bind(EC::MoveToLineStart));
150    kb.add_binding(KM::CONTROL, KC::Char('a'), edit_bind(EC::MoveToLineStart));
151    kb.add_binding(
152        KM::NONE,
153        KC::End,
154        ReedlineEvent::UntilFound(vec![
155            ReedlineEvent::HistoryHintComplete,
156            edit_bind(EC::MoveToLineEnd),
157        ]),
158    );
159    kb.add_binding(
160        KM::CONTROL,
161        KC::Char('e'),
162        ReedlineEvent::UntilFound(vec![
163            ReedlineEvent::HistoryHintComplete,
164            edit_bind(EC::MoveToLineEnd),
165        ]),
166    );
167    // Ctrl Home/End
168    kb.add_binding(KM::CONTROL, KC::Home, edit_bind(EC::MoveToStart));
169    kb.add_binding(KM::CONTROL, KC::End, edit_bind(EC::MoveToEnd));
170    // EMACS arrows
171    kb.add_binding(
172        KM::CONTROL,
173        KC::Char('p'),
174        ReedlineEvent::UntilFound(vec![ReedlineEvent::MenuUp, ReedlineEvent::Up]),
175    );
176    kb.add_binding(
177        KM::CONTROL,
178        KC::Char('n'),
179        ReedlineEvent::UntilFound(vec![ReedlineEvent::MenuDown, ReedlineEvent::Down]),
180    );
181}
182
183/// Add basic functionality to edit
184///
185/// `Delete`, `Backspace` and the basic variants do delete words
186pub fn add_common_edit_bindings(kb: &mut Keybindings) {
187    use EditCommand as EC;
188    use KeyCode as KC;
189    use KeyModifiers as KM;
190    kb.add_binding(KM::NONE, KC::Backspace, edit_bind(EC::Backspace));
191    kb.add_binding(KM::NONE, KC::Delete, edit_bind(EC::Delete));
192    kb.add_binding(KM::CONTROL, KC::Backspace, edit_bind(EC::BackspaceWord));
193    kb.add_binding(KM::CONTROL, KC::Delete, edit_bind(EC::DeleteWord));
194    // Base commands should not affect cut buffer
195    kb.add_binding(KM::CONTROL, KC::Char('h'), edit_bind(EC::Backspace));
196    kb.add_binding(KM::CONTROL, KC::Char('w'), edit_bind(EC::BackspaceWord));
197}