From dfeb2459739e898faa5506f97a7a0b62b98d8bbd Mon Sep 17 00:00:00 2001 From: Thom Chiovoloni Date: Tue, 11 Feb 2020 17:10:02 -0800 Subject: [PATCH 1/5] Split KeyPress into separate `Key` and `KeyMods` types --- Cargo.toml | 1 + examples/example.rs | 4 +- src/keymap.rs | 264 ++++++++++++------------ src/keys.rs | 463 +++++++++++++++++++++++++++++++++++++----- src/lib.rs | 6 +- src/test/common.rs | 128 ++++++------ src/test/emacs.rs | 148 +++++++------- src/test/history.rs | 112 +++++----- src/test/mod.rs | 6 +- src/test/vi_cmd.rs | 321 ++++++++++++++--------------- src/test/vi_insert.rs | 12 +- src/tty/test.rs | 2 +- src/tty/unix.rs | 156 +++++++------- src/tty/windows.rs | 98 ++++----- 14 files changed, 1030 insertions(+), 691 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6f53564672..5e27f37012 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ log = "0.4" unicode-width = "0.1" unicode-segmentation = "1.0" memchr = "2.0" +bitflags = "1.2" [target.'cfg(unix)'.dependencies] nix = "0.17" diff --git a/examples/example.rs b/examples/example.rs index 9614fc09ea..261c94003a 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -105,8 +105,8 @@ fn main() -> rustyline::Result<()> { }; let mut rl = Editor::with_config(config); rl.set_helper(Some(h)); - rl.bind_sequence(KeyPress::Meta('N'), Cmd::HistorySearchForward); - rl.bind_sequence(KeyPress::Meta('P'), Cmd::HistorySearchBackward); + rl.bind_sequence(KeyPress::meta('N'), Cmd::HistorySearchForward); + rl.bind_sequence(KeyPress::meta('P'), Cmd::HistorySearchBackward); if rl.load_history("history.txt").is_err() { println!("No previous history."); } diff --git a/src/keymap.rs b/src/keymap.rs index abc1022966..0e753c07b7 100644 --- a/src/keymap.rs +++ b/src/keymap.rs @@ -392,7 +392,7 @@ impl InputState { let key = rdr.next_key(true)?; #[allow(clippy::cast_possible_truncation)] match key { - KeyPress::Char(digit @ '0'..='9') | KeyPress::Meta(digit @ '0'..='9') => { + key_press!(digit @ '0'..='9') | key_press!(META, digit @ '0'..='9') => { if self.num_args == -1 { self.num_args *= digit.to_digit(10).unwrap() as i16; } else if self.num_args.abs() < 1000 { @@ -403,7 +403,7 @@ impl InputState { .saturating_add(digit.to_digit(10).unwrap() as i16); } } - KeyPress::Char('-') | KeyPress::Meta('-') => {} + key_press!('-') | key_press!(META, '-') => {} _ => { wrt.refresh_line()?; return Ok(key); @@ -419,9 +419,9 @@ impl InputState { single_esc_abort: bool, ) -> Result { let mut key = rdr.next_key(single_esc_abort)?; - if let KeyPress::Meta(digit @ '-') = key { + if let key_press!(META, digit @ '-') = key { key = self.emacs_digit_argument(rdr, wrt, digit)?; - } else if let KeyPress::Meta(digit @ '0'..='9') = key { + } else if let key_press!(META, digit @ '0'..='9') = key { key = self.emacs_digit_argument(rdr, wrt, digit)?; } let (n, positive) = self.emacs_num_args(); // consume them in all cases @@ -437,39 +437,39 @@ impl InputState { } } let cmd = match key { - KeyPress::Char(c) => { + key_press!(Char(c)) => { if positive { Cmd::SelfInsert(n, c) } else { Cmd::Unknown } } - KeyPress::Ctrl('A') => Cmd::Move(Movement::BeginningOfLine), - KeyPress::Ctrl('B') => { + key_press!(CTRL, 'A') => Cmd::Move(Movement::BeginningOfLine), + key_press!(CTRL, 'B') => { if positive { Cmd::Move(Movement::BackwardChar(n)) } else { Cmd::Move(Movement::ForwardChar(n)) } } - KeyPress::Ctrl('E') => Cmd::Move(Movement::EndOfLine), - KeyPress::Ctrl('F') => { + key_press!(CTRL, 'E') => Cmd::Move(Movement::EndOfLine), + key_press!(CTRL, 'F') => { if positive { Cmd::Move(Movement::ForwardChar(n)) } else { Cmd::Move(Movement::BackwardChar(n)) } } - KeyPress::Ctrl('G') | KeyPress::Esc | KeyPress::Meta('\x07') => Cmd::Abort, - KeyPress::Ctrl('H') | KeyPress::Backspace => { + key_press!(CTRL, 'G') | key_press!(Esc) | key_press!(META, '\x07') => Cmd::Abort, + key_press!(CTRL, 'H') | key_press!(Backspace) => { if positive { Cmd::Kill(Movement::BackwardChar(n)) } else { Cmd::Kill(Movement::ForwardChar(n)) } } - KeyPress::BackTab => Cmd::CompleteBackward, - KeyPress::Tab => { + key_press!(BackTab) => Cmd::CompleteBackward, + key_press!(Tab) => { if positive { Cmd::Complete } else { @@ -477,60 +477,60 @@ impl InputState { } } // Don't complete hints when the cursor is not at the end of a line - KeyPress::Right if wrt.has_hint() && wrt.is_cursor_at_end() => Cmd::CompleteHint, - KeyPress::Ctrl('K') => { + key_press!(Right) if wrt.has_hint() && wrt.is_cursor_at_end() => Cmd::CompleteHint, + key_press!(CTRL, 'K') => { if positive { Cmd::Kill(Movement::EndOfLine) } else { Cmd::Kill(Movement::BeginningOfLine) } } - KeyPress::Ctrl('L') => Cmd::ClearScreen, - KeyPress::Ctrl('N') => Cmd::NextHistory, - KeyPress::Ctrl('P') => Cmd::PreviousHistory, - KeyPress::Ctrl('X') => { + key_press!(CTRL, 'L') => Cmd::ClearScreen, + key_press!(CTRL, 'N') => Cmd::NextHistory, + key_press!(CTRL, 'P') => Cmd::PreviousHistory, + key_press!(CTRL, 'X') => { let snd_key = rdr.next_key(true)?; match snd_key { - KeyPress::Ctrl('G') | KeyPress::Esc => Cmd::Abort, - KeyPress::Ctrl('U') => Cmd::Undo(n), + key_press!(CTRL, 'G') | key_press!(Esc) => Cmd::Abort, + key_press!(CTRL, 'U') => Cmd::Undo(n), _ => Cmd::Unknown, } } - KeyPress::Meta('\x08') | KeyPress::Meta('\x7f') => { + key_press!(META, '\x08') | key_press!(META, '\x7f') => { if positive { Cmd::Kill(Movement::BackwardWord(n, Word::Emacs)) } else { Cmd::Kill(Movement::ForwardWord(n, At::AfterEnd, Word::Emacs)) } } - KeyPress::Meta('<') => Cmd::BeginningOfHistory, - KeyPress::Meta('>') => Cmd::EndOfHistory, - KeyPress::Meta('B') | KeyPress::Meta('b') => { + key_press!(META, '<') => Cmd::BeginningOfHistory, + key_press!(META, '>') => Cmd::EndOfHistory, + key_press!(META, 'B') | key_press!(META, 'b') => { if positive { Cmd::Move(Movement::BackwardWord(n, Word::Emacs)) } else { Cmd::Move(Movement::ForwardWord(n, At::AfterEnd, Word::Emacs)) } } - KeyPress::Meta('C') | KeyPress::Meta('c') => Cmd::CapitalizeWord, - KeyPress::Meta('D') | KeyPress::Meta('d') => { + key_press!(META, 'C') | key_press!(META, 'c') => Cmd::CapitalizeWord, + key_press!(META, 'D') | key_press!(META, 'd') => { if positive { Cmd::Kill(Movement::ForwardWord(n, At::AfterEnd, Word::Emacs)) } else { Cmd::Kill(Movement::BackwardWord(n, Word::Emacs)) } } - KeyPress::Meta('F') | KeyPress::Meta('f') => { + key_press!(META, 'F') | key_press!(META, 'f') => { if positive { Cmd::Move(Movement::ForwardWord(n, At::AfterEnd, Word::Emacs)) } else { Cmd::Move(Movement::BackwardWord(n, Word::Emacs)) } } - KeyPress::Meta('L') | KeyPress::Meta('l') => Cmd::DowncaseWord, - KeyPress::Meta('T') | KeyPress::Meta('t') => Cmd::TransposeWords(n), - KeyPress::Meta('U') | KeyPress::Meta('u') => Cmd::UpcaseWord, - KeyPress::Meta('Y') | KeyPress::Meta('y') => Cmd::YankPop, + key_press!(META, 'L') | key_press!(META, 'l') => Cmd::DowncaseWord, + key_press!(META, 'T') | key_press!(META, 't') => Cmd::TransposeWords(n), + key_press!(META, 'U') | key_press!(META, 'u') => Cmd::UpcaseWord, + key_press!(META, 'Y') | key_press!(META, 'y') => Cmd::YankPop, _ => self.common(rdr, key, n, positive)?, }; debug!(target: "rustyline", "Emacs command: {:?}", cmd); @@ -548,7 +548,7 @@ impl InputState { loop { wrt.refresh_prompt_and_line(&format!("(arg: {}) ", self.num_args))?; let key = rdr.next_key(false)?; - if let KeyPress::Char(digit @ '0'..='9') = key { + if let key_press!(digit @ '0'..='9') = key { if self.num_args.abs() < 1000 { // shouldn't ever need more than 4 digits self.num_args = self @@ -565,7 +565,7 @@ impl InputState { fn vi_command(&mut self, rdr: &mut R, wrt: &mut dyn Refresher) -> Result { let mut key = rdr.next_key(false)?; - if let KeyPress::Char(digit @ '1'..='9') = key { + if let key_press!(Char(digit @ '1'..='9')) = key { key = self.vi_arg_digit(rdr, wrt, digit)?; } let no_num_args = self.num_args == 0; @@ -586,8 +586,8 @@ impl InputState { } } let cmd = match key { - KeyPress::Char('$') | KeyPress::End => Cmd::Move(Movement::EndOfLine), - KeyPress::Char('.') => { + key_press!('$') | key_press!(End) => Cmd::Move(Movement::EndOfLine), + key_press!('.') => { // vi-redo (repeat last command) if no_num_args { self.last_cmd.redo(None, wrt) @@ -595,55 +595,55 @@ impl InputState { self.last_cmd.redo(Some(n), wrt) } } - // TODO KeyPress::Char('%') => Cmd::???, Move to the corresponding opening/closing + // TODO key_press!('%') => Cmd::???, Move to the corresponding opening/closing // bracket - KeyPress::Char('0') => Cmd::Move(Movement::BeginningOfLine), - KeyPress::Char('^') => Cmd::Move(Movement::ViFirstPrint), - KeyPress::Char('a') => { + key_press!('0') => Cmd::Move(Movement::BeginningOfLine), + key_press!('^') => Cmd::Move(Movement::ViFirstPrint), + key_press!('a') => { // vi-append-mode self.input_mode = InputMode::Insert; wrt.doing_insert(); Cmd::Move(Movement::ForwardChar(n)) } - KeyPress::Char('A') => { + key_press!('A') => { // vi-append-eol self.input_mode = InputMode::Insert; wrt.doing_insert(); Cmd::Move(Movement::EndOfLine) } - KeyPress::Char('b') => Cmd::Move(Movement::BackwardWord(n, Word::Vi)), // vi-prev-word - KeyPress::Char('B') => Cmd::Move(Movement::BackwardWord(n, Word::Big)), - KeyPress::Char('c') => { + key_press!('b') => Cmd::Move(Movement::BackwardWord(n, Word::Vi)), // vi-prev-word + key_press!('B') => Cmd::Move(Movement::BackwardWord(n, Word::Big)), + key_press!('c') => { self.input_mode = InputMode::Insert; match self.vi_cmd_motion(rdr, wrt, key, n)? { Some(mvt) => Cmd::Replace(mvt, None), None => Cmd::Unknown, } } - KeyPress::Char('C') => { + key_press!('C') => { self.input_mode = InputMode::Insert; Cmd::Replace(Movement::EndOfLine, None) } - KeyPress::Char('d') => match self.vi_cmd_motion(rdr, wrt, key, n)? { + key_press!('d') => match self.vi_cmd_motion(rdr, wrt, key, n)? { Some(mvt) => Cmd::Kill(mvt), None => Cmd::Unknown, }, - KeyPress::Char('D') | KeyPress::Ctrl('K') => Cmd::Kill(Movement::EndOfLine), - KeyPress::Char('e') => Cmd::Move(Movement::ForwardWord(n, At::BeforeEnd, Word::Vi)), - KeyPress::Char('E') => Cmd::Move(Movement::ForwardWord(n, At::BeforeEnd, Word::Big)), - KeyPress::Char('i') => { + key_press!('D') | key_press!(CTRL, 'K') => Cmd::Kill(Movement::EndOfLine), + key_press!('e') => Cmd::Move(Movement::ForwardWord(n, At::BeforeEnd, Word::Vi)), + key_press!('E') => Cmd::Move(Movement::ForwardWord(n, At::BeforeEnd, Word::Big)), + key_press!('i') => { // vi-insertion-mode self.input_mode = InputMode::Insert; wrt.doing_insert(); Cmd::Noop } - KeyPress::Char('I') => { + key_press!('I') => { // vi-insert-beg self.input_mode = InputMode::Insert; wrt.doing_insert(); Cmd::Move(Movement::BeginningOfLine) } - KeyPress::Char(c) if c == 'f' || c == 'F' || c == 't' || c == 'T' => { + key_press!(Char(c)) if c == 'f' || c == 'F' || c == 't' || c == 'T' => { // vi-char-search let cs = self.vi_char_search(rdr, c)?; match cs { @@ -651,74 +651,76 @@ impl InputState { None => Cmd::Unknown, } } - KeyPress::Char(';') => match self.last_char_search { + key_press!(';') => match self.last_char_search { Some(cs) => Cmd::Move(Movement::ViCharSearch(n, cs)), None => Cmd::Noop, }, - KeyPress::Char(',') => match self.last_char_search { + key_press!(',') => match self.last_char_search { Some(ref cs) => Cmd::Move(Movement::ViCharSearch(n, cs.opposite())), None => Cmd::Noop, }, - // TODO KeyPress::Char('G') => Cmd::???, Move to the history line n - KeyPress::Char('p') => Cmd::Yank(n, Anchor::After), // vi-put - KeyPress::Char('P') => Cmd::Yank(n, Anchor::Before), // vi-put - KeyPress::Char('r') => { + // TODO key_press!('G') => Cmd::???, Move to the history line n + key_press!('p') => Cmd::Yank(n, Anchor::After), // vi-put + key_press!('P') => Cmd::Yank(n, Anchor::Before), // vi-put + key_press!('r') => { // vi-replace-char: let ch = rdr.next_key(false)?; match ch { - KeyPress::Char(c) => Cmd::ReplaceChar(n, c), - KeyPress::Esc => Cmd::Noop, + key_press!(Char(c)) => Cmd::ReplaceChar(n, c), + key_press!(Esc) => Cmd::Noop, _ => Cmd::Unknown, } } - KeyPress::Char('R') => { + key_press!('R') => { // vi-replace-mode (overwrite-mode) self.input_mode = InputMode::Replace; Cmd::Replace(Movement::ForwardChar(0), None) } - KeyPress::Char('s') => { + key_press!('s') => { // vi-substitute-char: self.input_mode = InputMode::Insert; Cmd::Replace(Movement::ForwardChar(n), None) } - KeyPress::Char('S') => { + key_press!('S') => { // vi-substitute-line: self.input_mode = InputMode::Insert; Cmd::Replace(Movement::WholeLine, None) } - KeyPress::Char('u') => Cmd::Undo(n), - // KeyPress::Char('U') => Cmd::???, // revert-line - KeyPress::Char('w') => Cmd::Move(Movement::ForwardWord(n, At::Start, Word::Vi)), /* vi-next-word */ - KeyPress::Char('W') => Cmd::Move(Movement::ForwardWord(n, At::Start, Word::Big)), /* vi-next-word */ + + key_press!('u') => Cmd::Undo(n), + // key_press!('U') => Cmd::???, // revert-line + key_press!('w') => Cmd::Move(Movement::ForwardWord(n, At::Start, Word::Vi)), /* vi-next-word */ + key_press!('W') => Cmd::Move(Movement::ForwardWord(n, At::Start, Word::Big)), /* vi-next-word */ // TODO move backward if eol - KeyPress::Char('x') => Cmd::Kill(Movement::ForwardChar(n)), // vi-delete - KeyPress::Char('X') => Cmd::Kill(Movement::BackwardChar(n)), // vi-rubout - KeyPress::Char('y') => match self.vi_cmd_motion(rdr, wrt, key, n)? { + key_press!('x') => Cmd::Kill(Movement::ForwardChar(n)), // vi-delete + key_press!('X') => Cmd::Kill(Movement::BackwardChar(n)), // vi-rubout + key_press!('y') => match self.vi_cmd_motion(rdr, wrt, key, n)? { Some(mvt) => Cmd::ViYankTo(mvt), None => Cmd::Unknown, }, - // KeyPress::Char('Y') => Cmd::???, // vi-yank-to - KeyPress::Char('h') | KeyPress::Ctrl('H') | KeyPress::Backspace => { + // key_press!('Y') => Cmd::???, // vi-yank-to + key_press!('h') | key_press!(CTRL, 'H') | key_press!(Backspace) => { Cmd::Move(Movement::BackwardChar(n)) } - KeyPress::Ctrl('G') => Cmd::Abort, - KeyPress::Char('l') | KeyPress::Char(' ') => Cmd::Move(Movement::ForwardChar(n)), - KeyPress::Ctrl('L') => Cmd::ClearScreen, - KeyPress::Char('+') | KeyPress::Char('j') => Cmd::Move(Movement::LineDown(n)), + key_press!(CTRL, 'G') => Cmd::Abort, + key_press!('l') | key_press!(' ') => Cmd::Move(Movement::ForwardChar(n)), + key_press!(CTRL, 'L') => Cmd::ClearScreen, + key_press!('+') | key_press!('j') => Cmd::Move(Movement::LineDown(n)), // TODO: move to the start of the line. - KeyPress::Ctrl('N') => Cmd::NextHistory, - KeyPress::Char('-') | KeyPress::Char('k') => Cmd::Move(Movement::LineUp(n)), + key_press!(CTRL, 'N') => Cmd::NextHistory, + key_press!('-') | key_press!('k') => Cmd::Move(Movement::LineUp(n)), + // TODO: move to the start of the line. - KeyPress::Ctrl('P') => Cmd::PreviousHistory, - KeyPress::Ctrl('R') => { + key_press!(CTRL, 'P') => Cmd::PreviousHistory, + key_press!(CTRL, 'R') => { self.input_mode = InputMode::Insert; // TODO Validate Cmd::ReverseSearchHistory } - KeyPress::Ctrl('S') => { + key_press!(CTRL, 'S') => { self.input_mode = InputMode::Insert; // TODO Validate Cmd::ForwardSearchHistory } - KeyPress::Esc => Cmd::Noop, + key_press!(Esc) => Cmd::Noop, _ => self.common(rdr, key, n, true)?, }; debug!(target: "rustyline", "Vi command: {:?}", cmd); @@ -742,19 +744,19 @@ impl InputState { } } let cmd = match key { - KeyPress::Char(c) => { + key_press!(Char(c)) => { if self.input_mode == InputMode::Replace { Cmd::Overwrite(c) } else { Cmd::SelfInsert(1, c) } } - KeyPress::Ctrl('H') | KeyPress::Backspace => Cmd::Kill(Movement::BackwardChar(1)), - KeyPress::BackTab => Cmd::CompleteBackward, - KeyPress::Tab => Cmd::Complete, + key_press!(CTRL, 'H') | key_press!(Backspace) => Cmd::Kill(Movement::BackwardChar(1)), + key_press!(BackTab) => Cmd::CompleteBackward, + key_press!(Tab) => Cmd::Complete, // Don't complete hints when the cursor is not at the end of a line - KeyPress::Right if wrt.has_hint() && wrt.is_cursor_at_end() => Cmd::CompleteHint, - KeyPress::Esc => { + key_press!(Right) if wrt.has_hint() && wrt.is_cursor_at_end() => Cmd::CompleteHint, + key_press!(Esc) => { // vi-movement-mode/vi-command-mode self.input_mode = InputMode::Command; wrt.done_inserting(); @@ -787,51 +789,51 @@ impl InputState { return Ok(Some(Movement::WholeLine)); } let mut n = n; - if let KeyPress::Char(digit @ '1'..='9') = mvt { + if let key_press!(Char(digit @ '1'..='9')) = mvt { // vi-arg-digit mvt = self.vi_arg_digit(rdr, wrt, digit)?; n = self.vi_num_args().saturating_mul(n); } Ok(match mvt { - KeyPress::Char('$') => Some(Movement::EndOfLine), - KeyPress::Char('0') => Some(Movement::BeginningOfLine), - KeyPress::Char('^') => Some(Movement::ViFirstPrint), - KeyPress::Char('b') => Some(Movement::BackwardWord(n, Word::Vi)), - KeyPress::Char('B') => Some(Movement::BackwardWord(n, Word::Big)), - KeyPress::Char('e') => Some(Movement::ForwardWord(n, At::AfterEnd, Word::Vi)), - KeyPress::Char('E') => Some(Movement::ForwardWord(n, At::AfterEnd, Word::Big)), - KeyPress::Char(c) if c == 'f' || c == 'F' || c == 't' || c == 'T' => { + key_press!('$') => Some(Movement::EndOfLine), + key_press!('0') => Some(Movement::BeginningOfLine), + key_press!('^') => Some(Movement::ViFirstPrint), + key_press!('b') => Some(Movement::BackwardWord(n, Word::Vi)), + key_press!('B') => Some(Movement::BackwardWord(n, Word::Big)), + key_press!('e') => Some(Movement::ForwardWord(n, At::AfterEnd, Word::Vi)), + key_press!('E') => Some(Movement::ForwardWord(n, At::AfterEnd, Word::Big)), + key_press!(Char(c)) if c == 'f' || c == 'F' || c == 't' || c == 'T' => { let cs = self.vi_char_search(rdr, c)?; match cs { Some(cs) => Some(Movement::ViCharSearch(n, cs)), None => None, } } - KeyPress::Char(';') => match self.last_char_search { + key_press!(';') => match self.last_char_search { Some(cs) => Some(Movement::ViCharSearch(n, cs)), None => None, }, - KeyPress::Char(',') => match self.last_char_search { + key_press!(',') => match self.last_char_search { Some(ref cs) => Some(Movement::ViCharSearch(n, cs.opposite())), None => None, }, - KeyPress::Char('h') | KeyPress::Ctrl('H') | KeyPress::Backspace => { + key_press!('h') | key_press!(CTRL, 'H') | key_press!(Backspace) => { Some(Movement::BackwardChar(n)) } - KeyPress::Char('l') | KeyPress::Char(' ') => Some(Movement::ForwardChar(n)), - KeyPress::Char('j') | KeyPress::Char('+') => Some(Movement::LineDown(n)), - KeyPress::Char('k') | KeyPress::Char('-') => Some(Movement::LineUp(n)), - KeyPress::Char('w') => { + key_press!('l') | key_press!(' ') => Some(Movement::ForwardChar(n)), + key_press!('j') | key_press!('+') => Some(Movement::LineDown(n)), + key_press!('k') | key_press!('-') => Some(Movement::LineUp(n)), + key_press!('w') => { // 'cw' is 'ce' - if key == KeyPress::Char('c') { + if key == key_press!('c') { Some(Movement::ForwardWord(n, At::AfterEnd, Word::Vi)) } else { Some(Movement::ForwardWord(n, At::Start, Word::Vi)) } } - KeyPress::Char('W') => { + key_press!('W') => { // 'cW' is 'cE' - if key == KeyPress::Char('c') { + if key == key_press!('c') { Some(Movement::ForwardWord(n, At::AfterEnd, Word::Big)) } else { Some(Movement::ForwardWord(n, At::Start, Word::Big)) @@ -848,7 +850,7 @@ impl InputState { ) -> Result> { let ch = rdr.next_key(false)?; Ok(match ch { - KeyPress::Char(ch) => { + key_press!(Char(ch)) => { let cs = match cmd { 'f' => CharSearch::Forward(ch), 't' => CharSearch::ForwardBefore(ch), @@ -871,65 +873,65 @@ impl InputState { positive: bool, ) -> Result { Ok(match key { - KeyPress::Home => Cmd::Move(Movement::BeginningOfLine), - KeyPress::Left => { + key_press!(Home) => Cmd::Move(Movement::BeginningOfLine), + key_press!(Left) => { if positive { Cmd::Move(Movement::BackwardChar(n)) } else { Cmd::Move(Movement::ForwardChar(n)) } } - KeyPress::Ctrl('C') => Cmd::Interrupt, - KeyPress::Ctrl('D') => Cmd::EndOfFile, - KeyPress::Delete => { + key_press!(CTRL, 'C') => Cmd::Interrupt, + key_press!(CTRL, 'D') => Cmd::EndOfFile, + key_press!(Delete) => { if positive { Cmd::Kill(Movement::ForwardChar(n)) } else { Cmd::Kill(Movement::BackwardChar(n)) } } - KeyPress::End => Cmd::Move(Movement::EndOfLine), - KeyPress::Right => { + key_press!(End) => Cmd::Move(Movement::EndOfLine), + key_press!(Right) => { if positive { Cmd::Move(Movement::ForwardChar(n)) } else { Cmd::Move(Movement::BackwardChar(n)) } } - KeyPress::Ctrl('J') | - KeyPress::Enter => Cmd::AcceptLine, - KeyPress::Down => Cmd::LineDownOrNextHistory, - KeyPress::Up => Cmd::LineUpOrPreviousHistory, - KeyPress::Ctrl('R') => Cmd::ReverseSearchHistory, - KeyPress::Ctrl('S') => Cmd::ForwardSearchHistory, // most terminals override Ctrl+S to suspend execution - KeyPress::Ctrl('T') => Cmd::TransposeChars, - KeyPress::Ctrl('U') => { + key_press!(CTRL, 'J') | + key_press!(Enter) => Cmd::AcceptLine, + key_press!(Down) => Cmd::LineDownOrNextHistory, + key_press!(Up) => Cmd::LineUpOrPreviousHistory, + key_press!(CTRL, 'R') => Cmd::ReverseSearchHistory, + key_press!(CTRL, 'S') => Cmd::ForwardSearchHistory, // most terminals override Ctrl+S to suspend execution + key_press!(CTRL, 'T') => Cmd::TransposeChars, + key_press!(CTRL, 'U') => { if positive { Cmd::Kill(Movement::BeginningOfLine) } else { Cmd::Kill(Movement::EndOfLine) } }, - KeyPress::Ctrl('Q') | // most terminals override Ctrl+Q to resume execution - KeyPress::Ctrl('V') => Cmd::QuotedInsert, - KeyPress::Ctrl('W') => { + key_press!(CTRL, 'Q') | // most terminals override Ctrl+Q to resume execution + key_press!(CTRL, 'V') => Cmd::QuotedInsert, + key_press!(CTRL, 'W') => { if positive { Cmd::Kill(Movement::BackwardWord(n, Word::Big)) } else { Cmd::Kill(Movement::ForwardWord(n, At::AfterEnd, Word::Big)) } } - KeyPress::Ctrl('Y') => { + key_press!(CTRL, 'Y') => { if positive { Cmd::Yank(n, Anchor::Before) } else { Cmd::Unknown // TODO Validate } } - KeyPress::Ctrl('Z') => Cmd::Suspend, - KeyPress::Ctrl('_') => Cmd::Undo(n), - KeyPress::UnknownEscSeq => Cmd::Noop, - KeyPress::BracketedPasteStart => { + key_press!(CTRL, 'Z') => Cmd::Suspend, + key_press!(CTRL, '_') => Cmd::Undo(n), + key_press!(UnknownEscSeq) => Cmd::Noop, + key_press!(BracketedPasteStart) => { let paste = rdr.read_pasted_text()?; Cmd::Insert(1, paste) }, diff --git a/src/keys.rs b/src/keys.rs index 72876d8f6a..2e895151eb 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -1,19 +1,14 @@ //! Key constants -// #[non_exhaustive] +/// A key, independent of any modifiers. See KeyPress for the equivalent with modifers. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum KeyPress { +pub enum Key { + Char(char), UnknownEscSeq, Backspace, // Ctrl('H') BackTab, BracketedPasteStart, BracketedPasteEnd, - Char(char), - ControlDown, - ControlLeft, - ControlRight, - ControlUp, - Ctrl(char), Delete, Down, End, @@ -23,68 +18,436 @@ pub enum KeyPress { Home, Insert, Left, - Meta(char), Null, PageDown, PageUp, Right, - ShiftDown, - ShiftLeft, - ShiftRight, - ShiftUp, Tab, // Ctrl('I') Up, + #[doc(hidden)] + __NonExhaustive, +} + +impl From for Key { + #[inline] + fn from(c: char) -> Self { + Self::Char(c) + } +} + +impl Key { + #[inline] + pub const fn with_mods(self, mods: KeyMods) -> KeyPress { + KeyPress::new(self, mods) + } + + #[inline] + pub const fn shift(self) -> KeyPress { + self.with_mods(KeyMods::SHIFT) + } + + #[inline] + pub const fn ctrl(self) -> KeyPress { + self.with_mods(KeyMods::CTRL) + } + + #[inline] + pub const fn meta(self) -> KeyPress { + self.with_mods(KeyMods::META) + } +} + +bitflags::bitflags! { + /// The set of modifier keys that were triggered along with a key press. + pub struct KeyMods: u8 { + const CTRL = 0b0001; + const META = 0b0010; + const SHIFT = 0b0100; + // TODO: Should there be an `ALT`? + + const NONE = 0; + const CTRL_SHIFT = Self::CTRL.bits | Self::META.bits; + const META_SHIFT = Self::META.bits | Self::SHIFT.bits; + const CTRL_META = Self::META.bits | Self::CTRL.bits; + const CTRL_META_SHIFT = Self::META.bits | Self::CTRL.bits | Self::SHIFT.bits; + } +} + +impl KeyMods { + #[inline] + pub fn ctrl_meta_shift(ctrl: bool, meta: bool, shift: bool) -> Self { + (if ctrl { Self::CTRL } else { Self::NONE }) + | (if meta { Self::META } else { Self::NONE }) + | (if shift { Self::SHIFT } else { Self::NONE }) + } +} + +/// A key press is key and some modifiers. Note that there's overlap between +/// keys (ctrl-H and delete, for example), and not all keys may be represented. +/// +/// See the [`key_press!`] macro which allows using these in a pattern (e.g. a +/// match / if let binding) conveniently, but can also be used to construct +/// KeyPress values. +/// +/// ## Notes +/// - for a `Key::Char` with modifiers, the upper-case character should be used. +/// e.g. `key_press!(CTRL, 'A')` and not `key_press!(CTRL, 'a')`. +/// - Upper-case letters generally will not have `KeyMods::SHIFT` associated with +/// them. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct KeyPress { + pub key: Key, + pub mods: KeyMods, +} + +impl From for KeyPress { + #[inline] + fn from(k: Key) -> Self { + Self::new(k, KeyMods::NONE) + } +} +impl From for KeyPress { + #[inline] + fn from(k: char) -> Self { + Self::new(k.into(), KeyMods::NONE) + } +} + +impl KeyPress { + #[inline] + pub const fn new(key: Key, mods: KeyMods) -> Self { + Self { key, mods } + } + + #[inline] + pub fn normal(k: impl Into) -> Self { + Self::new(k.into(), KeyMods::NONE) + } + + #[inline] + pub fn ctrl(k: impl Into) -> Self { + Self::new(k.into(), KeyMods::CTRL) + } + + #[inline] + pub fn shift(k: impl Into) -> Self { + Self::new(k.into(), KeyMods::SHIFT) + } + + #[inline] + pub fn meta(k: impl Into) -> Self { + Self::new(k.into(), KeyMods::META) + } + + // These are partially here because make updating a lot easier (just turn + // `KeyPress::Home` into `KeyPress::HOME`). OTOH, + + /// Constant value representing an unmodified press of `Key::Backspace`. + pub const BACKSPACE: Self = Self::new(Key::Backspace, KeyMods::NONE); + + /// Constant value representing an unmodified press of `Key::BackTab`. + pub const BACK_TAB: Self = Self::new(Key::BackTab, KeyMods::NONE); + + /// Constant value representing an unmodified press of `Key::Delete`. + pub const DELETE: Self = Self::new(Key::Delete, KeyMods::NONE); + + /// Constant value representing an unmodified press of `Key::Down`. + pub const DOWN: Self = Self::new(Key::Down, KeyMods::NONE); + + /// Constant value representing an unmodified press of `Key::End`. + pub const END: Self = Self::new(Key::End, KeyMods::NONE); + + /// Constant value representing an unmodified press of `Key::Enter`. + pub const ENTER: Self = Self::new(Key::Enter, KeyMods::NONE); + + /// Constant value representing an unmodified press of `Key::Esc`. + pub const ESC: Self = Self::new(Key::Esc, KeyMods::NONE); + + /// Constant value representing an unmodified press of `Key::Home`. + pub const HOME: Self = Self::new(Key::Home, KeyMods::NONE); + + /// Constant value representing an unmodified press of `Key::Insert`. + pub const INSERT: Self = Self::new(Key::Insert, KeyMods::NONE); + + /// Constant value representing an unmodified press of `Key::Left`. + pub const LEFT: Self = Self::new(Key::Left, KeyMods::NONE); + + /// Constant value representing an unmodified press of `Key::PageDown`. + pub const PAGE_DOWN: Self = Self::new(Key::PageDown, KeyMods::NONE); + + /// Constant value representing an unmodified press of `Key::PageUp`. + pub const PAGE_UP: Self = Self::new(Key::PageUp, KeyMods::NONE); + + /// Constant value representing an unmodified press of `Key::Right`. + pub const RIGHT: Self = Self::new(Key::Right, KeyMods::NONE); + + /// Constant value representing an unmodified press of `Key::Tab`. + pub const TAB: Self = Self::new(Key::Tab, KeyMods::NONE); + + /// Constant value representing an unmodified press of `Key::Up`. + pub const UP: Self = Self::new(Key::Up, KeyMods::NONE); +} + +/// Macro to work around the fact that you can't use the result of a function as +/// a pattern. This basically exists so that you can match on the result. +/// +/// # Usage: +/// ``` +/// # use rustyline::{key_press, KeyPress}; +/// fn handle_key_press(k: KeyPress) { +/// match k { +/// key_press!('c') => println!("C was pressed"), +/// key_press!(CTRL, 'o') => println!("CTRL-o was pressed"), +/// key_press!(Right) => println!("Right was pressed"), +/// key_press!(SHIFT, Left) => println!("Shift-left were pressed"), +/// key_press!(Char(c)) => println!("the char {} was pressed", c), +/// key_press!(META, c @ '0'..='9') | key_press!(c @ 'a'..='z') => { +/// # let _ = c; +/// println!("You get the idea..."); +/// } +/// _ => {} +/// } +/// } +/// ``` +#[macro_export] +macro_rules! key_press { + // This is written pretty weirdly but I couldn't find a less verbose way of + // doing it (keep in mind that the macro expander doesn't backtrack and + // accepts the first thing that could match). It also makes the code many, + // many times more readable, IMO. + + // That said... This macro itself? Not that readable. + + (Backspace) => { $crate::KeyPress { key: $crate::Key::Backspace, mods: $crate::KeyMods::NONE } }; + (BackTab) => { $crate::KeyPress { key: $crate::Key::BackTab, mods: $crate::KeyMods::NONE } }; + (Delete) => { $crate::KeyPress { key: $crate::Key::Delete, mods: $crate::KeyMods::NONE } }; + (Down) => { $crate::KeyPress { key: $crate::Key::Down, mods: $crate::KeyMods::NONE } }; + (End) => { $crate::KeyPress { key: $crate::Key::End, mods: $crate::KeyMods::NONE } }; + (Enter) => { $crate::KeyPress { key: $crate::Key::Enter, mods: $crate::KeyMods::NONE } }; + (Esc) => { $crate::KeyPress { key: $crate::Key::Esc, mods: $crate::KeyMods::NONE } }; + (Home) => { $crate::KeyPress { key: $crate::Key::Home, mods: $crate::KeyMods::NONE } }; + (Insert) => { $crate::KeyPress { key: $crate::Key::Insert, mods: $crate::KeyMods::NONE } }; + (Left) => { $crate::KeyPress { key: $crate::Key::Left, mods: $crate::KeyMods::NONE } }; + (PageDown) => { $crate::KeyPress { key: $crate::Key::PageDown, mods: $crate::KeyMods::NONE } }; + (PageUp) => { $crate::KeyPress { key: $crate::Key::PageUp, mods: $crate::KeyMods::NONE } }; + (Right) => { $crate::KeyPress { key: $crate::Key::Right, mods: $crate::KeyMods::NONE } }; + (Tab) => { $crate::KeyPress { key: $crate::Key::Tab, mods: $crate::KeyMods::NONE } }; + (Up) => { $crate::KeyPress { key: $crate::Key::Up, mods: $crate::KeyMods::NONE } }; + (Null) => { $crate::KeyPress { key: $crate::Key::Null, mods: $crate::KeyMods::NONE }}; + (UnknownEscSeq) => { $crate::KeyPress { key: $crate::Key::UnknownEscSeq, mods: $crate::KeyMods::NONE } }; + (BracketedPasteStart) => { $crate::KeyPress { key: $crate::Key::BracketedPasteStart, mods: $crate::KeyMods::NONE }}; + (BracketedPasteEnd) => { $crate::KeyPress { key: $crate::Key::BracketedPasteEnd, mods: $crate::KeyMods::NONE }}; + + (Char($($c:tt)*)) => { $crate::KeyPress { key: $crate::Key::Char($($c)*), mods: $crate::KeyMods::NONE } }; + (F($($c:tt)*)) => { $crate::KeyPress { key: $crate::Key::F($($c)*), mods: $crate::KeyMods::NONE } }; + + (SHIFT, $($t:tt)*) => { $crate::KeyPress { key: $crate::key_press!(@__just_key $($t)*), mods: $crate::KeyMods::SHIFT } }; + (CTRL, $($t:tt)*) => { $crate::KeyPress { key: $crate::key_press!(@__just_key $($t)*), mods: $crate::KeyMods::CTRL } }; + (META, $($t:tt)*) => { $crate::KeyPress { key: $crate::key_press!(@__just_key $($t)*), mods: $crate::KeyMods::META } }; + + (NONE, $($t:tt)*) => { $crate::KeyPress { key: $crate::key_press!(@__just_key $($t)*), mods: $crate::KeyMods::NONE } }; + (CTRL_SHIFT, $($t:tt)*) => { $crate::KeyPress { key: $crate::key_press!(@__just_key $($t)*), mods: $crate::KeyMods::CTRL_SHIFT } }; + (META_SHIFT, $($t:tt)*) => { $crate::KeyPress { key: $crate::key_press!(@__just_key $($t)*), mods: $crate::KeyMods::META_SHIFT } }; + (CTRL_META, $($t:tt)*) => { $crate::KeyPress { key: $crate::key_press!(@__just_key $($t)*), mods: $crate::KeyMods::CTRL_META } }; + (CTRL_META_SHIFT, $($t:tt)*) => { $crate::KeyPress { key: $crate::key_press!(@__just_key $($t)*), mods: $crate::KeyMods::CTRL_META_SHIFT } }; + (_, $($t:tt)*) => { $crate::KeyPress { key: $crate::key_press!(@__just_key $($t)*), mods: _ } }; + + // To easy to accidentally bind this if you get `SHIFT_CTRL` and `CTRL_SHIFT` mixed up... + // ($mods:pat, $($t:tt)*) => { $crate::KeyPress { key: $crate::key_press!(@__just_key $($t)*), mods: $mods } }; + + ($ch:literal) => { $crate::KeyPress { key: $crate::Key::Char($ch), mods: $crate::KeyMods::NONE } }; + + // Allow patterns like `key_press(d @ '0'..='9')`, but explicitly forbid + // idents as it's confusing (when does `if let key_press!(c) = some_key {}` + // match?) + ($ch:ident) => { compile_error!("`key_press!(c)` is ambiguous, try `key_press!(Char(c))`?") }; + + ($ch:pat) => { $crate::KeyPress { key: $crate::Key::Char($ch), mods: $crate::KeyMods::NONE } }; + + // Has to be duplicated. (Forwarding the above to this causes it to fail to + // match...) + (@__just_key Backspace) => { $crate::Key::Backspace }; + (@__just_key BackTab) => { $crate::Key::BackTab }; + (@__just_key Delete) => { $crate::Key::Delete }; + (@__just_key Down) => { $crate::Key::Down }; + (@__just_key End) => { $crate::Key::End }; + (@__just_key Enter) => { $crate::Key::Enter }; + (@__just_key Esc) => { $crate::Key::Esc }; + (@__just_key Home) => { $crate::Key::Home }; + (@__just_key Insert) => { $crate::Key::Insert }; + (@__just_key Left) => { $crate::Key::Left }; + (@__just_key PageDown) => { $crate::Key::PageDown }; + (@__just_key PageUp) => { $crate::Key::PageUp }; + (@__just_key Right) => { $crate::Key::Right }; + (@__just_key Tab) => { $crate::Key::Tab }; + (@__just_key Up) => { $crate::Key::Up }; + (@__just_key Null) => { $crate::Key::Null }; + (@__just_key UnknownEscSeq) => { $crate::Key::UnknownEscSeq }; + (@__just_key BracketedPasteStart) => { $crate::Key::BracketedPasteStart }; + (@__just_key BracketedPasteEnd) => { $crate::Key::BracketedPasteEnd }; + (@__just_key Char($($c:tt)*)) => { $crate::Key::Char($($c)*) }; + (@__just_key F($($c:tt)*)) => { $crate::Key::F($($c)*) }; + + (@__just_key $ch:literal) => { $crate::Key::Char($ch) }; + + (@__just_key $ch:ident) => { compile_error!("`key_press!(, c)` is ambiguous, try `key_press!(, Char(c))`?") }; + + (@__just_key $ch:pat) => { $crate::Key::Char($ch) }; } #[cfg(any(windows, unix))] pub fn char_to_key_press(c: char) -> KeyPress { if !c.is_control() { - return KeyPress::Char(c); + return c.into(); } #[allow(clippy::match_same_arms)] match c { - '\x00' => KeyPress::Ctrl(' '), - '\x01' => KeyPress::Ctrl('A'), - '\x02' => KeyPress::Ctrl('B'), - '\x03' => KeyPress::Ctrl('C'), - '\x04' => KeyPress::Ctrl('D'), - '\x05' => KeyPress::Ctrl('E'), - '\x06' => KeyPress::Ctrl('F'), - '\x07' => KeyPress::Ctrl('G'), - '\x08' => KeyPress::Backspace, // '\b' - '\x09' => KeyPress::Tab, // '\t' - '\x0a' => KeyPress::Ctrl('J'), // '\n' (10) - '\x0b' => KeyPress::Ctrl('K'), - '\x0c' => KeyPress::Ctrl('L'), - '\x0d' => KeyPress::Enter, // '\r' (13) - '\x0e' => KeyPress::Ctrl('N'), - '\x0f' => KeyPress::Ctrl('O'), - '\x10' => KeyPress::Ctrl('P'), - '\x12' => KeyPress::Ctrl('R'), - '\x13' => KeyPress::Ctrl('S'), - '\x14' => KeyPress::Ctrl('T'), - '\x15' => KeyPress::Ctrl('U'), - '\x16' => KeyPress::Ctrl('V'), - '\x17' => KeyPress::Ctrl('W'), - '\x18' => KeyPress::Ctrl('X'), - '\x19' => KeyPress::Ctrl('Y'), - '\x1a' => KeyPress::Ctrl('Z'), - '\x1b' => KeyPress::Esc, // Ctrl-[ - '\x1c' => KeyPress::Ctrl('\\'), - '\x1d' => KeyPress::Ctrl(']'), - '\x1e' => KeyPress::Ctrl('^'), - '\x1f' => KeyPress::Ctrl('_'), - '\x7f' => KeyPress::Backspace, // Rubout - _ => KeyPress::Null, + '\x00' => KeyPress::ctrl(' '), + '\x01' => KeyPress::ctrl('A'), + '\x02' => KeyPress::ctrl('B'), + '\x03' => KeyPress::ctrl('C'), + '\x04' => KeyPress::ctrl('D'), + '\x05' => KeyPress::ctrl('E'), + '\x06' => KeyPress::ctrl('F'), + '\x07' => KeyPress::ctrl('G'), + '\x08' => KeyPress::BACKSPACE, // '\b' + '\x09' => KeyPress::TAB, // '\t' + '\x0a' => KeyPress::ctrl('J'), // '\n' (1) + '\x0b' => KeyPress::ctrl('K'), + '\x0c' => KeyPress::ctrl('L'), + '\x0d' => KeyPress::ENTER, // '\r' (13) + '\x0e' => KeyPress::ctrl('N'), + '\x0f' => KeyPress::ctrl('O'), + '\x10' => KeyPress::ctrl('P'), + '\x12' => KeyPress::ctrl('R'), + '\x13' => KeyPress::ctrl('S'), + '\x14' => KeyPress::ctrl('T'), + '\x15' => KeyPress::ctrl('U'), + '\x16' => KeyPress::ctrl('V'), + '\x17' => KeyPress::ctrl('W'), + '\x18' => KeyPress::ctrl('X'), + '\x19' => KeyPress::ctrl('Y'), + '\x1a' => KeyPress::ctrl('Z'), + '\x1b' => KeyPress::ESC, // Ctrl-[ + '\x1c' => KeyPress::ctrl('\\'), + '\x1d' => KeyPress::ctrl(']'), + '\x1e' => KeyPress::ctrl('^'), + '\x1f' => KeyPress::ctrl('_'), + '\x7f' => KeyPress::BACKSPACE, // Rubout + _ => Key::Null.into(), } } #[cfg(test)] mod tests { - use super::{char_to_key_press, KeyPress}; - + use super::{char_to_key_press, Key, KeyMods, KeyPress}; + use assert_matches::assert_matches; #[test] fn char_to_key() { - assert_eq!(KeyPress::Esc, char_to_key_press('\x1b')); + assert_eq!(KeyPress::ESC, char_to_key_press('\x1b')); + } + #[test] + fn test_macro_keyidents() { + assert_matches!(Key::Backspace.into(), key_press!(Backspace)); + assert_matches!(Key::BackTab.into(), key_press!(BackTab)); + assert_matches!(Key::Delete.into(), key_press!(Delete)); + assert_matches!(Key::Down.into(), key_press!(Down)); + assert_matches!(Key::End.into(), key_press!(End)); + assert_matches!(Key::Enter.into(), key_press!(Enter)); + assert_matches!(Key::Esc.into(), key_press!(Esc)); + assert_matches!(Key::Home.into(), key_press!(Home)); + assert_matches!(Key::Insert.into(), key_press!(Insert)); + assert_matches!(Key::Left.into(), key_press!(Left)); + assert_matches!(Key::PageDown.into(), key_press!(PageDown)); + assert_matches!(Key::PageUp.into(), key_press!(PageUp)); + assert_matches!(Key::Right.into(), key_press!(Right)); + assert_matches!(Key::Tab.into(), key_press!(Tab)); + assert_matches!(Key::Up.into(), key_press!(Up)); + assert_matches!(Key::Null.into(), key_press!(Null)); + assert_matches!(Key::UnknownEscSeq.into(), key_press!(UnknownEscSeq)); + + assert_matches!(Key::Char('c').into(), key_press!(Char('c'))); + + assert_matches!(Key::F(3).into(), key_press!(F(3))); + assert_matches!( + Key::BracketedPasteStart.into(), + key_press!(BracketedPasteStart) + ); + assert_matches!(Key::BracketedPasteEnd.into(), key_press!(BracketedPasteEnd)); + } + + #[test] + fn test_macro_patterns() { + assert_matches!(KeyPress::from('x'), key_press!(Char(_c))); + assert_matches!(KeyPress::from('x'), key_press!(Char(..))); + assert_matches!(KeyPress::from('x'), key_press!(Char('a'..='z'))); + assert_matches!(KeyPress::from('x'), key_press!('a'..='z')); + assert_matches!(KeyPress::from('x'), key_press!('x')); + assert_matches!(KeyPress::from('x'), key_press!(letter @ 'a' ..= 'z') if letter == 'x'); + } + + #[test] + fn test_macro_mods() { + assert_matches!( + KeyPress::new('x'.into(), KeyMods::SHIFT), + key_press!(SHIFT, 'x') + ); + assert_matches!( + KeyPress::new('x'.into(), KeyMods::CTRL), + key_press!(CTRL, 'x') + ); + assert_matches!( + KeyPress::new('x'.into(), KeyMods::META), + key_press!(META, 'x') + ); + assert_matches!( + KeyPress::new('x'.into(), KeyMods::NONE), + key_press!(NONE, 'x') + ); + + assert_matches!( + KeyPress::new('x'.into(), KeyMods::CTRL_SHIFT), + key_press!(CTRL_SHIFT, 'x') + ); + assert_matches!( + KeyPress::new('x'.into(), KeyMods::META_SHIFT), + key_press!(META_SHIFT, 'x') + ); + assert_matches!( + KeyPress::new('x'.into(), KeyMods::CTRL_META), + key_press!(CTRL_META, 'x') + ); + assert_matches!( + KeyPress::new('x'.into(), KeyMods::CTRL_META_SHIFT), + key_press!(CTRL_META_SHIFT, 'x') + ); + } + #[test] + fn test_macro_mod_key_idents() { + assert_matches!(Key::Backspace.into(), key_press!(NONE, Backspace)); + assert_matches!(Key::BackTab.into(), key_press!(NONE, BackTab)); + assert_matches!(Key::Delete.into(), key_press!(NONE, Delete)); + assert_matches!(Key::Down.into(), key_press!(NONE, Down)); + assert_matches!(Key::End.into(), key_press!(NONE, End)); + assert_matches!(Key::Enter.into(), key_press!(NONE, Enter)); + assert_matches!(Key::Esc.into(), key_press!(NONE, Esc)); + assert_matches!(Key::Home.into(), key_press!(NONE, Home)); + assert_matches!(Key::Insert.into(), key_press!(NONE, Insert)); + assert_matches!(Key::Left.into(), key_press!(NONE, Left)); + assert_matches!(Key::PageDown.into(), key_press!(NONE, PageDown)); + assert_matches!(Key::PageUp.into(), key_press!(NONE, PageUp)); + assert_matches!(Key::Right.into(), key_press!(NONE, Right)); + assert_matches!(Key::Tab.into(), key_press!(NONE, Tab)); + assert_matches!(Key::Up.into(), key_press!(NONE, Up)); + assert_matches!(Key::Null.into(), key_press!(NONE, Null)); + assert_matches!(Key::UnknownEscSeq.into(), key_press!(NONE, UnknownEscSeq)); + + assert_matches!(Key::Char('c').into(), key_press!(NONE, Char('c'))); + + assert_matches!(Key::F(3).into(), key_press!(NONE, F(3))); + assert_matches!( + Key::BracketedPasteStart.into(), + key_press!(NONE, BracketedPasteStart) + ); } } diff --git a/src/lib.rs b/src/lib.rs index 7fcfde85d2..14a3c4fbf6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,7 +16,8 @@ //! } //! ``` // #![feature(non_exhaustive)] - +#[macro_use] +mod keys; pub mod completion; pub mod config; mod edit; @@ -25,7 +26,6 @@ pub mod highlight; pub mod hint; pub mod history; mod keymap; -mod keys; mod kill_ring; mod layout; pub mod line_buffer; @@ -55,7 +55,7 @@ use crate::hint::Hinter; use crate::history::{Direction, History}; pub use crate::keymap::{Anchor, At, CharSearch, Cmd, Movement, RepeatCount, Word}; use crate::keymap::{InputState, Refresher}; -pub use crate::keys::KeyPress; +pub use crate::keys::{Key, KeyMods, KeyPress}; use crate::kill_ring::{KillRing, Mode}; use crate::line_buffer::WordAction; use crate::validate::Validator; diff --git a/src/test/common.rs b/src/test/common.rs index 798d61904c..90f815959f 100644 --- a/src/test/common.rs +++ b/src/test/common.rs @@ -2,7 +2,7 @@ use super::{assert_cursor, assert_line, assert_line_with_initial, init_editor}; use crate::config::EditMode; use crate::error::ReadlineError; -use crate::keys::KeyPress; +use crate::keys::{Key, KeyPress}; #[test] fn home_key() { @@ -10,13 +10,13 @@ fn home_key() { assert_cursor( *mode, ("", ""), - &[KeyPress::Home, KeyPress::Enter], + &[Key::Home.into(), Key::Enter.into()], ("", ""), ); assert_cursor( *mode, ("Hi", ""), - &[KeyPress::Home, KeyPress::Enter], + &[Key::Home.into(), Key::Enter.into()], ("", "Hi"), ); if *mode == EditMode::Vi { @@ -24,7 +24,7 @@ fn home_key() { assert_cursor( *mode, ("Hi", ""), - &[KeyPress::Esc, KeyPress::Home, KeyPress::Enter], + &[Key::Esc.into(), Key::Home.into(), Key::Enter.into()], ("", "Hi"), ); } @@ -34,17 +34,17 @@ fn home_key() { #[test] fn end_key() { for mode in &[EditMode::Emacs, EditMode::Vi] { - assert_cursor(*mode, ("", ""), &[KeyPress::End, KeyPress::Enter], ("", "")); + assert_cursor(*mode, ("", ""), &[KeyPress::END, KeyPress::ENTER], ("", "")); assert_cursor( *mode, ("H", "i"), - &[KeyPress::End, KeyPress::Enter], + &[KeyPress::END, KeyPress::ENTER], ("Hi", ""), ); assert_cursor( *mode, ("", "Hi"), - &[KeyPress::End, KeyPress::Enter], + &[KeyPress::END, KeyPress::ENTER], ("Hi", ""), ); if *mode == EditMode::Vi { @@ -52,7 +52,7 @@ fn end_key() { assert_cursor( *mode, ("", "Hi"), - &[KeyPress::Esc, KeyPress::End, KeyPress::Enter], + &[KeyPress::ESC, KeyPress::END, KeyPress::ENTER], ("Hi", ""), ); } @@ -65,19 +65,19 @@ fn left_key() { assert_cursor( *mode, ("Hi", ""), - &[KeyPress::Left, KeyPress::Enter], + &[KeyPress::LEFT, KeyPress::ENTER], ("H", "i"), ); assert_cursor( *mode, ("H", "i"), - &[KeyPress::Left, KeyPress::Enter], + &[KeyPress::LEFT, KeyPress::ENTER], ("", "Hi"), ); assert_cursor( *mode, ("", "Hi"), - &[KeyPress::Left, KeyPress::Enter], + &[KeyPress::LEFT, KeyPress::ENTER], ("", "Hi"), ); if *mode == EditMode::Vi { @@ -85,7 +85,7 @@ fn left_key() { assert_cursor( *mode, ("Bye", ""), - &[KeyPress::Esc, KeyPress::Left, KeyPress::Enter], + &[KeyPress::ESC, KeyPress::LEFT, KeyPress::ENTER], ("B", "ye"), ); } @@ -98,25 +98,25 @@ fn right_key() { assert_cursor( *mode, ("", ""), - &[KeyPress::Right, KeyPress::Enter], + &[KeyPress::RIGHT, KeyPress::ENTER], ("", ""), ); assert_cursor( *mode, ("", "Hi"), - &[KeyPress::Right, KeyPress::Enter], + &[KeyPress::RIGHT, KeyPress::ENTER], ("H", "i"), ); assert_cursor( *mode, ("B", "ye"), - &[KeyPress::Right, KeyPress::Enter], + &[KeyPress::RIGHT, KeyPress::ENTER], ("By", "e"), ); assert_cursor( *mode, ("H", "i"), - &[KeyPress::Right, KeyPress::Enter], + &[KeyPress::RIGHT, KeyPress::ENTER], ("Hi", ""), ); if *mode == EditMode::Vi { @@ -124,7 +124,7 @@ fn right_key() { assert_cursor( *mode, ("", "Hi"), - &[KeyPress::Esc, KeyPress::Right, KeyPress::Enter], + &[KeyPress::ESC, KeyPress::RIGHT, KeyPress::ENTER], ("H", "i"), ); } @@ -134,22 +134,22 @@ fn right_key() { #[test] fn enter_key() { for mode in &[EditMode::Emacs, EditMode::Vi] { - assert_line(*mode, &[KeyPress::Enter], ""); - assert_line(*mode, &[KeyPress::Char('a'), KeyPress::Enter], "a"); - assert_line_with_initial(*mode, ("Hi", ""), &[KeyPress::Enter], "Hi"); - assert_line_with_initial(*mode, ("", "Hi"), &[KeyPress::Enter], "Hi"); - assert_line_with_initial(*mode, ("H", "i"), &[KeyPress::Enter], "Hi"); + assert_line(*mode, &[KeyPress::ENTER], ""); + assert_line(*mode, &[KeyPress::normal('a'), KeyPress::ENTER], "a"); + assert_line_with_initial(*mode, ("Hi", ""), &[KeyPress::ENTER], "Hi"); + assert_line_with_initial(*mode, ("", "Hi"), &[KeyPress::ENTER], "Hi"); + assert_line_with_initial(*mode, ("H", "i"), &[KeyPress::ENTER], "Hi"); if *mode == EditMode::Vi { // vi command mode - assert_line(*mode, &[KeyPress::Esc, KeyPress::Enter], ""); + assert_line(*mode, &[KeyPress::ESC, KeyPress::ENTER], ""); assert_line( *mode, - &[KeyPress::Char('a'), KeyPress::Esc, KeyPress::Enter], + &[KeyPress::normal('a'), KeyPress::ESC, KeyPress::ENTER], "a", ); - assert_line_with_initial(*mode, ("Hi", ""), &[KeyPress::Esc, KeyPress::Enter], "Hi"); - assert_line_with_initial(*mode, ("", "Hi"), &[KeyPress::Esc, KeyPress::Enter], "Hi"); - assert_line_with_initial(*mode, ("H", "i"), &[KeyPress::Esc, KeyPress::Enter], "Hi"); + assert_line_with_initial(*mode, ("Hi", ""), &[KeyPress::ESC, KeyPress::ENTER], "Hi"); + assert_line_with_initial(*mode, ("", "Hi"), &[KeyPress::ESC, KeyPress::ENTER], "Hi"); + assert_line_with_initial(*mode, ("H", "i"), &[KeyPress::ESC, KeyPress::ENTER], "Hi"); } } } @@ -157,14 +157,14 @@ fn enter_key() { #[test] fn newline_key() { for mode in &[EditMode::Emacs, EditMode::Vi] { - assert_line(*mode, &[KeyPress::Ctrl('J')], ""); - assert_line(*mode, &[KeyPress::Char('a'), KeyPress::Ctrl('J')], "a"); + assert_line(*mode, &[KeyPress::ctrl('J')], ""); + assert_line(*mode, &[KeyPress::normal('a'), KeyPress::ctrl('J')], "a"); if *mode == EditMode::Vi { // vi command mode - assert_line(*mode, &[KeyPress::Esc, KeyPress::Ctrl('J')], ""); + assert_line(*mode, &[KeyPress::ESC, KeyPress::ctrl('J')], ""); assert_line( *mode, - &[KeyPress::Char('a'), KeyPress::Esc, KeyPress::Ctrl('J')], + &[KeyPress::normal('a'), KeyPress::ESC, KeyPress::ctrl('J')], "a", ); } @@ -174,36 +174,36 @@ fn newline_key() { #[test] fn eof_key() { for mode in &[EditMode::Emacs, EditMode::Vi] { - let mut editor = init_editor(*mode, &[KeyPress::Ctrl('D')]); + let mut editor = init_editor(*mode, &[KeyPress::ctrl('D')]); let err = editor.readline(">>"); assert_matches!(err, Err(ReadlineError::Eof)); } assert_line( EditMode::Emacs, - &[KeyPress::Char('a'), KeyPress::Ctrl('D'), KeyPress::Enter], + &[KeyPress::normal('a'), KeyPress::ctrl('D'), KeyPress::ENTER], "a", ); assert_line( EditMode::Vi, - &[KeyPress::Char('a'), KeyPress::Ctrl('D')], + &[KeyPress::normal('a'), KeyPress::ctrl('D')], "a", ); assert_line( EditMode::Vi, - &[KeyPress::Char('a'), KeyPress::Esc, KeyPress::Ctrl('D')], + &[KeyPress::normal('a'), KeyPress::ESC, KeyPress::ctrl('D')], "a", ); assert_line_with_initial( EditMode::Emacs, ("", "Hi"), - &[KeyPress::Ctrl('D'), KeyPress::Enter], + &[KeyPress::ctrl('D'), KeyPress::ENTER], "i", ); - assert_line_with_initial(EditMode::Vi, ("", "Hi"), &[KeyPress::Ctrl('D')], "Hi"); + assert_line_with_initial(EditMode::Vi, ("", "Hi"), &[KeyPress::ctrl('D')], "Hi"); assert_line_with_initial( EditMode::Vi, ("", "Hi"), - &[KeyPress::Esc, KeyPress::Ctrl('D')], + &[KeyPress::ESC, KeyPress::ctrl('D')], "Hi", ); } @@ -211,16 +211,16 @@ fn eof_key() { #[test] fn interrupt_key() { for mode in &[EditMode::Emacs, EditMode::Vi] { - let mut editor = init_editor(*mode, &[KeyPress::Ctrl('C')]); + let mut editor = init_editor(*mode, &[KeyPress::ctrl('C')]); let err = editor.readline(">>"); assert_matches!(err, Err(ReadlineError::Interrupted)); - let mut editor = init_editor(*mode, &[KeyPress::Ctrl('C')]); + let mut editor = init_editor(*mode, &[KeyPress::ctrl('C')]); let err = editor.readline_with_initial(">>", ("Hi", "")); assert_matches!(err, Err(ReadlineError::Interrupted)); if *mode == EditMode::Vi { // vi command mode - let mut editor = init_editor(*mode, &[KeyPress::Esc, KeyPress::Ctrl('C')]); + let mut editor = init_editor(*mode, &[KeyPress::ESC, KeyPress::ctrl('C')]); let err = editor.readline_with_initial(">>", ("Hi", "")); assert_matches!(err, Err(ReadlineError::Interrupted)); } @@ -233,13 +233,13 @@ fn delete_key() { assert_cursor( *mode, ("a", ""), - &[KeyPress::Delete, KeyPress::Enter], + &[KeyPress::DELETE, KeyPress::ENTER], ("a", ""), ); assert_cursor( *mode, ("", "a"), - &[KeyPress::Delete, KeyPress::Enter], + &[KeyPress::DELETE, KeyPress::ENTER], ("", ""), ); if *mode == EditMode::Vi { @@ -247,7 +247,7 @@ fn delete_key() { assert_cursor( *mode, ("", "a"), - &[KeyPress::Esc, KeyPress::Delete, KeyPress::Enter], + &[KeyPress::ESC, KeyPress::DELETE, KeyPress::ENTER], ("", ""), ); } @@ -260,13 +260,13 @@ fn ctrl_t() { assert_cursor( *mode, ("a", "b"), - &[KeyPress::Ctrl('T'), KeyPress::Enter], + &[KeyPress::ctrl('T'), KeyPress::ENTER], ("ba", ""), ); assert_cursor( *mode, ("ab", "cd"), - &[KeyPress::Ctrl('T'), KeyPress::Enter], + &[KeyPress::ctrl('T'), KeyPress::ENTER], ("acb", "d"), ); if *mode == EditMode::Vi { @@ -274,7 +274,7 @@ fn ctrl_t() { assert_cursor( *mode, ("ab", ""), - &[KeyPress::Esc, KeyPress::Ctrl('T'), KeyPress::Enter], + &[KeyPress::ESC, KeyPress::ctrl('T'), KeyPress::ENTER], ("ba", ""), ); } @@ -287,13 +287,13 @@ fn ctrl_u() { assert_cursor( *mode, ("start of line ", "end"), - &[KeyPress::Ctrl('U'), KeyPress::Enter], + &[KeyPress::ctrl('U'), KeyPress::ENTER], ("", "end"), ); assert_cursor( *mode, ("", "end"), - &[KeyPress::Ctrl('U'), KeyPress::Enter], + &[KeyPress::ctrl('U'), KeyPress::ENTER], ("", "end"), ); if *mode == EditMode::Vi { @@ -301,7 +301,7 @@ fn ctrl_u() { assert_cursor( *mode, ("start of line ", "end"), - &[KeyPress::Esc, KeyPress::Ctrl('U'), KeyPress::Enter], + &[KeyPress::ESC, KeyPress::ctrl('U'), KeyPress::ENTER], ("", " end"), ); } @@ -315,7 +315,7 @@ fn ctrl_v() { assert_cursor( *mode, ("", ""), - &[KeyPress::Ctrl('V'), KeyPress::Char('\t'), KeyPress::Enter], + &[KeyPress::ctrl('V'), KeyPress::normal('\t'), KeyPress::ENTER], ("\t", ""), ); if *mode == EditMode::Vi { @@ -324,10 +324,10 @@ fn ctrl_v() { *mode, ("", ""), &[ - KeyPress::Esc, - KeyPress::Ctrl('V'), - KeyPress::Char('\t'), - KeyPress::Enter, + KeyPress::ESC, + KeyPress::ctrl('V'), + KeyPress::normal('\t'), + KeyPress::ENTER, ], ("\t", ""), ); @@ -341,13 +341,13 @@ fn ctrl_w() { assert_cursor( *mode, ("Hello, ", "world"), - &[KeyPress::Ctrl('W'), KeyPress::Enter], + &[KeyPress::ctrl('W'), KeyPress::ENTER], ("", "world"), ); assert_cursor( *mode, ("Hello, world.", ""), - &[KeyPress::Ctrl('W'), KeyPress::Enter], + &[KeyPress::ctrl('W'), KeyPress::ENTER], ("Hello, ", ""), ); if *mode == EditMode::Vi { @@ -355,7 +355,7 @@ fn ctrl_w() { assert_cursor( *mode, ("Hello, world.", ""), - &[KeyPress::Esc, KeyPress::Ctrl('W'), KeyPress::Enter], + &[KeyPress::ESC, KeyPress::ctrl('W'), KeyPress::ENTER], ("Hello, ", "."), ); } @@ -368,7 +368,7 @@ fn ctrl_y() { assert_cursor( *mode, ("Hello, ", "world"), - &[KeyPress::Ctrl('W'), KeyPress::Ctrl('Y'), KeyPress::Enter], + &[KeyPress::ctrl('W'), KeyPress::ctrl('Y'), KeyPress::ENTER], ("Hello, ", "world"), ); } @@ -380,7 +380,7 @@ fn ctrl__() { assert_cursor( *mode, ("Hello, ", "world"), - &[KeyPress::Ctrl('W'), KeyPress::Ctrl('_'), KeyPress::Enter], + &[KeyPress::ctrl('W'), KeyPress::ctrl('_'), KeyPress::ENTER], ("Hello, ", "world"), ); if *mode == EditMode::Vi { @@ -389,10 +389,10 @@ fn ctrl__() { *mode, ("Hello, ", "world"), &[ - KeyPress::Esc, - KeyPress::Ctrl('W'), - KeyPress::Ctrl('_'), - KeyPress::Enter, + KeyPress::ESC, + KeyPress::ctrl('W'), + KeyPress::ctrl('_'), + KeyPress::ENTER, ], ("Hello,", " world"), ); diff --git a/src/test/emacs.rs b/src/test/emacs.rs index 9fd395e54c..f7ed161777 100644 --- a/src/test/emacs.rs +++ b/src/test/emacs.rs @@ -8,13 +8,13 @@ fn ctrl_a() { assert_cursor( EditMode::Emacs, ("Hi", ""), - &[KeyPress::Ctrl('A'), KeyPress::Enter], + &[KeyPress::ctrl('A'), KeyPress::ENTER], ("", "Hi"), ); assert_cursor( EditMode::Emacs, ("test test\n123", "foo"), - &[KeyPress::Ctrl('A'), KeyPress::Enter], + &[KeyPress::ctrl('A'), KeyPress::ENTER], ("test test\n", "123foo"), ); } @@ -24,13 +24,13 @@ fn ctrl_e() { assert_cursor( EditMode::Emacs, ("", "Hi"), - &[KeyPress::Ctrl('E'), KeyPress::Enter], + &[KeyPress::ctrl('E'), KeyPress::ENTER], ("Hi", ""), ); assert_cursor( EditMode::Emacs, ("foo", "test test\n123"), - &[KeyPress::Ctrl('E'), KeyPress::Enter], + &[KeyPress::ctrl('E'), KeyPress::ENTER], ("footest test", "\n123"), ); } @@ -40,23 +40,23 @@ fn ctrl_b() { assert_cursor( EditMode::Emacs, ("Hi", ""), - &[KeyPress::Ctrl('B'), KeyPress::Enter], + &[KeyPress::ctrl('B'), KeyPress::ENTER], ("H", "i"), ); assert_cursor( EditMode::Emacs, ("Hi", ""), - &[KeyPress::Meta('2'), KeyPress::Ctrl('B'), KeyPress::Enter], + &[KeyPress::meta('2'), KeyPress::ctrl('B'), KeyPress::ENTER], ("", "Hi"), ); assert_cursor( EditMode::Emacs, ("", "Hi"), &[ - KeyPress::Meta('-'), - KeyPress::Meta('2'), - KeyPress::Ctrl('B'), - KeyPress::Enter, + KeyPress::meta('-'), + KeyPress::meta('2'), + KeyPress::ctrl('B'), + KeyPress::ENTER, ], ("Hi", ""), ); @@ -67,23 +67,23 @@ fn ctrl_f() { assert_cursor( EditMode::Emacs, ("", "Hi"), - &[KeyPress::Ctrl('F'), KeyPress::Enter], + &[KeyPress::ctrl('F'), KeyPress::ENTER], ("H", "i"), ); assert_cursor( EditMode::Emacs, ("", "Hi"), - &[KeyPress::Meta('2'), KeyPress::Ctrl('F'), KeyPress::Enter], + &[KeyPress::meta('2'), KeyPress::ctrl('F'), KeyPress::ENTER], ("Hi", ""), ); assert_cursor( EditMode::Emacs, ("Hi", ""), &[ - KeyPress::Meta('-'), - KeyPress::Meta('2'), - KeyPress::Ctrl('F'), - KeyPress::Enter, + KeyPress::meta('-'), + KeyPress::meta('2'), + KeyPress::ctrl('F'), + KeyPress::ENTER, ], ("", "Hi"), ); @@ -94,23 +94,23 @@ fn ctrl_h() { assert_cursor( EditMode::Emacs, ("Hi", ""), - &[KeyPress::Ctrl('H'), KeyPress::Enter], + &[KeyPress::ctrl('H'), KeyPress::ENTER], ("H", ""), ); assert_cursor( EditMode::Emacs, ("Hi", ""), - &[KeyPress::Meta('2'), KeyPress::Ctrl('H'), KeyPress::Enter], + &[KeyPress::meta('2'), KeyPress::ctrl('H'), KeyPress::ENTER], ("", ""), ); assert_cursor( EditMode::Emacs, ("", "Hi"), &[ - KeyPress::Meta('-'), - KeyPress::Meta('2'), - KeyPress::Ctrl('H'), - KeyPress::Enter, + KeyPress::meta('-'), + KeyPress::meta('2'), + KeyPress::ctrl('H'), + KeyPress::ENTER, ], ("", ""), ); @@ -121,19 +121,19 @@ fn backspace() { assert_cursor( EditMode::Emacs, ("", ""), - &[KeyPress::Backspace, KeyPress::Enter], + &[KeyPress::BACKSPACE, KeyPress::ENTER], ("", ""), ); assert_cursor( EditMode::Emacs, ("Hi", ""), - &[KeyPress::Backspace, KeyPress::Enter], + &[KeyPress::BACKSPACE, KeyPress::ENTER], ("H", ""), ); assert_cursor( EditMode::Emacs, ("", "Hi"), - &[KeyPress::Backspace, KeyPress::Enter], + &[KeyPress::BACKSPACE, KeyPress::ENTER], ("", "Hi"), ); } @@ -143,37 +143,37 @@ fn ctrl_k() { assert_cursor( EditMode::Emacs, ("Hi", ""), - &[KeyPress::Ctrl('K'), KeyPress::Enter], + &[KeyPress::ctrl('K'), KeyPress::ENTER], ("Hi", ""), ); assert_cursor( EditMode::Emacs, ("", "Hi"), - &[KeyPress::Ctrl('K'), KeyPress::Enter], + &[KeyPress::ctrl('K'), KeyPress::ENTER], ("", ""), ); assert_cursor( EditMode::Emacs, ("B", "ye"), - &[KeyPress::Ctrl('K'), KeyPress::Enter], + &[KeyPress::ctrl('K'), KeyPress::ENTER], ("B", ""), ); assert_cursor( EditMode::Emacs, ("Hi", "foo\nbar"), - &[KeyPress::Ctrl('K'), KeyPress::Enter], + &[KeyPress::ctrl('K'), KeyPress::ENTER], ("Hi", "\nbar"), ); assert_cursor( EditMode::Emacs, ("Hi", "\nbar"), - &[KeyPress::Ctrl('K'), KeyPress::Enter], + &[KeyPress::ctrl('K'), KeyPress::ENTER], ("Hi", "bar"), ); assert_cursor( EditMode::Emacs, ("Hi", "bar"), - &[KeyPress::Ctrl('K'), KeyPress::Enter], + &[KeyPress::ctrl('K'), KeyPress::ENTER], ("Hi", ""), ); } @@ -183,37 +183,37 @@ fn ctrl_u() { assert_cursor( EditMode::Emacs, ("", "Hi"), - &[KeyPress::Ctrl('U'), KeyPress::Enter], + &[KeyPress::ctrl('U'), KeyPress::ENTER], ("", "Hi"), ); assert_cursor( EditMode::Emacs, ("Hi", ""), - &[KeyPress::Ctrl('U'), KeyPress::Enter], + &[KeyPress::ctrl('U'), KeyPress::ENTER], ("", ""), ); assert_cursor( EditMode::Emacs, ("B", "ye"), - &[KeyPress::Ctrl('U'), KeyPress::Enter], + &[KeyPress::ctrl('U'), KeyPress::ENTER], ("", "ye"), ); assert_cursor( EditMode::Emacs, ("foo\nbar", "Hi"), - &[KeyPress::Ctrl('U'), KeyPress::Enter], + &[KeyPress::ctrl('U'), KeyPress::ENTER], ("foo\n", "Hi"), ); assert_cursor( EditMode::Emacs, ("foo\n", "Hi"), - &[KeyPress::Ctrl('U'), KeyPress::Enter], + &[KeyPress::ctrl('U'), KeyPress::ENTER], ("foo", "Hi"), ); assert_cursor( EditMode::Emacs, ("foo", "Hi"), - &[KeyPress::Ctrl('U'), KeyPress::Enter], + &[KeyPress::ctrl('U'), KeyPress::ENTER], ("", "Hi"), ); } @@ -223,10 +223,10 @@ fn ctrl_n() { EditMode::Emacs, &["line1", "line2"], &[ - KeyPress::Ctrl('P'), - KeyPress::Ctrl('P'), - KeyPress::Ctrl('N'), - KeyPress::Enter, + KeyPress::ctrl('P'), + KeyPress::ctrl('P'), + KeyPress::ctrl('N'), + KeyPress::ENTER, ], "", ("line2", ""), @@ -238,7 +238,7 @@ fn ctrl_p() { assert_history( EditMode::Emacs, &["line1"], - &[KeyPress::Ctrl('P'), KeyPress::Enter], + &[KeyPress::ctrl('P'), KeyPress::ENTER], "", ("line1", ""), ); @@ -249,7 +249,7 @@ fn ctrl_t() { /* FIXME assert_cursor( ("ab", "cd"), - &[KeyPress::Meta('2'), KeyPress::Ctrl('T'), KeyPress::Enter], + &[KeyPress::meta('2'), KeyPress::ctrl('T'), KeyPress::ENTER], ("acdb", ""), );*/ } @@ -260,10 +260,10 @@ fn ctrl_x_ctrl_u() { EditMode::Emacs, ("Hello, ", "world"), &[ - KeyPress::Ctrl('W'), - KeyPress::Ctrl('X'), - KeyPress::Ctrl('U'), - KeyPress::Enter, + KeyPress::ctrl('W'), + KeyPress::ctrl('X'), + KeyPress::ctrl('U'), + KeyPress::ENTER, ], ("Hello, ", "world"), ); @@ -274,19 +274,19 @@ fn meta_b() { assert_cursor( EditMode::Emacs, ("Hello, world!", ""), - &[KeyPress::Meta('B'), KeyPress::Enter], + &[KeyPress::meta('B'), KeyPress::ENTER], ("Hello, ", "world!"), ); assert_cursor( EditMode::Emacs, ("Hello, world!", ""), - &[KeyPress::Meta('2'), KeyPress::Meta('B'), KeyPress::Enter], + &[KeyPress::meta('2'), KeyPress::meta('B'), KeyPress::ENTER], ("", "Hello, world!"), ); assert_cursor( EditMode::Emacs, ("", "Hello, world!"), - &[KeyPress::Meta('-'), KeyPress::Meta('B'), KeyPress::Enter], + &[KeyPress::meta('-'), KeyPress::meta('B'), KeyPress::ENTER], ("Hello", ", world!"), ); } @@ -296,19 +296,19 @@ fn meta_f() { assert_cursor( EditMode::Emacs, ("", "Hello, world!"), - &[KeyPress::Meta('F'), KeyPress::Enter], + &[KeyPress::meta('F'), KeyPress::ENTER], ("Hello", ", world!"), ); assert_cursor( EditMode::Emacs, ("", "Hello, world!"), - &[KeyPress::Meta('2'), KeyPress::Meta('F'), KeyPress::Enter], + &[KeyPress::meta('2'), KeyPress::meta('F'), KeyPress::ENTER], ("Hello, world", "!"), ); assert_cursor( EditMode::Emacs, ("Hello, world!", ""), - &[KeyPress::Meta('-'), KeyPress::Meta('F'), KeyPress::Enter], + &[KeyPress::meta('-'), KeyPress::meta('F'), KeyPress::ENTER], ("Hello, ", "world!"), ); } @@ -318,19 +318,19 @@ fn meta_c() { assert_cursor( EditMode::Emacs, ("hi", ""), - &[KeyPress::Meta('C'), KeyPress::Enter], + &[KeyPress::meta('C'), KeyPress::ENTER], ("hi", ""), ); assert_cursor( EditMode::Emacs, ("", "hi"), - &[KeyPress::Meta('C'), KeyPress::Enter], + &[KeyPress::meta('C'), KeyPress::ENTER], ("Hi", ""), ); /* FIXME assert_cursor( ("", "hi test"), - &[KeyPress::Meta('2'), KeyPress::Meta('C'), KeyPress::Enter], + &[KeyPress::meta('2'), KeyPress::meta('C'), KeyPress::ENTER], ("Hi Test", ""), );*/ } @@ -340,19 +340,19 @@ fn meta_l() { assert_cursor( EditMode::Emacs, ("Hi", ""), - &[KeyPress::Meta('L'), KeyPress::Enter], + &[KeyPress::meta('L'), KeyPress::ENTER], ("Hi", ""), ); assert_cursor( EditMode::Emacs, ("", "HI"), - &[KeyPress::Meta('L'), KeyPress::Enter], + &[KeyPress::meta('L'), KeyPress::ENTER], ("hi", ""), ); /* FIXME assert_cursor( ("", "HI TEST"), - &[KeyPress::Meta('2'), KeyPress::Meta('L'), KeyPress::Enter], + &[KeyPress::meta('2'), KeyPress::meta('L'), KeyPress::ENTER], ("hi test", ""), );*/ } @@ -362,19 +362,19 @@ fn meta_u() { assert_cursor( EditMode::Emacs, ("hi", ""), - &[KeyPress::Meta('U'), KeyPress::Enter], + &[KeyPress::meta('U'), KeyPress::ENTER], ("hi", ""), ); assert_cursor( EditMode::Emacs, ("", "hi"), - &[KeyPress::Meta('U'), KeyPress::Enter], + &[KeyPress::meta('U'), KeyPress::ENTER], ("HI", ""), ); /* FIXME assert_cursor( ("", "hi test"), - &[KeyPress::Meta('2'), KeyPress::Meta('U'), KeyPress::Enter], + &[KeyPress::meta('2'), KeyPress::meta('U'), KeyPress::ENTER], ("HI TEST", ""), );*/ } @@ -384,13 +384,13 @@ fn meta_d() { assert_cursor( EditMode::Emacs, ("Hello", ", world!"), - &[KeyPress::Meta('D'), KeyPress::Enter], + &[KeyPress::meta('D'), KeyPress::ENTER], ("Hello", "!"), ); assert_cursor( EditMode::Emacs, ("Hello", ", world!"), - &[KeyPress::Meta('2'), KeyPress::Meta('D'), KeyPress::Enter], + &[KeyPress::meta('2'), KeyPress::meta('D'), KeyPress::ENTER], ("Hello", ""), ); } @@ -400,13 +400,13 @@ fn meta_t() { assert_cursor( EditMode::Emacs, ("Hello", ", world!"), - &[KeyPress::Meta('T'), KeyPress::Enter], + &[KeyPress::meta('T'), KeyPress::ENTER], ("world, Hello", "!"), ); /* FIXME assert_cursor( ("One Two", " Three Four"), - &[KeyPress::Meta('T'), KeyPress::Enter], + &[KeyPress::meta('T'), KeyPress::ENTER], ("One Four Three Two", ""), );*/ } @@ -417,12 +417,12 @@ fn meta_y() { EditMode::Emacs, ("Hello, world", "!"), &[ - KeyPress::Ctrl('W'), - KeyPress::Left, - KeyPress::Ctrl('W'), - KeyPress::Ctrl('Y'), - KeyPress::Meta('Y'), - KeyPress::Enter, + KeyPress::ctrl('W'), + KeyPress::LEFT, + KeyPress::ctrl('W'), + KeyPress::ctrl('Y'), + KeyPress::meta('Y'), + KeyPress::ENTER, ], ("world", " !"), ); @@ -433,7 +433,7 @@ fn meta_backspace() { assert_cursor( EditMode::Emacs, ("Hello, wor", "ld!"), - &[KeyPress::Meta('\x08'), KeyPress::Enter], + &[KeyPress::meta('\x08'), KeyPress::ENTER], ("Hello, ", "ld!"), ); } @@ -443,7 +443,7 @@ fn meta_digit() { assert_cursor( EditMode::Emacs, ("", ""), - &[KeyPress::Meta('3'), KeyPress::Char('h'), KeyPress::Enter], + &[KeyPress::meta('3'), KeyPress::normal('h'), KeyPress::ENTER], ("hhh", ""), ); } diff --git a/src/test/history.rs b/src/test/history.rs index 88cb3b5b87..b971e077a2 100644 --- a/src/test/history.rs +++ b/src/test/history.rs @@ -9,14 +9,14 @@ fn down_key() { assert_history( *mode, &["line1"], - &[KeyPress::Down, KeyPress::Enter], + &[KeyPress::DOWN, KeyPress::ENTER], "", ("", ""), ); assert_history( *mode, &["line1", "line2"], - &[KeyPress::Up, KeyPress::Up, KeyPress::Down, KeyPress::Enter], + &[KeyPress::UP, KeyPress::UP, KeyPress::DOWN, KeyPress::ENTER], "", ("line2", ""), ); @@ -24,10 +24,10 @@ fn down_key() { *mode, &["line1"], &[ - KeyPress::Char('a'), - KeyPress::Up, - KeyPress::Down, // restore original line - KeyPress::Enter, + KeyPress::from('a'), + KeyPress::UP, + KeyPress::DOWN, // restore original line + KeyPress::ENTER, ], "", ("a", ""), @@ -36,9 +36,9 @@ fn down_key() { *mode, &["line1"], &[ - KeyPress::Char('a'), - KeyPress::Down, // noop - KeyPress::Enter, + KeyPress::from('a'), + KeyPress::DOWN, // noop + KeyPress::ENTER, ], "", ("a", ""), @@ -49,18 +49,18 @@ fn down_key() { #[test] fn up_key() { for mode in &[EditMode::Emacs, EditMode::Vi] { - assert_history(*mode, &[], &[KeyPress::Up, KeyPress::Enter], "", ("", "")); + assert_history(*mode, &[], &[KeyPress::UP, KeyPress::ENTER], "", ("", "")); assert_history( *mode, &["line1"], - &[KeyPress::Up, KeyPress::Enter], + &[KeyPress::UP, KeyPress::ENTER], "", ("line1", ""), ); assert_history( *mode, &["line1", "line2"], - &[KeyPress::Up, KeyPress::Up, KeyPress::Enter], + &[KeyPress::UP, KeyPress::UP, KeyPress::ENTER], "", ("line1", ""), ); @@ -73,7 +73,7 @@ fn ctrl_r() { assert_history( *mode, &[], - &[KeyPress::Ctrl('R'), KeyPress::Char('o'), KeyPress::Enter], + &[KeyPress::ctrl('R'), KeyPress::from('o'), KeyPress::ENTER], "", ("o", ""), ); @@ -81,10 +81,10 @@ fn ctrl_r() { *mode, &["rustc", "cargo"], &[ - KeyPress::Ctrl('R'), - KeyPress::Char('o'), - KeyPress::Right, // just to assert cursor pos - KeyPress::Enter, + KeyPress::ctrl('R'), + KeyPress::from('o'), + KeyPress::RIGHT, // just to assert cursor pos + KeyPress::ENTER, ], "", ("cargo", ""), @@ -93,10 +93,10 @@ fn ctrl_r() { *mode, &["rustc", "cargo"], &[ - KeyPress::Ctrl('R'), - KeyPress::Char('u'), - KeyPress::Right, // just to assert cursor pos - KeyPress::Enter, + KeyPress::ctrl('R'), + KeyPress::from('u'), + KeyPress::RIGHT, // just to assert cursor pos + KeyPress::ENTER, ], "", ("ru", "stc"), @@ -105,11 +105,11 @@ fn ctrl_r() { *mode, &["rustc", "cargo"], &[ - KeyPress::Ctrl('R'), - KeyPress::Char('r'), - KeyPress::Char('u'), - KeyPress::Right, // just to assert cursor pos - KeyPress::Enter, + KeyPress::ctrl('R'), + KeyPress::from('r'), + KeyPress::from('u'), + KeyPress::RIGHT, // just to assert cursor pos + KeyPress::ENTER, ], "", ("r", "ustc"), @@ -118,11 +118,11 @@ fn ctrl_r() { *mode, &["rustc", "cargo"], &[ - KeyPress::Ctrl('R'), - KeyPress::Char('r'), - KeyPress::Ctrl('R'), - KeyPress::Right, // just to assert cursor pos - KeyPress::Enter, + KeyPress::ctrl('R'), + KeyPress::from('r'), + KeyPress::ctrl('R'), + KeyPress::RIGHT, // just to assert cursor pos + KeyPress::ENTER, ], "", ("r", "ustc"), @@ -131,11 +131,11 @@ fn ctrl_r() { *mode, &["rustc", "cargo"], &[ - KeyPress::Ctrl('R'), - KeyPress::Char('r'), - KeyPress::Char('z'), // no match - KeyPress::Right, // just to assert cursor pos - KeyPress::Enter, + KeyPress::ctrl('R'), + KeyPress::from('r'), + KeyPress::from('z'), // no match + KeyPress::RIGHT, // just to assert cursor pos + KeyPress::ENTER, ], "", ("car", "go"), @@ -144,11 +144,11 @@ fn ctrl_r() { EditMode::Emacs, &["rustc", "cargo"], &[ - KeyPress::Char('a'), - KeyPress::Ctrl('R'), - KeyPress::Char('r'), - KeyPress::Ctrl('G'), // abort (FIXME: doesn't work with vi mode) - KeyPress::Enter, + KeyPress::from('a'), + KeyPress::ctrl('R'), + KeyPress::from('r'), + KeyPress::ctrl('G'), // abort (FIXME: doesn't work with vi mode) + KeyPress::ENTER, ], "", ("a", ""), @@ -162,7 +162,7 @@ fn ctrl_r_with_long_prompt() { assert_history( *mode, &["rustc", "cargo"], - &[KeyPress::Ctrl('R'), KeyPress::Char('o'), KeyPress::Enter], + &[KeyPress::ctrl('R'), KeyPress::from('o'), KeyPress::ENTER], ">>>>>>>>>>>>>>>>>>>>>>>>>>> ", ("cargo", ""), ); @@ -176,12 +176,12 @@ fn ctrl_s() { *mode, &["rustc", "cargo"], &[ - KeyPress::Ctrl('R'), - KeyPress::Char('r'), - KeyPress::Ctrl('R'), - KeyPress::Ctrl('S'), - KeyPress::Right, // just to assert cursor pos - KeyPress::Enter, + KeyPress::ctrl('R'), + KeyPress::from('r'), + KeyPress::ctrl('R'), + KeyPress::ctrl('S'), + KeyPress::RIGHT, // just to assert cursor pos + KeyPress::ENTER, ], "", ("car", "go"), @@ -194,14 +194,14 @@ fn meta_lt() { assert_history( EditMode::Emacs, &[""], - &[KeyPress::Meta('<'), KeyPress::Enter], + &[KeyPress::meta('<'), KeyPress::ENTER], "", ("", ""), ); assert_history( EditMode::Emacs, &["rustc", "cargo"], - &[KeyPress::Meta('<'), KeyPress::Enter], + &[KeyPress::meta('<'), KeyPress::ENTER], "", ("rustc", ""), ); @@ -212,14 +212,14 @@ fn meta_gt() { assert_history( EditMode::Emacs, &[""], - &[KeyPress::Meta('>'), KeyPress::Enter], + &[KeyPress::meta('>'), KeyPress::ENTER], "", ("", ""), ); assert_history( EditMode::Emacs, &["rustc", "cargo"], - &[KeyPress::Meta('<'), KeyPress::Meta('>'), KeyPress::Enter], + &[KeyPress::meta('<'), KeyPress::meta('>'), KeyPress::ENTER], "", ("", ""), ); @@ -227,10 +227,10 @@ fn meta_gt() { EditMode::Emacs, &["rustc", "cargo"], &[ - KeyPress::Char('a'), - KeyPress::Meta('<'), - KeyPress::Meta('>'), // restore original line - KeyPress::Enter, + KeyPress::from('a'), + KeyPress::meta('<'), + KeyPress::meta('>'), // restore original line + KeyPress::ENTER, ], "", ("a", ""), diff --git a/src/test/mod.rs b/src/test/mod.rs index 3ed6e8c274..bc1ddd5f6c 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -8,7 +8,7 @@ use crate::edit::init_state; use crate::highlight::Highlighter; use crate::hint::Hinter; use crate::keymap::{Cmd, InputState}; -use crate::keys::KeyPress; +use crate::keys::{Key, KeyPress}; use crate::tty::Sink; use crate::validate::Validator; use crate::{Context, Editor, Helper, Result}; @@ -54,7 +54,7 @@ fn complete_line() { let mut s = init_state(&mut out, "rus", 3, helper.as_ref(), &history); let config = Config::default(); let mut input_state = InputState::new(&config, Arc::new(RwLock::new(HashMap::new()))); - let keys = vec![KeyPress::Enter]; + let keys = vec![KeyPress::ENTER]; let mut rdr: IntoIter = keys.into_iter(); let cmd = super::complete_line(&mut rdr, &mut s, &mut input_state, &Config::default()).unwrap(); assert_eq!(Some(Cmd::AcceptLine), cmd); @@ -118,7 +118,7 @@ fn assert_history( #[test] fn unknown_esc_key() { for mode in &[EditMode::Emacs, EditMode::Vi] { - assert_line(*mode, &[KeyPress::UnknownEscSeq, KeyPress::Enter], ""); + assert_line(*mode, &[Key::UnknownEscSeq.into(), KeyPress::ENTER], ""); } } diff --git a/src/test/vi_cmd.rs b/src/test/vi_cmd.rs index 02c5c0965a..2fae1f6e9b 100644 --- a/src/test/vi_cmd.rs +++ b/src/test/vi_cmd.rs @@ -8,7 +8,7 @@ fn dollar() { assert_cursor( EditMode::Vi, ("", "Hi"), - &[KeyPress::Esc, KeyPress::Char('$'), KeyPress::Enter], + &[KeyPress::ESC, KeyPress::from('$'), KeyPress::ENTER], ("Hi", ""), // FIXME ); } @@ -24,11 +24,11 @@ fn semi_colon() { EditMode::Vi, ("", "Hello, world!"), &[ - KeyPress::Esc, - KeyPress::Char('f'), - KeyPress::Char('o'), - KeyPress::Char(';'), - KeyPress::Enter, + KeyPress::ESC, + KeyPress::from('f'), + KeyPress::from('o'), + KeyPress::from(';'), + KeyPress::ENTER, ], ("Hello, w", "orld!"), ); @@ -40,11 +40,11 @@ fn comma() { EditMode::Vi, ("Hello, w", "orld!"), &[ - KeyPress::Esc, - KeyPress::Char('f'), - KeyPress::Char('l'), - KeyPress::Char(','), - KeyPress::Enter, + KeyPress::ESC, + KeyPress::from('f'), + KeyPress::from('l'), + KeyPress::from(','), + KeyPress::ENTER, ], ("Hel", "lo, world!"), ); @@ -55,7 +55,7 @@ fn zero() { assert_cursor( EditMode::Vi, ("Hi", ""), - &[KeyPress::Esc, KeyPress::Char('0'), KeyPress::Enter], + &[KeyPress::ESC, KeyPress::from('0'), KeyPress::ENTER], ("", "Hi"), ); } @@ -65,7 +65,7 @@ fn caret() { assert_cursor( EditMode::Vi, (" Hi", ""), - &[KeyPress::Esc, KeyPress::Char('^'), KeyPress::Enter], + &[KeyPress::ESC, KeyPress::from('^'), KeyPress::ENTER], (" ", "Hi"), ); } @@ -76,10 +76,10 @@ fn a() { EditMode::Vi, ("B", "e"), &[ - KeyPress::Esc, - KeyPress::Char('a'), - KeyPress::Char('y'), - KeyPress::Enter, + KeyPress::ESC, + KeyPress::from('a'), + KeyPress::from('y'), + KeyPress::ENTER, ], ("By", "e"), ); @@ -91,10 +91,10 @@ fn uppercase_a() { EditMode::Vi, ("", "By"), &[ - KeyPress::Esc, - KeyPress::Char('A'), - KeyPress::Char('e'), - KeyPress::Enter, + KeyPress::ESC, + KeyPress::from('A'), + KeyPress::from('e'), + KeyPress::ENTER, ], ("Bye", ""), ); @@ -105,17 +105,17 @@ fn b() { assert_cursor( EditMode::Vi, ("Hello, world!", ""), - &[KeyPress::Esc, KeyPress::Char('b'), KeyPress::Enter], + &[KeyPress::ESC, KeyPress::from('b'), KeyPress::ENTER], ("Hello, ", "world!"), ); assert_cursor( EditMode::Vi, ("Hello, world!", ""), &[ - KeyPress::Esc, - KeyPress::Char('2'), - KeyPress::Char('b'), - KeyPress::Enter, + KeyPress::ESC, + KeyPress::from('2'), + KeyPress::from('b'), + KeyPress::ENTER, ], ("Hello", ", world!"), ); @@ -126,17 +126,17 @@ fn uppercase_b() { assert_cursor( EditMode::Vi, ("Hello, world!", ""), - &[KeyPress::Esc, KeyPress::Char('B'), KeyPress::Enter], + &[KeyPress::ESC, KeyPress::from('B'), KeyPress::ENTER], ("Hello, ", "world!"), ); assert_cursor( EditMode::Vi, ("Hello, world!", ""), &[ - KeyPress::Esc, - KeyPress::Char('2'), - KeyPress::Char('B'), - KeyPress::Enter, + KeyPress::ESC, + KeyPress::from('2'), + KeyPress::from('B'), + KeyPress::ENTER, ], ("", "Hello, world!"), ); @@ -148,10 +148,10 @@ fn uppercase_c() { EditMode::Vi, ("Hello, w", "orld!"), &[ - KeyPress::Esc, - KeyPress::Char('C'), - KeyPress::Char('i'), - KeyPress::Enter, + KeyPress::ESC, + KeyPress::from('C'), + KeyPress::from('i'), + KeyPress::ENTER, ], ("Hello, i", ""), ); @@ -159,23 +159,23 @@ fn uppercase_c() { #[test] fn ctrl_k() { - for key in &[KeyPress::Char('D'), KeyPress::Ctrl('K')] { + for key in &[KeyPress::from('D'), KeyPress::ctrl('K')] { assert_cursor( EditMode::Vi, ("Hi", ""), - &[KeyPress::Esc, *key, KeyPress::Enter], + &[KeyPress::ESC, *key, KeyPress::ENTER], ("H", ""), ); assert_cursor( EditMode::Vi, ("", "Hi"), - &[KeyPress::Esc, *key, KeyPress::Enter], + &[KeyPress::ESC, *key, KeyPress::ENTER], ("", ""), ); assert_cursor( EditMode::Vi, ("By", "e"), - &[KeyPress::Esc, *key, KeyPress::Enter], + &[KeyPress::ESC, *key, KeyPress::ENTER], ("B", ""), ); } @@ -186,17 +186,17 @@ fn e() { assert_cursor( EditMode::Vi, ("", "Hello, world!"), - &[KeyPress::Esc, KeyPress::Char('e'), KeyPress::Enter], + &[KeyPress::ESC, KeyPress::from('e'), KeyPress::ENTER], ("Hell", "o, world!"), ); assert_cursor( EditMode::Vi, ("", "Hello, world!"), &[ - KeyPress::Esc, - KeyPress::Char('2'), - KeyPress::Char('e'), - KeyPress::Enter, + KeyPress::ESC, + KeyPress::from('2'), + KeyPress::from('e'), + KeyPress::ENTER, ], ("Hello, worl", "d!"), ); @@ -207,17 +207,17 @@ fn uppercase_e() { assert_cursor( EditMode::Vi, ("", "Hello, world!"), - &[KeyPress::Esc, KeyPress::Char('E'), KeyPress::Enter], + &[KeyPress::ESC, KeyPress::from('E'), KeyPress::ENTER], ("Hello", ", world!"), ); assert_cursor( EditMode::Vi, ("", "Hello, world!"), &[ - KeyPress::Esc, - KeyPress::Char('2'), - KeyPress::Char('E'), - KeyPress::Enter, + KeyPress::ESC, + KeyPress::from('2'), + KeyPress::from('E'), + KeyPress::ENTER, ], ("Hello, world", "!"), ); @@ -229,10 +229,10 @@ fn f() { EditMode::Vi, ("", "Hello, world!"), &[ - KeyPress::Esc, - KeyPress::Char('f'), - KeyPress::Char('r'), - KeyPress::Enter, + KeyPress::ESC, + KeyPress::from('f'), + KeyPress::from('r'), + KeyPress::ENTER, ], ("Hello, wo", "rld!"), ); @@ -240,11 +240,11 @@ fn f() { EditMode::Vi, ("", "Hello, world!"), &[ - KeyPress::Esc, - KeyPress::Char('3'), - KeyPress::Char('f'), - KeyPress::Char('l'), - KeyPress::Enter, + KeyPress::ESC, + KeyPress::from('3'), + KeyPress::from('f'), + KeyPress::from('l'), + KeyPress::ENTER, ], ("Hello, wor", "ld!"), ); @@ -256,10 +256,10 @@ fn uppercase_f() { EditMode::Vi, ("Hello, world!", ""), &[ - KeyPress::Esc, - KeyPress::Char('F'), - KeyPress::Char('r'), - KeyPress::Enter, + KeyPress::ESC, + KeyPress::from('F'), + KeyPress::from('r'), + KeyPress::ENTER, ], ("Hello, wo", "rld!"), ); @@ -267,11 +267,11 @@ fn uppercase_f() { EditMode::Vi, ("Hello, world!", ""), &[ - KeyPress::Esc, - KeyPress::Char('3'), - KeyPress::Char('F'), - KeyPress::Char('l'), - KeyPress::Enter, + KeyPress::ESC, + KeyPress::from('3'), + KeyPress::from('F'), + KeyPress::from('l'), + KeyPress::ENTER, ], ("He", "llo, world!"), ); @@ -283,10 +283,10 @@ fn i() { EditMode::Vi, ("Be", ""), &[ - KeyPress::Esc, - KeyPress::Char('i'), - KeyPress::Char('y'), - KeyPress::Enter, + KeyPress::ESC, + KeyPress::from('i'), + KeyPress::from('y'), + KeyPress::ENTER, ], ("By", "e"), ); @@ -298,10 +298,10 @@ fn uppercase_i() { EditMode::Vi, ("Be", ""), &[ - KeyPress::Esc, - KeyPress::Char('I'), - KeyPress::Char('y'), - KeyPress::Enter, + KeyPress::ESC, + KeyPress::from('I'), + KeyPress::from('y'), + KeyPress::ENTER, ], ("y", "Be"), ); @@ -313,10 +313,10 @@ fn u() { EditMode::Vi, ("Hello, ", "world"), &[ - KeyPress::Esc, - KeyPress::Ctrl('W'), - KeyPress::Char('u'), - KeyPress::Enter, + KeyPress::ESC, + KeyPress::ctrl('W'), + KeyPress::from('u'), + KeyPress::ENTER, ], ("Hello,", " world"), ); @@ -327,17 +327,17 @@ fn w() { assert_cursor( EditMode::Vi, ("", "Hello, world!"), - &[KeyPress::Esc, KeyPress::Char('w'), KeyPress::Enter], + &[KeyPress::ESC, KeyPress::from('w'), KeyPress::ENTER], ("Hello", ", world!"), ); assert_cursor( EditMode::Vi, ("", "Hello, world!"), &[ - KeyPress::Esc, - KeyPress::Char('2'), - KeyPress::Char('w'), - KeyPress::Enter, + KeyPress::ESC, + KeyPress::from('2'), + KeyPress::from('w'), + KeyPress::ENTER, ], ("Hello, ", "world!"), ); @@ -348,17 +348,17 @@ fn uppercase_w() { assert_cursor( EditMode::Vi, ("", "Hello, world!"), - &[KeyPress::Esc, KeyPress::Char('W'), KeyPress::Enter], + &[KeyPress::ESC, KeyPress::from('W'), KeyPress::ENTER], ("Hello, ", "world!"), ); assert_cursor( EditMode::Vi, ("", "Hello, world!"), &[ - KeyPress::Esc, - KeyPress::Char('2'), - KeyPress::Char('W'), - KeyPress::Enter, + KeyPress::ESC, + KeyPress::from('2'), + KeyPress::from('W'), + KeyPress::ENTER, ], ("Hello, world", "!"), ); @@ -369,7 +369,7 @@ fn x() { assert_cursor( EditMode::Vi, ("", "a"), - &[KeyPress::Esc, KeyPress::Char('x'), KeyPress::Enter], + &[KeyPress::ESC, KeyPress::from('x'), KeyPress::ENTER], ("", ""), ); } @@ -379,7 +379,7 @@ fn uppercase_x() { assert_cursor( EditMode::Vi, ("Hi", ""), - &[KeyPress::Esc, KeyPress::Char('X'), KeyPress::Enter], + &[KeyPress::ESC, KeyPress::from('X'), KeyPress::ENTER], ("", "i"), ); } @@ -387,20 +387,20 @@ fn uppercase_x() { #[test] fn h() { for key in &[ - KeyPress::Char('h'), - KeyPress::Ctrl('H'), - KeyPress::Backspace, + KeyPress::from('h'), + KeyPress::ctrl('H'), + KeyPress::BACKSPACE, ] { assert_cursor( EditMode::Vi, ("Bye", ""), - &[KeyPress::Esc, *key, KeyPress::Enter], + &[KeyPress::ESC, *key, KeyPress::ENTER], ("B", "ye"), ); assert_cursor( EditMode::Vi, ("Bye", ""), - &[KeyPress::Esc, KeyPress::Char('2'), *key, KeyPress::Enter], + &[KeyPress::ESC, KeyPress::from('2'), *key, KeyPress::ENTER], ("", "Bye"), ); } @@ -408,17 +408,17 @@ fn h() { #[test] fn l() { - for key in &[KeyPress::Char('l'), KeyPress::Char(' ')] { + for key in &[KeyPress::from('l'), KeyPress::from(' ')] { assert_cursor( EditMode::Vi, ("", "Hi"), - &[KeyPress::Esc, *key, KeyPress::Enter], + &[KeyPress::ESC, *key, KeyPress::ENTER], ("H", "i"), ); assert_cursor( EditMode::Vi, ("", "Hi"), - &[KeyPress::Esc, KeyPress::Char('2'), *key, KeyPress::Enter], + &[KeyPress::ESC, KeyPress::from('2'), *key, KeyPress::ENTER], ("Hi", ""), ); } @@ -426,25 +426,26 @@ fn l() { #[test] fn j() { - for key in &[KeyPress::Char('j'), KeyPress::Char('+')] { + for key in &[KeyPress::from('j'), KeyPress::from('+')] { + assert_cursor( EditMode::Vi, ("Hel", "lo,\nworld!"), // NOTE: escape moves backwards on char - &[KeyPress::Esc, *key, KeyPress::Enter], + &[KeyPress::ESC, *key, KeyPress::ENTER], ("Hello,\nwo", "rld!"), ); assert_cursor( EditMode::Vi, ("", "One\nTwo\nThree"), - &[KeyPress::Esc, KeyPress::Char('2'), *key, KeyPress::Enter], + &[KeyPress::ESC, KeyPress::from('2'), *key, KeyPress::ENTER], ("One\nTwo\n", "Three"), ); assert_cursor( EditMode::Vi, ("Hel", "lo,\nworld!"), // NOTE: escape moves backwards on char - &[KeyPress::Esc, KeyPress::Char('7'), *key, KeyPress::Enter], + &[KeyPress::ESC, KeyPress::from('7'), *key, KeyPress::ENTER], ("Hello,\nwo", "rld!"), ); } @@ -452,32 +453,32 @@ fn j() { #[test] fn k() { - for key in &[KeyPress::Char('k'), KeyPress::Char('-')] { + for key in &[KeyPress::from('k'), KeyPress::from('-')] { assert_cursor( EditMode::Vi, ("Hello,\nworl", "d!"), // NOTE: escape moves backwards on char - &[KeyPress::Esc, *key, KeyPress::Enter], + &[KeyPress::ESC, *key, KeyPress::ENTER], ("Hel", "lo,\nworld!"), ); assert_cursor( EditMode::Vi, ("One\nTwo\nT", "hree"), // NOTE: escape moves backwards on char - &[KeyPress::Esc, KeyPress::Char('2'), *key, KeyPress::Enter], + &[KeyPress::ESC, KeyPress::from('2'), *key, KeyPress::ENTER], ("", "One\nTwo\nThree"), ); assert_cursor( EditMode::Vi, ("Hello,\nworl", "d!"), // NOTE: escape moves backwards on char - &[KeyPress::Esc, KeyPress::Char('5'), *key, KeyPress::Enter], + &[KeyPress::ESC, KeyPress::from('5'), *key, KeyPress::ENTER], ("Hel", "lo,\nworld!"), ); assert_cursor( EditMode::Vi, ("first line\nshort\nlong line", ""), - &[KeyPress::Esc, *key, KeyPress::Enter], + &[KeyPress::ESC, *key, KeyPress::ENTER], ("first line\nshort", "\nlong line"), ); } @@ -485,16 +486,16 @@ fn k() { #[test] fn ctrl_n() { - for key in &[KeyPress::Ctrl('N')] { + for key in &[KeyPress::ctrl('N')] { assert_history( EditMode::Vi, &["line1", "line2"], &[ - KeyPress::Esc, - KeyPress::Ctrl('P'), - KeyPress::Ctrl('P'), + KeyPress::ESC, + KeyPress::ctrl('P'), + KeyPress::ctrl('P'), *key, - KeyPress::Enter, + KeyPress::ENTER, ], "", ("line2", ""), @@ -504,11 +505,11 @@ fn ctrl_n() { #[test] fn ctrl_p() { - for key in &[KeyPress::Ctrl('P')] { + for key in &[KeyPress::ctrl('P')] { assert_history( EditMode::Vi, &["line1"], - &[KeyPress::Esc, *key, KeyPress::Enter], + &[KeyPress::ESC, *key, KeyPress::ENTER], "", ("line1", ""), ); @@ -521,10 +522,10 @@ fn p() { EditMode::Vi, ("Hello, ", "world"), &[ - KeyPress::Esc, - KeyPress::Ctrl('W'), - KeyPress::Char('p'), - KeyPress::Enter, + KeyPress::ESC, + KeyPress::ctrl('W'), + KeyPress::from('p'), + KeyPress::ENTER, ], (" Hello", ",world"), ); @@ -536,10 +537,10 @@ fn uppercase_p() { EditMode::Vi, ("Hello, ", "world"), &[ - KeyPress::Esc, - KeyPress::Ctrl('W'), - KeyPress::Char('P'), - KeyPress::Enter, + KeyPress::ESC, + KeyPress::ctrl('W'), + KeyPress::from('P'), + KeyPress::ENTER, ], ("Hello", ", world"), ); @@ -551,10 +552,10 @@ fn r() { EditMode::Vi, ("Hi", ", world!"), &[ - KeyPress::Esc, - KeyPress::Char('r'), - KeyPress::Char('o'), - KeyPress::Enter, + KeyPress::ESC, + KeyPress::from('r'), + KeyPress::from('o'), + KeyPress::ENTER, ], ("H", "o, world!"), ); @@ -562,11 +563,11 @@ fn r() { EditMode::Vi, ("He", "llo, world!"), &[ - KeyPress::Esc, - KeyPress::Char('4'), - KeyPress::Char('r'), - KeyPress::Char('i'), - KeyPress::Enter, + KeyPress::ESC, + KeyPress::from('4'), + KeyPress::from('r'), + KeyPress::from('i'), + KeyPress::ENTER, ], ("Hiii", "i, world!"), ); @@ -578,10 +579,10 @@ fn s() { EditMode::Vi, ("Hi", ", world!"), &[ - KeyPress::Esc, - KeyPress::Char('s'), - KeyPress::Char('o'), - KeyPress::Enter, + KeyPress::ESC, + KeyPress::from('s'), + KeyPress::from('o'), + KeyPress::ENTER, ], ("Ho", ", world!"), ); @@ -589,11 +590,11 @@ fn s() { EditMode::Vi, ("He", "llo, world!"), &[ - KeyPress::Esc, - KeyPress::Char('4'), - KeyPress::Char('s'), - KeyPress::Char('i'), - KeyPress::Enter, + KeyPress::ESC, + KeyPress::from('4'), + KeyPress::from('s'), + KeyPress::from('i'), + KeyPress::ENTER, ], ("Hi", ", world!"), ); @@ -604,7 +605,7 @@ fn uppercase_s() { assert_cursor( EditMode::Vi, ("Hello, ", "world"), - &[KeyPress::Esc, KeyPress::Char('S'), KeyPress::Enter], + &[KeyPress::ESC, KeyPress::from('S'), KeyPress::ENTER], ("", ""), ); } @@ -615,10 +616,10 @@ fn t() { EditMode::Vi, ("", "Hello, world!"), &[ - KeyPress::Esc, - KeyPress::Char('t'), - KeyPress::Char('r'), - KeyPress::Enter, + KeyPress::ESC, + KeyPress::from('t'), + KeyPress::from('r'), + KeyPress::ENTER, ], ("Hello, w", "orld!"), ); @@ -626,11 +627,11 @@ fn t() { EditMode::Vi, ("", "Hello, world!"), &[ - KeyPress::Esc, - KeyPress::Char('3'), - KeyPress::Char('t'), - KeyPress::Char('l'), - KeyPress::Enter, + KeyPress::ESC, + KeyPress::from('3'), + KeyPress::from('t'), + KeyPress::from('l'), + KeyPress::ENTER, ], ("Hello, wo", "rld!"), ); @@ -642,10 +643,10 @@ fn uppercase_t() { EditMode::Vi, ("Hello, world!", ""), &[ - KeyPress::Esc, - KeyPress::Char('T'), - KeyPress::Char('r'), - KeyPress::Enter, + KeyPress::ESC, + KeyPress::from('T'), + KeyPress::from('r'), + KeyPress::ENTER, ], ("Hello, wor", "ld!"), ); @@ -653,11 +654,11 @@ fn uppercase_t() { EditMode::Vi, ("Hello, world!", ""), &[ - KeyPress::Esc, - KeyPress::Char('3'), - KeyPress::Char('T'), - KeyPress::Char('l'), - KeyPress::Enter, + KeyPress::ESC, + KeyPress::from('3'), + KeyPress::from('T'), + KeyPress::from('l'), + KeyPress::ENTER, ], ("Hel", "lo, world!"), ); diff --git a/src/test/vi_insert.rs b/src/test/vi_insert.rs index 608e2ac760..f4afbbe313 100644 --- a/src/test/vi_insert.rs +++ b/src/test/vi_insert.rs @@ -8,7 +8,7 @@ fn insert_mode_by_default() { assert_cursor( EditMode::Vi, ("", ""), - &[KeyPress::Char('a'), KeyPress::Enter], + &[KeyPress::from('a'), KeyPress::ENTER], ("a", ""), ); } @@ -18,7 +18,7 @@ fn ctrl_h() { assert_cursor( EditMode::Vi, ("Hi", ""), - &[KeyPress::Ctrl('H'), KeyPress::Enter], + &[KeyPress::ctrl('H'), KeyPress::ENTER], ("H", ""), ); } @@ -28,19 +28,19 @@ fn backspace() { assert_cursor( EditMode::Vi, ("", ""), - &[KeyPress::Backspace, KeyPress::Enter], + &[KeyPress::BACKSPACE, KeyPress::ENTER], ("", ""), ); assert_cursor( EditMode::Vi, ("Hi", ""), - &[KeyPress::Backspace, KeyPress::Enter], + &[KeyPress::BACKSPACE, KeyPress::ENTER], ("H", ""), ); assert_cursor( EditMode::Vi, ("", "Hi"), - &[KeyPress::Backspace, KeyPress::Enter], + &[KeyPress::BACKSPACE, KeyPress::ENTER], ("", "Hi"), ); } @@ -50,7 +50,7 @@ fn esc() { assert_cursor( EditMode::Vi, ("", ""), - &[KeyPress::Char('a'), KeyPress::Esc, KeyPress::Enter], + &[KeyPress::from('a'), KeyPress::ESC, KeyPress::ENTER], ("", "a"), ); } diff --git a/src/tty/test.rs b/src/tty/test.rs index 34333b1a87..34331f9d3d 100644 --- a/src/tty/test.rs +++ b/src/tty/test.rs @@ -49,7 +49,7 @@ impl RawReader for IntoIter { #[cfg(unix)] fn next_char(&mut self) -> Result { match self.next() { - Some(KeyPress::Char(c)) => Ok(c), + Some(key_press!(Char(c))) => Ok(c), None => Err(ReadlineError::Eof), _ => unimplemented!(), } diff --git a/src/tty/unix.rs b/src/tty/unix.rs index 80c45437d7..377d629446 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -18,7 +18,7 @@ use super::{RawMode, RawReader, Renderer, Term}; use crate::config::{BellStyle, ColorMode, Config, OutputStreamType}; use crate::error; use crate::highlight::Highlighter; -use crate::keys::{self, KeyPress}; +use crate::keys::{self, Key, KeyPress}; use crate::layout::{Layout, Position}; use crate::line_buffer::LineBuffer; use crate::tty::add_prompt_and_highlight; @@ -167,10 +167,10 @@ impl PosixRawReader { self.escape_o() } else if seq1 == '\x1b' { // ESC ESC - Ok(KeyPress::Esc) + Ok(KeyPress::ESC) } else { // TODO ESC-R (r): Undo all changes made to this line. - Ok(KeyPress::Meta(seq1)) + Ok(KeyPress::meta(seq1)) } } @@ -181,7 +181,7 @@ impl PosixRawReader { match seq2 { '0' | '9' => { debug!(target: "rustyline", "unsupported esc sequence: ESC [ {:?}", seq2); - Ok(KeyPress::UnknownEscSeq) + Ok(Key::UnknownEscSeq.into()) } _ => { // Extended escape, read additional byte. @@ -192,29 +192,29 @@ impl PosixRawReader { let seq3 = self.next_char()?; // Linux console Ok(match seq3 { - 'A' => KeyPress::F(1), - 'B' => KeyPress::F(2), - 'C' => KeyPress::F(3), - 'D' => KeyPress::F(4), - 'E' => KeyPress::F(5), + 'A' => key_press!(F(1)), + 'B' => key_press!(F(2)), + 'C' => key_press!(F(3)), + 'D' => key_press!(F(4)), + 'E' => key_press!(F(5)), _ => { debug!(target: "rustyline", "unsupported esc sequence: ESC [ [ {:?}", seq3); - KeyPress::UnknownEscSeq + Key::UnknownEscSeq.into() } }) } else { // ANSI Ok(match seq2 { - 'A' => KeyPress::Up, // kcuu1 - 'B' => KeyPress::Down, // kcud1 - 'C' => KeyPress::Right, // kcuf1 - 'D' => KeyPress::Left, // kcub1 - 'F' => KeyPress::End, - 'H' => KeyPress::Home, // khome - 'Z' => KeyPress::BackTab, + 'A' => Key::Up.into(), // kcuu1 + 'B' => Key::Down.into(), // kcud1 + 'C' => Key::Right.into(), // kcuf1 + 'D' => Key::Left.into(), // kcub1 + 'F' => Key::End.into(), + 'H' => Key::Home.into(), // khome + 'Z' => Key::BackTab.into(), _ => { debug!(target: "rustyline", "unsupported esc sequence: ESC [ {:?}", seq2); - KeyPress::UnknownEscSeq + Key::UnknownEscSeq.into() } }) } @@ -226,38 +226,38 @@ impl PosixRawReader { let seq3 = self.next_char()?; if seq3 == '~' { Ok(match seq2 { - '1' | '7' => KeyPress::Home, // tmux, xrvt - '2' => KeyPress::Insert, - '3' => KeyPress::Delete, // kdch1 - '4' | '8' => KeyPress::End, // tmux, xrvt - '5' => KeyPress::PageUp, // kpp - '6' => KeyPress::PageDown, // knp + '1' | '7' => KeyPress::HOME, // tmux, xrvt + '2' => KeyPress::INSERT, + '3' => KeyPress::DELETE, // kdch1 + '4' | '8' => KeyPress::END, // tmux, xrvt + '5' => KeyPress::PAGE_UP, // kpp + '6' => KeyPress::PAGE_DOWN, // knp _ => { debug!(target: "rustyline", "unsupported esc sequence: ESC [ {} ~", seq2); - KeyPress::UnknownEscSeq + Key::UnknownEscSeq.into() } }) } else if seq3.is_digit(10) { let seq4 = self.next_char()?; if seq4 == '~' { Ok(match (seq2, seq3) { - ('1', '1') => KeyPress::F(1), // rxvt-unicode - ('1', '2') => KeyPress::F(2), // rxvt-unicode - ('1', '3') => KeyPress::F(3), // rxvt-unicode - ('1', '4') => KeyPress::F(4), // rxvt-unicode - ('1', '5') => KeyPress::F(5), // kf5 - ('1', '7') => KeyPress::F(6), // kf6 - ('1', '8') => KeyPress::F(7), // kf7 - ('1', '9') => KeyPress::F(8), // kf8 - ('2', '0') => KeyPress::F(9), // kf9 - ('2', '1') => KeyPress::F(10), // kf10 - ('2', '3') => KeyPress::F(11), // kf11 - ('2', '4') => KeyPress::F(12), // kf12 + ('1', '1') => Key::F(1).into(), // rxvt-unicode + ('1', '2') => Key::F(2).into(), // rxvt-unicode + ('1', '3') => Key::F(3).into(), // rxvt-unicode + ('1', '4') => Key::F(4).into(), // rxvt-unicode + ('1', '5') => Key::F(5).into(), // kf5 + ('1', '7') => Key::F(6).into(), // kf6 + ('1', '8') => Key::F(7).into(), // kf7 + ('1', '9') => Key::F(8).into(), // kf8 + ('2', '0') => Key::F(9).into(), // kf9 + ('2', '1') => Key::F(10).into(), // kf10 + ('2', '3') => Key::F(11).into(), // kf11 + ('2', '4') => Key::F(12).into(), // kf12 _ => { debug!(target: "rustyline", "unsupported esc sequence: ESC [ {}{} ~", seq2, seq3); - KeyPress::UnknownEscSeq + Key::UnknownEscSeq.into() } }) } else if seq4 == ';' { @@ -275,28 +275,28 @@ impl PosixRawReader { debug!(target: "rustyline", "unsupported esc sequence: ESC [ {}{} ; {:?}", seq2, seq3, seq5); } - Ok(KeyPress::UnknownEscSeq) + Ok(Key::UnknownEscSeq.into()) } else if seq4.is_digit(10) { let seq5 = self.next_char()?; if seq5 == '~' { Ok(match (seq2, seq3, seq4) { - ('2', '0', '0') => KeyPress::BracketedPasteStart, - ('2', '0', '1') => KeyPress::BracketedPasteEnd, + ('2', '0', '0') => Key::BracketedPasteStart.into(), + ('2', '0', '1') => Key::BracketedPasteEnd.into(), _ => { debug!(target: "rustyline", "unsupported esc sequence: ESC [ {}{}{}~", seq2, seq3, seq4); - KeyPress::UnknownEscSeq + Key::UnknownEscSeq.into() } }) } else { debug!(target: "rustyline", "unsupported esc sequence: ESC [ {}{}{} {}", seq2, seq3, seq4, seq5); - Ok(KeyPress::UnknownEscSeq) + Ok(Key::UnknownEscSeq.into()) } } else { debug!(target: "rustyline", "unsupported esc sequence: ESC [ {}{} {:?}", seq2, seq3, seq4); - Ok(KeyPress::UnknownEscSeq) + Ok(Key::UnknownEscSeq.into()) } } else if seq3 == ';' { let seq4 = self.next_char()?; @@ -304,43 +304,43 @@ impl PosixRawReader { let seq5 = self.next_char()?; if seq5.is_digit(10) { self.next_char()?; // 'R' expected - Ok(KeyPress::UnknownEscSeq) + Ok(Key::UnknownEscSeq.into()) } else if seq2 == '1' { Ok(match (seq4, seq5) { - ('5', 'A') => KeyPress::ControlUp, - ('5', 'B') => KeyPress::ControlDown, - ('5', 'C') => KeyPress::ControlRight, - ('5', 'D') => KeyPress::ControlLeft, - ('2', 'A') => KeyPress::ShiftUp, - ('2', 'B') => KeyPress::ShiftDown, - ('2', 'C') => KeyPress::ShiftRight, - ('2', 'D') => KeyPress::ShiftLeft, + ('5', 'A') => KeyPress::ctrl(Key::Up), + ('5', 'B') => KeyPress::ctrl(Key::Down), + ('5', 'C') => KeyPress::ctrl(Key::Right), + ('5', 'D') => KeyPress::ctrl(Key::Left), + ('2', 'A') => KeyPress::shift(Key::Up), + ('2', 'B') => KeyPress::shift(Key::Down), + ('2', 'C') => KeyPress::shift(Key::Right), + ('2', 'D') => KeyPress::shift(Key::Left), _ => { debug!(target: "rustyline", "unsupported esc sequence: ESC [ 1 ; {} {:?}", seq4, seq5); - KeyPress::UnknownEscSeq + Key::UnknownEscSeq.into() } }) } else { debug!(target: "rustyline", "unsupported esc sequence: ESC [ {} ; {} {:?}", seq2, seq4, seq5); - Ok(KeyPress::UnknownEscSeq) + Ok(Key::UnknownEscSeq.into()) } } else { debug!(target: "rustyline", "unsupported esc sequence: ESC [ {} ; {:?}", seq2, seq4); - Ok(KeyPress::UnknownEscSeq) + Ok(Key::UnknownEscSeq.into()) } } else { Ok(match (seq2, seq3) { - ('5', 'A') => KeyPress::ControlUp, - ('5', 'B') => KeyPress::ControlDown, - ('5', 'C') => KeyPress::ControlRight, - ('5', 'D') => KeyPress::ControlLeft, + ('5', 'A') => KeyPress::ctrl(Key::Up), + ('5', 'B') => KeyPress::ctrl(Key::Down), + ('5', 'C') => KeyPress::ctrl(Key::Right), + ('5', 'D') => KeyPress::ctrl(Key::Left), _ => { debug!(target: "rustyline", "unsupported esc sequence: ESC [ {} {:?}", seq2, seq3); - KeyPress::UnknownEscSeq + Key::UnknownEscSeq.into() } }) } @@ -350,23 +350,23 @@ impl PosixRawReader { fn escape_o(&mut self) -> Result { let seq2 = self.next_char()?; Ok(match seq2 { - 'A' => KeyPress::Up, // kcuu1 - 'B' => KeyPress::Down, // kcud1 - 'C' => KeyPress::Right, // kcuf1 - 'D' => KeyPress::Left, // kcub1 - 'F' => KeyPress::End, // kend - 'H' => KeyPress::Home, // khome - 'P' => KeyPress::F(1), // kf1 - 'Q' => KeyPress::F(2), // kf2 - 'R' => KeyPress::F(3), // kf3 - 'S' => KeyPress::F(4), // kf4 - 'a' => KeyPress::ControlUp, - 'b' => KeyPress::ControlDown, - 'c' => KeyPress::ControlRight, // rxvt - 'd' => KeyPress::ControlLeft, // rxvt + 'A' => KeyPress::UP, // kcuu1 + 'B' => KeyPress::DOWN, // kcud1 + 'C' => KeyPress::RIGHT, // kcuf1 + 'D' => KeyPress::LEFT, // kcub1 + 'F' => KeyPress::END, // kend + 'H' => KeyPress::HOME, // khome + 'P' => Key::F(1).into(), // kf1 + 'Q' => Key::F(2).into(), // kf2 + 'R' => Key::F(3).into(), // kf3 + 'S' => Key::F(4).into(), // kf4 + 'a' => KeyPress::ctrl(Key::Up), + 'b' => KeyPress::ctrl(Key::Down), + 'c' => KeyPress::ctrl(Key::Right), // rxvt + 'd' => KeyPress::ctrl(Key::Left), // rxvt _ => { debug!(target: "rustyline", "unsupported esc sequence: ESC O {:?}", seq2); - KeyPress::UnknownEscSeq + Key::UnknownEscSeq.into() } }) } @@ -382,7 +382,7 @@ impl RawReader for PosixRawReader { let c = self.next_char()?; let mut key = keys::char_to_key_press(c); - if key == KeyPress::Esc { + if key == KeyPress::ESC { let timeout_ms = if single_esc_abort && self.timeout_ms == -1 { 0 } else { @@ -426,7 +426,7 @@ impl RawReader for PosixRawReader { match self.next_char()? { '\x1b' => { let key = self.escape_sequence()?; - if key == KeyPress::BracketedPasteEnd { + if key.key == Key::BracketedPasteEnd { break; } else { continue; // TODO validate diff --git a/src/tty/windows.rs b/src/tty/windows.rs index f4f852d903..5b0c2390af 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -13,7 +13,7 @@ use super::{RawMode, RawReader, Renderer, Term}; use crate::config::{BellStyle, ColorMode, Config, OutputStreamType}; use crate::error; use crate::highlight::Highlighter; -use crate::keys::{self, KeyPress}; +use crate::keys::{self, Key, KeyMods, KeyPress}; use crate::layout::{Layout, Position}; use crate::line_buffer::LineBuffer; use crate::tty::add_prompt_and_highlight; @@ -147,70 +147,40 @@ impl RawReader for ConsoleRawReader { let ctrl = key_event.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED) != 0; let meta = alt && !alt_gr; let shift = key_event.dwControlKeyState & SHIFT_PRESSED != 0; + let mods = KeyMods::ctrl_meta_shift(ctrl, meta, shift); let utf16 = unsafe { *key_event.uChar.UnicodeChar() }; if utf16 == 0 { - match i32::from(key_event.wVirtualKeyCode) { - winuser::VK_LEFT => { - return Ok(if ctrl { - KeyPress::ControlLeft - } else if shift { - KeyPress::ShiftLeft - } else { - KeyPress::Left - }); - } - winuser::VK_RIGHT => { - return Ok(if ctrl { - KeyPress::ControlRight - } else if shift { - KeyPress::ShiftRight - } else { - KeyPress::Right - }); - } - winuser::VK_UP => { - return Ok(if ctrl { - KeyPress::ControlUp - } else if shift { - KeyPress::ShiftUp - } else { - KeyPress::Up - }); - } - winuser::VK_DOWN => { - return Ok(if ctrl { - KeyPress::ControlDown - } else if shift { - KeyPress::ShiftDown - } else { - KeyPress::Down - }); - } - winuser::VK_DELETE => return Ok(KeyPress::Delete), - winuser::VK_HOME => return Ok(KeyPress::Home), - winuser::VK_END => return Ok(KeyPress::End), - winuser::VK_PRIOR => return Ok(KeyPress::PageUp), - winuser::VK_NEXT => return Ok(KeyPress::PageDown), - winuser::VK_INSERT => return Ok(KeyPress::Insert), - winuser::VK_F1 => return Ok(KeyPress::F(1)), - winuser::VK_F2 => return Ok(KeyPress::F(2)), - winuser::VK_F3 => return Ok(KeyPress::F(3)), - winuser::VK_F4 => return Ok(KeyPress::F(4)), - winuser::VK_F5 => return Ok(KeyPress::F(5)), - winuser::VK_F6 => return Ok(KeyPress::F(6)), - winuser::VK_F7 => return Ok(KeyPress::F(7)), - winuser::VK_F8 => return Ok(KeyPress::F(8)), - winuser::VK_F9 => return Ok(KeyPress::F(9)), - winuser::VK_F10 => return Ok(KeyPress::F(10)), - winuser::VK_F11 => return Ok(KeyPress::F(11)), - winuser::VK_F12 => return Ok(KeyPress::F(12)), + return Ok(match i32::from(key_event.wVirtualKeyCode) { + winuser::VK_LEFT => Key::Left, + winuser::VK_RIGHT => Key::Right, + winuser::VK_UP => Key::Up, + winuser::VK_DOWN => Key::Down, + winuser::VK_DELETE => Key::Delete, + winuser::VK_HOME => Key::Home, + winuser::VK_END => Key::End, + winuser::VK_PRIOR => Key::PageUp, + winuser::VK_NEXT => Key::PageDown, + winuser::VK_INSERT => Key::Insert, + winuser::VK_F1 => Key::F(1), + winuser::VK_F2 => Key::F(2), + winuser::VK_F3 => Key::F(3), + winuser::VK_F4 => Key::F(4), + winuser::VK_F5 => Key::F(5), + winuser::VK_F6 => Key::F(6), + winuser::VK_F7 => Key::F(7), + winuser::VK_F8 => Key::F(8), + winuser::VK_F9 => Key::F(9), + winuser::VK_F10 => Key::F(10), + winuser::VK_F11 => Key::F(11), + winuser::VK_F12 => Key::F(12), // winuser::VK_BACK is correctly handled because the key_event.UnicodeChar is // also set. _ => continue, - }; + } + .with_mods(mods)); } else if utf16 == 27 { - return Ok(KeyPress::Esc); + return Ok(Key::Esc.with_mods(mods)); } else { if utf16 >= 0xD800 && utf16 < 0xDC00 { surrogate = utf16; @@ -228,14 +198,16 @@ impl RawReader for ConsoleRawReader { }; let c = rc?; if meta { - return Ok(KeyPress::Meta(c)); + return Ok(KeyPress::meta(c)); } else { let mut key = keys::char_to_key_press(c); - if key == KeyPress::Tab && shift { - key = KeyPress::BackTab; - } else if key == KeyPress::Char(' ') && ctrl { - key = KeyPress::Ctrl(' '); + if key == KeyPress::TAB && shift { + key = KeyPress::BACK_TAB; + } else if key == KeyPress::from(' ') && ctrl { + key = KeyPress::ctrl(' '); } + // XXX should this be key.with_mods(mods)? Leaving it as-is + // for now because it seems deliberate. return Ok(key); } } From a67c6d1a3cc83bd73c9d8b95cefea73fd6dbaec7 Mon Sep 17 00:00:00 2001 From: Thom Chiovoloni Date: Tue, 11 Feb 2020 23:51:01 -0800 Subject: [PATCH 2/5] Make escape sequence parser data driven --- Cargo.toml | 1 + src/keymap.rs | 4 +- src/tty/unix.rs | 519 ++++++++++++++++++++++++++++-------------------- 3 files changed, 312 insertions(+), 212 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5e27f37012..7109a7d272 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ unicode-width = "0.1" unicode-segmentation = "1.0" memchr = "2.0" bitflags = "1.2" +smallvec = "1.2" [target.'cfg(unix)'.dependencies] nix = "0.17" diff --git a/src/keymap.rs b/src/keymap.rs index 0e753c07b7..d9ad7e6608 100644 --- a/src/keymap.rs +++ b/src/keymap.rs @@ -505,7 +505,7 @@ impl InputState { } key_press!(META, '<') => Cmd::BeginningOfHistory, key_press!(META, '>') => Cmd::EndOfHistory, - key_press!(META, 'B') | key_press!(META, 'b') => { + key_press!(META, 'B') | key_press!(META, 'b') | key_press!(META, Left) => { if positive { Cmd::Move(Movement::BackwardWord(n, Word::Emacs)) } else { @@ -520,7 +520,7 @@ impl InputState { Cmd::Kill(Movement::BackwardWord(n, Word::Emacs)) } } - key_press!(META, 'F') | key_press!(META, 'f') => { + key_press!(META, 'F') | key_press!(META, 'f') | key_press!(META, Right) => { if positive { Cmd::Move(Movement::ForwardWord(n, At::AfterEnd, Word::Emacs)) } else { diff --git a/src/tty/unix.rs b/src/tty/unix.rs index 377d629446..d130c63021 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -1,5 +1,6 @@ //! Unix specific definitions use std::cmp::Ordering; +use std::collections::{HashMap, HashSet}; use std::io::{self, Read, Write}; use std::os::unix::io::{AsRawFd, RawFd}; use std::sync; @@ -18,7 +19,7 @@ use super::{RawMode, RawReader, Renderer, Term}; use crate::config::{BellStyle, ColorMode, Config, OutputStreamType}; use crate::error; use crate::highlight::Highlighter; -use crate::keys::{self, Key, KeyPress}; +use crate::keys::{self, Key, KeyMods, KeyPress}; use crate::layout::{Layout, Position}; use crate::line_buffer::LineBuffer; use crate::tty::add_prompt_and_highlight; @@ -126,6 +127,285 @@ impl Read for StdinRaw { } } +// Escape sequences tend to be small (They also aren't all UTF8 but in practice +// they pretty much are) +type EscapeSeq = smallvec::SmallVec<[u8; 12]>; + +/// Basically a dictionary from escape sequences to keys. It can also tell us if +/// a sequence cannot possibly result in +struct EscapeBindings { + bindings: HashMap, + // Stores each prefix of a binding in bindings. For example: if we have + // b"\x1b[18;2~" as a binding, `prefixes` will store `\x1b`, `\x1b[`, + // `\x1b[1`, `\x1b[18`, `\x1b[18;`, and so on. We also use it to detect if + // there's an ambiguous binding during startup. + // + // In an ideal world we'd use some prefix tree structure, but I surprisingly + // couldn't find one that actually supported an `item_with_prefix_exists` + // (or similar) function. + // + // This exists so we know when to emit an "unknown escape code" complaint. + prefixes: HashSet, + max_len: usize, +} + +impl EscapeBindings { + pub fn common_unix() -> Self { + let mut res = Self { + bindings: HashMap::new(), + prefixes: HashSet::new(), + max_len: 0, + }; + res.add_common_unix(); + res + } + + // Note: the overwrite flag is always false during initialization. It means + // we panic if there's a dupe or if one escape is the prefix of another. and + // just here for the hard-coded binding list, that we don't accidentally + // break various bindings when stuff inevitably gets added to the default + // list. If we implement populating the binding set dynamically (via + // terminfo, a config file, ...) then we should allow overwriting. + // + // Also this takes string input only because it debug formats much nicer. As + // I mentioned above, escape sequences aren't generally utf8-encoded. + fn bind(&mut self, binding: impl Into, seq_str: &str, overwrite: bool) { + let binding = binding.into(); + let seq = seq_str.as_bytes(); + assert!(!seq.is_empty()); + assert!(overwrite || !self.prefixes.contains(seq), "{:?}", seq_str); + assert!( + overwrite || !self.bindings.contains_key(seq), + "{:?}", + seq_str + ); + if seq.len() > 1 { + // Go in reverse so that we don't add the same prefixes again and + // again -- + for i in (1..seq.len()).rev() { + let existed = self.prefixes.insert(seq[..i].into()); + if existed { + break; + } + // Check if the thing we're adding is already blocked because it + // has a prefix. + assert!( + overwrite || !self.bindings.contains_key(&seq[..i]), + "{:?} is prefix of {:?}: {:?}", + &seq_str[..i], + seq_str, + binding + ); + } + } + let existed = self.bindings.insert(seq.into(), binding); + if !overwrite { + assert_eq!(existed, None, "{:?} {:?}", seq_str, binding); + } + if seq.len() >= self.max_len { + self.max_len = seq.len(); + } + } + + // Ideally some of this would be read out of terminfo, but... that's a pain + // and terminfo doesn't have entries for everything, and sometime's it's + // just wrong anyway (*cough* iTerm *cough*). So instead we just add + // more-or-less everything we care about to the set of bindings. That said, + // this is actually fine. These are the strings that are coming out of the + // terminal, so long as two terminals don't use the same strings to mean + // different things, it won't be a problem that we have so many items in our list. + fn add_common_unix(&mut self) { + // Ansi, vt220+, xterm, ... + self.bind(Key::Up, "\x1b[A", false); + self.bind(Key::Down, "\x1b[B", false); + self.bind(Key::Right, "\x1b[C", false); + self.bind(Key::Left, "\x1b[D", false); + self.bind(Key::End, "\x1b[F", false); + self.bind(Key::Home, "\x1b[H", false); + self.bind(Key::BackTab, "\x1b[Z", false); + + // v220-style special keys + self.bind(Key::Home, "\x1b[1~", false); + self.bind(Key::Insert, "\x1b[2~", false); + self.bind(Key::Delete, "\x1b[3~", false); + self.bind(Key::End, "\x1b[4~", false); + self.bind(Key::PageUp, "\x1b[5~", false); + self.bind(Key::PageDown, "\x1b[6~", false); + + // Apparently from tmux or rxvt? okay. + self.bind(Key::Home, "\x1b[7~", false); + self.bind(Key::End, "\x1b[8~", false); + + // xterm family "app mode" + self.bind(Key::Up, "\x1bOA", false); + self.bind(Key::Down, "\x1bOB", false); + self.bind(Key::Right, "\x1bOC", false); + self.bind(Key::Left, "\x1bOD", false); + self.bind(Key::Home, "\x1bOH", false); + self.bind(Key::End, "\x1bOF", false); + + // Present in the last version of this code, but I think i + self.bind(Key::Up.ctrl(), "\x1bOa", false); + self.bind(Key::Down.ctrl(), "\x1bOb", false); + self.bind(Key::Right.ctrl(), "\x1bOc", false); + self.bind(Key::Left.ctrl(), "\x1bOd", false); + + // vt100-style (F1-F4 are more common) + self.bind(Key::F(1), "\x1bOP", false); + self.bind(Key::F(2), "\x1bOQ", false); + self.bind(Key::F(3), "\x1bOR", false); + self.bind(Key::F(4), "\x1bOS", false); + self.bind(Key::F(5), "\x1bOt", false); + self.bind(Key::F(6), "\x1bOu", false); + self.bind(Key::F(7), "\x1bOv", false); + self.bind(Key::F(8), "\x1bOl", false); + self.bind(Key::F(9), "\x1bOw", false); + self.bind(Key::F(10), "\x1bOx", false); + + // linux console + self.bind(Key::F(1), "\x1b[[A", false); + self.bind(Key::F(2), "\x1b[[B", false); + self.bind(Key::F(3), "\x1b[[C", false); + self.bind(Key::F(4), "\x1b[[D", false); + self.bind(Key::F(5), "\x1b[[E", false); + // rxvt-family, but follows the v220 format + self.bind(Key::F(1), "\x1b[11~", false); + self.bind(Key::F(2), "\x1b[12~", false); + self.bind(Key::F(3), "\x1b[13~", false); + self.bind(Key::F(4), "\x1b[14~", false); + self.bind(Key::F(5), "\x1b[15~", false); + // these are common, though. + self.bind(Key::F(6), "\x1b[17~", false); + self.bind(Key::F(7), "\x1b[18~", false); + self.bind(Key::F(8), "\x1b[19~", false); + self.bind(Key::F(9), "\x1b[20~", false); + self.bind(Key::F(10), "\x1b[21~", false); + self.bind(Key::F(11), "\x1b[23~", false); + self.bind(Key::F(12), "\x1b[24~", false); + + // RXVT among others + self.bind(Key::Up.ctrl(), "\x1b[Oa", false); + self.bind(Key::Down.ctrl(), "\x1b[Ob", false); + self.bind(Key::Right.ctrl(), "\x1b[Oc", false); + self.bind(Key::Left.ctrl(), "\x1b[Od", false); + self.bind(Key::Home.ctrl(), "\x1b[7^", false); + self.bind(Key::End.ctrl(), "\x1b[8^", false); + + self.bind(Key::Up.shift(), "\x1b[a", false); + self.bind(Key::Down.shift(), "\x1b[b", false); + self.bind(Key::Right.shift(), "\x1b[c", false); + self.bind(Key::Left.shift(), "\x1b[d", false); + self.bind(Key::Home.shift(), "\x1b[7$", false); + self.bind(Key::End.shift(), "\x1b[8$", false); + + if cfg!(target_os = "macos") { + // Ugh. These are annoying, since I like these terminals, but the + // codes they send are annoying special cases, so we actually check + // for them directly. Thankfully, they announce their presence via + // the `TERM_PROGRAM` var. + if let Ok(v) = std::env::var("TERM_PROGRAM") { + debug!(target: "rustyline", "term program: {}", v); + match v.as_str() { + "Apple_Terminal" => { + // Yep, really. + self.bind(Key::Left.meta(), "\x1bb", false); + self.bind(Key::Right.meta(), "\x1bf", false); + } + "iTerm.app" => { + self.bind(Key::Up.meta(), "\x1b\x1b[A", false); + self.bind(Key::Down.meta(), "\x1b\x1b[B", false); + self.bind(Key::Right.meta(), "\x1b\x1b[C", false); + self.bind(Key::Left.meta(), "\x1b\x1b[D", false); + } + _ => {} + } + } + } + + // xterm style key mods. Some of these are in terminfos (kLFT3 and so + // on), at least in the extensions section (e.g. pass -x to infocmp). + // But they're all documented here: + // https://invisible-island.net/xterm/ctlseqs/ctlseqs.html + let mods = [ + ("2", KeyMods::SHIFT), + // Bind alt to meta, since it should be harmless to do so, and might + // prevent issues for someone. + ("3", KeyMods::META), // Alt + ("4", KeyMods::META_SHIFT), // Alt + Shift + ("5", KeyMods::CTRL), + ("6", KeyMods::CTRL_SHIFT), + ("7", KeyMods::CTRL_META), // Ctrl + Alt + ("8", KeyMods::CTRL_META_SHIFT), // Ctrl + Alt + Shift + ("9", KeyMods::META), + ("10", KeyMods::META_SHIFT), + ("11", KeyMods::META), // Meta + Alt + ("12", KeyMods::META_SHIFT), // Meta + Alt + Shift + ("13", KeyMods::CTRL_META), + ("14", KeyMods::CTRL_META_SHIFT), + ("15", KeyMods::CTRL_META), // Meta + Ctrl + Alt + ("16", KeyMods::CTRL_META_SHIFT), // Meta + Ctrl + Alt + Shift + ]; + let keys = [ + ("A", Key::Up), + ("B", Key::Down), + ("C", Key::Right), + ("D", Key::Left), + ("H", Key::Home), + ("F", Key::End), + ]; + + for &(num, m) in mods.iter() { + for &(ch, k) in keys.iter() { + // e.g. \E[1;2A + self.bind(k.with_mods(m), &format!("\x1b[1;{}{}", num, ch), false); + } + } + // still xterm, seems to be a slight variation... + self.bind(Key::PageUp.shift(), "\x1b[5;2~", false); + self.bind(Key::PageDown.shift(), "\x1b[6;2~", false); + + self.bind(Key::BracketedPasteStart, "\x1b[200~", false); + self.bind(Key::BracketedPasteEnd, "\x1b[201~", false); + } + + pub fn lookup(&self, v: &[u8]) -> EscapeSearchResult { + if let Some(k) = self.bindings.get(v) { + EscapeSearchResult::Matched(*k) + } else if self.prefixes.contains(v) { + EscapeSearchResult::IsPrefix + } else { + EscapeSearchResult::NoMatch + } + } +} + +struct EscDebug<'a>(&'a [u8]); +impl<'a> std::fmt::Debug for EscDebug<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for (i, &b) in self.0.iter().enumerate() { + if i != 0 { + f.write_str(" ")?; + } + if b == 0x1b { + f.write_str("ESC")?; + } else if b.is_ascii_graphic() && !b.is_ascii_whitespace() { + write!(f, "{}", b as char)?; + } else { + write!(f, "'\\x{:02x}'", b)?; + } + } + Ok(()) + } +} + +/// Return value of `EscapeBinding::lookup` +#[derive(Clone, Copy)] +enum EscapeSearchResult { + Matched(KeyPress), + IsPrefix, + NoMatch, +} + /// Console input reader pub struct PosixRawReader { stdin: StdinRaw, @@ -133,6 +413,7 @@ pub struct PosixRawReader { buf: [u8; 1], parser: Parser, receiver: Utf8, + escapes: EscapeBindings, } struct Utf8 { @@ -147,6 +428,7 @@ impl PosixRawReader { timeout_ms: config.keyseq_timeout(), buf: [0; 1], parser: Parser::new(), + escapes: EscapeBindings::common_unix(), receiver: Utf8 { c: None, valid: true, @@ -154,221 +436,38 @@ impl PosixRawReader { }) } - /// Handle ESC sequences fn escape_sequence(&mut self) -> Result { - // Read the next byte representing the escape sequence. - let seq1 = self.next_char()?; - if seq1 == '[' { - // ESC [ sequences. (CSI) - self.escape_csi() - } else if seq1 == 'O' { - // xterm - // ESC O sequences. (SS3) - self.escape_o() - } else if seq1 == '\x1b' { - // ESC ESC - Ok(KeyPress::ESC) - } else { - // TODO ESC-R (r): Undo all changes made to this line. - Ok(KeyPress::meta(seq1)) - } - } + let mut buffer = EscapeSeq::new(); + buffer.push(b'\x1b'); + let mut first = true; + loop { + let c = self.next_char()?; + buffer.extend_from_slice(c.encode_utf8(&mut [0; 4]).as_bytes()); - /// Handle ESC [ escape sequences - fn escape_csi(&mut self) -> Result { - let seq2 = self.next_char()?; - if seq2.is_digit(10) { - match seq2 { - '0' | '9' => { - debug!(target: "rustyline", "unsupported esc sequence: ESC [ {:?}", seq2); - Ok(Key::UnknownEscSeq.into()) - } - _ => { - // Extended escape, read additional byte. - self.extended_escape(seq2) - } - } - } else if seq2 == '[' { - let seq3 = self.next_char()?; - // Linux console - Ok(match seq3 { - 'A' => key_press!(F(1)), - 'B' => key_press!(F(2)), - 'C' => key_press!(F(3)), - 'D' => key_press!(F(4)), - 'E' => key_press!(F(5)), - _ => { - debug!(target: "rustyline", "unsupported esc sequence: ESC [ [ {:?}", seq3); - Key::UnknownEscSeq.into() - } - }) - } else { - // ANSI - Ok(match seq2 { - 'A' => Key::Up.into(), // kcuu1 - 'B' => Key::Down.into(), // kcud1 - 'C' => Key::Right.into(), // kcuf1 - 'D' => Key::Left.into(), // kcub1 - 'F' => Key::End.into(), - 'H' => Key::Home.into(), // khome - 'Z' => Key::BackTab.into(), - _ => { - debug!(target: "rustyline", "unsupported esc sequence: ESC [ {:?}", seq2); - Key::UnknownEscSeq.into() + match self.escapes.lookup(&buffer) { + EscapeSearchResult::Matched(k) => { + return Ok(k); } - }) - } - } - - /// Handle ESC [ escape sequences - #[allow(clippy::cognitive_complexity)] - fn extended_escape(&mut self, seq2: char) -> Result { - let seq3 = self.next_char()?; - if seq3 == '~' { - Ok(match seq2 { - '1' | '7' => KeyPress::HOME, // tmux, xrvt - '2' => KeyPress::INSERT, - '3' => KeyPress::DELETE, // kdch1 - '4' | '8' => KeyPress::END, // tmux, xrvt - '5' => KeyPress::PAGE_UP, // kpp - '6' => KeyPress::PAGE_DOWN, // knp - _ => { - debug!(target: "rustyline", - "unsupported esc sequence: ESC [ {} ~", seq2); - Key::UnknownEscSeq.into() - } - }) - } else if seq3.is_digit(10) { - let seq4 = self.next_char()?; - if seq4 == '~' { - Ok(match (seq2, seq3) { - ('1', '1') => Key::F(1).into(), // rxvt-unicode - ('1', '2') => Key::F(2).into(), // rxvt-unicode - ('1', '3') => Key::F(3).into(), // rxvt-unicode - ('1', '4') => Key::F(4).into(), // rxvt-unicode - ('1', '5') => Key::F(5).into(), // kf5 - ('1', '7') => Key::F(6).into(), // kf6 - ('1', '8') => Key::F(7).into(), // kf7 - ('1', '9') => Key::F(8).into(), // kf8 - ('2', '0') => Key::F(9).into(), // kf9 - ('2', '1') => Key::F(10).into(), // kf10 - ('2', '3') => Key::F(11).into(), // kf11 - ('2', '4') => Key::F(12).into(), // kf12 - _ => { - debug!(target: "rustyline", - "unsupported esc sequence: ESC [ {}{} ~", seq2, seq3); - Key::UnknownEscSeq.into() - } - }) - } else if seq4 == ';' { - let seq5 = self.next_char()?; - if seq5.is_digit(10) { - let seq6 = self.next_char()?; - if seq6.is_digit(10) { - self.next_char()?; // 'R' expected - } else if seq6 == 'R' { - } else { - debug!(target: "rustyline", - "unsupported esc sequence: ESC [ {}{} ; {} {}", seq2, seq3, seq5, seq6); - } - } else { - debug!(target: "rustyline", - "unsupported esc sequence: ESC [ {}{} ; {:?}", seq2, seq3, seq5); - } - Ok(Key::UnknownEscSeq.into()) - } else if seq4.is_digit(10) { - let seq5 = self.next_char()?; - if seq5 == '~' { - Ok(match (seq2, seq3, seq4) { - ('2', '0', '0') => Key::BracketedPasteStart.into(), - ('2', '0', '1') => Key::BracketedPasteEnd.into(), - _ => { - debug!(target: "rustyline", - "unsupported esc sequence: ESC [ {}{}{}~", seq2, seq3, seq4); - Key::UnknownEscSeq.into() - } - }) - } else { - debug!(target: "rustyline", - "unsupported esc sequence: ESC [ {}{}{} {}", seq2, seq3, seq4, seq5); - Ok(Key::UnknownEscSeq.into()) - } - } else { - debug!(target: "rustyline", - "unsupported esc sequence: ESC [ {}{} {:?}", seq2, seq3, seq4); - Ok(Key::UnknownEscSeq.into()) - } - } else if seq3 == ';' { - let seq4 = self.next_char()?; - if seq4.is_digit(10) { - let seq5 = self.next_char()?; - if seq5.is_digit(10) { - self.next_char()?; // 'R' expected - Ok(Key::UnknownEscSeq.into()) - } else if seq2 == '1' { - Ok(match (seq4, seq5) { - ('5', 'A') => KeyPress::ctrl(Key::Up), - ('5', 'B') => KeyPress::ctrl(Key::Down), - ('5', 'C') => KeyPress::ctrl(Key::Right), - ('5', 'D') => KeyPress::ctrl(Key::Left), - ('2', 'A') => KeyPress::shift(Key::Up), - ('2', 'B') => KeyPress::shift(Key::Down), - ('2', 'C') => KeyPress::shift(Key::Right), - ('2', 'D') => KeyPress::shift(Key::Left), - _ => { - debug!(target: "rustyline", - "unsupported esc sequence: ESC [ 1 ; {} {:?}", seq4, seq5); - Key::UnknownEscSeq.into() + EscapeSearchResult::IsPrefix => {} + EscapeSearchResult::NoMatch => { + return if first { + // This is a bit cludgey, but works for now. Ideally we'd do + // this based on timing out on a read instead. + if c == '\x1b' { + // ESC ESC + Ok(KeyPress::ESC) + } else { + // TODO ESC-R (r): Undo all changes made to this line. + Ok(KeyPress::meta(c)) } - }) - } else { - debug!(target: "rustyline", - "unsupported esc sequence: ESC [ {} ; {} {:?}", seq2, seq4, seq5); - Ok(Key::UnknownEscSeq.into()) - } - } else { - debug!(target: "rustyline", - "unsupported esc sequence: ESC [ {} ; {:?}", seq2, seq4); - Ok(Key::UnknownEscSeq.into()) - } - } else { - Ok(match (seq2, seq3) { - ('5', 'A') => KeyPress::ctrl(Key::Up), - ('5', 'B') => KeyPress::ctrl(Key::Down), - ('5', 'C') => KeyPress::ctrl(Key::Right), - ('5', 'D') => KeyPress::ctrl(Key::Left), - _ => { - debug!(target: "rustyline", - "unsupported esc sequence: ESC [ {} {:?}", seq2, seq3); - Key::UnknownEscSeq.into() + } else { + debug!(target: "rustyline", "unsupported esc sequence: {:?}", EscDebug(&buffer)); + Ok(Key::UnknownEscSeq.into()) + }; } - }) - } - } - - /// Handle ESC O escape sequences - fn escape_o(&mut self) -> Result { - let seq2 = self.next_char()?; - Ok(match seq2 { - 'A' => KeyPress::UP, // kcuu1 - 'B' => KeyPress::DOWN, // kcud1 - 'C' => KeyPress::RIGHT, // kcuf1 - 'D' => KeyPress::LEFT, // kcub1 - 'F' => KeyPress::END, // kend - 'H' => KeyPress::HOME, // khome - 'P' => Key::F(1).into(), // kf1 - 'Q' => Key::F(2).into(), // kf2 - 'R' => Key::F(3).into(), // kf3 - 'S' => Key::F(4).into(), // kf4 - 'a' => KeyPress::ctrl(Key::Up), - 'b' => KeyPress::ctrl(Key::Down), - 'c' => KeyPress::ctrl(Key::Right), // rxvt - 'd' => KeyPress::ctrl(Key::Left), // rxvt - _ => { - debug!(target: "rustyline", "unsupported esc sequence: ESC O {:?}", seq2); - Key::UnknownEscSeq.into() } - }) + first = false; + } } fn poll(&mut self, timeout_ms: i32) -> ::nix::Result { From 4a9f8accd7d700fe49b223d0a79caa6aa8e8f4f1 Mon Sep 17 00:00:00 2001 From: Thom Chiovoloni Date: Fri, 6 Mar 2020 21:48:49 -0800 Subject: [PATCH 3/5] Improve support for rxvt-likes --- src/keys.rs | 15 +++++++++++ src/tty/unix.rs | 70 +++++++++++++++++++++++++++++++++++-------------- 2 files changed, 66 insertions(+), 19 deletions(-) diff --git a/src/keys.rs b/src/keys.rs index 2e895151eb..350d6cae69 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -55,6 +55,21 @@ impl Key { pub const fn meta(self) -> KeyPress { self.with_mods(KeyMods::META) } + + #[inline] + pub const fn meta_shift(self) -> KeyPress { + self.with_mods(KeyMods::META_SHIFT) + } + + #[inline] + pub const fn ctrl_shift(self) -> KeyPress { + self.with_mods(KeyMods::CTRL_SHIFT) + } + + #[inline] + pub const fn ctrl_meta_shift(self) -> KeyPress { + self.with_mods(KeyMods::CTRL_META_SHIFT) + } } bitflags::bitflags! { diff --git a/src/tty/unix.rs b/src/tty/unix.rs index d130c63021..398cad628d 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -244,7 +244,6 @@ impl EscapeBindings { self.bind(Key::Home, "\x1bOH", false); self.bind(Key::End, "\x1bOF", false); - // Present in the last version of this code, but I think i self.bind(Key::Up.ctrl(), "\x1bOa", false); self.bind(Key::Down.ctrl(), "\x1bOb", false); self.bind(Key::Right.ctrl(), "\x1bOc", false); @@ -288,6 +287,8 @@ impl EscapeBindings { self.bind(Key::Down.ctrl(), "\x1b[Ob", false); self.bind(Key::Right.ctrl(), "\x1b[Oc", false); self.bind(Key::Left.ctrl(), "\x1b[Od", false); + self.bind(Key::PageUp.ctrl(), "\x1b[5^", false); + self.bind(Key::PageDown.ctrl(), "\x1b[6^", false); self.bind(Key::Home.ctrl(), "\x1b[7^", false); self.bind(Key::End.ctrl(), "\x1b[8^", false); @@ -297,27 +298,57 @@ impl EscapeBindings { self.bind(Key::Left.shift(), "\x1b[d", false); self.bind(Key::Home.shift(), "\x1b[7$", false); self.bind(Key::End.shift(), "\x1b[8$", false); + self.bind(Key::PageUp.shift(), "\x1b[5$", false); + self.bind(Key::PageDown.shift(), "\x1b[6$", false); + + // iTerm2, rxvt. The logic seems to be that it adds an additional `\x1b` + // for meta, which I guess makes sense, and is *sort of* documented. + self.bind(Key::Up.meta(), "\x1b\x1b[A", false); + self.bind(Key::Down.meta(), "\x1b\x1b[B", false); + self.bind(Key::Right.meta(), "\x1b\x1b[C", false); + self.bind(Key::Left.meta(), "\x1b\x1b[D", false); + + // rxvt and similar (but not iTerm), same logic as above. + self.bind(Key::Delete.meta(), "\x1b\x1b[3~", false); + // nothing uses \x1b\x1b[4~ that I can find. + self.bind(Key::PageUp.meta(), "\x1b\x1b[5~", false); + self.bind(Key::PageDown.meta(), "\x1b\x1b[6~", false); + self.bind(Key::Home.meta(), "\x1b\x1b[7~", false); + self.bind(Key::End.meta(), "\x1b\x1b[8~", false); + + self.bind(Key::Up.meta_shift(), "\x1b\x1b[a", false); + self.bind(Key::Down.meta_shift(), "\x1b\x1b[b", false); + self.bind(Key::Right.meta_shift(), "\x1b\x1b[c", false); + self.bind(Key::Left.meta_shift(), "\x1b\x1b[d", false); + + self.bind(Key::Delete.meta_shift(), "\x1b\x1b[3$", false); + self.bind(Key::PageUp.meta_shift(), "\x1b\x1b[5$", false); + self.bind(Key::PageDown.meta_shift(), "\x1b\x1b[6$", false); + self.bind(Key::Home.meta_shift(), "\x1b\x1b[7$", false); + self.bind(Key::End.meta_shift(), "\x1b\x1b[8$", false); + + self.bind(Key::Delete.ctrl_shift(), "\x1b[3@", false); + self.bind(Key::PageUp.ctrl_shift(), "\x1b[5@", false); + self.bind(Key::PageDown.ctrl_shift(), "\x1b[6@", false); + self.bind(Key::Home.ctrl_shift(), "\x1b[7@", false); + self.bind(Key::End.ctrl_shift(), "\x1b[8@", false); + + self.bind(Key::Delete.ctrl_meta_shift(), "\x1b\x1b[3@", false); + self.bind(Key::PageUp.ctrl_meta_shift(), "\x1b\x1b[5@", false); + self.bind(Key::PageDown.ctrl_meta_shift(), "\x1b\x1b[6@", false); + self.bind(Key::Home.ctrl_meta_shift(), "\x1b\x1b[7@", false); + self.bind(Key::End.ctrl_meta_shift(), "\x1b\x1b[8@", false); if cfg!(target_os = "macos") { - // Ugh. These are annoying, since I like these terminals, but the - // codes they send are annoying special cases, so we actually check - // for them directly. Thankfully, they announce their presence via - // the `TERM_PROGRAM` var. + // Apple terminal is an annoying special case, since the codes it + // would otherwise parse as meta-f and meta-b. Test for it directly + // via the `TERM_PROGRAM` var. if let Ok(v) = std::env::var("TERM_PROGRAM") { debug!(target: "rustyline", "term program: {}", v); - match v.as_str() { - "Apple_Terminal" => { - // Yep, really. - self.bind(Key::Left.meta(), "\x1bb", false); - self.bind(Key::Right.meta(), "\x1bf", false); - } - "iTerm.app" => { - self.bind(Key::Up.meta(), "\x1b\x1b[A", false); - self.bind(Key::Down.meta(), "\x1b\x1b[B", false); - self.bind(Key::Right.meta(), "\x1b\x1b[C", false); - self.bind(Key::Left.meta(), "\x1b\x1b[D", false); - } - _ => {} + if v.as_str() == "Apple_Terminal" { + // Yep, really. + self.bind(Key::Left.meta(), "\x1bb", false); + self.bind(Key::Right.meta(), "\x1bf", false); } } } @@ -454,10 +485,11 @@ impl PosixRawReader { // This is a bit cludgey, but works for now. Ideally we'd do // this based on timing out on a read instead. if c == '\x1b' { - // ESC ESC + // ESC ESC. Does anything actually emit this? Ok(KeyPress::ESC) } else { // TODO ESC-R (r): Undo all changes made to this line. + // (Is the above comment stale? Didn't want to delete a TODO) Ok(KeyPress::meta(c)) } } else { From e9e47cabe4d3f786f3520f0b3ecd960a4faf673e Mon Sep 17 00:00:00 2001 From: Thom Chiovoloni Date: Sat, 7 Mar 2020 21:04:06 -0800 Subject: [PATCH 4/5] Use qp_trie instead of HashMap + HashSet-of-prefixes for keybindings --- Cargo.toml | 1 + src/tty/unix.rs | 48 +++++++++++++++--------------------------------- 2 files changed, 16 insertions(+), 33 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7109a7d272..5de4a0fc2b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ unicode-segmentation = "1.0" memchr = "2.0" bitflags = "1.2" smallvec = "1.2" +qp-trie = "0.7" [target.'cfg(unix)'.dependencies] nix = "0.17" diff --git a/src/tty/unix.rs b/src/tty/unix.rs index 398cad628d..c2335734f2 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -1,6 +1,5 @@ //! Unix specific definitions use std::cmp::Ordering; -use std::collections::{HashMap, HashSet}; use std::io::{self, Read, Write}; use std::os::unix::io::{AsRawFd, RawFd}; use std::sync; @@ -14,6 +13,8 @@ use nix::sys::termios::SetArg; use unicode_segmentation::UnicodeSegmentation; use unicode_width::UnicodeWidthStr; use utf8parse::{Parser, Receiver}; +use qp_trie::Trie; + use super::{RawMode, RawReader, Renderer, Term}; use crate::config::{BellStyle, ColorMode, Config, OutputStreamType}; @@ -134,32 +135,22 @@ type EscapeSeq = smallvec::SmallVec<[u8; 12]>; /// Basically a dictionary from escape sequences to keys. It can also tell us if /// a sequence cannot possibly result in struct EscapeBindings { - bindings: HashMap, - // Stores each prefix of a binding in bindings. For example: if we have - // b"\x1b[18;2~" as a binding, `prefixes` will store `\x1b`, `\x1b[`, - // `\x1b[1`, `\x1b[18`, `\x1b[18;`, and so on. We also use it to detect if - // there's an ambiguous binding during startup. - // - // In an ideal world we'd use some prefix tree structure, but I surprisingly - // couldn't find one that actually supported an `item_with_prefix_exists` - // (or similar) function. - // - // This exists so we know when to emit an "unknown escape code" complaint. - prefixes: HashSet, - max_len: usize, + bindings: Trie } impl EscapeBindings { pub fn common_unix() -> Self { let mut res = Self { - bindings: HashMap::new(), - prefixes: HashSet::new(), - max_len: 0, + bindings: Trie::new() }; res.add_common_unix(); res } + fn is_prefix>(&self, invalid_sequence: &B) -> bool { + !self.bindings.subtrie(invalid_sequence.as_ref()).is_empty() + } + // Note: the overwrite flag is always false during initialization. It means // we panic if there's a dupe or if one escape is the prefix of another. and // just here for the hard-coded binding list, that we don't accidentally @@ -173,24 +164,18 @@ impl EscapeBindings { let binding = binding.into(); let seq = seq_str.as_bytes(); assert!(!seq.is_empty()); - assert!(overwrite || !self.prefixes.contains(seq), "{:?}", seq_str); + assert!(overwrite || !self.is_prefix(seq), "{:?}", seq_str); assert!( overwrite || !self.bindings.contains_key(seq), "{:?}", seq_str ); - if seq.len() > 1 { - // Go in reverse so that we don't add the same prefixes again and - // again -- - for i in (1..seq.len()).rev() { - let existed = self.prefixes.insert(seq[..i].into()); - if existed { - break; - } - // Check if the thing we're adding is already blocked because it - // has a prefix. + if seq.len() > 1 && !overwrite { + // Check if the thing we're adding is already blocked because it + // has a prefix which already exists in the trie + for i in 1..seq.len() { assert!( - overwrite || !self.bindings.contains_key(&seq[..i]), + !self.bindings.contains_key(&seq[..i]), "{:?} is prefix of {:?}: {:?}", &seq_str[..i], seq_str, @@ -202,9 +187,6 @@ impl EscapeBindings { if !overwrite { assert_eq!(existed, None, "{:?} {:?}", seq_str, binding); } - if seq.len() >= self.max_len { - self.max_len = seq.len(); - } } // Ideally some of this would be read out of terminfo, but... that's a pain @@ -402,7 +384,7 @@ impl EscapeBindings { pub fn lookup(&self, v: &[u8]) -> EscapeSearchResult { if let Some(k) = self.bindings.get(v) { EscapeSearchResult::Matched(*k) - } else if self.prefixes.contains(v) { + } else if self.is_prefix(v) { EscapeSearchResult::IsPrefix } else { EscapeSearchResult::NoMatch From 3c6ca9a416be9e03a3d44472988d5cce6c3078e2 Mon Sep 17 00:00:00 2001 From: Thom Chiovoloni Date: Sat, 7 Mar 2020 21:07:21 -0800 Subject: [PATCH 5/5] Typo fix in comment --- src/keys.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/keys.rs b/src/keys.rs index 350d6cae69..affd9cd264 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -327,7 +327,7 @@ pub fn char_to_key_press(c: char) -> KeyPress { '\x07' => KeyPress::ctrl('G'), '\x08' => KeyPress::BACKSPACE, // '\b' '\x09' => KeyPress::TAB, // '\t' - '\x0a' => KeyPress::ctrl('J'), // '\n' (1) + '\x0a' => KeyPress::ctrl('J'), // '\n' (10) '\x0b' => KeyPress::ctrl('K'), '\x0c' => KeyPress::ctrl('L'), '\x0d' => KeyPress::ENTER, // '\r' (13)