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
14pub 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 kb.add_binding(KM::NONE, KC::Enter, ReedlineEvent::Enter);
27
28 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 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 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 kb.add_binding(KM::CONTROL, KC::Char('t'), edit_bind(EC::SwapGraphemes));
58
59 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 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 kb.add_binding(KM::ALT, KC::Char('d'), edit_bind(EC::CutWordRight));
89 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
97pub 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 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 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}