liner/keymap/
emacs.rs

1use std::io::{self, Write};
2use termion::event::Key;
3
4use crate::CursorPosition;
5use crate::Editor;
6use crate::KeyMap;
7
8/// Emacs keybindings for `Editor`. This is the default for `Context::read_line()`.
9///
10/// ```
11/// use liner::*;
12/// let mut context = Context::new();
13/// context.key_bindings = KeyBindings::Emacs;
14/// ```
15#[derive(Default)]
16pub struct Emacs {
17    last_arg_fetch_index: Option<usize>,
18}
19
20impl Emacs {
21    pub fn new() -> Self {
22        Self::default()
23    }
24
25    fn handle_ctrl_key<W: Write>(&mut self, c: char, ed: &mut Editor<'_, W>) -> io::Result<()> {
26        match c {
27            'l' => ed.clear(),
28            'a' => ed.move_cursor_to_start_of_line(),
29            'e' => ed.move_cursor_to_end_of_line(),
30            'b' => ed.move_cursor_left(1),
31            'f' => ed.move_cursor_right(1),
32            'd' => ed.delete_after_cursor(),
33            'p' => ed.move_up(),
34            'n' => ed.move_down(),
35            'u' => ed.delete_all_before_cursor(),
36            'k' => ed.delete_all_after_cursor(),
37            'w' => ed.delete_word_before_cursor(true),
38            'x' => {
39                ed.undo()?;
40                Ok(())
41            }
42            _ => Ok(()),
43        }
44    }
45
46    fn handle_alt_key<W: Write>(&mut self, c: char, ed: &mut Editor<'_, W>) -> io::Result<()> {
47        match c {
48            '<' => ed.move_to_start_of_history(),
49            '>' => ed.move_to_end_of_history(),
50            '\x7F' => ed.delete_word_before_cursor(true),
51            'f' => emacs_move_word(ed, EmacsMoveDir::Right),
52            'b' => emacs_move_word(ed, EmacsMoveDir::Left),
53            'r' => {
54                ed.revert()?;
55                Ok(())
56            }
57            '.' => self.handle_last_arg_fetch(ed),
58            _ => Ok(()),
59        }
60    }
61
62    fn handle_last_arg_fetch<W: Write>(&mut self, ed: &mut Editor<'_, W>) -> io::Result<()> {
63        // Empty history means no last arg to fetch.
64        if ed.context().history.is_empty() {
65            return Ok(());
66        }
67
68        let history_index = match self.last_arg_fetch_index {
69            Some(0) => return Ok(()),
70            Some(x) => x - 1,
71            None => ed
72                .current_history_location()
73                .unwrap_or(ed.context().history.len() - 1),
74        };
75
76        // If did a last arg fetch just before this, we need to delete it so it can be replaced by
77        // this last arg fetch.
78        if self.last_arg_fetch_index.is_some() {
79            let buffer_len = ed.current_buffer().num_chars();
80            if let Some(last_arg_len) = ed.current_buffer().last_arg().map(|x| x.len()) {
81                ed.delete_until(buffer_len - last_arg_len)?;
82            }
83        }
84
85        // Actually insert it
86        let buf = ed.context().history[history_index].clone();
87        if let Some(last_arg) = buf.last_arg() {
88            ed.insert_chars_after_cursor(last_arg)?;
89        }
90
91        // Edit the index in case the user does a last arg fetch again.
92        self.last_arg_fetch_index = Some(history_index);
93
94        Ok(())
95    }
96}
97
98impl KeyMap for Emacs {
99    fn handle_key_core<W: Write>(&mut self, key: Key, ed: &mut Editor<'_, W>) -> io::Result<()> {
100        match key {
101            Key::Alt('.') => {}
102            _ => self.last_arg_fetch_index = None,
103        }
104
105        match key {
106            Key::Char(c) => ed.insert_after_cursor(c),
107            Key::Alt(c) => self.handle_alt_key(c, ed),
108            Key::Ctrl(c) => self.handle_ctrl_key(c, ed),
109            Key::Left => ed.move_cursor_left(1),
110            Key::Right => ed.move_cursor_right(1),
111            Key::Up => ed.move_up(),
112            Key::Down => ed.move_down(),
113            Key::Home => ed.move_cursor_to_start_of_line(),
114            Key::End => ed.move_cursor_to_end_of_line(),
115            Key::Backspace => ed.delete_before_cursor(),
116            Key::Delete => ed.delete_after_cursor(),
117            Key::Null => Ok(()),
118            _ => Ok(()),
119        }
120    }
121}
122
123#[derive(PartialEq, Clone, Copy)]
124enum EmacsMoveDir {
125    Left,
126    Right,
127}
128
129fn emacs_move_word<W: Write>(ed: &mut Editor<W>, direction: EmacsMoveDir) -> io::Result<()> {
130    let (words, pos) = ed.get_words_and_cursor_position();
131
132    let word_index = match pos {
133        CursorPosition::InWord(i) => Some(i),
134        CursorPosition::OnWordLeftEdge(mut i) => {
135            if i > 0 && direction == EmacsMoveDir::Left {
136                i -= 1;
137            }
138            Some(i)
139        }
140        CursorPosition::OnWordRightEdge(mut i) => {
141            if i < words.len() - 1 && direction == EmacsMoveDir::Right {
142                i += 1;
143            }
144            Some(i)
145        }
146        CursorPosition::InSpace(left, right) => match direction {
147            EmacsMoveDir::Left => left,
148            EmacsMoveDir::Right => right,
149        },
150    };
151
152    match word_index {
153        None => Ok(()),
154        Some(i) => {
155            let (start, end) = words[i];
156
157            let new_cursor_pos = match direction {
158                EmacsMoveDir::Left => start,
159                EmacsMoveDir::Right => end,
160            };
161
162            ed.move_cursor_to(new_cursor_pos)
163        }
164    }
165}
166
167#[cfg(test)]
168mod tests {
169    use super::*;
170    use crate::editor::Prompt;
171    use crate::{Completer, Context, Editor, KeyMap};
172    use std::io::Write;
173    use termion::event::Key;
174
175    fn simulate_keys<'a, 'b, W: Write, M: KeyMap, I>(
176        keymap: &mut M,
177        ed: &mut Editor<'a, W>,
178        keys: I,
179    ) -> bool
180    where
181        I: IntoIterator<Item = &'b Key>,
182    {
183        for k in keys {
184            if keymap.handle_key(*k, ed, &mut EmptyCompleter).unwrap() {
185                return true;
186            }
187        }
188
189        false
190    }
191
192    struct EmptyCompleter;
193
194    impl Completer for EmptyCompleter {
195        fn completions(&mut self, _start: &str) -> Vec<String> {
196            Vec::default()
197        }
198    }
199
200    #[test]
201    fn enter_is_done() {
202        let mut context = Context::new();
203        let out = Vec::new();
204        let mut ed = Editor::new(out, Prompt::from("prompt"), None, &mut context).unwrap();
205        let mut map = Emacs::new();
206        ed.insert_str_after_cursor("done").unwrap();
207        assert_eq!(ed.cursor(), 4);
208
209        assert!(simulate_keys(&mut map, &mut ed, [Key::Char('\n')].iter()));
210
211        assert_eq!(ed.cursor(), 4);
212        assert_eq!(String::from(ed), "done");
213    }
214
215    #[test]
216    fn move_cursor_left() {
217        let mut context = Context::new();
218        let out = Vec::new();
219        let mut ed = Editor::new(out, Prompt::from("prompt"), None, &mut context).unwrap();
220        let mut map = Emacs::new();
221        ed.insert_str_after_cursor("let").unwrap();
222        assert_eq!(ed.cursor(), 3);
223
224        simulate_keys(&mut map, &mut ed, [Key::Left, Key::Char('f')].iter());
225
226        assert_eq!(ed.cursor(), 3);
227        assert_eq!(String::from(ed), "left");
228    }
229
230    #[test]
231    fn move_word() {
232        let mut context = Context::new();
233        let out = Vec::new();
234
235        let mut ed = Editor::new(out, Prompt::from("prompt"), None, &mut context).unwrap();
236        let mut map = Emacs::new();
237        ed.insert_str_after_cursor("abc def ghi").unwrap();
238        assert_eq!(ed.cursor(), 11);
239
240        simulate_keys(&mut map, &mut ed, [Key::Alt('b')].iter());
241
242        // Move to `g`
243        assert_eq!(ed.cursor(), 8);
244
245        simulate_keys(&mut map, &mut ed, [Key::Alt('b'), Key::Alt('f')].iter());
246
247        // Move to the char after `f`
248        assert_eq!(ed.cursor(), 7);
249    }
250
251    #[test]
252    fn cursor_movement() {
253        let mut context = Context::new();
254        let out = Vec::new();
255        let mut ed = Editor::new(out, Prompt::from("prompt"), None, &mut context).unwrap();
256        let mut map = Emacs::new();
257        ed.insert_str_after_cursor("right").unwrap();
258        assert_eq!(ed.cursor(), 5);
259
260        simulate_keys(&mut map, &mut ed, [Key::Left, Key::Left, Key::Right].iter());
261
262        assert_eq!(ed.cursor(), 4);
263    }
264
265    #[test]
266    /// ctrl-h should act as backspace
267    fn ctrl_h() {
268        let mut context = Context::new();
269        let out = Vec::new();
270        let mut ed = Editor::new(out, Prompt::from("prompt"), None, &mut context).unwrap();
271        let mut map = Emacs::new();
272        ed.insert_str_after_cursor("not empty").unwrap();
273
274        let res = map.handle_key(Key::Ctrl('h'), &mut ed, &mut EmptyCompleter);
275        assert_eq!(res.is_ok(), true);
276        assert_eq!(ed.current_buffer().to_string(), "not empt".to_string());
277    }
278}