brush_rustyline_fork/
keymap.rs

1//! Bindings from keys to command for Emacs and Vi modes
2use log::debug;
3
4use super::Result;
5use crate::keys::{KeyCode as K, KeyEvent, KeyEvent as E, Modifiers as M};
6use crate::tty::{self, RawReader, Term, Terminal};
7use crate::{Config, EditMode};
8#[cfg(feature = "custom-bindings")]
9use crate::{Event, EventContext, EventHandler};
10
11/// The number of times one command should be repeated.
12pub type RepeatCount = usize;
13
14/// Commands
15#[derive(Debug, Clone, Eq, PartialEq)]
16#[non_exhaustive]
17pub enum Cmd {
18    /// abort
19    Abort, // Miscellaneous Command
20    /// accept-line
21    ///
22    /// See also AcceptOrInsertLine
23    AcceptLine,
24    /// beginning-of-history
25    BeginningOfHistory,
26    /// capitalize-word
27    CapitalizeWord,
28    /// clear-screen
29    ClearScreen,
30    /// Paste from the clipboard
31    #[cfg(windows)]
32    PasteFromClipboard,
33    /// complete
34    Complete,
35    /// complete-backward
36    CompleteBackward,
37    /// complete-hint
38    CompleteHint,
39    /// Dedent current line
40    Dedent(Movement),
41    /// downcase-word
42    DowncaseWord,
43    /// vi-eof-maybe
44    EndOfFile,
45    /// end-of-history
46    EndOfHistory,
47    /// forward-search-history (incremental search)
48    ForwardSearchHistory,
49    /// history-search-backward (common prefix search)
50    HistorySearchBackward,
51    /// history-search-forward (common prefix search)
52    HistorySearchForward,
53    /// Indent current line
54    Indent(Movement),
55    /// Insert text
56    Insert(RepeatCount, String),
57    /// Interrupt signal (Ctrl-C)
58    Interrupt,
59    /// backward-delete-char, backward-kill-line, backward-kill-word
60    /// delete-char, kill-line, kill-word, unix-line-discard, unix-word-rubout,
61    /// vi-delete, vi-delete-to, vi-rubout
62    Kill(Movement),
63    /// backward-char, backward-word, beginning-of-line, end-of-line,
64    /// forward-char, forward-word, vi-char-search, vi-end-word, vi-next-word,
65    /// vi-prev-word
66    Move(Movement),
67    /// next-history
68    NextHistory,
69    /// No action
70    Noop,
71    /// repaint
72    Repaint,
73    /// vi-replace
74    Overwrite(char),
75    /// previous-history
76    PreviousHistory,
77    /// quoted-insert
78    QuotedInsert,
79    /// vi-change-char
80    ReplaceChar(RepeatCount, char),
81    /// vi-change-to, vi-substitute
82    Replace(Movement, Option<String>),
83    /// reverse-search-history (incremental search)
84    ReverseSearchHistory,
85    /// self-insert
86    SelfInsert(RepeatCount, char),
87    /// Suspend signal (Ctrl-Z on unix platform)
88    Suspend,
89    /// transpose-chars
90    TransposeChars,
91    /// transpose-words
92    TransposeWords(RepeatCount),
93    /// undo
94    Undo(RepeatCount),
95    /// Unsupported / unexpected
96    Unknown,
97    /// upcase-word
98    UpcaseWord,
99    /// vi-yank-to
100    ViYankTo(Movement),
101    /// yank, vi-put
102    Yank(RepeatCount, Anchor),
103    /// yank-pop
104    YankPop,
105    /// moves cursor to the line above or switches to prev history entry if
106    /// the cursor is already on the first line
107    LineUpOrPreviousHistory(RepeatCount),
108    /// moves cursor to the line below or switches to next history entry if
109    /// the cursor is already on the last line
110    LineDownOrNextHistory(RepeatCount),
111    /// Inserts a newline
112    Newline,
113    /// Either accepts or inserts a newline
114    ///
115    /// Always inserts newline if input is non-valid. Can also insert newline
116    /// if cursor is in the middle of the text
117    ///
118    /// If you support multi-line input:
119    /// * Use `accept_in_the_middle: true` for mostly single-line cases, for
120    ///   example command-line.
121    /// * Use `accept_in_the_middle: false` for mostly multi-line cases, for
122    ///   example SQL or JSON input.
123    AcceptOrInsertLine {
124        /// Whether this commands accepts input if the cursor not at the end
125        /// of the current input
126        accept_in_the_middle: bool,
127    },
128}
129
130impl Cmd {
131    /// Tells if current command should reset kill ring.
132    #[must_use]
133    pub const fn should_reset_kill_ring(&self) -> bool {
134        #[allow(clippy::match_same_arms)]
135        match *self {
136            Cmd::Kill(Movement::BackwardChar(_) | Movement::ForwardChar(_)) => true,
137            Cmd::ClearScreen
138            | Cmd::Kill(_)
139            | Cmd::Replace(..)
140            | Cmd::Noop
141            | Cmd::Suspend
142            | Cmd::Yank(..)
143            | Cmd::YankPop => false,
144            _ => true,
145        }
146    }
147
148    const fn is_repeatable_change(&self) -> bool {
149        matches!(
150            *self,
151            Cmd::Dedent(..)
152                | Cmd::Indent(..)
153                | Cmd::Insert(..)
154                | Cmd::Kill(_)
155                | Cmd::ReplaceChar(..)
156                | Cmd::Replace(..)
157                | Cmd::SelfInsert(..)
158                | Cmd::ViYankTo(_)
159                | Cmd::Yank(..) // Cmd::TransposeChars | TODO Validate
160        )
161    }
162
163    const fn is_repeatable(&self) -> bool {
164        match *self {
165            Cmd::Move(_) => true,
166            _ => self.is_repeatable_change(),
167        }
168    }
169
170    // Replay this command with a possible different `RepeatCount`.
171    fn redo(&self, new: Option<RepeatCount>, wrt: &dyn Refresher) -> Self {
172        match *self {
173            Cmd::Dedent(ref mvt) => Cmd::Dedent(mvt.redo(new)),
174            Cmd::Indent(ref mvt) => Cmd::Indent(mvt.redo(new)),
175            Cmd::Insert(previous, ref text) => {
176                Cmd::Insert(repeat_count(previous, new), text.clone())
177            }
178            Cmd::Kill(ref mvt) => Cmd::Kill(mvt.redo(new)),
179            Cmd::Move(ref mvt) => Cmd::Move(mvt.redo(new)),
180            Cmd::ReplaceChar(previous, c) => Cmd::ReplaceChar(repeat_count(previous, new), c),
181            Cmd::Replace(ref mvt, ref text) => {
182                if text.is_none() {
183                    let last_insert = wrt.last_insert();
184                    if let Movement::ForwardChar(0) = mvt {
185                        Cmd::Replace(
186                            Movement::ForwardChar(last_insert.as_ref().map_or(0, String::len)),
187                            last_insert,
188                        )
189                    } else {
190                        Cmd::Replace(mvt.redo(new), last_insert)
191                    }
192                } else {
193                    Cmd::Replace(mvt.redo(new), text.clone())
194                }
195            }
196            Cmd::SelfInsert(previous, c) => {
197                // consecutive char inserts are repeatable not only the last one...
198                if let Some(text) = wrt.last_insert() {
199                    Cmd::Insert(repeat_count(previous, new), text)
200                } else {
201                    Cmd::SelfInsert(repeat_count(previous, new), c)
202                }
203            }
204            // Cmd::TransposeChars => Cmd::TransposeChars,
205            Cmd::ViYankTo(ref mvt) => Cmd::ViYankTo(mvt.redo(new)),
206            Cmd::Yank(previous, anchor) => Cmd::Yank(repeat_count(previous, new), anchor),
207            _ => unreachable!(),
208        }
209    }
210}
211
212const fn repeat_count(previous: RepeatCount, new: Option<RepeatCount>) -> RepeatCount {
213    match new {
214        Some(n) => n,
215        None => previous,
216    }
217}
218
219/// Different word definitions
220#[derive(Debug, Clone, Eq, PartialEq, Copy)]
221pub enum Word {
222    /// non-blanks characters
223    Big,
224    /// alphanumeric characters
225    Emacs,
226    /// alphanumeric (and '_') characters
227    Vi,
228}
229
230/// Where to move with respect to word boundary
231#[derive(Debug, Clone, Eq, PartialEq, Copy)]
232pub enum At {
233    /// Start of word.
234    Start,
235    /// Before end of word.
236    BeforeEnd,
237    /// After end of word.
238    AfterEnd,
239}
240
241/// Where to paste (relative to cursor position)
242#[derive(Debug, Clone, Eq, PartialEq, Copy)]
243pub enum Anchor {
244    /// After cursor
245    After,
246    /// Before cursor
247    Before,
248}
249
250/// character search
251#[derive(Debug, Clone, Eq, PartialEq, Copy)]
252pub enum CharSearch {
253    /// Forward search
254    Forward(char),
255    /// Forward search until
256    ForwardBefore(char),
257    /// Backward search
258    Backward(char),
259    /// Backward search until
260    BackwardAfter(char),
261}
262
263impl CharSearch {
264    const fn opposite(self) -> Self {
265        match self {
266            CharSearch::Forward(c) => CharSearch::Backward(c),
267            CharSearch::ForwardBefore(c) => CharSearch::BackwardAfter(c),
268            CharSearch::Backward(c) => CharSearch::Forward(c),
269            CharSearch::BackwardAfter(c) => CharSearch::ForwardBefore(c),
270        }
271    }
272}
273
274/// Where to move
275#[derive(Debug, Clone, Eq, PartialEq)]
276#[non_exhaustive]
277pub enum Movement {
278    /// Whole current line (not really a movement but a range)
279    WholeLine,
280    /// beginning-of-line
281    BeginningOfLine,
282    /// end-of-line
283    EndOfLine,
284    /// backward-word, vi-prev-word
285    BackwardWord(RepeatCount, Word), // Backward until start of word
286    /// forward-word, vi-end-word, vi-next-word
287    ForwardWord(RepeatCount, At, Word), // Forward until start/end of word
288    /// character-search, character-search-backward, vi-char-search
289    ViCharSearch(RepeatCount, CharSearch),
290    /// vi-first-print
291    ViFirstPrint,
292    /// backward-char
293    BackwardChar(RepeatCount),
294    /// forward-char
295    ForwardChar(RepeatCount),
296    /// move to the same column on the previous line
297    LineUp(RepeatCount),
298    /// move to the same column on the next line
299    LineDown(RepeatCount),
300    /// Whole user input (not really a movement but a range)
301    WholeBuffer,
302    /// beginning-of-buffer
303    BeginningOfBuffer,
304    /// end-of-buffer
305    EndOfBuffer,
306}
307
308impl Movement {
309    // Replay this movement with a possible different `RepeatCount`.
310    const fn redo(&self, new: Option<RepeatCount>) -> Self {
311        match *self {
312            Movement::WholeLine => Movement::WholeLine,
313            Movement::BeginningOfLine => Movement::BeginningOfLine,
314            Movement::ViFirstPrint => Movement::ViFirstPrint,
315            Movement::EndOfLine => Movement::EndOfLine,
316            Movement::BackwardWord(previous, word) => {
317                Movement::BackwardWord(repeat_count(previous, new), word)
318            }
319            Movement::ForwardWord(previous, at, word) => {
320                Movement::ForwardWord(repeat_count(previous, new), at, word)
321            }
322            Movement::ViCharSearch(previous, char_search) => {
323                Movement::ViCharSearch(repeat_count(previous, new), char_search)
324            }
325            Movement::BackwardChar(previous) => Movement::BackwardChar(repeat_count(previous, new)),
326            Movement::ForwardChar(previous) => Movement::ForwardChar(repeat_count(previous, new)),
327            Movement::LineUp(previous) => Movement::LineUp(repeat_count(previous, new)),
328            Movement::LineDown(previous) => Movement::LineDown(repeat_count(previous, new)),
329            Movement::WholeBuffer => Movement::WholeBuffer,
330            Movement::BeginningOfBuffer => Movement::BeginningOfBuffer,
331            Movement::EndOfBuffer => Movement::EndOfBuffer,
332        }
333    }
334}
335
336/// Vi input modes
337#[derive(Clone, Copy, Eq, PartialEq)]
338pub enum InputMode {
339    /// Vi Command/Alternate
340    Command,
341    /// Insert/Input mode
342    Insert,
343    /// Overwrite mode
344    Replace,
345}
346
347/// Transform key(s) to commands based on current input mode
348pub struct InputState<'b> {
349    pub(crate) mode: EditMode,
350    #[cfg_attr(not(feature = "custom-bindings"), allow(dead_code))]
351    custom_bindings: &'b Bindings,
352    pub(crate) input_mode: InputMode, // vi only ?
353    // numeric arguments: http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC7
354    num_args: i16,
355    last_cmd: Cmd,                        // vi only
356    last_char_search: Option<CharSearch>, // vi only
357}
358
359/// Provide indirect mutation to user input.
360pub trait Invoke {
361    /// currently edited line
362    fn input(&self) -> &str;
363    // TODO
364    //fn invoke(&mut self, cmd: Cmd) -> Result<?>;
365}
366
367impl Invoke for &str {
368    fn input(&self) -> &str {
369        self
370    }
371}
372
373pub trait Refresher {
374    /// Rewrite the currently edited line accordingly to the buffer content,
375    /// cursor position, and number of columns of the terminal.
376    fn refresh_line(&mut self) -> Result<()>;
377    /// Same as [`refresh_line`] with a specific message instead of hint
378    fn refresh_line_with_msg(&mut self, msg: Option<&str>) -> Result<()>;
379    /// Same as `refresh_line` but with a dynamic prompt.
380    fn refresh_prompt_and_line(&mut self, prompt: &str) -> Result<()>;
381    /// Vi only, switch to insert mode.
382    fn doing_insert(&mut self);
383    /// Vi only, switch to command mode.
384    fn done_inserting(&mut self);
385    /// Vi only, last text inserted.
386    fn last_insert(&self) -> Option<String>;
387    /// Returns `true` if the cursor is currently at the end of the line.
388    fn is_cursor_at_end(&self) -> bool;
389    /// Returns `true` if there is a hint displayed.
390    fn has_hint(&self) -> bool;
391    /// Returns the hint text that is shown after the current cursor position.
392    #[cfg_attr(not(feature = "custom-bindings"), allow(dead_code))]
393    fn hint_text(&self) -> Option<&str>;
394    /// currently edited line
395    fn line(&self) -> &str;
396    /// Current cursor position (byte position)
397    #[cfg_attr(not(feature = "custom-bindings"), allow(dead_code))]
398    fn pos(&self) -> usize;
399    /// Display `msg` above currently edited line.
400    fn external_print(&mut self, msg: String) -> Result<()>;
401}
402
403impl<'b> InputState<'b> {
404    pub fn new(config: &Config, custom_bindings: &'b Bindings) -> Self {
405        Self {
406            mode: config.edit_mode(),
407            custom_bindings,
408            input_mode: InputMode::Insert,
409            num_args: 0,
410            last_cmd: Cmd::Noop,
411            last_char_search: None,
412        }
413    }
414
415    pub fn is_emacs_mode(&self) -> bool {
416        self.mode == EditMode::Emacs
417    }
418
419    /// Parse user input into one command
420    /// `single_esc_abort` is used in emacs mode on unix platform when a single
421    /// esc key is expected to abort current action.
422    pub fn next_cmd(
423        &mut self,
424        rdr: &mut <Terminal as Term>::Reader,
425        wrt: &mut dyn Refresher,
426        single_esc_abort: bool,
427        ignore_external_print: bool,
428    ) -> Result<Cmd> {
429        let single_esc_abort = self.single_esc_abort(single_esc_abort);
430        let key;
431        if ignore_external_print {
432            key = rdr.next_key(single_esc_abort)?;
433        } else {
434            loop {
435                let event = rdr.wait_for_input(single_esc_abort)?;
436                match event {
437                    tty::Event::KeyPress(k) => {
438                        key = k;
439                        break;
440                    }
441                    tty::Event::ExternalPrint(msg) => {
442                        wrt.external_print(msg)?;
443                    }
444                }
445            }
446        }
447        match self.mode {
448            EditMode::Emacs => self.emacs(rdr, wrt, key),
449            EditMode::Vi if self.input_mode != InputMode::Command => self.vi_insert(rdr, wrt, key),
450            EditMode::Vi => self.vi_command(rdr, wrt, key),
451        }
452    }
453
454    fn single_esc_abort(&self, single_esc_abort: bool) -> bool {
455        match self.mode {
456            EditMode::Emacs => single_esc_abort,
457            EditMode::Vi => false,
458        }
459    }
460
461    /// Terminal peculiar binding
462    fn term_binding<R: RawReader>(rdr: &R, wrt: &dyn Refresher, key: &KeyEvent) -> Option<Cmd> {
463        let cmd = rdr.find_binding(key);
464        if cmd == Some(Cmd::EndOfFile) && !wrt.line().is_empty() {
465            None // ReadlineError::Eof only if line is empty
466        } else {
467            cmd
468        }
469    }
470
471    fn emacs_digit_argument<R: RawReader>(
472        &mut self,
473        rdr: &mut R,
474        wrt: &mut dyn Refresher,
475        digit: char,
476    ) -> Result<KeyEvent> {
477        #[allow(clippy::cast_possible_truncation)]
478        match digit {
479            '0'..='9' => {
480                self.num_args = digit.to_digit(10).unwrap() as i16;
481            }
482            '-' => {
483                self.num_args = -1;
484            }
485            _ => unreachable!(),
486        }
487        loop {
488            wrt.refresh_prompt_and_line(&format!("(arg: {}) ", self.num_args))?;
489            let key = rdr.next_key(true)?;
490            #[allow(clippy::cast_possible_truncation)]
491            match key {
492                E(K::Char(digit @ '0'..='9'), m) if m == M::NONE || m == M::ALT => {
493                    if self.num_args == -1 {
494                        self.num_args *= digit.to_digit(10).unwrap() as i16;
495                    } else if self.num_args.abs() < 1000 {
496                        // shouldn't ever need more than 4 digits
497                        self.num_args = self
498                            .num_args
499                            .saturating_mul(10)
500                            .saturating_add(digit.to_digit(10).unwrap() as i16);
501                    }
502                }
503                E(K::Char('-'), m) if m == M::NONE || m == M::ALT => {}
504                _ => {
505                    wrt.refresh_line()?;
506                    return Ok(key);
507                }
508            };
509        }
510    }
511
512    fn emacs<R: RawReader>(
513        &mut self,
514        rdr: &mut R,
515        wrt: &mut dyn Refresher,
516        mut key: KeyEvent,
517    ) -> Result<Cmd> {
518        if let E(K::Char(digit @ '-'), M::ALT) = key {
519            key = self.emacs_digit_argument(rdr, wrt, digit)?;
520        } else if let E(K::Char(digit @ '0'..='9'), M::ALT) = key {
521            key = self.emacs_digit_argument(rdr, wrt, digit)?;
522        }
523        let (n, positive) = self.emacs_num_args(); // consume them in all cases
524
525        let mut evt = key.into();
526        if let Some(cmd) = self.custom_binding(wrt, &evt, n, positive) {
527            return Ok(if cmd.is_repeatable() {
528                cmd.redo(Some(n), wrt)
529            } else {
530                cmd
531            });
532        } else if let Some(cmd) = InputState::term_binding(rdr, wrt, &key) {
533            return Ok(cmd);
534        }
535        let cmd = match key {
536            E(K::Char(c), M::NONE) => {
537                if positive {
538                    Cmd::SelfInsert(n, c)
539                } else {
540                    Cmd::Unknown
541                }
542            }
543            E(K::Char('A'), M::CTRL) => Cmd::Move(Movement::BeginningOfLine),
544            E(K::Char('B'), M::CTRL) => Cmd::Move(if positive {
545                Movement::BackwardChar(n)
546            } else {
547                Movement::ForwardChar(n)
548            }),
549            E(K::Char('E'), M::CTRL) => Cmd::Move(Movement::EndOfLine),
550            E(K::Char('F'), M::CTRL) => Cmd::Move(if positive {
551                Movement::ForwardChar(n)
552            } else {
553                Movement::BackwardChar(n)
554            }),
555            E(K::Char('G'), M::CTRL | M::CTRL_ALT) | E::ESC => Cmd::Abort,
556            E(K::Char('H'), M::CTRL) | E::BACKSPACE => Cmd::Kill(if positive {
557                Movement::BackwardChar(n)
558            } else {
559                Movement::ForwardChar(n)
560            }),
561            E(K::BackTab, M::NONE) => Cmd::CompleteBackward,
562            E(K::Char('I'), M::CTRL) | E(K::Tab, M::NONE) => {
563                if positive {
564                    Cmd::Complete
565                } else {
566                    Cmd::CompleteBackward
567                }
568            }
569            // Don't complete hints when the cursor is not at the end of a line
570            E(K::Right, M::NONE) if wrt.has_hint() && wrt.is_cursor_at_end() => Cmd::CompleteHint,
571            E(K::Char('K'), M::CTRL) => Cmd::Kill(if positive {
572                Movement::EndOfLine
573            } else {
574                Movement::BeginningOfLine
575            }),
576            E(K::Char('L'), M::CTRL) => Cmd::ClearScreen,
577            E(K::Char('N'), M::CTRL) => Cmd::NextHistory,
578            E(K::Char('P'), M::CTRL) => Cmd::PreviousHistory,
579            E(K::Char('X'), M::CTRL) => {
580                if let Some(cmd) = self.custom_seq_binding(rdr, wrt, &mut evt, n, positive)? {
581                    cmd
582                } else {
583                    let snd_key = match evt {
584                        // we may have already read the second key in custom_seq_binding
585                        Event::KeySeq(ref key_seq) if key_seq.len() > 1 => key_seq[1],
586                        _ => rdr.next_key(true)?,
587                    };
588                    match snd_key {
589                        E(K::Char('G'), M::CTRL) | E::ESC => Cmd::Abort,
590                        E(K::Char('U'), M::CTRL) => Cmd::Undo(n),
591                        E(K::Backspace, M::NONE) => Cmd::Kill(if positive {
592                            Movement::BeginningOfLine
593                        } else {
594                            Movement::EndOfLine
595                        }),
596                        _ => Cmd::Unknown,
597                    }
598                }
599            }
600            // character-search, character-search-backward
601            E(K::Char(']'), m @ (M::CTRL | M::CTRL_ALT)) => {
602                let ch = rdr.next_key(false)?;
603                match ch {
604                    E(K::Char(ch), M::NONE) => Cmd::Move(Movement::ViCharSearch(
605                        n,
606                        if positive {
607                            if m.contains(M::ALT) {
608                                CharSearch::Backward(ch)
609                            } else {
610                                CharSearch::ForwardBefore(ch)
611                            }
612                        } else if m.contains(M::ALT) {
613                            CharSearch::ForwardBefore(ch)
614                        } else {
615                            CharSearch::Backward(ch)
616                        },
617                    )),
618                    _ => Cmd::Unknown,
619                }
620            }
621            E(K::Backspace, M::ALT) => Cmd::Kill(if positive {
622                Movement::BackwardWord(n, Word::Emacs)
623            } else {
624                Movement::ForwardWord(n, At::AfterEnd, Word::Emacs)
625            }),
626            E(K::Char('<'), M::ALT) => Cmd::BeginningOfHistory,
627            E(K::Char('>'), M::ALT) => Cmd::EndOfHistory,
628            E(K::Char('B' | 'b') | K::Left, M::ALT) | E(K::Left, M::CTRL) => {
629                Cmd::Move(if positive {
630                    Movement::BackwardWord(n, Word::Emacs)
631                } else {
632                    Movement::ForwardWord(n, At::AfterEnd, Word::Emacs)
633                })
634            }
635            E(K::Char('C' | 'c'), M::ALT) => Cmd::CapitalizeWord,
636            E(K::Char('D' | 'd'), M::ALT) => Cmd::Kill(if positive {
637                Movement::ForwardWord(n, At::AfterEnd, Word::Emacs)
638            } else {
639                Movement::BackwardWord(n, Word::Emacs)
640            }),
641            E(K::Char('F' | 'f') | K::Right, M::ALT) | E(K::Right, M::CTRL) => {
642                Cmd::Move(if positive {
643                    Movement::ForwardWord(n, At::AfterEnd, Word::Emacs)
644                } else {
645                    Movement::BackwardWord(n, Word::Emacs)
646                })
647            }
648            E(K::Char('L' | 'l'), M::ALT) => Cmd::DowncaseWord,
649            E(K::Char('T' | 't'), M::ALT) => Cmd::TransposeWords(n),
650            // TODO ESC-R (r): Undo all changes made to this line.
651            E(K::Char('U' | 'u'), M::ALT) => Cmd::UpcaseWord,
652            E(K::Char('Y' | 'y'), M::ALT) => Cmd::YankPop,
653            _ => self.common(rdr, wrt, evt, key, n, positive)?,
654        };
655        debug!(target: "rustyline", "Emacs command: {:?}", cmd);
656        Ok(cmd)
657    }
658
659    #[allow(clippy::cast_possible_truncation)]
660    fn vi_arg_digit<R: RawReader>(
661        &mut self,
662        rdr: &mut R,
663        wrt: &mut dyn Refresher,
664        digit: char,
665    ) -> Result<KeyEvent> {
666        self.num_args = digit.to_digit(10).unwrap() as i16;
667        loop {
668            wrt.refresh_prompt_and_line(&format!("(arg: {}) ", self.num_args))?;
669            let key = rdr.next_key(false)?;
670            if let E(K::Char(digit @ '0'..='9'), M::NONE) = key {
671                if self.num_args.abs() < 1000 {
672                    // shouldn't ever need more than 4 digits
673                    self.num_args = self
674                        .num_args
675                        .saturating_mul(10)
676                        .saturating_add(digit.to_digit(10).unwrap() as i16);
677                }
678            } else {
679                wrt.refresh_line()?;
680                return Ok(key);
681            };
682        }
683    }
684
685    fn vi_command<R: RawReader>(
686        &mut self,
687        rdr: &mut R,
688        wrt: &mut dyn Refresher,
689        mut key: KeyEvent,
690    ) -> Result<Cmd> {
691        if let E(K::Char(digit @ '1'..='9'), M::NONE) = key {
692            key = self.vi_arg_digit(rdr, wrt, digit)?;
693        }
694        let no_num_args = self.num_args == 0;
695        let n = self.vi_num_args(); // consume them in all cases
696        let evt = key.into();
697        if let Some(cmd) = self.custom_binding(wrt, &evt, n, true) {
698            return Ok(if cmd.is_repeatable() {
699                if no_num_args {
700                    cmd.redo(None, wrt)
701                } else {
702                    cmd.redo(Some(n), wrt)
703                }
704            } else {
705                cmd
706            });
707        } else if let Some(cmd) = InputState::term_binding(rdr, wrt, &key) {
708            return Ok(cmd);
709        }
710        let cmd = match key {
711            E(K::Char('$') | K::End, M::NONE) => Cmd::Move(Movement::EndOfLine),
712            E(K::Char('.'), M::NONE) => {
713                // vi-redo (repeat last command)
714                if !self.last_cmd.is_repeatable() {
715                    Cmd::Noop
716                } else if no_num_args {
717                    self.last_cmd.redo(None, wrt)
718                } else {
719                    self.last_cmd.redo(Some(n), wrt)
720                }
721            }
722            // TODO E(K::Char('%'), M::NONE) => Cmd::???, Move to the corresponding opening/closing
723            // bracket
724            E(K::Char('0'), M::NONE) => Cmd::Move(Movement::BeginningOfLine),
725            E(K::Char('^'), M::NONE) => Cmd::Move(Movement::ViFirstPrint),
726            E(K::Char('a'), M::NONE) => {
727                // vi-append-mode
728                self.input_mode = InputMode::Insert;
729                wrt.doing_insert();
730                Cmd::Move(Movement::ForwardChar(n))
731            }
732            E(K::Char('A'), M::NONE) => {
733                // vi-append-eol
734                self.input_mode = InputMode::Insert;
735                wrt.doing_insert();
736                Cmd::Move(Movement::EndOfLine)
737            }
738            E(K::Char('b'), M::NONE) => Cmd::Move(Movement::BackwardWord(n, Word::Vi)), /* vi-prev-word */
739            E(K::Char('B'), M::NONE) => Cmd::Move(Movement::BackwardWord(n, Word::Big)),
740            E(K::Char('c'), M::NONE) => {
741                self.input_mode = InputMode::Insert;
742                match self.vi_cmd_motion(rdr, wrt, key, n)? {
743                    Some(mvt) => Cmd::Replace(mvt, None),
744                    None => Cmd::Unknown,
745                }
746            }
747            E(K::Char('C'), M::NONE) => {
748                self.input_mode = InputMode::Insert;
749                Cmd::Replace(Movement::EndOfLine, None)
750            }
751            E(K::Char('d'), M::NONE) => match self.vi_cmd_motion(rdr, wrt, key, n)? {
752                Some(mvt) => Cmd::Kill(mvt),
753                None => Cmd::Unknown,
754            },
755            E(K::Char('D'), M::NONE) | E(K::Char('K'), M::CTRL) => Cmd::Kill(Movement::EndOfLine),
756            E(K::Char('e'), M::NONE) => {
757                Cmd::Move(Movement::ForwardWord(n, At::BeforeEnd, Word::Vi))
758            }
759            E(K::Char('E'), M::NONE) => {
760                Cmd::Move(Movement::ForwardWord(n, At::BeforeEnd, Word::Big))
761            }
762            E(K::Char('i'), M::NONE) => {
763                // vi-insertion-mode
764                self.input_mode = InputMode::Insert;
765                wrt.doing_insert();
766                Cmd::Noop
767            }
768            E(K::Char('I'), M::NONE) => {
769                // vi-insert-beg
770                self.input_mode = InputMode::Insert;
771                wrt.doing_insert();
772                Cmd::Move(Movement::BeginningOfLine)
773            }
774            E(K::Char(c), M::NONE) if c == 'f' || c == 'F' || c == 't' || c == 'T' => {
775                // vi-char-search
776                let cs = self.vi_char_search(rdr, c)?;
777                match cs {
778                    Some(cs) => Cmd::Move(Movement::ViCharSearch(n, cs)),
779                    None => Cmd::Unknown,
780                }
781            }
782            E(K::Char(';'), M::NONE) => match self.last_char_search {
783                Some(cs) => Cmd::Move(Movement::ViCharSearch(n, cs)),
784                None => Cmd::Noop,
785            },
786            E(K::Char(','), M::NONE) => match self.last_char_search {
787                Some(ref cs) => Cmd::Move(Movement::ViCharSearch(n, cs.opposite())),
788                None => Cmd::Noop,
789            },
790            // TODO E(K::Char('G'), M::NONE) => Cmd::???, Move to the history line n
791            E(K::Char('p'), M::NONE) => Cmd::Yank(n, Anchor::After), // vi-put
792            E(K::Char('P'), M::NONE) => Cmd::Yank(n, Anchor::Before), // vi-put
793            E(K::Char('r'), M::NONE) => {
794                // vi-replace-char:
795                let ch = rdr.next_key(false)?;
796                match ch {
797                    E(K::Char(c), M::NONE) => Cmd::ReplaceChar(n, c),
798                    E::ESC => Cmd::Noop,
799                    _ => Cmd::Unknown,
800                }
801            }
802            E(K::Char('R'), M::NONE) => {
803                //  vi-replace-mode (overwrite-mode)
804                self.input_mode = InputMode::Replace;
805                Cmd::Replace(Movement::ForwardChar(0), None)
806            }
807            E(K::Char('s'), M::NONE) => {
808                // vi-substitute-char:
809                self.input_mode = InputMode::Insert;
810                Cmd::Replace(Movement::ForwardChar(n), None)
811            }
812            E(K::Char('S'), M::NONE) => {
813                // vi-substitute-line:
814                self.input_mode = InputMode::Insert;
815                Cmd::Replace(Movement::WholeLine, None)
816            }
817            E(K::Char('u'), M::NONE) => Cmd::Undo(n),
818            // E(K::Char('U'), M::NONE) => Cmd::???, // revert-line
819            E(K::Char('w'), M::NONE) => Cmd::Move(Movement::ForwardWord(n, At::Start, Word::Vi)), /* vi-next-word */
820            E(K::Char('W'), M::NONE) => Cmd::Move(Movement::ForwardWord(n, At::Start, Word::Big)), /* vi-next-word */
821            // TODO move backward if eol
822            E(K::Char('x'), M::NONE) => Cmd::Kill(Movement::ForwardChar(n)), // vi-delete
823            E(K::Char('X'), M::NONE) => Cmd::Kill(Movement::BackwardChar(n)), // vi-rubout
824            E(K::Char('y'), M::NONE) => match self.vi_cmd_motion(rdr, wrt, key, n)? {
825                Some(mvt) => Cmd::ViYankTo(mvt),
826                None => Cmd::Unknown,
827            },
828            // E(K::Char('Y'), M::NONE) => Cmd::???, // vi-yank-to
829            E(K::Char('h'), M::NONE) | E(K::Char('H'), M::CTRL) | E::BACKSPACE => {
830                Cmd::Move(Movement::BackwardChar(n))
831            }
832            E(K::Char('G'), M::CTRL) => Cmd::Abort,
833            E(K::Char('l' | ' '), M::NONE) => Cmd::Move(Movement::ForwardChar(n)),
834            E(K::Char('L'), M::CTRL) => Cmd::ClearScreen,
835            E(K::Char('+' | 'j'), M::NONE) => Cmd::LineDownOrNextHistory(n),
836            // TODO: move to the start of the line.
837            E(K::Char('N'), M::CTRL) => Cmd::NextHistory,
838            E(K::Char('-' | 'k'), M::NONE) => Cmd::LineUpOrPreviousHistory(n),
839            // TODO: move to the start of the line.
840            E(K::Char('P'), M::CTRL) => Cmd::PreviousHistory,
841            E(K::Char('R'), M::CTRL) => {
842                self.input_mode = InputMode::Insert; // TODO Validate
843                Cmd::ReverseSearchHistory
844            }
845            E(K::Char('S'), M::CTRL) => {
846                self.input_mode = InputMode::Insert; // TODO Validate
847                Cmd::ForwardSearchHistory
848            }
849            E(K::Char('<'), M::NONE) => match self.vi_cmd_motion(rdr, wrt, key, n)? {
850                Some(mvt) => Cmd::Dedent(mvt),
851                None => Cmd::Unknown,
852            },
853            E(K::Char('>'), M::NONE) => match self.vi_cmd_motion(rdr, wrt, key, n)? {
854                Some(mvt) => Cmd::Indent(mvt),
855                None => Cmd::Unknown,
856            },
857            E::ESC => Cmd::Noop,
858            _ => self.common(rdr, wrt, evt, key, n, true)?,
859        };
860        debug!(target: "rustyline", "Vi command: {:?}", cmd);
861        if cmd.is_repeatable_change() {
862            self.last_cmd = cmd.clone();
863        }
864        Ok(cmd)
865    }
866
867    fn vi_insert<R: RawReader>(
868        &mut self,
869        rdr: &mut R,
870        wrt: &mut dyn Refresher,
871        key: KeyEvent,
872    ) -> Result<Cmd> {
873        let evt = key.into();
874        if let Some(cmd) = self.custom_binding(wrt, &evt, 0, true) {
875            return Ok(if cmd.is_repeatable() {
876                cmd.redo(None, wrt)
877            } else {
878                cmd
879            });
880        } else if let Some(cmd) = InputState::term_binding(rdr, wrt, &key) {
881            return Ok(cmd);
882        }
883        let cmd = match key {
884            E(K::Char(c), M::NONE) => {
885                if self.input_mode == InputMode::Replace {
886                    Cmd::Overwrite(c)
887                } else {
888                    Cmd::SelfInsert(1, c)
889                }
890            }
891            E(K::Char('H'), M::CTRL) | E::BACKSPACE => Cmd::Kill(Movement::BackwardChar(1)),
892            E(K::BackTab, M::NONE) => Cmd::CompleteBackward,
893            E(K::Char('I'), M::CTRL) | E(K::Tab, M::NONE) => Cmd::Complete,
894            // Don't complete hints when the cursor is not at the end of a line
895            E(K::Right, M::NONE) if wrt.has_hint() && wrt.is_cursor_at_end() => Cmd::CompleteHint,
896            E(K::Char(k), M::ALT) => {
897                debug!(target: "rustyline", "Vi fast command mode: {}", k);
898                self.input_mode = InputMode::Command;
899                wrt.done_inserting();
900
901                self.vi_command(rdr, wrt, E(K::Char(k), M::NONE))?
902            }
903            E::ESC => {
904                // vi-movement-mode/vi-command-mode
905                self.input_mode = InputMode::Command;
906                wrt.done_inserting();
907                Cmd::Move(Movement::BackwardChar(1))
908            }
909            _ => self.common(rdr, wrt, evt, key, 1, true)?,
910        };
911        debug!(target: "rustyline", "Vi insert: {:?}", cmd);
912        if cmd.is_repeatable_change() {
913            #[allow(clippy::if_same_then_else)]
914            if let (Cmd::Replace(..), Cmd::SelfInsert(..)) = (&self.last_cmd, &cmd) {
915                // replacing...
916            } else if let (Cmd::SelfInsert(..), Cmd::SelfInsert(..)) = (&self.last_cmd, &cmd) {
917                // inserting...
918            } else {
919                self.last_cmd = cmd.clone();
920            }
921        }
922        Ok(cmd)
923    }
924
925    fn vi_cmd_motion<R: RawReader>(
926        &mut self,
927        rdr: &mut R,
928        wrt: &mut dyn Refresher,
929        key: KeyEvent,
930        n: RepeatCount,
931    ) -> Result<Option<Movement>> {
932        let mut mvt = rdr.next_key(false)?;
933        if mvt == key {
934            return Ok(Some(Movement::WholeLine));
935        }
936        let mut n = n;
937        if let E(K::Char(digit @ '1'..='9'), M::NONE) = mvt {
938            // vi-arg-digit
939            mvt = self.vi_arg_digit(rdr, wrt, digit)?;
940            n = self.vi_num_args().saturating_mul(n);
941        }
942        Ok(match mvt {
943            E(K::Char('$'), M::NONE) => Some(Movement::EndOfLine),
944            E(K::Char('0'), M::NONE) => Some(Movement::BeginningOfLine),
945            E(K::Char('^'), M::NONE) => Some(Movement::ViFirstPrint),
946            E(K::Char('b'), M::NONE) => Some(Movement::BackwardWord(n, Word::Vi)),
947            E(K::Char('B'), M::NONE) => Some(Movement::BackwardWord(n, Word::Big)),
948            E(K::Char('e'), M::NONE) => Some(Movement::ForwardWord(n, At::AfterEnd, Word::Vi)),
949            E(K::Char('E'), M::NONE) => Some(Movement::ForwardWord(n, At::AfterEnd, Word::Big)),
950            E(K::Char(c), M::NONE) if c == 'f' || c == 'F' || c == 't' || c == 'T' => {
951                let cs = self.vi_char_search(rdr, c)?;
952                cs.map(|cs| Movement::ViCharSearch(n, cs))
953            }
954            E(K::Char(';'), M::NONE) => self
955                .last_char_search
956                .map(|cs| Movement::ViCharSearch(n, cs)),
957            E(K::Char(','), M::NONE) => self
958                .last_char_search
959                .map(|cs| Movement::ViCharSearch(n, cs.opposite())),
960            E(K::Char('h'), M::NONE) | E(K::Char('H'), M::CTRL) | E::BACKSPACE => {
961                Some(Movement::BackwardChar(n))
962            }
963            E(K::Char('l' | ' '), M::NONE) => Some(Movement::ForwardChar(n)),
964            E(K::Char('j' | '+'), M::NONE) => Some(Movement::LineDown(n)),
965            E(K::Char('k' | '-'), M::NONE) => Some(Movement::LineUp(n)),
966            E(K::Char('w'), M::NONE) => {
967                // 'cw' is 'ce'
968                if key == E(K::Char('c'), M::NONE) {
969                    Some(Movement::ForwardWord(n, At::AfterEnd, Word::Vi))
970                } else {
971                    Some(Movement::ForwardWord(n, At::Start, Word::Vi))
972                }
973            }
974            E(K::Char('W'), M::NONE) => {
975                // 'cW' is 'cE'
976                if key == E(K::Char('c'), M::NONE) {
977                    Some(Movement::ForwardWord(n, At::AfterEnd, Word::Big))
978                } else {
979                    Some(Movement::ForwardWord(n, At::Start, Word::Big))
980                }
981            }
982            _ => None,
983        })
984    }
985
986    fn vi_char_search<R: RawReader>(
987        &mut self,
988        rdr: &mut R,
989        cmd: char,
990    ) -> Result<Option<CharSearch>> {
991        let ch = rdr.next_key(false)?;
992        Ok(match ch {
993            E(K::Char(ch), M::NONE) => {
994                let cs = match cmd {
995                    'f' => CharSearch::Forward(ch),
996                    't' => CharSearch::ForwardBefore(ch),
997                    'F' => CharSearch::Backward(ch),
998                    'T' => CharSearch::BackwardAfter(ch),
999                    _ => unreachable!(),
1000                };
1001                self.last_char_search = Some(cs);
1002                Some(cs)
1003            }
1004            _ => None,
1005        })
1006    }
1007
1008    fn common<R: RawReader>(
1009        &mut self,
1010        rdr: &mut R,
1011        wrt: &dyn Refresher,
1012        mut evt: Event,
1013        key: KeyEvent,
1014        n: RepeatCount,
1015        positive: bool,
1016    ) -> Result<Cmd> {
1017        Ok(match key {
1018            E(K::Home, M::NONE) => Cmd::Move(Movement::BeginningOfLine),
1019            E(K::Left, M::NONE) => Cmd::Move(if positive {
1020                Movement::BackwardChar(n)
1021            } else {
1022                Movement::ForwardChar(n)
1023            }),
1024            #[cfg(any(windows, test))]
1025            E(K::Char('C'), M::CTRL) => Cmd::Interrupt,
1026            E(K::Char('D'), M::CTRL) => {
1027                if self.is_emacs_mode() && !wrt.line().is_empty() {
1028                    Cmd::Kill(if positive {
1029                        Movement::ForwardChar(n)
1030                    } else {
1031                        Movement::BackwardChar(n)
1032                    })
1033                } else if cfg!(windows) || cfg!(test) || !wrt.line().is_empty() {
1034                    Cmd::EndOfFile
1035                } else {
1036                    Cmd::Unknown
1037                }
1038            }
1039            E(K::Delete, M::NONE) => Cmd::Kill(if positive {
1040                Movement::ForwardChar(n)
1041            } else {
1042                Movement::BackwardChar(n)
1043            }),
1044            E(K::End, M::NONE) => Cmd::Move(Movement::EndOfLine),
1045            E(K::Right, M::NONE) => Cmd::Move(if positive {
1046                Movement::ForwardChar(n)
1047            } else {
1048                Movement::BackwardChar(n)
1049            }),
1050            E(K::Char('J' | 'M'), M::CTRL) | E::ENTER => Cmd::AcceptOrInsertLine {
1051                accept_in_the_middle: true,
1052            },
1053            E(K::Down, M::NONE) => Cmd::LineDownOrNextHistory(1),
1054            E(K::Up, M::NONE) => Cmd::LineUpOrPreviousHistory(1),
1055            E(K::Char('R'), M::CTRL) => Cmd::ReverseSearchHistory,
1056            // most terminals override Ctrl+S to suspend execution
1057            E(K::Char('S'), M::CTRL) => Cmd::ForwardSearchHistory,
1058            E(K::Char('T'), M::CTRL) => Cmd::TransposeChars,
1059            E(K::Char('U'), M::CTRL) => Cmd::Kill(if positive {
1060                Movement::BeginningOfLine
1061            } else {
1062                Movement::EndOfLine
1063            }),
1064            // most terminals override Ctrl+Q to resume execution
1065            E(K::Char('Q'), M::CTRL) => Cmd::QuotedInsert,
1066            #[cfg(not(windows))]
1067            E(K::Char('V'), M::CTRL) => Cmd::QuotedInsert,
1068            #[cfg(windows)]
1069            E(K::Char('V'), M::CTRL) => Cmd::PasteFromClipboard,
1070            E(K::Char('W'), M::CTRL) => Cmd::Kill(if positive {
1071                Movement::BackwardWord(n, Word::Big)
1072            } else {
1073                Movement::ForwardWord(n, At::AfterEnd, Word::Big)
1074            }),
1075            E(K::Char('Y'), M::CTRL) => {
1076                if positive {
1077                    Cmd::Yank(n, Anchor::Before)
1078                } else {
1079                    Cmd::Unknown // TODO Validate
1080                }
1081            }
1082            E(K::Char('_'), M::CTRL) => Cmd::Undo(n),
1083            E(K::UnknownEscSeq, M::NONE) => Cmd::Noop,
1084            E(K::BracketedPasteStart, M::NONE) => {
1085                let paste = rdr.read_pasted_text()?;
1086                Cmd::Insert(1, paste)
1087            }
1088            _ => self
1089                .custom_seq_binding(rdr, wrt, &mut evt, n, positive)?
1090                .unwrap_or(Cmd::Unknown),
1091        })
1092    }
1093
1094    fn num_args(&mut self) -> i16 {
1095        let num_args = match self.num_args {
1096            0 => 1,
1097            _ => self.num_args,
1098        };
1099        self.num_args = 0;
1100        num_args
1101    }
1102
1103    #[allow(clippy::cast_sign_loss)]
1104    fn emacs_num_args(&mut self) -> (RepeatCount, bool) {
1105        let num_args = self.num_args();
1106        if num_args < 0 {
1107            if let (n, false) = num_args.overflowing_abs() {
1108                (n as RepeatCount, false)
1109            } else {
1110                (RepeatCount::MAX, false)
1111            }
1112        } else {
1113            (num_args as RepeatCount, true)
1114        }
1115    }
1116
1117    #[allow(clippy::cast_sign_loss)]
1118    fn vi_num_args(&mut self) -> RepeatCount {
1119        let num_args = self.num_args();
1120        if num_args < 0 {
1121            unreachable!()
1122        } else {
1123            num_args.unsigned_abs() as RepeatCount
1124        }
1125    }
1126}
1127
1128#[cfg(feature = "custom-bindings")]
1129impl<'b> InputState<'b> {
1130    /// Application customized binding
1131    fn custom_binding(
1132        &self,
1133        wrt: &dyn Refresher,
1134        evt: &Event,
1135        n: RepeatCount,
1136        positive: bool,
1137    ) -> Option<Cmd> {
1138        let bindings = self.custom_bindings;
1139        let handler = bindings.get(evt).or_else(|| bindings.get(&Event::Any));
1140        if let Some(handler) = handler {
1141            match handler {
1142                EventHandler::Simple(cmd) => Some(cmd.clone()),
1143                EventHandler::Conditional(handler) => {
1144                    let ctx = EventContext::new(self, wrt);
1145                    handler.handle(evt, n, positive, &ctx)
1146                }
1147            }
1148        } else {
1149            None
1150        }
1151    }
1152
1153    fn custom_seq_binding<R: RawReader>(
1154        &self,
1155        rdr: &mut R,
1156        wrt: &dyn Refresher,
1157        evt: &mut Event,
1158        n: RepeatCount,
1159        positive: bool,
1160    ) -> Result<Option<Cmd>> {
1161        while let Some(subtrie) = self.custom_bindings.get_raw_descendant(evt) {
1162            let snd_key = rdr.next_key(true)?;
1163            if let Event::KeySeq(ref mut key_seq) = evt {
1164                key_seq.push(snd_key);
1165            } else {
1166                break;
1167            }
1168            let handler = subtrie.get(evt).unwrap();
1169            if let Some(handler) = handler {
1170                let cmd = match handler {
1171                    EventHandler::Simple(cmd) => Some(cmd.clone()),
1172                    EventHandler::Conditional(handler) => {
1173                        let ctx = EventContext::new(self, wrt);
1174                        handler.handle(evt, n, positive, &ctx)
1175                    }
1176                };
1177                if cmd.is_some() {
1178                    return Ok(cmd);
1179                }
1180            }
1181        }
1182        Ok(None)
1183    }
1184}
1185
1186#[cfg(not(feature = "custom-bindings"))]
1187impl<'b> InputState<'b> {
1188    fn custom_binding(&self, _: &dyn Refresher, _: &Event, _: RepeatCount, _: bool) -> Option<Cmd> {
1189        None
1190    }
1191
1192    fn custom_seq_binding<R: RawReader>(
1193        &self,
1194        _: &mut R,
1195        _: &dyn Refresher,
1196        _: &mut Event,
1197        _: RepeatCount,
1198        _: bool,
1199    ) -> Result<Option<Cmd>> {
1200        Ok(None)
1201    }
1202}
1203
1204cfg_if::cfg_if! {
1205    if #[cfg(feature = "custom-bindings")] {
1206pub type Bindings = radix_trie::Trie<Event, EventHandler>;
1207    } else {
1208enum Event {
1209   KeySeq([KeyEvent; 1]),
1210}
1211impl From<KeyEvent> for Event {
1212    fn from(k: KeyEvent) -> Event {
1213        Event::KeySeq([k])
1214    }
1215}
1216pub struct Bindings {}
1217impl Bindings {
1218    pub fn new() -> Bindings {
1219        Bindings {}
1220    }
1221}
1222    }
1223}