emacs.rs 8.28 KB
Newer Older
1 2 3
use std::io::{self, Write};
use termion::event::Key;

AdminXVII's avatar
AdminXVII committed
4 5 6
use crate::CursorPosition;
use crate::Editor;
use crate::KeyMap;
7

MovingtoMars's avatar
MovingtoMars committed
8 9 10
/// Emacs keybindings for `Editor`. This is the default for `Context::read_line()`.
///
/// ```
MovingtoMars's avatar
MovingtoMars committed
11 12
/// use liner::*;
/// let mut context = Context::new();
MovingtoMars's avatar
MovingtoMars committed
13 14
/// context.key_bindings = KeyBindings::Emacs;
/// ```
AdminXVII's avatar
AdminXVII committed
15
#[derive(Default)]
16
pub struct Emacs {
17
    last_arg_fetch_index: Option<usize>,
18 19
}

20 21
impl Emacs {
    pub fn new() -> Self {
AdminXVII's avatar
AdminXVII committed
22
        Self::default()
23
    }
24

25
    fn handle_ctrl_key<'a, W: Write>(&mut self, c: char, ed: &mut Editor<'a, W>) -> io::Result<()> {
26
        match c {
27 28 29 30 31 32 33 34 35 36 37
            'l' => ed.clear(),
            'a' => ed.move_cursor_to_start_of_line(),
            'e' => ed.move_cursor_to_end_of_line(),
            'b' => ed.move_cursor_left(1),
            'f' => ed.move_cursor_right(1),
            'd' => ed.delete_after_cursor(),
            'p' => ed.move_up(),
            'n' => ed.move_down(),
            'u' => ed.delete_all_before_cursor(),
            'k' => ed.delete_all_after_cursor(),
            'w' => ed.delete_word_before_cursor(true),
38
            'x' => {
39
                ed.undo()?;
40 41 42 43 44 45
                Ok(())
            }
            _ => Ok(()),
        }
    }

46
    fn handle_alt_key<'a, W: Write>(&mut self, c: char, ed: &mut Editor<'a, W>) -> io::Result<()> {
47
        match c {
48 49 50 51 52
            '<' => ed.move_to_start_of_history(),
            '>' => ed.move_to_end_of_history(),
            '\x7F' => ed.delete_word_before_cursor(true),
            'f' => emacs_move_word(ed, EmacsMoveDir::Right),
            'b' => emacs_move_word(ed, EmacsMoveDir::Left),
53
            'r' => {
54
                ed.revert()?;
55 56
                Ok(())
            }
57
            '.' => self.handle_last_arg_fetch(ed),
58 59 60
            _ => Ok(()),
        }
    }
61

62
    fn handle_last_arg_fetch<'a, W: Write>(&mut self, ed: &mut Editor<'a, W>) -> io::Result<()> {
63
        // Empty history means no last arg to fetch.
AdminXVII's avatar
AdminXVII committed
64
        if ed.context().history.is_empty() {
65 66 67 68 69 70
            return Ok(());
        }

        let history_index = match self.last_arg_fetch_index {
            Some(0) => return Ok(()),
            Some(x) => x - 1,
71
            None => ed
72
                .current_history_location()
73
                .unwrap_or(ed.context().history.len() - 1),
74 75 76 77 78
        };

        // If did a last arg fetch just before this, we need to delete it so it can be replaced by
        // this last arg fetch.
        if self.last_arg_fetch_index.is_some() {
79 80 81
            let buffer_len = ed.current_buffer().num_chars();
            if let Some(last_arg_len) = ed.current_buffer().last_arg().map(|x| x.len()) {
                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 92 93 94 95
        }

        // Edit the index in case the user does a last arg fetch again.
        self.last_arg_fetch_index = Some(history_index);

        Ok(())
    }
96 97
}

98 99 100 101 102 103
impl KeyMap for Emacs {
    fn handle_key_core<'a, W: Write>(
        &mut self,
        key: Key,
        ed: &mut Editor<'a, W>,
    ) -> io::Result<()> {
104
        match key {
105
            Key::Alt('.') => {}
106 107 108
            _ => self.last_arg_fetch_index = None,
        }

109
        match key {
110 111 112 113 114 115 116 117 118 119 120
            Key::Char(c) => ed.insert_after_cursor(c),
            Key::Alt(c) => self.handle_alt_key(c, ed),
            Key::Ctrl(c) => self.handle_ctrl_key(c, ed),
            Key::Left => ed.move_cursor_left(1),
            Key::Right => ed.move_cursor_right(1),
            Key::Up => ed.move_up(),
            Key::Down => ed.move_down(),
            Key::Home => ed.move_cursor_to_start_of_line(),
            Key::End => ed.move_cursor_to_end_of_line(),
            Key::Backspace => ed.delete_before_cursor(),
            Key::Delete => ed.delete_after_cursor(),
121 122 123
            Key::Null => Ok(()),
            _ => Ok(()),
        }
124 125
    }
}
126

127 128 129 130 131 132 133
#[derive(PartialEq, Clone, Copy)]
enum EmacsMoveDir {
    Left,
    Right,
}

fn emacs_move_word<W: Write>(ed: &mut Editor<W>, direction: EmacsMoveDir) -> io::Result<()> {
134 135 136
    let (words, pos) = ed.get_words_and_cursor_position();

    let word_index = match pos {
137
        CursorPosition::InWord(i) => Some(i),
138 139 140 141 142
        CursorPosition::OnWordLeftEdge(mut i) => {
            if i > 0 && direction == EmacsMoveDir::Left {
                i -= 1;
            }
            Some(i)
143
        }
144 145 146 147 148
        CursorPosition::OnWordRightEdge(mut i) => {
            if i < words.len() - 1 && direction == EmacsMoveDir::Right {
                i += 1;
            }
            Some(i)
149 150 151 152
        }
        CursorPosition::InSpace(left, right) => match direction {
            EmacsMoveDir::Left => left,
            EmacsMoveDir::Right => right,
153 154 155 156 157 158 159 160 161 162 163
        },
    };

    match word_index {
        None => Ok(()),
        Some(i) => {
            let (start, end) = words[i];

            let new_cursor_pos = match direction {
                EmacsMoveDir::Left => start,
                EmacsMoveDir::Right => end,
164 165
            };

166
            ed.move_cursor_to(new_cursor_pos)
167 168 169 170
        }
    }
}

171 172 173
#[cfg(test)]
mod tests {
    use super::*;
AdminXVII's avatar
AdminXVII committed
174
    use crate::{Completer, Context, Editor, KeyMap};
175
    use std::io::Write;
176 177
    use termion::event::Key;

178 179 180 181 182
    fn simulate_keys<'a, 'b, W: Write, M: KeyMap, I>(
        keymap: &mut M,
        ed: &mut Editor<'a, W>,
        keys: I,
    ) -> bool
183
    where
184
        I: IntoIterator<Item = &'b Key>,
185 186
    {
        for k in keys {
187
            if keymap.handle_key(*k, ed, &mut EmptyCompleter).unwrap() {
188 189 190 191 192 193 194
                return true;
            }
        }

        false
    }

195 196
    struct EmptyCompleter;

197
    impl Completer for EmptyCompleter {
198 199 200 201 202
        fn completions(&mut self, _start: &str) -> Vec<String> {
            Vec::default()
        }
    }

203 204 205 206
    #[test]
    fn enter_is_done() {
        let mut context = Context::new();
        let out = Vec::new();
207 208 209 210
        let mut ed = Editor::new(out, "prompt".to_owned(), None, &mut context).unwrap();
        let mut map = Emacs::new();
        ed.insert_str_after_cursor("done").unwrap();
        assert_eq!(ed.cursor(), 4);
211

212
        assert!(simulate_keys(&mut map, &mut ed, [Key::Char('\n')].iter()));
213

214 215
        assert_eq!(ed.cursor(), 4);
        assert_eq!(String::from(ed), "done");
216 217 218 219 220 221
    }

    #[test]
    fn move_cursor_left() {
        let mut context = Context::new();
        let out = Vec::new();
222 223 224 225
        let mut ed = Editor::new(out, "prompt".to_owned(), None, &mut context).unwrap();
        let mut map = Emacs::new();
        ed.insert_str_after_cursor("let").unwrap();
        assert_eq!(ed.cursor(), 3);
226

227
        simulate_keys(&mut map, &mut ed, [Key::Left, Key::Char('f')].iter());
228

229 230
        assert_eq!(ed.cursor(), 3);
        assert_eq!(String::from(ed), "left");
231 232
    }

233 234 235 236 237
    #[test]
    fn move_word() {
        let mut context = Context::new();
        let out = Vec::new();

238 239 240 241 242 243
        let mut ed = Editor::new(out, "prompt".to_owned(), None, &mut context).unwrap();
        let mut map = Emacs::new();
        ed.insert_str_after_cursor("abc def ghi").unwrap();
        assert_eq!(ed.cursor(), 11);

        simulate_keys(&mut map, &mut ed, [Key::Alt('b')].iter());
244 245

        // Move to `g`
246
        assert_eq!(ed.cursor(), 8);
247

248
        simulate_keys(&mut map, &mut ed, [Key::Alt('b'), Key::Alt('f')].iter());
249 250

        // Move to the char after `f`
251
        assert_eq!(ed.cursor(), 7);
252 253
    }

254 255 256 257
    #[test]
    fn cursor_movement() {
        let mut context = Context::new();
        let out = Vec::new();
258 259 260 261
        let mut ed = Editor::new(out, "prompt".to_owned(), None, &mut context).unwrap();
        let mut map = Emacs::new();
        ed.insert_str_after_cursor("right").unwrap();
        assert_eq!(ed.cursor(), 5);
262

263
        simulate_keys(&mut map, &mut ed, [Key::Left, Key::Left, Key::Right].iter());
264

265
        assert_eq!(ed.cursor(), 4);
266
    }
MovingtoMars's avatar
MovingtoMars committed
267 268 269 270 271 272

    #[test]
    /// ctrl-h should act as backspace
    fn ctrl_h() {
        let mut context = Context::new();
        let out = Vec::new();
273 274 275
        let mut ed = Editor::new(out, "prompt".to_owned(), None, &mut context).unwrap();
        let mut map = Emacs::new();
        ed.insert_str_after_cursor("not empty").unwrap();
MovingtoMars's avatar
MovingtoMars committed
276

277
        let res = map.handle_key(Key::Ctrl('h'), &mut ed, &mut EmptyCompleter);
MovingtoMars's avatar
MovingtoMars committed
278
        assert_eq!(res.is_ok(), true);
279
        assert_eq!(ed.current_buffer().to_string(), "not empt".to_string());
MovingtoMars's avatar
MovingtoMars committed
280
    }
281
}