sl_liner/keymap/
mod.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
//! Interface for Vi and Emacs KeyMaps
use crate::{Completer, Editor, Event, EventKind};
use sl_console::event::{Key, KeyCode, KeyMod};
use std::io::{self, ErrorKind};

pub trait KeyMap {
    //: Default {
    fn handle_key_core<'a>(&mut self, key: Key, editor: &mut Editor<'a>) -> io::Result<()>;

    fn init<'a>(&mut self, _editor: &mut Editor<'a>) {}

    fn handle_key<'a>(
        &mut self,
        mut key: Key,
        editor: &mut Editor<'a>,
        handler: &mut dyn Completer,
    ) -> io::Result<bool> {
        let mut done = false;

        handler.on_event(Event::new(editor, EventKind::BeforeKey(key)));

        let is_empty = editor.current_buffer().is_empty();

        if key.code == KeyCode::Char('h') && key.mods == Some(KeyMod::Ctrl) {
            // XXX: Might need to change this when remappable keybindings are added.
            key = Key::new(KeyCode::Backspace);
        }

        match (key.code, key.mods) {
            (KeyCode::Char('c'), Some(KeyMod::Ctrl)) => {
                editor.handle_newline()?;
                return Err(io::Error::new(ErrorKind::Interrupted, "ctrl-c"));
            }
            // if the current buffer is empty, treat ctrl-d as eof
            (KeyCode::Char('d'), Some(KeyMod::Ctrl)) if is_empty => {
                editor.handle_newline()?;
                return Err(io::Error::new(ErrorKind::UnexpectedEof, "ctrl-d"));
            }
            (KeyCode::Char('\t'), None) => editor.complete(handler)?,
            (KeyCode::Char('\n'), None) => {
                done = editor.handle_newline()?;
            }
            (KeyCode::Char('f'), Some(KeyMod::Ctrl))
                if editor.is_currently_showing_autosuggestion() =>
            {
                editor.accept_autosuggestion()?;
            }
            (KeyCode::Char('r'), Some(KeyMod::Ctrl)) => {
                editor.search(false)?;
            }
            (KeyCode::Char('s'), Some(KeyMod::Ctrl)) => {
                editor.search(true)?;
            }
            (KeyCode::Right, None)
                if editor.is_currently_showing_autosuggestion()
                    && editor.is_cursor_at_end_of_line() =>
            {
                editor.accept_autosuggestion()?;
            }
            _ => {
                self.handle_key_core(key, editor)?;
                editor.skip_completions_hint();
            }
        };

        handler.on_event(Event::new(editor, EventKind::AfterKey(key)));

        editor.flush()?;

        Ok(done)
    }
}

pub mod vi;
pub use vi::Vi;

pub mod emacs;
pub use emacs::Emacs;

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{DefaultEditorRules, History, Prompt};
    use sl_console::event::Key;
    use std::io::ErrorKind;

    #[derive(Default)]
    struct TestKeyMap;

    impl KeyMap for TestKeyMap {
        fn handle_key_core<'a>(&mut self, _: Key, _: &mut Editor<'a>) -> io::Result<()> {
            Ok(())
        }
    }

    struct EmptyCompleter;

    impl Completer for EmptyCompleter {
        fn completions(&mut self, _start: &str) -> Vec<String> {
            Vec::default()
        }
    }

    #[test]
    /// when the current buffer is empty, ctrl-d generates and eof error
    fn ctrl_d_empty() {
        let mut out = Vec::new();
        let mut history = History::new();
        let rules = DefaultEditorRules::default();
        let mut buf = String::with_capacity(512);
        let mut ed = Editor::new(
            &mut out,
            Prompt::from("prompt"),
            None,
            &mut history,
            &mut buf,
            &rules,
        )
        .unwrap();
        let mut map = TestKeyMap;

        let res = map.handle_key(
            Key::new_mod(KeyCode::Char('d'), KeyMod::Ctrl),
            &mut ed,
            &mut EmptyCompleter,
        );
        assert_eq!(res.is_err(), true);
        assert_eq!(res.err().unwrap().kind(), ErrorKind::UnexpectedEof);
    }

    #[test]
    /// when the current buffer is not empty, ctrl-d should be ignored
    fn ctrl_d_non_empty() {
        let mut out = Vec::new();
        let mut history = History::new();
        let mut buf = String::with_capacity(512);
        let rules = DefaultEditorRules::default();
        let mut ed = Editor::new(
            &mut out,
            Prompt::from("prompt"),
            None,
            &mut history,
            &mut buf,
            &rules,
        )
        .unwrap();
        let mut map = TestKeyMap;
        ed.insert_str_after_cursor("not empty").unwrap();

        let res = map.handle_key(
            Key::new_mod(KeyCode::Char('d'), KeyMod::Ctrl),
            &mut ed,
            &mut EmptyCompleter,
        );
        assert_eq!(res.is_ok(), true);
    }

    #[test]
    /// ctrl-c should generate an error
    fn ctrl_c() {
        let mut out = Vec::new();
        let mut history = History::new();
        let mut buf = String::with_capacity(512);
        let rules = DefaultEditorRules::default();
        let mut ed = Editor::new(
            &mut out,
            Prompt::from("prompt"),
            None,
            &mut history,
            &mut buf,
            &rules,
        )
        .unwrap();
        let mut map = TestKeyMap;

        let res = map.handle_key(
            Key::new_mod(KeyCode::Char('c'), KeyMod::Ctrl),
            &mut ed,
            &mut EmptyCompleter,
        );
        assert_eq!(res.is_err(), true);
        assert_eq!(res.err().unwrap().kind(), ErrorKind::Interrupted);
    }
}