rustyline/
keymap.rs

1//! Bindings from keys to command for Emacs and Vi modes
2use std::collections::HashMap;
3use std::sync::{Arc, RwLock};
4
5use super::Result;
6use config::Config;
7use config::EditMode;
8use keys::KeyPress;
9use tty::RawReader;
10
11/// The number of times one command should be repeated.
12pub type RepeatCount = usize;
13
14/// Commands
15/// #[non_exhaustive]
16#[derive(Debug, Clone, PartialEq)]
17pub enum Cmd {
18    /// abort
19    Abort, // Miscellaneous Command
20    /// accept-line
21    AcceptLine,
22    /// beginning-of-history
23    BeginningOfHistory,
24    /// capitalize-word
25    CapitalizeWord,
26    /// clear-screen
27    ClearScreen,
28    /// complete
29    Complete,
30    /// downcase-word
31    DowncaseWord,
32    /// vi-eof-maybe
33    EndOfFile,
34    /// end-of-history
35    EndOfHistory,
36    /// forward-search-history
37    ForwardSearchHistory,
38    /// history-search-backward
39    HistorySearchBackward,
40    /// history-search-forward
41    HistorySearchForward,
42    Insert(RepeatCount, String),
43    Interrupt,
44    /// backward-delete-char, backward-kill-line, backward-kill-word
45    /// delete-char, kill-line, kill-word, unix-line-discard, unix-word-rubout,
46    /// vi-delete, vi-delete-to, vi-rubout
47    Kill(Movement),
48    /// backward-char, backward-word, beginning-of-line, end-of-line,
49    /// forward-char, forward-word, vi-char-search, vi-end-word, vi-next-word,
50    /// vi-prev-word
51    Move(Movement),
52    /// next-history
53    NextHistory,
54    Noop,
55    /// vi-replace
56    Overwrite(char),
57    /// previous-history
58    PreviousHistory,
59    /// quoted-insert
60    QuotedInsert,
61    /// vi-change-char
62    ReplaceChar(RepeatCount, char),
63    /// vi-change-to, vi-substitute
64    Replace(Movement, Option<String>),
65    /// reverse-search-history
66    ReverseSearchHistory,
67    /// self-insert
68    SelfInsert(RepeatCount, char),
69    Suspend,
70    /// transpose-chars
71    TransposeChars,
72    /// transpose-words
73    TransposeWords(RepeatCount),
74    /// undo
75    Undo(RepeatCount),
76    Unknown,
77    /// upcase-word
78    UpcaseWord,
79    /// vi-yank-to
80    ViYankTo(Movement),
81    /// yank, vi-put
82    Yank(RepeatCount, Anchor),
83    /// yank-pop
84    YankPop,
85}
86
87impl Cmd {
88    pub fn should_reset_kill_ring(&self) -> bool {
89        match *self {
90            Cmd::Kill(Movement::BackwardChar(_)) | Cmd::Kill(Movement::ForwardChar(_)) => true,
91            Cmd::ClearScreen
92            | Cmd::Kill(_)
93            | Cmd::Replace(_, _)
94            | Cmd::Noop
95            | Cmd::Suspend
96            | Cmd::Yank(_, _)
97            | Cmd::YankPop => false,
98            _ => true,
99        }
100    }
101
102    fn is_repeatable_change(&self) -> bool {
103        match *self {
104            Cmd::Insert(_, _)
105            | Cmd::Kill(_)
106            | Cmd::ReplaceChar(_, _)
107            | Cmd::Replace(_, _)
108            | Cmd::SelfInsert(_, _)
109            | Cmd::ViYankTo(_)
110            | Cmd::Yank(_, _) => true,
111            Cmd::TransposeChars => false, // TODO Validate
112            _ => false,
113        }
114    }
115
116    fn is_repeatable(&self) -> bool {
117        match *self {
118            Cmd::Move(_) => true,
119            _ => self.is_repeatable_change(),
120        }
121    }
122
123    // Replay this command with a possible different `RepeatCount`.
124    fn redo(&self, new: Option<RepeatCount>, wrt: &dyn Refresher) -> Cmd {
125        match *self {
126            Cmd::Insert(previous, ref text) => {
127                Cmd::Insert(repeat_count(previous, new), text.clone())
128            }
129            Cmd::Kill(ref mvt) => Cmd::Kill(mvt.redo(new)),
130            Cmd::Move(ref mvt) => Cmd::Move(mvt.redo(new)),
131            Cmd::ReplaceChar(previous, c) => Cmd::ReplaceChar(repeat_count(previous, new), c),
132            Cmd::Replace(ref mvt, ref text) => {
133                if text.is_none() {
134                    let last_insert = wrt.last_insert();
135                    if let Movement::ForwardChar(0) = mvt {
136                        Cmd::Replace(
137                            Movement::ForwardChar(
138                                last_insert.as_ref().map_or(0, |text| text.len()),
139                            ),
140                            last_insert,
141                        )
142                    } else {
143                        Cmd::Replace(mvt.redo(new), last_insert)
144                    }
145                } else {
146                    Cmd::Replace(mvt.redo(new), text.clone())
147                }
148            }
149            Cmd::SelfInsert(previous, c) => {
150                // consecutive char inserts are repeatable not only the last one...
151                if let Some(text) = wrt.last_insert() {
152                    Cmd::Insert(repeat_count(previous, new), text)
153                } else {
154                    Cmd::SelfInsert(repeat_count(previous, new), c)
155                }
156            }
157            // Cmd::TransposeChars => Cmd::TransposeChars,
158            Cmd::ViYankTo(ref mvt) => Cmd::ViYankTo(mvt.redo(new)),
159            Cmd::Yank(previous, anchor) => Cmd::Yank(repeat_count(previous, new), anchor),
160            _ => unreachable!(),
161        }
162    }
163}
164
165fn repeat_count(previous: RepeatCount, new: Option<RepeatCount>) -> RepeatCount {
166    match new {
167        Some(n) => n,
168        None => previous,
169    }
170}
171
172/// Different word definitions
173#[derive(Debug, Clone, PartialEq, Copy)]
174pub enum Word {
175    /// non-blanks characters
176    Big,
177    /// alphanumeric characters
178    Emacs,
179    /// alphanumeric (and '_') characters
180    Vi,
181}
182
183/// Where to move with respect to word boundary
184#[derive(Debug, Clone, PartialEq, Copy)]
185pub enum At {
186    Start,
187    BeforeEnd,
188    AfterEnd,
189}
190
191/// Where to paste (relative to cursor position)
192#[derive(Debug, Clone, PartialEq, Copy)]
193pub enum Anchor {
194    After,
195    Before,
196}
197
198/// Vi character search
199#[derive(Debug, Clone, PartialEq, Copy)]
200pub enum CharSearch {
201    Forward(char),
202    // until
203    ForwardBefore(char),
204    Backward(char),
205    // until
206    BackwardAfter(char),
207}
208
209impl CharSearch {
210    fn opposite(self) -> CharSearch {
211        match self {
212            CharSearch::Forward(c) => CharSearch::Backward(c),
213            CharSearch::ForwardBefore(c) => CharSearch::BackwardAfter(c),
214            CharSearch::Backward(c) => CharSearch::Forward(c),
215            CharSearch::BackwardAfter(c) => CharSearch::ForwardBefore(c),
216        }
217    }
218}
219
220/// Where to move
221#[derive(Debug, Clone, PartialEq)]
222pub enum Movement {
223    WholeLine, // not really a movement
224    /// beginning-of-line
225    BeginningOfLine,
226    /// end-of-line
227    EndOfLine,
228    /// backward-word, vi-prev-word
229    BackwardWord(RepeatCount, Word), // Backward until start of word
230    /// forward-word, vi-end-word, vi-next-word
231    ForwardWord(RepeatCount, At, Word), // Forward until start/end of word
232    /// vi-char-search
233    ViCharSearch(RepeatCount, CharSearch),
234    /// vi-first-print
235    ViFirstPrint,
236    /// backward-char
237    BackwardChar(RepeatCount),
238    /// forward-char
239    ForwardChar(RepeatCount),
240}
241
242impl Movement {
243    // Replay this movement with a possible different `RepeatCount`.
244    fn redo(&self, new: Option<RepeatCount>) -> Movement {
245        match *self {
246            Movement::WholeLine => Movement::WholeLine,
247            Movement::BeginningOfLine => Movement::BeginningOfLine,
248            Movement::ViFirstPrint => Movement::ViFirstPrint,
249            Movement::EndOfLine => Movement::EndOfLine,
250            Movement::BackwardWord(previous, word) => {
251                Movement::BackwardWord(repeat_count(previous, new), word)
252            }
253            Movement::ForwardWord(previous, at, word) => {
254                Movement::ForwardWord(repeat_count(previous, new), at, word)
255            }
256            Movement::ViCharSearch(previous, char_search) => {
257                Movement::ViCharSearch(repeat_count(previous, new), char_search)
258            }
259            Movement::BackwardChar(previous) => Movement::BackwardChar(repeat_count(previous, new)),
260            Movement::ForwardChar(previous) => Movement::ForwardChar(repeat_count(previous, new)),
261        }
262    }
263}
264
265#[derive(PartialEq)]
266enum InputMode {
267    /// Vi Command/Alternate
268    Command,
269    /// Insert/Input mode
270    Insert,
271    /// Overwrite mode
272    Replace,
273}
274
275/// Tranform key(s) to commands based on current input mode
276pub struct InputState {
277    mode: EditMode,
278    custom_bindings: Arc<RwLock<HashMap<KeyPress, Cmd>>>,
279    input_mode: InputMode, // vi only ?
280    // numeric arguments: http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC7
281    num_args: i16,
282    last_cmd: Cmd,                        // vi only
283    last_char_search: Option<CharSearch>, // vi only
284}
285
286pub trait Refresher {
287    /// Rewrite the currently edited line accordingly to the buffer content,
288    /// cursor position, and number of columns of the terminal.
289    fn refresh_line(&mut self) -> Result<()>;
290    /// Same as `refresh_line` but with a dynamic prompt.
291    fn refresh_prompt_and_line(&mut self, prompt: &str) -> Result<()>;
292    /// Vi only, switch to insert mode.
293    fn doing_insert(&mut self);
294    /// Vi only, switch to command mode.
295    fn done_inserting(&mut self);
296    /// Vi only, last text inserted.
297    fn last_insert(&self) -> Option<String>;
298}
299
300impl InputState {
301    pub fn new(
302        config: &Config,
303        custom_bindings: Arc<RwLock<HashMap<KeyPress, Cmd>>>,
304    ) -> InputState {
305        InputState {
306            mode: config.edit_mode(),
307            custom_bindings,
308            input_mode: InputMode::Insert,
309            num_args: 0,
310            last_cmd: Cmd::Noop,
311            last_char_search: None,
312        }
313    }
314
315    pub fn is_emacs_mode(&self) -> bool {
316        self.mode == EditMode::Emacs
317    }
318
319    /// Parse user input into one command
320    /// `single_esc_abort` is used in emacs mode on unix platform when a single
321    /// esc key is expected to abort current action.
322    pub fn next_cmd<R: RawReader>(
323        &mut self,
324        rdr: &mut R,
325        wrt: &mut dyn Refresher,
326        single_esc_abort: bool,
327    ) -> Result<Cmd> {
328        match self.mode {
329            EditMode::Emacs => self.emacs(rdr, wrt, single_esc_abort),
330            EditMode::Vi if self.input_mode != InputMode::Command => self.vi_insert(rdr, wrt),
331            EditMode::Vi => self.vi_command(rdr, wrt),
332        }
333    }
334
335    // TODO dynamic prompt (arg: ?)
336    fn emacs_digit_argument<R: RawReader>(
337        &mut self,
338        rdr: &mut R,
339        wrt: &mut dyn Refresher,
340        digit: char,
341    ) -> Result<KeyPress> {
342        match digit {
343            '0'..='9' => {
344                self.num_args = digit.to_digit(10).unwrap() as i16;
345            }
346            '-' => {
347                self.num_args = -1;
348            }
349            _ => unreachable!(),
350        }
351        loop {
352            try!(wrt.refresh_prompt_and_line(&format!("(arg: {}) ", self.num_args)));
353            let key = try!(rdr.next_key(true));
354            match key {
355                KeyPress::Char(digit @ '0'..='9') | KeyPress::Meta(digit @ '0'..='9') => {
356                    if self.num_args == -1 {
357                        self.num_args *= digit.to_digit(10).unwrap() as i16;
358                    } else if self.num_args.abs() < 1000 {
359                        // shouldn't ever need more than 4 digits
360                        self.num_args = self
361                            .num_args
362                            .saturating_mul(10)
363                            .saturating_add(digit.to_digit(10).unwrap() as i16);
364                    }
365                }
366                KeyPress::Char('-') | KeyPress::Meta('-') => {}
367                _ => {
368                    try!(wrt.refresh_line());
369                    return Ok(key);
370                }
371            };
372        }
373    }
374
375    fn emacs<R: RawReader>(
376        &mut self,
377        rdr: &mut R,
378        wrt: &mut dyn Refresher,
379        single_esc_abort: bool,
380    ) -> Result<Cmd> {
381        let mut key = try!(rdr.next_key(single_esc_abort));
382        if let KeyPress::Meta(digit @ '-') = key {
383            key = try!(self.emacs_digit_argument(rdr, wrt, digit));
384        } else if let KeyPress::Meta(digit @ '0'..='9') = key {
385            key = try!(self.emacs_digit_argument(rdr, wrt, digit));
386        }
387        let (n, positive) = self.emacs_num_args(); // consume them in all cases
388        {
389            let bindings = self.custom_bindings.read().unwrap();
390            if let Some(cmd) = bindings.get(&key) {
391                debug!(target: "rustyline", "Custom command: {:?}", cmd);
392                return Ok(if cmd.is_repeatable() {
393                    cmd.redo(Some(n), wrt)
394                } else {
395                    cmd.clone()
396                });
397            }
398        }
399        let cmd = match key {
400            KeyPress::Char(c) => {
401                if positive {
402                    Cmd::SelfInsert(n, c)
403                } else {
404                    Cmd::Unknown
405                }
406            }
407            KeyPress::Ctrl('A') => Cmd::Move(Movement::BeginningOfLine),
408            KeyPress::Ctrl('B') => {
409                if positive {
410                    Cmd::Move(Movement::BackwardChar(n))
411                } else {
412                    Cmd::Move(Movement::ForwardChar(n))
413                }
414            }
415            KeyPress::Ctrl('E') => Cmd::Move(Movement::EndOfLine),
416            KeyPress::Ctrl('F') => {
417                if positive {
418                    Cmd::Move(Movement::ForwardChar(n))
419                } else {
420                    Cmd::Move(Movement::BackwardChar(n))
421                }
422            }
423            KeyPress::Ctrl('G') | KeyPress::Esc | KeyPress::Meta('\x07') => Cmd::Abort,
424            KeyPress::Ctrl('H') | KeyPress::Backspace => {
425                if positive {
426                    Cmd::Kill(Movement::BackwardChar(n))
427                } else {
428                    Cmd::Kill(Movement::ForwardChar(n))
429                }
430            }
431            KeyPress::Tab => Cmd::Complete,
432            KeyPress::Ctrl('K') => {
433                if positive {
434                    Cmd::Kill(Movement::EndOfLine)
435                } else {
436                    Cmd::Kill(Movement::BeginningOfLine)
437                }
438            }
439            KeyPress::Ctrl('L') => Cmd::ClearScreen,
440            KeyPress::Ctrl('N') => Cmd::NextHistory,
441            KeyPress::Ctrl('P') => Cmd::PreviousHistory,
442            KeyPress::Ctrl('X') => {
443                let snd_key = try!(rdr.next_key(true));
444                match snd_key {
445                    KeyPress::Ctrl('G') | KeyPress::Esc => Cmd::Abort,
446                    KeyPress::Ctrl('U') => Cmd::Undo(n),
447                    _ => Cmd::Unknown,
448                }
449            }
450            KeyPress::Meta('\x08') | KeyPress::Meta('\x7f') => {
451                if positive {
452                    Cmd::Kill(Movement::BackwardWord(n, Word::Emacs))
453                } else {
454                    Cmd::Kill(Movement::ForwardWord(n, At::AfterEnd, Word::Emacs))
455                }
456            }
457            KeyPress::Meta('<') => Cmd::BeginningOfHistory,
458            KeyPress::Meta('>') => Cmd::EndOfHistory,
459            KeyPress::Meta('B') | KeyPress::Meta('b') => {
460                if positive {
461                    Cmd::Move(Movement::BackwardWord(n, Word::Emacs))
462                } else {
463                    Cmd::Move(Movement::ForwardWord(n, At::AfterEnd, Word::Emacs))
464                }
465            }
466            KeyPress::Meta('C') | KeyPress::Meta('c') => Cmd::CapitalizeWord,
467            KeyPress::Meta('D') | KeyPress::Meta('d') => {
468                if positive {
469                    Cmd::Kill(Movement::ForwardWord(n, At::AfterEnd, Word::Emacs))
470                } else {
471                    Cmd::Kill(Movement::BackwardWord(n, Word::Emacs))
472                }
473            }
474            KeyPress::Meta('F') | KeyPress::Meta('f') => {
475                if positive {
476                    Cmd::Move(Movement::ForwardWord(n, At::AfterEnd, Word::Emacs))
477                } else {
478                    Cmd::Move(Movement::BackwardWord(n, Word::Emacs))
479                }
480            }
481            KeyPress::Meta('L') | KeyPress::Meta('l') => Cmd::DowncaseWord,
482            KeyPress::Meta('T') | KeyPress::Meta('t') => Cmd::TransposeWords(n),
483            KeyPress::Meta('U') | KeyPress::Meta('u') => Cmd::UpcaseWord,
484            KeyPress::Meta('Y') | KeyPress::Meta('y') => Cmd::YankPop,
485            _ => self.common(key, n, positive),
486        };
487        debug!(target: "rustyline", "Emacs command: {:?}", cmd);
488        Ok(cmd)
489    }
490
491    fn vi_arg_digit<R: RawReader>(
492        &mut self,
493        rdr: &mut R,
494        wrt: &mut dyn Refresher,
495        digit: char,
496    ) -> Result<KeyPress> {
497        self.num_args = digit.to_digit(10).unwrap() as i16;
498        loop {
499            try!(wrt.refresh_prompt_and_line(&format!("(arg: {}) ", self.num_args)));
500            let key = try!(rdr.next_key(false));
501            if let KeyPress::Char(digit @ '0'..='9') = key {
502                if self.num_args.abs() < 1000 {
503                    // shouldn't ever need more than 4 digits
504                    self.num_args = self
505                        .num_args
506                        .saturating_mul(10)
507                        .saturating_add(digit.to_digit(10).unwrap() as i16);
508                }
509            } else {
510                try!(wrt.refresh_line());
511                return Ok(key);
512            };
513        }
514    }
515
516    fn vi_command<R: RawReader>(&mut self, rdr: &mut R, wrt: &mut dyn Refresher) -> Result<Cmd> {
517        let mut key = try!(rdr.next_key(false));
518        if let KeyPress::Char(digit @ '1'..='9') = key {
519            key = try!(self.vi_arg_digit(rdr, wrt, digit));
520        }
521        let no_num_args = self.num_args == 0;
522        let n = self.vi_num_args(); // consume them in all cases
523        {
524            let bindings = self.custom_bindings.read().unwrap();
525            if let Some(cmd) = bindings.get(&key) {
526                debug!(target: "rustyline", "Custom command: {:?}", cmd);
527                return Ok(if cmd.is_repeatable() {
528                    if no_num_args {
529                        cmd.redo(None, wrt)
530                    } else {
531                        cmd.redo(Some(n), wrt)
532                    }
533                } else {
534                    cmd.clone()
535                });
536            }
537        }
538        let cmd = match key {
539            KeyPress::Char('$') |
540            KeyPress::End => Cmd::Move(Movement::EndOfLine),
541            KeyPress::Char('.') => { // vi-redo (repeat last command)
542                if no_num_args {
543                    self.last_cmd.redo(None, wrt)
544                } else {
545                    self.last_cmd.redo(Some(n), wrt)
546                }
547            },
548            // TODO KeyPress::Char('%') => Cmd::???, Move to the corresponding opening/closing bracket
549            KeyPress::Char('0') => Cmd::Move(Movement::BeginningOfLine),
550            KeyPress::Char('^') => Cmd::Move(Movement::ViFirstPrint),
551            KeyPress::Char('a') => {
552                // vi-append-mode
553                self.input_mode = InputMode::Insert;
554                wrt.doing_insert();
555                Cmd::Move(Movement::ForwardChar(n))
556            }
557            KeyPress::Char('A') => {
558                // vi-append-eol
559                self.input_mode = InputMode::Insert;
560                wrt.doing_insert();
561                Cmd::Move(Movement::EndOfLine)
562            }
563            KeyPress::Char('b') => Cmd::Move(Movement::BackwardWord(n, Word::Vi)), // vi-prev-word
564            KeyPress::Char('B') => Cmd::Move(Movement::BackwardWord(n, Word::Big)),
565            KeyPress::Char('c') => {
566                self.input_mode = InputMode::Insert;
567                match try!(self.vi_cmd_motion(rdr, wrt, key, n)) {
568                    Some(mvt) => Cmd::Replace(mvt, None),
569                    None => Cmd::Unknown,
570                }
571            }
572            KeyPress::Char('C') => {
573                self.input_mode = InputMode::Insert;
574                Cmd::Replace(Movement::EndOfLine, None)
575            }
576            KeyPress::Char('d') => {
577                match try!(self.vi_cmd_motion(rdr, wrt, key, n)) {
578                    Some(mvt) => Cmd::Kill(mvt),
579                    None => Cmd::Unknown,
580                }
581            }
582            KeyPress::Char('D') |
583            KeyPress::Ctrl('K') => Cmd::Kill(Movement::EndOfLine),
584            KeyPress::Char('e') => Cmd::Move(Movement::ForwardWord(n, At::BeforeEnd, Word::Vi)),
585            KeyPress::Char('E') => Cmd::Move(Movement::ForwardWord(n, At::BeforeEnd, Word::Big)),
586            KeyPress::Char('i') => {
587                // vi-insertion-mode
588                self.input_mode = InputMode::Insert;
589                wrt.doing_insert();
590                Cmd::Noop
591            }
592            KeyPress::Char('I') => {
593                // vi-insert-beg
594                self.input_mode = InputMode::Insert;
595                wrt.doing_insert();
596                Cmd::Move(Movement::BeginningOfLine)
597            }
598            KeyPress::Char(c) if c == 'f' || c == 'F' || c == 't' || c == 'T' => {
599                // vi-char-search
600                let cs = try!(self.vi_char_search(rdr, c));
601                match cs {
602                    Some(cs) => Cmd::Move(Movement::ViCharSearch(n, cs)),
603                    None => Cmd::Unknown,
604                }
605            }
606            KeyPress::Char(';') => {
607                match self.last_char_search {
608                    Some(cs) => Cmd::Move(Movement::ViCharSearch(n, cs)),
609                    None => Cmd::Noop,
610                }
611            }
612            KeyPress::Char(',') => {
613                match self.last_char_search {
614                    Some(ref cs) => Cmd::Move(Movement::ViCharSearch(n, cs.opposite())),
615                    None => Cmd::Noop,
616                }
617            }
618            // TODO KeyPress::Char('G') => Cmd::???, Move to the history line n
619            KeyPress::Char('p') => Cmd::Yank(n, Anchor::After), // vi-put
620            KeyPress::Char('P') => Cmd::Yank(n, Anchor::Before), // vi-put
621            KeyPress::Char('r') => {
622                // vi-replace-char:
623                let ch = try!(rdr.next_key(false));
624                match ch {
625                    KeyPress::Char(c) => Cmd::ReplaceChar(n, c),
626                    KeyPress::Esc => Cmd::Noop,
627                    _ => Cmd::Unknown,
628                }
629            }
630            KeyPress::Char('R') => {
631                //  vi-replace-mode (overwrite-mode)
632                self.input_mode = InputMode::Replace;
633                Cmd::Replace(Movement::ForwardChar(0), None)
634            }
635            KeyPress::Char('s') => {
636                // vi-substitute-char:
637                self.input_mode = InputMode::Insert;
638                Cmd::Replace(Movement::ForwardChar(n), None)
639            }
640            KeyPress::Char('S') => {
641                // vi-substitute-line:
642                self.input_mode = InputMode::Insert;
643                Cmd::Replace(Movement::WholeLine, None)
644            }
645            KeyPress::Char('u') => Cmd::Undo(n),
646            // KeyPress::Char('U') => Cmd::???, // revert-line
647            KeyPress::Char('w') => Cmd::Move(Movement::ForwardWord(n, At::Start, Word::Vi)), // vi-next-word
648            KeyPress::Char('W') => Cmd::Move(Movement::ForwardWord(n, At::Start, Word::Big)), // vi-next-word
649            KeyPress::Char('x') => Cmd::Kill(Movement::ForwardChar(n)), // vi-delete: TODO move backward if eol
650            KeyPress::Char('X') => Cmd::Kill(Movement::BackwardChar(n)), // vi-rubout
651            KeyPress::Char('y') => {
652                match try!(self.vi_cmd_motion(rdr, wrt, key, n)) {
653                    Some(mvt) => Cmd::ViYankTo(mvt),
654                    None => Cmd::Unknown,
655                }
656            }
657            // KeyPress::Char('Y') => Cmd::???, // vi-yank-to
658            KeyPress::Char('h') |
659            KeyPress::Ctrl('H') |
660            KeyPress::Backspace => Cmd::Move(Movement::BackwardChar(n)),
661            KeyPress::Ctrl('G') => Cmd::Abort,
662            KeyPress::Char('l') |
663            KeyPress::Char(' ') => Cmd::Move(Movement::ForwardChar(n)),
664            KeyPress::Ctrl('L') => Cmd::ClearScreen,
665            KeyPress::Char('+') |
666            KeyPress::Char('j') | // TODO: move to the start of the line.
667            KeyPress::Ctrl('N') => Cmd::NextHistory,
668            KeyPress::Char('-') |
669            KeyPress::Char('k') | // TODO: move to the start of the line.
670            KeyPress::Ctrl('P') => Cmd::PreviousHistory,
671            KeyPress::Ctrl('R') => {
672                self.input_mode = InputMode::Insert; // TODO Validate
673                Cmd::ReverseSearchHistory
674            }
675            KeyPress::Ctrl('S') => {
676                self.input_mode = InputMode::Insert; // TODO Validate
677                Cmd::ForwardSearchHistory
678            }
679            KeyPress::Esc => Cmd::Noop,
680            _ => self.common(key, n, true),
681        };
682        debug!(target: "rustyline", "Vi command: {:?}", cmd);
683        if cmd.is_repeatable_change() {
684            self.last_cmd = cmd.clone();
685        }
686        Ok(cmd)
687    }
688
689    fn vi_insert<R: RawReader>(&mut self, rdr: &mut R, wrt: &mut dyn Refresher) -> Result<Cmd> {
690        let key = try!(rdr.next_key(false));
691        {
692            let bindings = self.custom_bindings.read().unwrap();
693            if let Some(cmd) = bindings.get(&key) {
694                debug!(target: "rustyline", "Custom command: {:?}", cmd);
695                return Ok(if cmd.is_repeatable() {
696                    cmd.redo(None, wrt)
697                } else {
698                    cmd.clone()
699                });
700            }
701        }
702        let cmd = match key {
703            KeyPress::Char(c) => {
704                if self.input_mode == InputMode::Replace {
705                    Cmd::Overwrite(c)
706                } else {
707                    Cmd::SelfInsert(1, c)
708                }
709            }
710            KeyPress::Ctrl('H') | KeyPress::Backspace => Cmd::Kill(Movement::BackwardChar(1)),
711            KeyPress::Tab => Cmd::Complete,
712            KeyPress::Esc => {
713                // vi-movement-mode/vi-command-mode
714                self.input_mode = InputMode::Command;
715                wrt.done_inserting();
716                Cmd::Move(Movement::BackwardChar(1))
717            }
718            _ => self.common(key, 1, true),
719        };
720        debug!(target: "rustyline", "Vi insert: {:?}", cmd);
721        if cmd.is_repeatable_change() {
722            if let (Cmd::Replace(_, _), Cmd::SelfInsert(_, _)) = (&self.last_cmd, &cmd) {
723                // replacing...
724            } else if let (Cmd::SelfInsert(_, _), Cmd::SelfInsert(_, _)) = (&self.last_cmd, &cmd) {
725                // inserting...
726            } else {
727                self.last_cmd = cmd.clone();
728            }
729        }
730        Ok(cmd)
731    }
732
733    fn vi_cmd_motion<R: RawReader>(
734        &mut self,
735        rdr: &mut R,
736        wrt: &mut dyn Refresher,
737        key: KeyPress,
738        n: RepeatCount,
739    ) -> Result<Option<Movement>> {
740        let mut mvt = try!(rdr.next_key(false));
741        if mvt == key {
742            return Ok(Some(Movement::WholeLine));
743        }
744        let mut n = n;
745        if let KeyPress::Char(digit @ '1'..='9') = mvt {
746            // vi-arg-digit
747            mvt = try!(self.vi_arg_digit(rdr, wrt, digit));
748            n = self.vi_num_args().saturating_mul(n);
749        }
750        Ok(match mvt {
751            KeyPress::Char('$') => Some(Movement::EndOfLine),
752            KeyPress::Char('0') => Some(Movement::BeginningOfLine),
753            KeyPress::Char('^') => Some(Movement::ViFirstPrint),
754            KeyPress::Char('b') => Some(Movement::BackwardWord(n, Word::Vi)),
755            KeyPress::Char('B') => Some(Movement::BackwardWord(n, Word::Big)),
756            KeyPress::Char('e') => Some(Movement::ForwardWord(n, At::AfterEnd, Word::Vi)),
757            KeyPress::Char('E') => Some(Movement::ForwardWord(n, At::AfterEnd, Word::Big)),
758            KeyPress::Char(c) if c == 'f' || c == 'F' || c == 't' || c == 'T' => {
759                let cs = try!(self.vi_char_search(rdr, c));
760                match cs {
761                    Some(cs) => Some(Movement::ViCharSearch(n, cs)),
762                    None => None,
763                }
764            }
765            KeyPress::Char(';') => match self.last_char_search {
766                Some(cs) => Some(Movement::ViCharSearch(n, cs)),
767                None => None,
768            },
769            KeyPress::Char(',') => match self.last_char_search {
770                Some(ref cs) => Some(Movement::ViCharSearch(n, cs.opposite())),
771                None => None,
772            },
773            KeyPress::Char('h') | KeyPress::Ctrl('H') | KeyPress::Backspace => {
774                Some(Movement::BackwardChar(n))
775            }
776            KeyPress::Char('l') | KeyPress::Char(' ') => Some(Movement::ForwardChar(n)),
777            KeyPress::Char('w') => {
778                // 'cw' is 'ce'
779                if key == KeyPress::Char('c') {
780                    Some(Movement::ForwardWord(n, At::AfterEnd, Word::Vi))
781                } else {
782                    Some(Movement::ForwardWord(n, At::Start, Word::Vi))
783                }
784            }
785            KeyPress::Char('W') => {
786                // 'cW' is 'cE'
787                if key == KeyPress::Char('c') {
788                    Some(Movement::ForwardWord(n, At::AfterEnd, Word::Big))
789                } else {
790                    Some(Movement::ForwardWord(n, At::Start, Word::Big))
791                }
792            }
793            _ => None,
794        })
795    }
796
797    fn vi_char_search<R: RawReader>(
798        &mut self,
799        rdr: &mut R,
800        cmd: char,
801    ) -> Result<Option<CharSearch>> {
802        let ch = try!(rdr.next_key(false));
803        Ok(match ch {
804            KeyPress::Char(ch) => {
805                let cs = match cmd {
806                    'f' => CharSearch::Forward(ch),
807                    't' => CharSearch::ForwardBefore(ch),
808                    'F' => CharSearch::Backward(ch),
809                    'T' => CharSearch::BackwardAfter(ch),
810                    _ => unreachable!(),
811                };
812                self.last_char_search = Some(cs);
813                Some(cs)
814            }
815            _ => None,
816        })
817    }
818
819    fn common(&mut self, key: KeyPress, n: RepeatCount, positive: bool) -> Cmd {
820        match key {
821            KeyPress::Home => Cmd::Move(Movement::BeginningOfLine),
822            KeyPress::Left => {
823                if positive {
824                    Cmd::Move(Movement::BackwardChar(n))
825                } else {
826                    Cmd::Move(Movement::ForwardChar(n))
827                }
828            }
829            KeyPress::Ctrl('C') => Cmd::Interrupt,
830            KeyPress::Ctrl('D') => Cmd::EndOfFile,
831            KeyPress::Delete => {
832                if positive {
833                    Cmd::Kill(Movement::ForwardChar(n))
834                } else {
835                    Cmd::Kill(Movement::BackwardChar(n))
836                }
837            }
838            KeyPress::End => Cmd::Move(Movement::EndOfLine),
839            KeyPress::Right => {
840                if positive {
841                    Cmd::Move(Movement::ForwardChar(n))
842                } else {
843                    Cmd::Move(Movement::BackwardChar(n))
844                }
845            }
846            KeyPress::Ctrl('J') |
847            KeyPress::Enter => Cmd::AcceptLine,
848            KeyPress::Down => Cmd::NextHistory,
849            KeyPress::Up => Cmd::PreviousHistory,
850            KeyPress::Ctrl('R') => Cmd::ReverseSearchHistory,
851            KeyPress::Ctrl('S') => Cmd::ForwardSearchHistory, // most terminals override Ctrl+S to suspend execution
852            KeyPress::Ctrl('T') => Cmd::TransposeChars,
853            KeyPress::Ctrl('U') => {
854                if positive {
855                    Cmd::Kill(Movement::BeginningOfLine)
856                } else {
857                    Cmd::Kill(Movement::EndOfLine)
858                }
859            },
860            KeyPress::Ctrl('Q') | // most terminals override Ctrl+Q to resume execution
861            KeyPress::Ctrl('V') => Cmd::QuotedInsert,
862            KeyPress::Ctrl('W') => {
863                if positive {
864                    Cmd::Kill(Movement::BackwardWord(n, Word::Big))
865                } else {
866                    Cmd::Kill(Movement::ForwardWord(n, At::AfterEnd, Word::Big))
867                }
868            }
869            KeyPress::Ctrl('Y') => {
870                if positive {
871                    Cmd::Yank(n, Anchor::Before)
872                } else {
873                    Cmd::Unknown // TODO Validate
874                }
875            }
876            KeyPress::Ctrl('Z') => Cmd::Suspend,
877            KeyPress::Ctrl('_') => Cmd::Undo(n),
878            KeyPress::UnknownEscSeq => Cmd::Noop,
879            _ => Cmd::Unknown,
880        }
881    }
882
883    fn num_args(&mut self) -> i16 {
884        let num_args = match self.num_args {
885            0 => 1,
886            _ => self.num_args,
887        };
888        self.num_args = 0;
889        num_args
890    }
891
892    fn emacs_num_args(&mut self) -> (RepeatCount, bool) {
893        let num_args = self.num_args();
894        if num_args < 0 {
895            if let (n, false) = num_args.overflowing_abs() {
896                (n as RepeatCount, false)
897            } else {
898                (RepeatCount::max_value(), false)
899            }
900        } else {
901            (num_args as RepeatCount, true)
902        }
903    }
904
905    fn vi_num_args(&mut self) -> RepeatCount {
906        let num_args = self.num_args();
907        if num_args < 0 {
908            unreachable!()
909        } else {
910            num_args.abs() as RepeatCount
911        }
912    }
913}