Repository: kkawakam/rustyline
Pull request #435 (2020-09-06 17:08:23)

Split KeyPress into separate Key and Modifier types.
Validate:
* Pros and cons of tuple struct versus  struct ?
* Tuple struct order: `(key, mods)` versus `(mods, key)` ?
* Remove `BackTab` and keep only `SHIFT-Tab` ?
* Remove `Modifiers::CTRL_SHIFT`, ... ?
* Normalize:
    - Ctrl-a => Ctrl-A
    - Shift-A => A

Todo:
\E\E[ sequences on unix (we should use `poll`).

This PR replaces/enhances:
* #333 (except \E\E[ sequences)
* #422
* #121 (should we support `SUPER` modifier ?)

This PR should fixes:
* #318
* #421


Patch:
diff --git a/Cargo.toml b/Cargo.toml
index 143574b..8588eca 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -19,6 +19,7 @@ maintenance = { status = "actively-developed" }
 members = ["rustyline-derive"]
 
 [dependencies]
+bitflags = "1.2"
 cfg-if = "0.1.6"
 dirs-next = { version = "1.0", optional = true }
 fs2 = "0.4"
diff --git a/examples/example.rs b/examples/example.rs
index 4826f4d..c627471 100644
--- a/examples/example.rs
+++ b/examples/example.rs
@@ -6,7 +6,7 @@ use rustyline::error::ReadlineError;
 use rustyline::highlight::{Highlighter, MatchingBracketHighlighter};
 use rustyline::hint::{Hinter, HistoryHinter};
 use rustyline::validate::{self, MatchingBracketValidator, Validator};
-use rustyline::{Cmd, CompletionType, Config, Context, EditMode, Editor, KeyPress};
+use rustyline::{Cmd, CompletionType, Config, Context, EditMode, Editor, KeyEvent};
 use rustyline_derive::Helper;
 
 #[derive(Helper)]
@@ -97,8 +97,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(KeyEvent::alt('N'), Cmd::HistorySearchForward);
+    rl.bind_sequence(KeyEvent::alt('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 0943849..1b891c8 100644
--- a/src/keymap.rs
+++ b/src/keymap.rs
@@ -7,7 +7,7 @@ use log::debug;
 use super::Result;
 use crate::config::Config;
 use crate::config::EditMode;
-use crate::keys::KeyPress;
+use crate::keys::{KeyCode as K, KeyEvent, KeyEvent as E, Modifiers as M};
 use crate::tty::{RawReader, Term, Terminal};
 
 /// The number of times one command should be repeated.
@@ -333,7 +333,7 @@ enum InputMode {
 /// Transform key(s) to commands based on current input mode
 pub struct InputState {
     mode: EditMode,
-    custom_bindings: Arc<RwLock<HashMap<KeyPress, Cmd>>>,
+    custom_bindings: Arc<RwLock<HashMap<KeyEvent, Cmd>>>,
     input_mode: InputMode, // vi only ?
     // numeric arguments: http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC7
     num_args: i16,
@@ -370,7 +370,7 @@ pub trait Refresher {
 }
 
 impl InputState {
-    pub fn new(config: &Config, custom_bindings: Arc<RwLock<HashMap<KeyPress, Cmd>>>) -> Self {
+    pub fn new(config: &Config, custom_bindings: Arc<RwLock<HashMap<KeyEvent, Cmd>>>) -> Self {
         Self {
             mode: config.edit_mode(),
             custom_bindings,
@@ -415,7 +415,7 @@ impl InputState {
         rdr: &mut R,
         wrt: &mut dyn Refresher,
         digit: char,
-    ) -> Result<KeyPress> {
+    ) -> Result<KeyEvent> {
         #[allow(clippy::cast_possible_truncation)]
         match digit {
             '0'..='9' => {
@@ -431,7 +431,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') => {
+                E(K::Char(digit @ '0'..='9'), m) if m == M::NONE || m == M::ALT => {
                     if self.num_args == -1 {
                         self.num_args *= digit.to_digit(10).unwrap() as i16;
                     } else if self.num_args.abs() < 1000 {
@@ -442,7 +442,7 @@ impl InputState {
                             .saturating_add(digit.to_digit(10).unwrap() as i16);
                     }
                 }
-                KeyPress::Char('-') | KeyPress::Meta('-') => {}
+                E(K::Char('-'), m) if m == M::NONE || m == M::ALT => {}
                 _ => {
                     wrt.refresh_line()?;
                     return Ok(key);
@@ -455,11 +455,11 @@ impl InputState {
         &mut self,
         rdr: &mut R,
         wrt: &mut dyn Refresher,
-        mut key: KeyPress,
+        mut key: KeyEvent,
     ) -> Result<Cmd> {
-        if let KeyPress::Meta(digit @ '-') = key {
+        if let E(K::Char(digit @ '-'), M::ALT) = key {
             key = self.emacs_digit_argument(rdr, wrt, digit)?;
-        } else if let KeyPress::Meta(digit @ '0'..='9') = key {
+        } else if let E(K::Char(digit @ '0'..='9'), M::ALT) = key {
             key = self.emacs_digit_argument(rdr, wrt, digit)?;
         }
         let (n, positive) = self.emacs_num_args(); // consume them in all cases
@@ -475,39 +475,39 @@ impl InputState {
             }
         }
         let cmd = match key {
-            KeyPress::Char(c) => {
+            E(K::Char(c), M::NONE) => {
                 if positive {
                     Cmd::SelfInsert(n, c)
                 } else {
                     Cmd::Unknown
                 }
             }
-            KeyPress::Ctrl('A') => Cmd::Move(Movement::BeginningOfLine),
-            KeyPress::Ctrl('B') => {
+            E(K::Char('A'), M::CTRL) => Cmd::Move(Movement::BeginningOfLine),
+            E(K::Char('B'), M::CTRL) => {
                 if positive {
                     Cmd::Move(Movement::BackwardChar(n))
                 } else {
                     Cmd::Move(Movement::ForwardChar(n))
                 }
             }
-            KeyPress::Ctrl('E') => Cmd::Move(Movement::EndOfLine),
-            KeyPress::Ctrl('F') => {
+            E(K::Char('E'), M::CTRL) => Cmd::Move(Movement::EndOfLine),
+            E(K::Char('F'), M::CTRL) => {
                 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 => {
+            E(K::Char('G'), M::CTRL) | E::ESC | E(K::Char('G'), M::CTRL_ALT) => Cmd::Abort,
+            E(K::Char('H'), M::CTRL) | E::BACKSPACE => {
                 if positive {
                     Cmd::Kill(Movement::BackwardChar(n))
                 } else {
                     Cmd::Kill(Movement::ForwardChar(n))
                 }
             }
-            KeyPress::BackTab => Cmd::CompleteBackward,
-            KeyPress::Tab => {
+            E(K::BackTab, M::NONE) => Cmd::CompleteBackward,
+            E(K::Tab, M::NONE) => {
                 if positive {
                     Cmd::Complete
                 } else {
@@ -515,60 +515,61 @@ 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') => {
+            E(K::Right, M::NONE) if wrt.has_hint() && wrt.is_cursor_at_end() => Cmd::CompleteHint,
+            E(K::Char('K'), M::CTRL) => {
                 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') => {
+            E(K::Char('L'), M::CTRL) => Cmd::ClearScreen,
+            E(K::Char('N'), M::CTRL) => Cmd::NextHistory,
+            E(K::Char('P'), M::CTRL) => Cmd::PreviousHistory,
+            E(K::Char('X'), M::CTRL) => {
                 let snd_key = rdr.next_key(true)?;
                 match snd_key {
-                    KeyPress::Ctrl('G') | KeyPress::Esc => Cmd::Abort,
-                    KeyPress::Ctrl('U') => Cmd::Undo(n),
+                    E(K::Char('G'), M::CTRL) | E::ESC => Cmd::Abort,
+                    E(K::Char('U'), M::CTRL) => Cmd::Undo(n),
                     _ => Cmd::Unknown,
                 }
             }
-            KeyPress::Meta('\x08') | KeyPress::Meta('\x7f') => {
+            E(K::Backspace, M::ALT) => {
                 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') => {
+            E(K::Char('<'), M::ALT) => Cmd::BeginningOfHistory,
+            E(K::Char('>'), M::ALT) => Cmd::EndOfHistory,
+            E(K::Char('B'), M::ALT) | E(K::Char('b'), M::ALT) => {
                 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') => {
+            E(K::Char('C'), M::ALT) | E(K::Char('c'), M::ALT) => Cmd::CapitalizeWord,
+            E(K::Char('D'), M::ALT) | E(K::Char('d'), M::ALT) => {
                 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') => {
+            E(K::Char('F'), M::ALT) | E(K::Char('f'), M::ALT) => {
                 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,
+            E(K::Char('L'), M::ALT) | E(K::Char('l'), M::ALT) => Cmd::DowncaseWord,
+            E(K::Char('T'), M::ALT) | E(K::Char('t'), M::ALT) => Cmd::TransposeWords(n),
+            // TODO ESC-R (r): Undo all changes made to this line.
+            E(K::Char('U'), M::ALT) | E(K::Char('u'), M::ALT) => Cmd::UpcaseWord,
+            E(K::Char('Y'), M::ALT) | E(K::Char('y'), M::ALT) => Cmd::YankPop,
             _ => self.common(rdr, key, n, positive)?,
         };
         debug!(target: "rustyline", "Emacs command: {:?}", cmd);
@@ -581,12 +582,12 @@ impl InputState {
         rdr: &mut R,
         wrt: &mut dyn Refresher,
         digit: char,
-    ) -> Result<KeyPress> {
+    ) -> Result<KeyEvent> {
         self.num_args = digit.to_digit(10).unwrap() as i16;
         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 E(K::Char(digit @ '0'..='9'), M::NONE) = key {
                 if self.num_args.abs() < 1000 {
                     // shouldn't ever need more than 4 digits
                     self.num_args = self
@@ -605,9 +606,9 @@ impl InputState {
         &mut self,
         rdr: &mut R,
         wrt: &mut dyn Refresher,
-        mut key: KeyPress,
+        mut key: KeyEvent,
     ) -> Result<Cmd> {
-        if let KeyPress::Char(digit @ '1'..='9') = key {
+        if let E(K::Char(digit @ '1'..='9'), M::NONE) = key {
             key = self.vi_arg_digit(rdr, wrt, digit)?;
         }
         let no_num_args = self.num_args == 0;
@@ -628,8 +629,8 @@ impl InputState {
             }
         }
         let cmd = match key {
-            KeyPress::Char('$') | KeyPress::End => Cmd::Move(Movement::EndOfLine),
-            KeyPress::Char('.') => {
+            E(K::Char('$'), M::NONE) | E(K::End, M::NONE) => Cmd::Move(Movement::EndOfLine),
+            E(K::Char('.'), M::NONE) => {
                 // vi-redo (repeat last command)
                 if no_num_args {
                     self.last_cmd.redo(None, wrt)
@@ -637,55 +638,59 @@ impl InputState {
                     self.last_cmd.redo(Some(n), wrt)
                 }
             }
-            // TODO KeyPress::Char('%') => Cmd::???, Move to the corresponding opening/closing
+            // TODO E(K::Char('%'), M::NONE) => Cmd::???, Move to the corresponding opening/closing
             // bracket
-            KeyPress::Char('0') => Cmd::Move(Movement::BeginningOfLine),
-            KeyPress::Char('^') => Cmd::Move(Movement::ViFirstPrint),
-            KeyPress::Char('a') => {
+            E(K::Char('0'), M::NONE) => Cmd::Move(Movement::BeginningOfLine),
+            E(K::Char('^'), M::NONE) => Cmd::Move(Movement::ViFirstPrint),
+            E(K::Char('a'), M::NONE) => {
                 // vi-append-mode
                 self.input_mode = InputMode::Insert;
                 wrt.doing_insert();
                 Cmd::Move(Movement::ForwardChar(n))
             }
-            KeyPress::Char('A') => {
+            E(K::Char('A'), M::NONE) => {
                 // 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') => {
+            E(K::Char('b'), M::NONE) => Cmd::Move(Movement::BackwardWord(n, Word::Vi)), /* vi-prev-word */
+            E(K::Char('B'), M::NONE) => Cmd::Move(Movement::BackwardWord(n, Word::Big)),
+            E(K::Char('c'), M::NONE) => {
                 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') => {
+            E(K::Char('C'), M::NONE) => {
                 self.input_mode = InputMode::Insert;
                 Cmd::Replace(Movement::EndOfLine, None)
             }
-            KeyPress::Char('d') => match self.vi_cmd_motion(rdr, wrt, key, n)? {
+            E(K::Char('d'), M::NONE) => 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') => {
+            E(K::Char('D'), M::NONE) | E(K::Char('K'), M::CTRL) => Cmd::Kill(Movement::EndOfLine),
+            E(K::Char('e'), M::NONE) => {
+                Cmd::Move(Movement::ForwardWord(n, At::BeforeEnd, Word::Vi))
+            }
+            E(K::Char('E'), M::NONE) => {
+                Cmd::Move(Movement::ForwardWord(n, At::BeforeEnd, Word::Big))
+            }
+            E(K::Char('i'), M::NONE) => {
                 // vi-insertion-mode
                 self.input_mode = InputMode::Insert;
                 wrt.doing_insert();
                 Cmd::Noop
             }
-            KeyPress::Char('I') => {
+            E(K::Char('I'), M::NONE) => {
                 // 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' => {
+            E(K::Char(c), M::NONE) if c == 'f' || c == 'F' || c == 't' || c == 'T' => {
                 // vi-char-search
                 let cs = self.vi_char_search(rdr, c)?;
                 match cs {
@@ -693,74 +698,76 @@ impl InputState {
                     None => Cmd::Unknown,
                 }
             }
-            KeyPress::Char(';') => match self.last_char_search {
+            E(K::Char(';'), M::NONE) => match self.last_char_search {
                 Some(cs) => Cmd::Move(Movement::ViCharSearch(n, cs)),
                 None => Cmd::Noop,
             },
-            KeyPress::Char(',') => match self.last_char_search {
+            E(K::Char(','), M::NONE) => 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 E(K::Char('G'), M::NONE) => Cmd::???, Move to the history line n
+            E(K::Char('p'), M::NONE) => Cmd::Yank(n, Anchor::After), // vi-put
+            E(K::Char('P'), M::NONE) => Cmd::Yank(n, Anchor::Before), // vi-put
+            E(K::Char('r'), M::NONE) => {
                 // vi-replace-char:
                 let ch = rdr.next_key(false)?;
                 match ch {
-                    KeyPress::Char(c) => Cmd::ReplaceChar(n, c),
-                    KeyPress::Esc => Cmd::Noop,
+                    E(K::Char(c), M::NONE) => Cmd::ReplaceChar(n, c),
+                    E::ESC => Cmd::Noop,
                     _ => Cmd::Unknown,
                 }
             }
-            KeyPress::Char('R') => {
+            E(K::Char('R'), M::NONE) => {
                 //  vi-replace-mode (overwrite-mode)
                 self.input_mode = InputMode::Replace;
                 Cmd::Replace(Movement::ForwardChar(0), None)
             }
-            KeyPress::Char('s') => {
+            E(K::Char('s'), M::NONE) => {
                 // vi-substitute-char:
                 self.input_mode = InputMode::Insert;
                 Cmd::Replace(Movement::ForwardChar(n), None)
             }
-            KeyPress::Char('S') => {
+            E(K::Char('S'), M::NONE) => {
                 // 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 */
+            E(K::Char('u'), M::NONE) => Cmd::Undo(n),
+            // E(K::Char('U'), M::NONE) => Cmd::???, // revert-line
+            E(K::Char('w'), M::NONE) => Cmd::Move(Movement::ForwardWord(n, At::Start, Word::Vi)), /* vi-next-word */
+            E(K::Char('W'), M::NONE) => 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)? {
+            E(K::Char('x'), M::NONE) => Cmd::Kill(Movement::ForwardChar(n)), // vi-delete
+            E(K::Char('X'), M::NONE) => Cmd::Kill(Movement::BackwardChar(n)), // vi-rubout
+            E(K::Char('y'), M::NONE) => 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 => {
+            // E(K::Char('Y'), M::NONE) => Cmd::???, // vi-yank-to
+            E(K::Char('h'), M::NONE) | E(K::Char('H'), M::CTRL) | E::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::LineDownOrNextHistory(n),
+            E(K::Char('G'), M::CTRL) => Cmd::Abort,
+            E(K::Char('l'), M::NONE) | E(K::Char(' '), M::NONE) => {
+                Cmd::Move(Movement::ForwardChar(n))
+            }
+            E(K::Char('L'), M::CTRL) => Cmd::ClearScreen,
+            E(K::Char('+'), M::NONE) | E(K::Char('j'), M::NONE) => Cmd::LineDownOrNextHistory(n),
             // TODO: move to the start of the line.
-            KeyPress::Ctrl('N') => Cmd::NextHistory,
-            KeyPress::Char('-') | KeyPress::Char('k') => Cmd::LineUpOrPreviousHistory(n),
+            E(K::Char('N'), M::CTRL) => Cmd::NextHistory,
+            E(K::Char('-'), M::NONE) | E(K::Char('k'), M::NONE) => Cmd::LineUpOrPreviousHistory(n),
             // TODO: move to the start of the line.
-            KeyPress::Ctrl('P') => Cmd::PreviousHistory,
-            KeyPress::Ctrl('R') => {
+            E(K::Char('P'), M::CTRL) => Cmd::PreviousHistory,
+            E(K::Char('R'), M::CTRL) => {
                 self.input_mode = InputMode::Insert; // TODO Validate
                 Cmd::ReverseSearchHistory
             }
-            KeyPress::Ctrl('S') => {
+            E(K::Char('S'), M::CTRL) => {
                 self.input_mode = InputMode::Insert; // TODO Validate
                 Cmd::ForwardSearchHistory
             }
-            KeyPress::Esc => Cmd::Noop,
+            E::ESC => Cmd::Noop,
             _ => self.common(rdr, key, n, true)?,
         };
         debug!(target: "rustyline", "Vi command: {:?}", cmd);
@@ -774,7 +781,7 @@ impl InputState {
         &mut self,
         rdr: &mut R,
         wrt: &mut dyn Refresher,
-        key: KeyPress,
+        key: KeyEvent,
     ) -> Result<Cmd> {
         {
             let bindings = self.custom_bindings.read().unwrap();
@@ -788,26 +795,26 @@ impl InputState {
             }
         }
         let cmd = match key {
-            KeyPress::Char(c) => {
+            E(K::Char(c), M::NONE) => {
                 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,
+            E(K::Char('H'), M::CTRL) | E::BACKSPACE => Cmd::Kill(Movement::BackwardChar(1)),
+            E(K::BackTab, M::NONE) => Cmd::CompleteBackward,
+            E(K::Tab, M::NONE) => 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::Meta(k) => {
+            E(K::Right, M::NONE) if wrt.has_hint() && wrt.is_cursor_at_end() => Cmd::CompleteHint,
+            E(K::Char(k), M::ALT) => {
                 debug!(target: "rustyline", "Vi fast command mode: {}", k);
                 self.input_mode = InputMode::Command;
                 wrt.done_inserting();
 
-                self.vi_command(rdr, wrt, KeyPress::Char(k))?
+                self.vi_command(rdr, wrt, E(K::Char(k), M::NONE))?
             }
-            KeyPress::Esc => {
+            E::ESC => {
                 // vi-movement-mode/vi-command-mode
                 self.input_mode = InputMode::Command;
                 wrt.done_inserting();
@@ -832,7 +839,7 @@ impl InputState {
         &mut self,
         rdr: &mut R,
         wrt: &mut dyn Refresher,
-        key: KeyPress,
+        key: KeyEvent,
         n: RepeatCount,
     ) -> Result<Option<Movement>> {
         let mut mvt = rdr.next_key(false)?;
@@ -840,51 +847,51 @@ impl InputState {
             return Ok(Some(Movement::WholeLine));
         }
         let mut n = n;
-        if let KeyPress::Char(digit @ '1'..='9') = mvt {
+        if let E(K::Char(digit @ '1'..='9'), M::NONE) = 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' => {
+            E(K::Char('$'), M::NONE) => Some(Movement::EndOfLine),
+            E(K::Char('0'), M::NONE) => Some(Movement::BeginningOfLine),
+            E(K::Char('^'), M::NONE) => Some(Movement::ViFirstPrint),
+            E(K::Char('b'), M::NONE) => Some(Movement::BackwardWord(n, Word::Vi)),
+            E(K::Char('B'), M::NONE) => Some(Movement::BackwardWord(n, Word::Big)),
+            E(K::Char('e'), M::NONE) => Some(Movement::ForwardWord(n, At::AfterEnd, Word::Vi)),
+            E(K::Char('E'), M::NONE) => Some(Movement::ForwardWord(n, At::AfterEnd, Word::Big)),
+            E(K::Char(c), M::NONE) 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 {
+            E(K::Char(';'), M::NONE) => match self.last_char_search {
                 Some(cs) => Some(Movement::ViCharSearch(n, cs)),
                 None => None,
             },
-            KeyPress::Char(',') => match self.last_char_search {
+            E(K::Char(','), M::NONE) => match self.last_char_search {
                 Some(ref cs) => Some(Movement::ViCharSearch(n, cs.opposite())),
                 None => None,
             },
-            KeyPress::Char('h') | KeyPress::Ctrl('H') | KeyPress::Backspace => {
+            E(K::Char('h'), M::NONE) | E(K::Char('H'), M::CTRL) | E::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') => {
+            E(K::Char('l'), M::NONE) | E(K::Char(' '), M::NONE) => Some(Movement::ForwardChar(n)),
+            E(K::Char('j'), M::NONE) | E(K::Char('+'), M::NONE) => Some(Movement::LineDown(n)),
+            E(K::Char('k'), M::NONE) | E(K::Char('-'), M::NONE) => Some(Movement::LineUp(n)),
+            E(K::Char('w'), M::NONE) => {
                 // 'cw' is 'ce'
-                if key == KeyPress::Char('c') {
+                if key == E(K::Char('c'), M::NONE) {
                     Some(Movement::ForwardWord(n, At::AfterEnd, Word::Vi))
                 } else {
                     Some(Movement::ForwardWord(n, At::Start, Word::Vi))
                 }
             }
-            KeyPress::Char('W') => {
+            E(K::Char('W'), M::NONE) => {
                 // 'cW' is 'cE'
-                if key == KeyPress::Char('c') {
+                if key == E(K::Char('c'), M::NONE) {
                     Some(Movement::ForwardWord(n, At::AfterEnd, Word::Big))
                 } else {
                     Some(Movement::ForwardWord(n, At::Start, Word::Big))
@@ -901,7 +908,7 @@ impl InputState {
     ) -> Result<Option<CharSearch>> {
         let ch = rdr.next_key(false)?;
         Ok(match ch {
-            KeyPress::Char(ch) => {
+            E(K::Char(ch), M::NONE) => {
                 let cs = match cmd {
                     'f' => CharSearch::Forward(ch),
                     't' => CharSearch::ForwardBefore(ch),
@@ -919,72 +926,72 @@ impl InputState {
     fn common<R: RawReader>(
         &mut self,
         rdr: &mut R,
-        key: KeyPress,
+        key: KeyEvent,
         n: RepeatCount,
         positive: bool,
     ) -> Result<Cmd> {
         Ok(match key {
-            KeyPress::Home => Cmd::Move(Movement::BeginningOfLine),
-            KeyPress::Left => {
+            E(K::Home, M::NONE) => Cmd::Move(Movement::BeginningOfLine),
+            E(K::Left, M::NONE) => {
                 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 => {
+            E(K::Char('C'), M::CTRL) => Cmd::Interrupt,
+            E(K::Char('D'), M::CTRL) => Cmd::EndOfFile,
+            E(K::Delete, M::NONE) => {
                 if positive {
                     Cmd::Kill(Movement::ForwardChar(n))
                 } else {
                     Cmd::Kill(Movement::BackwardChar(n))
                 }
             }
-            KeyPress::End => Cmd::Move(Movement::EndOfLine),
-            KeyPress::Right => {
+            E(K::End, M::NONE) => Cmd::Move(Movement::EndOfLine),
+            E(K::Right, M::NONE) => {
                 if positive {
                     Cmd::Move(Movement::ForwardChar(n))
                 } else {
                     Cmd::Move(Movement::BackwardChar(n))
                 }
             }
-            KeyPress::Ctrl('J') |
-            KeyPress::Enter => {
+            E(K::Char('J'), M::CTRL) |
+            E::ENTER => {
                 Cmd::AcceptOrInsertLine { accept_in_the_middle: true }
             }
-            KeyPress::Down => Cmd::LineDownOrNextHistory(1),
-            KeyPress::Up => Cmd::LineUpOrPreviousHistory(1),
-            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') => {
+            E(K::Down, M::NONE) => Cmd::LineDownOrNextHistory(1),
+            E(K::Up, M::NONE) => Cmd::LineUpOrPreviousHistory(1),
+            E(K::Char('R'), M::CTRL) => Cmd::ReverseSearchHistory,
+            E(K::Char('S'), M::CTRL) => Cmd::ForwardSearchHistory, // most terminals override Ctrl+S to suspend execution
+            E(K::Char('T'), M::CTRL) => Cmd::TransposeChars,
+            E(K::Char('U'), M::CTRL) => {
                 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') => {
+            E(K::Char('Q'), M::CTRL) | // most terminals override Ctrl+Q to resume execution
+            E(K::Char('V'), M::CTRL) => Cmd::QuotedInsert,
+            E(K::Char('W'), M::CTRL) => {
                 if positive {
                     Cmd::Kill(Movement::BackwardWord(n, Word::Big))
                 } else {
                     Cmd::Kill(Movement::ForwardWord(n, At::AfterEnd, Word::Big))
                 }
             }
-            KeyPress::Ctrl('Y') => {
+            E(K::Char('Y'), M::CTRL) => {
                 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 => {
+            E(K::Char('Z'), M::CTRL) => Cmd::Suspend,
+            E(K::Char('_'), M::CTRL) => Cmd::Undo(n),
+            E(K::UnknownEscSeq, M::NONE) => Cmd::Noop,
+            E(K::BracketedPasteStart, M::NONE) => {
                 let paste = rdr.read_pasted_text()?;
                 Cmd::Insert(1, paste)
             },
diff --git a/src/keys.rs b/src/keys.rs
index bf06636..c147e49 100644
--- a/src/keys.rs
+++ b/src/keys.rs
@@ -1,12 +1,119 @@
 //! Key constants
 
+/// Input key pressed and modifiers
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct KeyEvent(pub KeyCode, pub Modifiers);
+
+impl KeyEvent {
+    /// Constant value representing an unmodified press of `KeyCode::Backspace`.
+    pub(crate) const BACKSPACE: Self = Self(KeyCode::Backspace, Modifiers::NONE);
+    /// Constant value representing an unmodified press of `KeyCode::Enter`.
+    pub(crate) const ENTER: Self = Self(KeyCode::Enter, Modifiers::NONE);
+    /// Constant value representing an unmodified press of `KeyCode::Esc`.
+    pub(crate) const ESC: Self = Self(KeyCode::Esc, Modifiers::NONE);
+
+    /// Constructor from `char` and modifiers
+    pub fn new(c: char, mut mods: Modifiers) -> Self {
+        use {KeyCode as K, KeyEvent as E, Modifiers as M};
+
+        if !c.is_control() {
+            if !mods.is_empty() {
+                mods.remove(M::SHIFT); // TODO Validate: no SHIFT even if
+                                       // `c` is uppercase
+            }
+            return E(K::Char(c), mods);
+        }
+        #[allow(clippy::match_same_arms)]
+        match c {
+            '\x00' => E(K::Char(' '), mods | M::CTRL),
+            '\x01' => E(K::Char('A'), mods | M::CTRL),
+            '\x02' => E(K::Char('B'), mods | M::CTRL),
+            '\x03' => E(K::Char('C'), mods | M::CTRL),
+            '\x04' => E(K::Char('D'), mods | M::CTRL),
+            '\x05' => E(K::Char('E'), mods | M::CTRL),
+            '\x06' => E(K::Char('F'), mods | M::CTRL),
+            '\x07' => E(K::Char('G'), mods | M::CTRL),
+            '\x08' => E(K::Backspace, mods), // '\b'
+            '\x09' => {
+                // '\t'
+                if mods.contains(M::SHIFT) {
+                    mods.remove(M::SHIFT);
+                    E(K::BackTab, mods)
+                } else {
+                    E(K::Tab, mods)
+                }
+            }
+            '\x0a' => E(K::Char('J'), mods | M::CTRL), // '\n' (10)
+            '\x0b' => E(K::Char('K'), mods | M::CTRL),
+            '\x0c' => E(K::Char('L'), mods | M::CTRL),
+            '\x0d' => E(K::Enter, mods), // '\r' (13)
+            '\x0e' => E(K::Char('N'), mods | M::CTRL),
+            '\x0f' => E(K::Char('O'), mods | M::CTRL),
+            '\x10' => E(K::Char('P'), mods | M::CTRL),
+            '\x11' => E(K::Char('Q'), mods | M::CTRL),
+            '\x12' => E(K::Char('R'), mods | M::CTRL),
+            '\x13' => E(K::Char('S'), mods | M::CTRL),
+            '\x14' => E(K::Char('T'), mods | M::CTRL),
+            '\x15' => E(K::Char('U'), mods | M::CTRL),
+            '\x16' => E(K::Char('V'), mods | M::CTRL),
+            '\x17' => E(K::Char('W'), mods | M::CTRL),
+            '\x18' => E(K::Char('X'), mods | M::CTRL),
+            '\x19' => E(K::Char('Y'), mods | M::CTRL),
+            '\x1a' => E(K::Char('Z'), mods | M::CTRL),
+            '\x1b' => E(K::Esc, mods), // Ctrl-[
+            '\x1c' => E(K::Char('\\'), mods | M::CTRL),
+            '\x1d' => E(K::Char(']'), mods | M::CTRL),
+            '\x1e' => E(K::Char('^'), mods | M::CTRL),
+            '\x1f' => E(K::Char('_'), mods | M::CTRL),
+            '\x7f' => E(K::Backspace, mods), // Rubout
+            '\u{9b}' => E(K::Esc, mods | M::SHIFT),
+            _ => E(K::Null, mods),
+        }
+    }
+
+    /// Constructor from `char` with Ctrl modifier
+    pub fn ctrl(c: char) -> Self {
+        Self::new(c, Modifiers::CTRL)
+    }
+
+    /// Constructor from `char` with Alt modifier
+    pub fn alt(c: char) -> Self {
+        Self::new(c, Modifiers::ALT)
+    }
+
+    /// ctrl-a => ctrl-A (uppercase)
+    /// shift-A => A (no SHIFT modifier)
+    /// shift-Tab => BackTab
+    pub fn normalize(e: Self) -> Self {
+        use {KeyCode as K, KeyEvent as E, Modifiers as M};
+
+        match e {
+            E(K::Char(c), m) if c.is_ascii_control() => Self::new(c, m),
+            E(K::Char(c), m) if c.is_ascii_lowercase() && m.contains(M::CTRL) => {
+                E(K::Char(c.to_ascii_uppercase()), m)
+            }
+            E(K::Char(c), m) if c.is_ascii_uppercase() && m.contains(M::SHIFT) => {
+                E(K::Char(c), m ^ M::SHIFT)
+            }
+            E(K::Tab, m) if m.contains(M::SHIFT) => E(K::BackTab, m ^ M::SHIFT),
+            _ => e,
+        }
+    }
+}
+
+impl From<char> for KeyEvent {
+    fn from(c: char) -> Self {
+        Self::new(c, Modifiers::NONE)
+    }
+}
+
 /// Input key pressed
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
 #[non_exhaustive]
-pub enum KeyPress {
+pub enum KeyCode {
     /// Unsupported escape sequence (on unix platform)
     UnknownEscSeq,
-    /// ⌫ or `KeyPress::Ctrl('H')`
+    /// ⌫ or Ctrl-H
     Backspace,
     /// ⇤ (usually Shift-Tab)
     BackTab,
@@ -16,25 +123,15 @@ pub enum KeyPress {
     BracketedPasteEnd,
     /// Single char
     Char(char),
-    /// Ctrl-↓
-    ControlDown,
-    /// Ctrl-←
-    ControlLeft,
-    /// Ctrl-→
-    ControlRight,
-    /// Ctrl-↑
-    ControlUp,
-    /// Ctrl-char
-    Ctrl(char),
     /// ⌦
     Delete,
     /// ↓ arrow key
     Down,
     /// ⇲
     End,
-    /// ↵ or `KeyPress::Ctrl('M')`
+    /// ↵ or Ctrl-M
     Enter,
-    /// Escape or `KeyPress::Ctrl('[')`
+    /// Escape or Ctrl-[
     Esc,
     /// Function key
     F(u8),
@@ -44,9 +141,7 @@ pub enum KeyPress {
     Insert,
     /// ← arrow key
     Left,
-    /// Escape-char or Alt-char
-    Meta(char),
-    /// `KeyPress::Char('\0')`
+    /// \0
     Null,
     /// ⇟
     PageDown,
@@ -54,69 +149,49 @@ pub enum KeyPress {
     PageUp,
     /// → arrow key
     Right,
-    /// Shift-↓
-    ShiftDown,
-    /// Shift-←
-    ShiftLeft,
-    /// Shift-→
-    ShiftRight,
-    /// Shift-↑
-    ShiftUp,
-    /// ⇥ or `KeyPress::Ctrl('I')`
+    /// ⇥ or Ctrl-I
     Tab,
     /// ↑ arrow key
     Up,
 }
 
-#[cfg(any(windows, unix))]
-pub fn char_to_key_press(c: char) -> KeyPress {
-    if !c.is_control() {
-        return KeyPress::Char(c);
-    }
-    #[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,
+bitflags::bitflags! {
+    /// The set of modifier keys that were triggered along with a key press.
+    pub struct Modifiers: u8 {
+        /// Control modifier
+        const CTRL  = 1<<3;
+        /// Escape or Alt modifier
+        const ALT  = 1<<2;
+        /// Shift modifier
+        const SHIFT = 1<<1;
+
+        /// No modifier
+        const NONE = 0;
+        /// Ctrl + Shift
+        const CTRL_SHIFT = Self::CTRL.bits | Self::SHIFT.bits;
+        /// Alt + Shift
+        const ALT_SHIFT = Self::ALT.bits | Self::SHIFT.bits;
+        /// Ctrl + Alt
+        const CTRL_ALT = Self::CTRL.bits | Self::ALT.bits;
+        /// Ctrl + Alt + Shift
+        const CTRL_ALT_SHIFT = Self::CTRL.bits | Self::ALT.bits | Self::SHIFT.bits;
     }
 }
 
 #[cfg(test)]
 mod tests {
-    use super::{char_to_key_press, KeyPress};
+    use super::{KeyCode as K, KeyEvent as E, Modifiers as M};
+
+    #[test]
+    fn new() {
+        assert_eq!(E::ESC, E::new('\x1b', M::NONE));
+    }
 
     #[test]
-    fn char_to_key() {
-        assert_eq!(KeyPress::Esc, char_to_key_press('\x1b'));
+    fn normalize() {
+        assert_eq!(E::ctrl('A'), E::normalize(E(K::Char('\x01'), M::NONE)));
+        assert_eq!(E::ctrl('A'), E::normalize(E::ctrl('a')));
+        assert_eq!(E::from('A'), E::normalize(E(K::Char('A'), M::SHIFT)));
+        assert_eq!(E(K::BackTab, M::NONE), E::normalize(E(K::Tab, M::SHIFT)));
     }
 }
diff --git a/src/lib.rs b/src/lib.rs
index 1d070f5..a4e98f9 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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::{KeyCode, KeyEvent, Modifiers};
 use crate::kill_ring::{KillRing, Mode};
 use crate::line_buffer::WordAction;
 use crate::validate::Validator;
@@ -804,7 +804,7 @@ pub struct Editor<H: Helper> {
     helper: Option<H>,
     kill_ring: Arc<Mutex<KillRing>>,
     config: Config,
-    custom_bindings: Arc<RwLock<HashMap<KeyPress, Cmd>>>,
+    custom_bindings: Arc<RwLock<HashMap<KeyEvent, Cmd>>>,
 }
 
 #[allow(clippy::new_without_default)]
@@ -923,18 +923,18 @@ impl<H: Helper> Editor<H> {
     }
 
     /// Bind a sequence to a command.
-    pub fn bind_sequence(&mut self, key_seq: KeyPress, cmd: Cmd) -> Option<Cmd> {
+    pub fn bind_sequence(&mut self, key_seq: KeyEvent, cmd: Cmd) -> Option<Cmd> {
         if let Ok(mut bindings) = self.custom_bindings.write() {
-            bindings.insert(key_seq, cmd)
+            bindings.insert(KeyEvent::normalize(key_seq), cmd)
         } else {
             None
         }
     }
 
     /// Remove a binding for the given sequence.
-    pub fn unbind_sequence(&mut self, key_seq: KeyPress) -> Option<Cmd> {
+    pub fn unbind_sequence(&mut self, key_seq: KeyEvent) -> Option<Cmd> {
         if let Ok(mut bindings) = self.custom_bindings.write() {
-            bindings.remove(&key_seq)
+            bindings.remove(&KeyEvent::normalize(key_seq))
         } else {
             None
         }
diff --git a/src/tty/mod.rs b/src/tty/mod.rs
index cfa3932..9f9e728 100644
--- a/src/tty/mod.rs
+++ b/src/tty/mod.rs
@@ -4,7 +4,7 @@ use unicode_width::UnicodeWidthStr;
 
 use crate::config::{BellStyle, ColorMode, Config, OutputStreamType};
 use crate::highlight::Highlighter;
-use crate::keys::KeyPress;
+use crate::keys::KeyEvent;
 use crate::layout::{Layout, Position};
 use crate::line_buffer::LineBuffer;
 use crate::Result;
@@ -18,7 +18,7 @@ pub trait RawMode: Sized {
 /// Translate bytes read from stdin to keys.
 pub trait RawReader {
     /// Blocking read of key pressed.
-    fn next_key(&mut self, single_esc_abort: bool) -> Result<KeyPress>;
+    fn next_key(&mut self, single_esc_abort: bool) -> Result<KeyEvent>;
     /// For CTRL-V support
     #[cfg(unix)]
     fn next_char(&mut self) -> Result<char>;
diff --git a/src/tty/unix.rs b/src/tty/unix.rs
index e4021f3..bce53dc 100644
--- a/src/tty/unix.rs
+++ b/src/tty/unix.rs
@@ -17,7 +17,7 @@ use super::{width, 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::{KeyCode as K, KeyEvent, KeyEvent as E, Modifiers as M};
 use crate::layout::{Layout, Position};
 use crate::line_buffer::LineBuffer;
 use crate::Result;
@@ -158,6 +158,32 @@ struct Utf8 {
     valid: bool,
 }
 
+const UP: char = 'A'; // kcuu1, kUP*
+const DOWN: char = 'B'; // kcud1, kDN*
+const RIGHT: char = 'C'; // kcuf1, kRIT*
+const LEFT: char = 'D'; // kcub1, kLFT*
+const END: char = 'F'; // kend*
+const HOME: char = 'H'; // khom*
+const INSERT: char = '2'; // kic*
+const DELETE: char = '3'; // kdch1, kDC*
+const PAGE_UP: char = '5'; // kpp, kPRV*
+const PAGE_DOWN: char = '6'; // knp, kNXT*
+
+const RXVT_HOME: char = '7';
+const RXVT_END: char = '8';
+
+const SHIFT: char = '2';
+const ALT: char = '3';
+const ALT_SHIFT: char = '4';
+const CTRL: char = '5';
+const CTRL_SHIFT: char = '6';
+const CTRL_ALT: char = '7';
+const CTRL_ALT_SHIFT: char = '8';
+
+const RXVT_SHIFT: char = '$';
+const RXVT_CTRL: char = '\x1e';
+const RXVT_CTRL_SHIFT: char = '@';
+
 impl PosixRawReader {
     fn new(config: &Config) -> Result<Self> {
         Ok(Self {
@@ -172,34 +198,39 @@ impl PosixRawReader {
         })
     }
 
-    /// Handle ESC <seq1> sequences
-    fn escape_sequence(&mut self) -> Result<KeyPress> {
+    /// Handle \E <seq1> sequences
+    // https://invisible-island.net/xterm/xterm-function-keys.html
+    fn escape_sequence(&mut self) -> Result<KeyEvent> {
         // Read the next byte representing the escape sequence.
         let seq1 = self.next_char()?;
         if seq1 == '[' {
-            // ESC [ sequences. (CSI)
+            // \E[ sequences. (CSI)
             self.escape_csi()
         } else if seq1 == 'O' {
             // xterm
-            // ESC O sequences. (SS3)
+            // \EO sequences. (SS3)
             self.escape_o()
         } else if seq1 == '\x1b' {
-            // ESC ESC
-            Ok(KeyPress::Esc)
+            // \E\E
+            // \E\E[A => Alt-Up
+            // \E\E[B => Alt-Down
+            // \E\E[C => Alt-Right
+            // \E\E[D => Alt-Left
+            // ...
+            Ok(E::ESC)
         } else {
-            // TODO ESC-R (r): Undo all changes made to this line.
-            Ok(KeyPress::Meta(seq1))
+            Ok(E::alt(seq1))
         }
     }
 
-    /// Handle ESC [ <seq2> escape sequences
-    fn escape_csi(&mut self) -> Result<KeyPress> {
+    /// Handle \E[ <seq2> escape sequences
+    fn escape_csi(&mut self) -> Result<KeyEvent> {
         let seq2 = self.next_char()?;
         if seq2.is_digit(10) {
             match seq2 {
                 '0' | '9' => {
-                    debug!(target: "rustyline", "unsupported esc sequence: ESC [ {:?}", seq2);
-                    Ok(KeyPress::UnknownEscSeq)
+                    debug!(target: "rustyline", "unsupported esc sequence: \\E[{:?}", seq2);
+                    Ok(E(K::UnknownEscSeq, M::NONE))
                 }
                 _ => {
                     // Extended escape, read additional byte.
@@ -210,72 +241,85 @@ 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' => E(K::F(1), M::NONE),
+                'B' => E(K::F(2), M::NONE),
+                'C' => E(K::F(3), M::NONE),
+                'D' => E(K::F(4), M::NONE),
+                'E' => E(K::F(5), M::NONE),
                 _ => {
-                    debug!(target: "rustyline", "unsupported esc sequence: ESC [ [ {:?}", seq3);
-                    KeyPress::UnknownEscSeq
+                    debug!(target: "rustyline", "unsupported esc sequence: \\E[[{:?}", seq3);
+                    E(K::UnknownEscSeq, M::NONE)
                 }
             })
         } 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,
+                UP => E(K::Up, M::NONE),
+                DOWN => E(K::Down, M::NONE),
+                RIGHT => E(K::Right, M::NONE),
+                LEFT => E(K::Left, M::NONE),
+                //'E' => E(K::, M::), // Ignore
+                END => E(K::End, M::NONE),
+                //'G' => E(K::, M::), // Ignore
+                HOME => E(K::Home, M::NONE), // khome
+                //'J' => E(K::, M::), // clr_eos
+                //'K' => E(K::, M::), // clr_eol
+                //'L' => E(K::, M::), // il1
+                //'M' => E(K::, M::), // kmous
+                //'P' => E(K::Delete, M::NONE), // dch1
+                'Z' => E(K::BackTab, M::NONE),
+                'a' => E(K::Up, M::SHIFT),    // rxvt: kind or kUP
+                'b' => E(K::Down, M::SHIFT),  // rxvt: kri or kDN
+                'c' => E(K::Right, M::SHIFT), // rxvt
+                'd' => E(K::Left, M::SHIFT),  // rxvt
                 _ => {
-                    debug!(target: "rustyline", "unsupported esc sequence: ESC [ {:?}", seq2);
-                    KeyPress::UnknownEscSeq
+                    debug!(target: "rustyline", "unsupported esc sequence: \\E[{:?}", seq2);
+                    E(K::UnknownEscSeq, M::NONE)
                 }
             })
         }
     }
 
-    /// Handle ESC [ <seq2:digit> escape sequences
+    /// Handle \E[ <seq2:digit> escape sequences
     #[allow(clippy::cognitive_complexity)]
-    fn extended_escape(&mut self, seq2: char) -> Result<KeyPress> {
+    fn extended_escape(&mut self, seq2: char) -> Result<KeyEvent> {
         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' | RXVT_HOME => E(K::Home, M::NONE), // tmux, xrvt
+                INSERT => E(K::Insert, M::NONE),
+                DELETE => E(K::Delete, M::NONE),
+                '4' | RXVT_END => E(K::End, M::NONE), // tmux, xrvt
+                PAGE_UP => E(K::PageUp, M::NONE),
+                PAGE_DOWN => E(K::PageDown, M::NONE),
                 _ => {
                     debug!(target: "rustyline",
-                           "unsupported esc sequence: ESC [ {} ~", seq2);
-                    KeyPress::UnknownEscSeq
+                           "unsupported esc sequence: \\E[{}~", seq2);
+                    E(K::UnknownEscSeq, M::NONE)
                 }
             })
         } 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') => E(K::F(1), M::NONE),  // rxvt-unicode
+                    ('1', '2') => E(K::F(2), M::NONE),  // rxvt-unicode
+                    ('1', '3') => E(K::F(3), M::NONE),  // rxvt-unicode
+                    ('1', '4') => E(K::F(4), M::NONE),  // rxvt-unicode
+                    ('1', '5') => E(K::F(5), M::NONE),  // kf5
+                    ('1', '7') => E(K::F(6), M::NONE),  // kf6
+                    ('1', '8') => E(K::F(7), M::NONE),  // kf7
+                    ('1', '9') => E(K::F(8), M::NONE),  // kf8
+                    ('2', '0') => E(K::F(9), M::NONE),  // kf9
+                    ('2', '1') => E(K::F(10), M::NONE), // kf10
+                    ('2', '3') => E(K::F(11), M::NONE), // kf11
+                    ('2', '4') => E(K::F(12), M::NONE), // kf12
+                    //('6', '2') => KeyCode::ScrollUp,
+                    //('6', '3') => KeyCode::ScrollDown,
                     _ => {
                         debug!(target: "rustyline",
-                               "unsupported esc sequence: ESC [ {}{} ~", seq2, seq3);
-                        KeyPress::UnknownEscSeq
+                               "unsupported esc sequence: \\E[{}{}~", seq2, seq3);
+                        E(K::UnknownEscSeq, M::NONE)
                     }
                 })
             } else if seq4 == ';' {
@@ -284,37 +328,63 @@ impl PosixRawReader {
                     let seq6 = self.next_char()?;
                     if seq6.is_digit(10) {
                         self.next_char()?; // 'R' expected
+                        Ok(E(K::UnknownEscSeq, M::NONE))
                     } else if seq6 == 'R' {
+                        Ok(E(K::UnknownEscSeq, M::NONE))
+                    } else if seq6 == '~' {
+                        Ok(match (seq2, seq3, seq5) {
+                            ('1', '5', CTRL) => E(K::F(5), M::CTRL),
+                            //('1', '5', '6') => E(K::F(17), M::CTRL),
+                            ('1', '7', CTRL) => E(K::F(6), M::CTRL),
+                            //('1', '7', '6') => E(K::F(18), M::CTRL),
+                            ('1', '8', CTRL) => E(K::F(7), M::CTRL),
+                            ('1', '9', CTRL) => E(K::F(8), M::CTRL),
+                            //('1', '9', '6') => E(K::F(19), M::CTRL),
+                            ('2', '0', CTRL) => E(K::F(9), M::CTRL),
+                            //('2', '0', '6') => E(K::F(21), M::CTRL),
+                            ('2', '1', CTRL) => E(K::F(10), M::CTRL),
+                            //('2', '1', '6') => E(K::F(22), M::CTRL),
+                            ('2', '3', CTRL) => E(K::F(11), M::CTRL),
+                            //('2', '3', '6') => E(K::F(23), M::CTRL),
+                            ('2', '4', CTRL) => E(K::F(12), M::CTRL),
+                            //('2', '4', '6') => E(K::F(24), M::CTRL),
+                            _ => {
+                                debug!(target: "rustyline",
+                                       "unsupported esc sequence: \\E[{}{};{}~", seq2, seq3, seq5);
+                                E(K::UnknownEscSeq, M::NONE)
+                            }
+                        })
                     } else {
                         debug!(target: "rustyline",
-                               "unsupported esc sequence: ESC [ {}{} ; {} {}", seq2, seq3, seq5, seq6);
+                               "unsupported esc sequence: \\E[{}{};{}{}", seq2, seq3, seq5, seq6);
+                        Ok(E(K::UnknownEscSeq, M::NONE))
                     }
                 } else {
                     debug!(target: "rustyline",
-                           "unsupported esc sequence: ESC [ {}{} ; {:?}", seq2, seq3, seq5);
+                           "unsupported esc sequence: \\E[{}{};{:?}", seq2, seq3, seq5);
+                    Ok(E(K::UnknownEscSeq, M::NONE))
                 }
-                Ok(KeyPress::UnknownEscSeq)
             } 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') => E(K::BracketedPasteStart, M::NONE),
+                        ('2', '0', '1') => E(K::BracketedPasteEnd, M::NONE),
                         _ => {
                             debug!(target: "rustyline",
-                                   "unsupported esc sequence: ESC [ {}{}{}~", seq2, seq3, seq4);
-                            KeyPress::UnknownEscSeq
+                                   "unsupported esc sequence: \\E[{}{}{}~", seq2, seq3, seq4);
+                            E(K::UnknownEscSeq, M::NONE)
                         }
                     })
                 } else {
                     debug!(target: "rustyline",
-                           "unsupported esc sequence: ESC [ {}{}{} {}", seq2, seq3, seq4, seq5);
-                    Ok(KeyPress::UnknownEscSeq)
+                           "unsupported esc sequence: \\E[{}{}{}{}", seq2, seq3, seq4, seq5);
+                    Ok(E(K::UnknownEscSeq, M::NONE))
                 }
             } else {
                 debug!(target: "rustyline",
-                       "unsupported esc sequence: ESC [ {}{} {:?}", seq2, seq3, seq4);
-                Ok(KeyPress::UnknownEscSeq)
+                       "unsupported esc sequence: \\E[{}{}{:?}", seq2, seq3, seq4);
+                Ok(E(K::UnknownEscSeq, M::NONE))
             }
         } else if seq3 == ';' {
             let seq4 = self.next_char()?;
@@ -322,69 +392,216 @@ impl PosixRawReader {
                 let seq5 = self.next_char()?;
                 if seq5.is_digit(10) {
                     self.next_char()?; // 'R' expected
-                    Ok(KeyPress::UnknownEscSeq)
+                                       //('1', '0', UP) => E(K::, M::), // Alt + Shift + Up
+                    Ok(E(K::UnknownEscSeq, M::NONE))
                 } 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,
+                        (SHIFT, UP) => E(K::Up, M::SHIFT),     // ~ key_sr
+                        (SHIFT, DOWN) => E(K::Down, M::SHIFT), // ~ key_sf
+                        (SHIFT, RIGHT) => E(K::Right, M::SHIFT),
+                        (SHIFT, LEFT) => E(K::Left, M::SHIFT),
+                        (SHIFT, END) => E(K::End, M::SHIFT), // kEND
+                        (SHIFT, HOME) => E(K::Home, M::SHIFT), // kHOM
+                        //('2', 'P') => E(K::F(13), M::NONE),
+                        //('2', 'Q') => E(K::F(14), M::NONE),
+                        //('2', 'S') => E(K::F(16), M::NONE),
+                        (ALT, UP) => E(K::Up, M::ALT),
+                        (ALT, DOWN) => E(K::Down, M::ALT),
+                        (ALT, RIGHT) => E(K::Right, M::ALT),
+                        (ALT, LEFT) => E(K::Left, M::ALT),
+                        (ALT, END) => E(K::End, M::ALT),
+                        (ALT, HOME) => E(K::Home, M::ALT),
+                        (ALT_SHIFT, UP) => E(K::Up, M::ALT_SHIFT),
+                        (ALT_SHIFT, DOWN) => E(K::Down, M::ALT_SHIFT),
+                        (ALT_SHIFT, RIGHT) => E(K::Right, M::ALT_SHIFT),
+                        (ALT_SHIFT, LEFT) => E(K::Left, M::ALT_SHIFT),
+                        (ALT_SHIFT, END) => E(K::End, M::ALT_SHIFT),
+                        (ALT_SHIFT, HOME) => E(K::Home, M::ALT_SHIFT),
+                        (CTRL, UP) => E(K::Up, M::CTRL),
+                        (CTRL, DOWN) => E(K::Down, M::CTRL),
+                        (CTRL, RIGHT) => E(K::Right, M::CTRL),
+                        (CTRL, LEFT) => E(K::Left, M::CTRL),
+                        (CTRL, END) => E(K::End, M::CTRL),
+                        (CTRL, HOME) => E(K::Home, M::CTRL),
+                        (CTRL, 'P') => E(K::F(1), M::CTRL),
+                        (CTRL, 'Q') => E(K::F(2), M::CTRL),
+                        (CTRL, 'S') => E(K::F(4), M::CTRL),
+                        (CTRL, 'p') => E(K::Char('0'), M::CTRL),
+                        (CTRL, 'q') => E(K::Char('1'), M::CTRL),
+                        (CTRL, 'r') => E(K::Char('2'), M::CTRL),
+                        (CTRL, 's') => E(K::Char('3'), M::CTRL),
+                        (CTRL, 't') => E(K::Char('4'), M::CTRL),
+                        (CTRL, 'u') => E(K::Char('5'), M::CTRL),
+                        (CTRL, 'v') => E(K::Char('6'), M::CTRL),
+                        (CTRL, 'w') => E(K::Char('7'), M::CTRL),
+                        (CTRL, 'x') => E(K::Char('8'), M::CTRL),
+                        (CTRL, 'y') => E(K::Char('9'), M::CTRL),
+                        (CTRL_SHIFT, UP) => E(K::Up, M::CTRL_SHIFT),
+                        (CTRL_SHIFT, DOWN) => E(K::Down, M::CTRL_SHIFT),
+                        (CTRL_SHIFT, RIGHT) => E(K::Right, M::CTRL_SHIFT),
+                        (CTRL_SHIFT, LEFT) => E(K::Left, M::CTRL_SHIFT),
+                        (CTRL_SHIFT, END) => E(K::End, M::CTRL_SHIFT),
+                        (CTRL_SHIFT, HOME) => E(K::Home, M::CTRL_SHIFT),
+                        //('6', 'P') => E(K::F(13), M::CTRL),
+                        //('6', 'Q') => E(K::F(14), M::CTRL),
+                        //('6', 'S') => E(K::F(16), M::CTRL),
+                        (CTRL_SHIFT, 'p') => E(K::Char('0'), M::CTRL_SHIFT),
+                        (CTRL_SHIFT, 'q') => E(K::Char('1'), M::CTRL_SHIFT),
+                        (CTRL_SHIFT, 'r') => E(K::Char('2'), M::CTRL_SHIFT),
+                        (CTRL_SHIFT, 's') => E(K::Char('3'), M::CTRL_SHIFT),
+                        (CTRL_SHIFT, 't') => E(K::Char('4'), M::CTRL_SHIFT),
+                        (CTRL_SHIFT, 'u') => E(K::Char('5'), M::CTRL_SHIFT),
+                        (CTRL_SHIFT, 'v') => E(K::Char('6'), M::CTRL_SHIFT),
+                        (CTRL_SHIFT, 'w') => E(K::Char('7'), M::CTRL_SHIFT),
+                        (CTRL_SHIFT, 'x') => E(K::Char('8'), M::CTRL_SHIFT),
+                        (CTRL_SHIFT, 'y') => E(K::Char('9'), M::CTRL_SHIFT),
+                        (CTRL_ALT, UP) => E(K::Up, M::CTRL_ALT),
+                        (CTRL_ALT, DOWN) => E(K::Down, M::CTRL_ALT),
+                        (CTRL_ALT, RIGHT) => E(K::Right, M::CTRL_ALT),
+                        (CTRL_ALT, LEFT) => E(K::Left, M::CTRL_ALT),
+                        (CTRL_ALT, END) => E(K::End, M::CTRL_ALT),
+                        (CTRL_ALT, HOME) => E(K::Home, M::CTRL_ALT),
+                        (CTRL_ALT, 'p') => E(K::Char('0'), M::CTRL_ALT),
+                        (CTRL_ALT, 'q') => E(K::Char('1'), M::CTRL_ALT),
+                        (CTRL_ALT, 'r') => E(K::Char('2'), M::CTRL_ALT),
+                        (CTRL_ALT, 's') => E(K::Char('3'), M::CTRL_ALT),
+                        (CTRL_ALT, 't') => E(K::Char('4'), M::CTRL_ALT),
+                        (CTRL_ALT, 'u') => E(K::Char('5'), M::CTRL_ALT),
+                        (CTRL_ALT, 'v') => E(K::Char('6'), M::CTRL_ALT),
+                        (CTRL_ALT, 'w') => E(K::Char('7'), M::CTRL_ALT),
+                        (CTRL_ALT, 'x') => E(K::Char('8'), M::CTRL_ALT),
+                        (CTRL_ALT, 'y') => E(K::Char('9'), M::CTRL_ALT),
+                        (CTRL_ALT_SHIFT, UP) => E(K::Up, M::CTRL_ALT_SHIFT),
+                        (CTRL_ALT_SHIFT, DOWN) => E(K::Down, M::CTRL_ALT_SHIFT),
+                        (CTRL_ALT_SHIFT, RIGHT) => E(K::Right, M::CTRL_ALT_SHIFT),
+                        (CTRL_ALT_SHIFT, LEFT) => E(K::Left, M::CTRL_ALT_SHIFT),
+                        (CTRL_ALT_SHIFT, END) => E(K::End, M::CTRL_ALT_SHIFT),
+                        (CTRL_ALT_SHIFT, HOME) => E(K::Home, M::CTRL_ALT_SHIFT),
+                        (CTRL_ALT_SHIFT, 'p') => E(K::Char('0'), M::CTRL_ALT_SHIFT),
+                        (CTRL_ALT_SHIFT, 'q') => E(K::Char('1'), M::CTRL_ALT_SHIFT),
+                        (CTRL_ALT_SHIFT, 'r') => E(K::Char('2'), M::CTRL_ALT_SHIFT),
+                        (CTRL_ALT_SHIFT, 's') => E(K::Char('3'), M::CTRL_ALT_SHIFT),
+                        (CTRL_ALT_SHIFT, 't') => E(K::Char('4'), M::CTRL_ALT_SHIFT),
+                        (CTRL_ALT_SHIFT, 'u') => E(K::Char('5'), M::CTRL_ALT_SHIFT),
+                        (CTRL_ALT_SHIFT, 'v') => E(K::Char('6'), M::CTRL_ALT_SHIFT),
+                        (CTRL_ALT_SHIFT, 'w') => E(K::Char('7'), M::CTRL_ALT_SHIFT),
+                        (CTRL_ALT_SHIFT, 'x') => E(K::Char('8'), M::CTRL_ALT_SHIFT),
+                        (CTRL_ALT_SHIFT, 'y') => E(K::Char('9'), M::CTRL_ALT_SHIFT),
+                        // Meta + arrow on (some?) Macs when using iTerm defaults
+                        ('9', UP) => E(K::Up, M::ALT),
+                        ('9', DOWN) => E(K::Down, M::ALT),
+                        ('9', RIGHT) => E(K::Right, M::ALT),
+                        ('9', LEFT) => E(K::Left, M::ALT),
+                        _ => {
+                            debug!(target: "rustyline",
+                                   "unsupported esc sequence: \\E[1;{}{:?}", seq4, seq5);
+                            E(K::UnknownEscSeq, M::NONE)
+                        }
+                    })
+                } else if seq5 == '~' {
+                    Ok(match (seq2, seq4) {
+                        (INSERT, SHIFT) => E(K::Insert, M::SHIFT),
+                        (INSERT, ALT) => E(K::Insert, M::ALT),
+                        (INSERT, ALT_SHIFT) => E(K::Insert, M::ALT_SHIFT),
+                        (INSERT, CTRL) => E(K::Insert, M::CTRL),
+                        (INSERT, CTRL_SHIFT) => E(K::Insert, M::CTRL_SHIFT),
+                        (INSERT, CTRL_ALT) => E(K::Insert, M::CTRL_ALT),
+                        (INSERT, CTRL_ALT_SHIFT) => E(K::Insert, M::CTRL_ALT_SHIFT),
+                        (DELETE, SHIFT) => E(K::Delete, M::SHIFT),
+                        (DELETE, ALT) => E(K::Delete, M::ALT),
+                        (DELETE, ALT_SHIFT) => E(K::Delete, M::ALT_SHIFT),
+                        (DELETE, CTRL) => E(K::Delete, M::CTRL),
+                        (DELETE, CTRL_SHIFT) => E(K::Delete, M::CTRL_SHIFT),
+                        (DELETE, CTRL_ALT) => E(K::Delete, M::CTRL_ALT),
+                        (DELETE, CTRL_ALT_SHIFT) => E(K::Delete, M::CTRL_ALT_SHIFT),
+                        (PAGE_UP, SHIFT) => E(K::PageUp, M::SHIFT),
+                        (PAGE_UP, ALT) => E(K::PageUp, M::ALT),
+                        (PAGE_UP, ALT_SHIFT) => E(K::PageUp, M::ALT_SHIFT),
+                        (PAGE_UP, CTRL) => E(K::PageUp, M::CTRL),
+                        (PAGE_UP, CTRL_SHIFT) => E(K::PageUp, M::CTRL_SHIFT),
+                        (PAGE_UP, CTRL_ALT) => E(K::PageUp, M::CTRL_ALT),
+                        (PAGE_UP, CTRL_ALT_SHIFT) => E(K::PageUp, M::CTRL_ALT_SHIFT),
+                        (PAGE_DOWN, SHIFT) => E(K::PageDown, M::SHIFT),
+                        (PAGE_DOWN, ALT) => E(K::PageDown, M::ALT),
+                        (PAGE_DOWN, ALT_SHIFT) => E(K::PageDown, M::ALT_SHIFT),
+                        (PAGE_DOWN, CTRL) => E(K::PageDown, M::CTRL),
+                        (PAGE_DOWN, CTRL_SHIFT) => E(K::PageDown, M::CTRL_SHIFT),
+                        (PAGE_DOWN, CTRL_ALT) => E(K::PageDown, M::CTRL_ALT),
+                        (PAGE_DOWN, CTRL_ALT_SHIFT) => E(K::PageDown, M::CTRL_ALT_SHIFT),
                         _ => {
                             debug!(target: "rustyline",
-                                   "unsupported esc sequence: ESC [ 1 ; {} {:?}", seq4, seq5);
-                            KeyPress::UnknownEscSeq
+                                   "unsupported esc sequence: \\E[{};{:?}~", seq2, seq4);
+                            E(K::UnknownEscSeq, M::NONE)
                         }
                     })
                 } else {
                     debug!(target: "rustyline",
-                           "unsupported esc sequence: ESC [ {} ; {} {:?}", seq2, seq4, seq5);
-                    Ok(KeyPress::UnknownEscSeq)
+                           "unsupported esc sequence: \\E[{};{}{:?}", seq2, seq4, seq5);
+                    Ok(E(K::UnknownEscSeq, M::NONE))
                 }
             } else {
                 debug!(target: "rustyline",
-                       "unsupported esc sequence: ESC [ {} ; {:?}", seq2, seq4);
-                Ok(KeyPress::UnknownEscSeq)
+                       "unsupported esc sequence: \\E[{};{:?}", seq2, seq4);
+                Ok(E(K::UnknownEscSeq, M::NONE))
             }
         } else {
             Ok(match (seq2, seq3) {
-                ('5', 'A') => KeyPress::ControlUp,
-                ('5', 'B') => KeyPress::ControlDown,
-                ('5', 'C') => KeyPress::ControlRight,
-                ('5', 'D') => KeyPress::ControlLeft,
+                (DELETE, RXVT_CTRL) => E(K::Delete, M::CTRL),
+                (DELETE, RXVT_CTRL_SHIFT) => E(K::Delete, M::CTRL_SHIFT),
+                (CTRL, UP) => E(K::Up, M::CTRL),
+                (CTRL, DOWN) => E(K::Down, M::CTRL),
+                (CTRL, RIGHT) => E(K::Right, M::CTRL),
+                (CTRL, LEFT) => E(K::Left, M::CTRL),
+                (PAGE_UP, RXVT_CTRL) => E(K::PageUp, M::CTRL),
+                (PAGE_UP, RXVT_SHIFT) => E(K::PageUp, M::SHIFT),
+                (PAGE_UP, RXVT_CTRL_SHIFT) => E(K::PageUp, M::CTRL_SHIFT),
+                (PAGE_DOWN, RXVT_CTRL) => E(K::PageDown, M::CTRL),
+                (PAGE_DOWN, RXVT_SHIFT) => E(K::PageDown, M::SHIFT),
+                (PAGE_DOWN, RXVT_CTRL_SHIFT) => E(K::PageDown, M::CTRL_SHIFT),
+                (RXVT_HOME, RXVT_CTRL) => E(K::Home, M::CTRL),
+                (RXVT_HOME, RXVT_SHIFT) => E(K::Home, M::SHIFT),
+                (RXVT_HOME, RXVT_CTRL_SHIFT) => E(K::Home, M::CTRL_SHIFT),
+                (RXVT_END, RXVT_CTRL) => E(K::End, M::CTRL), // kEND5 or kel
+                (RXVT_END, RXVT_SHIFT) => E(K::End, M::SHIFT),
+                (RXVT_END, RXVT_CTRL_SHIFT) => E(K::End, M::CTRL_SHIFT),
                 _ => {
                     debug!(target: "rustyline",
-                           "unsupported esc sequence: ESC [ {} {:?}", seq2, seq3);
-                    KeyPress::UnknownEscSeq
+                           "unsupported esc sequence: \\E[{}{:?}", seq2, seq3);
+                    E(K::UnknownEscSeq, M::NONE)
                 }
             })
         }
     }
 
-    /// Handle ESC O <seq2> escape sequences
-    fn escape_o(&mut self) -> Result<KeyPress> {
+    /// Handle \EO <seq2> escape sequences
+    fn escape_o(&mut self) -> Result<KeyEvent> {
         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
+            UP => E(K::Up, M::NONE),
+            DOWN => E(K::Down, M::NONE),
+            RIGHT => E(K::Right, M::NONE),
+            LEFT => E(K::Left, M::NONE),
+            //'E' => E(K::, M::),// key_b2, kb2
+            END => E(K::End, M::NONE),   // kend
+            HOME => E(K::Home, M::NONE), // khome
+            'M' => E::ENTER,             // kent
+            'P' => E(K::F(1), M::NONE),  // kf1
+            'Q' => E(K::F(2), M::NONE),  // kf2
+            'R' => E(K::F(3), M::NONE),  // kf3
+            'S' => E(K::F(4), M::NONE),  // kf4
+            'a' => E(K::Up, M::CTRL),
+            'b' => E(K::Down, M::CTRL),
+            'c' => E(K::Right, M::CTRL), // rxvt
+            'd' => E(K::Left, M::CTRL),  // rxvt
+            'l' => E(K::F(8), M::NONE),
+            't' => E(K::F(5), M::NONE),  // kf5 or kb1
+            'u' => E(K::F(6), M::NONE),  // kf6 or kb2
+            'v' => E(K::F(7), M::NONE),  // kf7 or kb3
+            'w' => E(K::F(9), M::NONE),  // kf9 or ka1
+            'x' => E(K::F(10), M::NONE), // kf10 or ka2
             _ => {
-                debug!(target: "rustyline", "unsupported esc sequence: ESC O {:?}", seq2);
-                KeyPress::UnknownEscSeq
+                debug!(target: "rustyline", "unsupported esc sequence: \\EO{:?}", seq2);
+                E(K::UnknownEscSeq, M::NONE)
             }
         })
     }
@@ -396,11 +613,11 @@ impl PosixRawReader {
 }
 
 impl RawReader for PosixRawReader {
-    fn next_key(&mut self, single_esc_abort: bool) -> Result<KeyPress> {
+    fn next_key(&mut self, single_esc_abort: bool) -> Result<KeyEvent> {
         let c = self.next_char()?;
 
-        let mut key = keys::char_to_key_press(c);
-        if key == KeyPress::Esc {
+        let mut key = KeyEvent::new(c, M::NONE);
+        if key == E::ESC {
             let timeout_ms = if single_esc_abort && self.timeout_ms == -1 {
                 0
             } else {
@@ -444,7 +661,7 @@ impl RawReader for PosixRawReader {
             match self.next_char()? {
                 '\x1b' => {
                     let key = self.escape_sequence()?;
-                    if key == KeyPress::BracketedPasteEnd {
+                    if key == E(K::BracketedPasteEnd, M::NONE) {
                         break;
                     } else {
                         continue; // TODO validate
diff --git a/src/tty/windows.rs b/src/tty/windows.rs
index 9fa9c6a..dd652cc 100644
--- a/src/tty/windows.rs
+++ b/src/tty/windows.rs
@@ -1,7 +1,7 @@
 //! Windows specific definitions
 #![allow(clippy::try_err)] // suggested fix does not work (cannot infer...)
 
-use std::io::{self, ErrorKind, Write};
+use std::io::{self, Write};
 use std::mem;
 use std::sync::atomic;
 
@@ -17,7 +17,7 @@ use super::{width, 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::{KeyCode as K, KeyEvent, Modifiers as M};
 use crate::layout::{Layout, Position};
 use crate::line_buffer::LineBuffer;
 use crate::Result;
@@ -101,7 +101,7 @@ impl ConsoleRawReader {
 }
 
 impl RawReader for ConsoleRawReader {
-    fn next_key(&mut self, _: bool) -> Result<KeyPress> {
+    fn next_key(&mut self, _: bool) -> Result<KeyEvent> {
         use std::char::decode_utf16;
         use winapi::um::wincon::{
             LEFT_ALT_PRESSED, LEFT_CTRL_PRESSED, RIGHT_ALT_PRESSED, RIGHT_CTRL_PRESSED,
@@ -137,73 +137,51 @@ impl RawReader for ConsoleRawReader {
             let alt_gr = key_event.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED)
                 == (LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED);
             let alt = key_event.dwControlKeyState & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED) != 0;
-            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 mut mods = M::NONE;
+            if key_event.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED) != 0 {
+                mods |= M::CTRL;
+            }
+            if alt && !alt_gr {
+                mods |= M::ALT;
+            }
+            if key_event.dwControlKeyState & SHIFT_PRESSED != 0 {
+                mods |= M::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)),
-                    // winuser::VK_BACK is correctly handled because the key_event.UnicodeChar is
-                    // also set.
-                    _ => continue,
-                };
+            let key = if utf16 == 0 {
+                KeyEvent(
+                    match i32::from(key_event.wVirtualKeyCode) {
+                        winuser::VK_LEFT => K::Left,
+                        winuser::VK_RIGHT => K::Right,
+                        winuser::VK_UP => K::Up,
+                        winuser::VK_DOWN => K::Down,
+                        winuser::VK_DELETE => K::Delete,
+                        winuser::VK_HOME => K::Home,
+                        winuser::VK_END => K::End,
+                        winuser::VK_PRIOR => K::PageUp,
+                        winuser::VK_NEXT => K::PageDown,
+                        winuser::VK_INSERT => K::Insert,
+                        winuser::VK_F1 => K::F(1),
+                        winuser::VK_F2 => K::F(2),
+                        winuser::VK_F3 => K::F(3),
+                        winuser::VK_F4 => K::F(4),
+                        winuser::VK_F5 => K::F(5),
+                        winuser::VK_F6 => K::F(6),
+                        winuser::VK_F7 => K::F(7),
+                        winuser::VK_F8 => K::F(8),
+                        winuser::VK_F9 => K::F(9),
+                        winuser::VK_F10 => K::F(10),
+                        winuser::VK_F11 => K::F(11),
+                        winuser::VK_F12 => K::F(12),
+                        // winuser::VK_BACK is correctly handled because the key_event.UnicodeChar
+                        // is also set.
+                        _ => continue,
+                    },
+                    mods,
+                )
             } else if utf16 == 27 {
-                return Ok(KeyPress::Esc);
+                KeyEvent(K::Esc, mods)
             } else {
                 if utf16 >= 0xD800 && utf16 < 0xDC00 {
                     surrogate = utf16;
@@ -220,18 +198,10 @@ impl RawReader for ConsoleRawReader {
                     return Err(error::ReadlineError::Eof);
                 };
                 let c = rc?;
-                if meta {
-                    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(' ');
-                    }
-                    return Ok(key);
-                }
-            }
+                KeyEvent::new(c, mods)
+            };
+            debug!(target: "rustyline", "key: {:?}", key);
+            return Ok(key);
         }
     }
 


Test patch:
diff --git a/src/test/common.rs b/src/test/common.rs
index 798d619..0f31dce 100644
--- a/src/test/common.rs
+++ b/src/test/common.rs
@@ -2,21 +2,16 @@
 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::{KeyCode as K, KeyEvent as E, Modifiers as M};
 
 #[test]
 fn home_key() {
     for mode in &[EditMode::Emacs, EditMode::Vi] {
-        assert_cursor(
-            *mode,
-            ("", ""),
-            &[KeyPress::Home, KeyPress::Enter],
-            ("", ""),
-        );
+        assert_cursor(*mode, ("", ""), &[E(K::Home, M::NONE), E::ENTER], ("", ""));
         assert_cursor(
             *mode,
             ("Hi", ""),
-            &[KeyPress::Home, KeyPress::Enter],
+            &[E(K::Home, M::NONE), E::ENTER],
             ("", "Hi"),
         );
         if *mode == EditMode::Vi {
@@ -24,7 +19,7 @@ fn home_key() {
             assert_cursor(
                 *mode,
                 ("Hi", ""),
-                &[KeyPress::Esc, KeyPress::Home, KeyPress::Enter],
+                &[E::ESC, E(K::Home, M::NONE), E::ENTER],
                 ("", "Hi"),
             );
         }
@@ -34,17 +29,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, ("", ""), &[E(K::End, M::NONE), E::ENTER], ("", ""));
         assert_cursor(
             *mode,
             ("H", "i"),
-            &[KeyPress::End, KeyPress::Enter],
+            &[E(K::End, M::NONE), E::ENTER],
             ("Hi", ""),
         );
         assert_cursor(
             *mode,
             ("", "Hi"),
-            &[KeyPress::End, KeyPress::Enter],
+            &[E(K::End, M::NONE), E::ENTER],
             ("Hi", ""),
         );
         if *mode == EditMode::Vi {
@@ -52,7 +47,7 @@ fn end_key() {
             assert_cursor(
                 *mode,
                 ("", "Hi"),
-                &[KeyPress::Esc, KeyPress::End, KeyPress::Enter],
+                &[E::ESC, E(K::End, M::NONE), E::ENTER],
                 ("Hi", ""),
             );
         }
@@ -65,19 +60,19 @@ fn left_key() {
         assert_cursor(
             *mode,
             ("Hi", ""),
-            &[KeyPress::Left, KeyPress::Enter],
+            &[E(K::Left, M::NONE), E::ENTER],
             ("H", "i"),
         );
         assert_cursor(
             *mode,
             ("H", "i"),
-            &[KeyPress::Left, KeyPress::Enter],
+            &[E(K::Left, M::NONE), E::ENTER],
             ("", "Hi"),
         );
         assert_cursor(
             *mode,
             ("", "Hi"),
-            &[KeyPress::Left, KeyPress::Enter],
+            &[E(K::Left, M::NONE), E::ENTER],
             ("", "Hi"),
         );
         if *mode == EditMode::Vi {
@@ -85,7 +80,7 @@ fn left_key() {
             assert_cursor(
                 *mode,
                 ("Bye", ""),
-                &[KeyPress::Esc, KeyPress::Left, KeyPress::Enter],
+                &[E::ESC, E(K::Left, M::NONE), E::ENTER],
                 ("B", "ye"),
             );
         }
@@ -95,28 +90,23 @@ fn left_key() {
 #[test]
 fn right_key() {
     for mode in &[EditMode::Emacs, EditMode::Vi] {
-        assert_cursor(
-            *mode,
-            ("", ""),
-            &[KeyPress::Right, KeyPress::Enter],
-            ("", ""),
-        );
+        assert_cursor(*mode, ("", ""), &[E(K::Right, M::NONE), E::ENTER], ("", ""));
         assert_cursor(
             *mode,
             ("", "Hi"),
-            &[KeyPress::Right, KeyPress::Enter],
+            &[E(K::Right, M::NONE), E::ENTER],
             ("H", "i"),
         );
         assert_cursor(
             *mode,
             ("B", "ye"),
-            &[KeyPress::Right, KeyPress::Enter],
+            &[E(K::Right, M::NONE), E::ENTER],
             ("By", "e"),
         );
         assert_cursor(
             *mode,
             ("H", "i"),
-            &[KeyPress::Right, KeyPress::Enter],
+            &[E(K::Right, M::NONE), E::ENTER],
             ("Hi", ""),
         );
         if *mode == EditMode::Vi {
@@ -124,7 +114,7 @@ fn right_key() {
             assert_cursor(
                 *mode,
                 ("", "Hi"),
-                &[KeyPress::Esc, KeyPress::Right, KeyPress::Enter],
+                &[E::ESC, E(K::Right, M::NONE), E::ENTER],
                 ("H", "i"),
             );
         }
@@ -134,22 +124,18 @@ 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, &[E::ENTER], "");
+        assert_line(*mode, &[E::from('a'), E::ENTER], "a");
+        assert_line_with_initial(*mode, ("Hi", ""), &[E::ENTER], "Hi");
+        assert_line_with_initial(*mode, ("", "Hi"), &[E::ENTER], "Hi");
+        assert_line_with_initial(*mode, ("H", "i"), &[E::ENTER], "Hi");
         if *mode == EditMode::Vi {
             // vi command mode
-            assert_line(*mode, &[KeyPress::Esc, KeyPress::Enter], "");
-            assert_line(
-                *mode,
-                &[KeyPress::Char('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(*mode, &[E::ESC, E::ENTER], "");
+            assert_line(*mode, &[E::from('a'), E::ESC, E::ENTER], "a");
+            assert_line_with_initial(*mode, ("Hi", ""), &[E::ESC, E::ENTER], "Hi");
+            assert_line_with_initial(*mode, ("", "Hi"), &[E::ESC, E::ENTER], "Hi");
+            assert_line_with_initial(*mode, ("H", "i"), &[E::ESC, E::ENTER], "Hi");
         }
     }
 }
@@ -157,16 +143,12 @@ 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, &[E::ctrl('J')], "");
+        assert_line(*mode, &[E::from('a'), E::ctrl('J')], "a");
         if *mode == EditMode::Vi {
             // vi command mode
-            assert_line(*mode, &[KeyPress::Esc, KeyPress::Ctrl('J')], "");
-            assert_line(
-                *mode,
-                &[KeyPress::Char('a'), KeyPress::Esc, KeyPress::Ctrl('J')],
-                "a",
-            );
+            assert_line(*mode, &[E::ESC, E::ctrl('J')], "");
+            assert_line(*mode, &[E::from('a'), E::ESC, E::ctrl('J')], "a");
         }
     }
 }
@@ -174,53 +156,35 @@ 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, &[E::ctrl('D')]);
         let err = editor.readline(">>");
         assert_matches!(err, Err(ReadlineError::Eof));
     }
     assert_line(
         EditMode::Emacs,
-        &[KeyPress::Char('a'), KeyPress::Ctrl('D'), KeyPress::Enter],
-        "a",
-    );
-    assert_line(
-        EditMode::Vi,
-        &[KeyPress::Char('a'), KeyPress::Ctrl('D')],
+        &[E::from('a'), E::ctrl('D'), E::ENTER],
         "a",
     );
-    assert_line(
-        EditMode::Vi,
-        &[KeyPress::Char('a'), KeyPress::Esc, KeyPress::Ctrl('D')],
-        "a",
-    );
-    assert_line_with_initial(
-        EditMode::Emacs,
-        ("", "Hi"),
-        &[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::Esc, KeyPress::Ctrl('D')],
-        "Hi",
-    );
+    assert_line(EditMode::Vi, &[E::from('a'), E::ctrl('D')], "a");
+    assert_line(EditMode::Vi, &[E::from('a'), E::ESC, E::ctrl('D')], "a");
+    assert_line_with_initial(EditMode::Emacs, ("", "Hi"), &[E::ctrl('D'), E::ENTER], "i");
+    assert_line_with_initial(EditMode::Vi, ("", "Hi"), &[E::ctrl('D')], "Hi");
+    assert_line_with_initial(EditMode::Vi, ("", "Hi"), &[E::ESC, E::ctrl('D')], "Hi");
 }
 
 #[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, &[E::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, &[E::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, &[E::ESC, E::ctrl('C')]);
             let err = editor.readline_with_initial(">>", ("Hi", ""));
             assert_matches!(err, Err(ReadlineError::Interrupted));
         }
@@ -233,13 +197,13 @@ fn delete_key() {
         assert_cursor(
             *mode,
             ("a", ""),
-            &[KeyPress::Delete, KeyPress::Enter],
+            &[E(K::Delete, M::NONE), E::ENTER],
             ("a", ""),
         );
         assert_cursor(
             *mode,
             ("", "a"),
-            &[KeyPress::Delete, KeyPress::Enter],
+            &[E(K::Delete, M::NONE), E::ENTER],
             ("", ""),
         );
         if *mode == EditMode::Vi {
@@ -247,7 +211,7 @@ fn delete_key() {
             assert_cursor(
                 *mode,
                 ("", "a"),
-                &[KeyPress::Esc, KeyPress::Delete, KeyPress::Enter],
+                &[E::ESC, E(K::Delete, M::NONE), E::ENTER],
                 ("", ""),
             );
         }
@@ -257,24 +221,14 @@ fn delete_key() {
 #[test]
 fn ctrl_t() {
     for mode in &[EditMode::Emacs, EditMode::Vi] {
-        assert_cursor(
-            *mode,
-            ("a", "b"),
-            &[KeyPress::Ctrl('T'), KeyPress::Enter],
-            ("ba", ""),
-        );
-        assert_cursor(
-            *mode,
-            ("ab", "cd"),
-            &[KeyPress::Ctrl('T'), KeyPress::Enter],
-            ("acb", "d"),
-        );
+        assert_cursor(*mode, ("a", "b"), &[E::ctrl('T'), E::ENTER], ("ba", ""));
+        assert_cursor(*mode, ("ab", "cd"), &[E::ctrl('T'), E::ENTER], ("acb", "d"));
         if *mode == EditMode::Vi {
             // vi command mode
             assert_cursor(
                 *mode,
                 ("ab", ""),
-                &[KeyPress::Esc, KeyPress::Ctrl('T'), KeyPress::Enter],
+                &[E::ESC, E::ctrl('T'), E::ENTER],
                 ("ba", ""),
             );
         }
@@ -287,21 +241,16 @@ fn ctrl_u() {
         assert_cursor(
             *mode,
             ("start of line ", "end"),
-            &[KeyPress::Ctrl('U'), KeyPress::Enter],
-            ("", "end"),
-        );
-        assert_cursor(
-            *mode,
-            ("", "end"),
-            &[KeyPress::Ctrl('U'), KeyPress::Enter],
+            &[E::ctrl('U'), E::ENTER],
             ("", "end"),
         );
+        assert_cursor(*mode, ("", "end"), &[E::ctrl('U'), E::ENTER], ("", "end"));
         if *mode == EditMode::Vi {
             // vi command mode
             assert_cursor(
                 *mode,
                 ("start of line ", "end"),
-                &[KeyPress::Esc, KeyPress::Ctrl('U'), KeyPress::Enter],
+                &[E::ESC, E::ctrl('U'), E::ENTER],
                 ("", " end"),
             );
         }
@@ -315,7 +264,7 @@ fn ctrl_v() {
         assert_cursor(
             *mode,
             ("", ""),
-            &[KeyPress::Ctrl('V'), KeyPress::Char('\t'), KeyPress::Enter],
+            &[E::ctrl('V'), E(K::Char('\t'), M::NONE), E::ENTER],
             ("\t", ""),
         );
         if *mode == EditMode::Vi {
@@ -323,12 +272,7 @@ fn ctrl_v() {
             assert_cursor(
                 *mode,
                 ("", ""),
-                &[
-                    KeyPress::Esc,
-                    KeyPress::Ctrl('V'),
-                    KeyPress::Char('\t'),
-                    KeyPress::Enter,
-                ],
+                &[E::ESC, E::ctrl('V'), E(K::Char('\t'), M::NONE), E::ENTER],
                 ("\t", ""),
             );
         }
@@ -341,13 +285,13 @@ fn ctrl_w() {
         assert_cursor(
             *mode,
             ("Hello, ", "world"),
-            &[KeyPress::Ctrl('W'), KeyPress::Enter],
+            &[E::ctrl('W'), E::ENTER],
             ("", "world"),
         );
         assert_cursor(
             *mode,
             ("Hello, world.", ""),
-            &[KeyPress::Ctrl('W'), KeyPress::Enter],
+            &[E::ctrl('W'), E::ENTER],
             ("Hello, ", ""),
         );
         if *mode == EditMode::Vi {
@@ -355,7 +299,7 @@ fn ctrl_w() {
             assert_cursor(
                 *mode,
                 ("Hello, world.", ""),
-                &[KeyPress::Esc, KeyPress::Ctrl('W'), KeyPress::Enter],
+                &[E::ESC, E::ctrl('W'), E::ENTER],
                 ("Hello, ", "."),
             );
         }
@@ -368,7 +312,7 @@ fn ctrl_y() {
         assert_cursor(
             *mode,
             ("Hello, ", "world"),
-            &[KeyPress::Ctrl('W'), KeyPress::Ctrl('Y'), KeyPress::Enter],
+            &[E::ctrl('W'), E::ctrl('Y'), E::ENTER],
             ("Hello, ", "world"),
         );
     }
@@ -380,7 +324,7 @@ fn ctrl__() {
         assert_cursor(
             *mode,
             ("Hello, ", "world"),
-            &[KeyPress::Ctrl('W'), KeyPress::Ctrl('_'), KeyPress::Enter],
+            &[E::ctrl('W'), E::ctrl('_'), E::ENTER],
             ("Hello, ", "world"),
         );
         if *mode == EditMode::Vi {
@@ -388,12 +332,7 @@ fn ctrl__() {
             assert_cursor(
                 *mode,
                 ("Hello, ", "world"),
-                &[
-                    KeyPress::Esc,
-                    KeyPress::Ctrl('W'),
-                    KeyPress::Ctrl('_'),
-                    KeyPress::Enter,
-                ],
+                &[E::ESC, E::ctrl('W'), E::ctrl('_'), E::ENTER],
                 ("Hello,", " world"),
             );
         }
diff --git a/src/test/emacs.rs b/src/test/emacs.rs
index 9fd395e..aa3e0b6 100644
--- a/src/test/emacs.rs
+++ b/src/test/emacs.rs
@@ -1,20 +1,20 @@
 //! Emacs specific key bindings
 use super::{assert_cursor, assert_history};
 use crate::config::EditMode;
-use crate::keys::KeyPress;
+use crate::keys::{KeyCode as K, KeyEvent as E, Modifiers as M};
 
 #[test]
 fn ctrl_a() {
     assert_cursor(
         EditMode::Emacs,
         ("Hi", ""),
-        &[KeyPress::Ctrl('A'), KeyPress::Enter],
+        &[E::ctrl('A'), E::ENTER],
         ("", "Hi"),
     );
     assert_cursor(
         EditMode::Emacs,
         ("test test\n123", "foo"),
-        &[KeyPress::Ctrl('A'), KeyPress::Enter],
+        &[E::ctrl('A'), E::ENTER],
         ("test test\n", "123foo"),
     );
 }
@@ -24,13 +24,13 @@ fn ctrl_e() {
     assert_cursor(
         EditMode::Emacs,
         ("", "Hi"),
-        &[KeyPress::Ctrl('E'), KeyPress::Enter],
+        &[E::ctrl('E'), E::ENTER],
         ("Hi", ""),
     );
     assert_cursor(
         EditMode::Emacs,
         ("foo", "test test\n123"),
-        &[KeyPress::Ctrl('E'), KeyPress::Enter],
+        &[E::ctrl('E'), E::ENTER],
         ("footest test", "\n123"),
     );
 }
@@ -40,24 +40,19 @@ fn ctrl_b() {
     assert_cursor(
         EditMode::Emacs,
         ("Hi", ""),
-        &[KeyPress::Ctrl('B'), KeyPress::Enter],
+        &[E::ctrl('B'), E::ENTER],
         ("H", "i"),
     );
     assert_cursor(
         EditMode::Emacs,
         ("Hi", ""),
-        &[KeyPress::Meta('2'), KeyPress::Ctrl('B'), KeyPress::Enter],
+        &[E::alt('2'), E::ctrl('B'), E::ENTER],
         ("", "Hi"),
     );
     assert_cursor(
         EditMode::Emacs,
         ("", "Hi"),
-        &[
-            KeyPress::Meta('-'),
-            KeyPress::Meta('2'),
-            KeyPress::Ctrl('B'),
-            KeyPress::Enter,
-        ],
+        &[E::alt('-'), E::alt('2'), E::ctrl('B'), E::ENTER],
         ("Hi", ""),
     );
 }
@@ -67,24 +62,19 @@ fn ctrl_f() {
     assert_cursor(
         EditMode::Emacs,
         ("", "Hi"),
-        &[KeyPress::Ctrl('F'), KeyPress::Enter],
+        &[E::ctrl('F'), E::ENTER],
         ("H", "i"),
     );
     assert_cursor(
         EditMode::Emacs,
         ("", "Hi"),
-        &[KeyPress::Meta('2'), KeyPress::Ctrl('F'), KeyPress::Enter],
+        &[E::alt('2'), E::ctrl('F'), E::ENTER],
         ("Hi", ""),
     );
     assert_cursor(
         EditMode::Emacs,
         ("Hi", ""),
-        &[
-            KeyPress::Meta('-'),
-            KeyPress::Meta('2'),
-            KeyPress::Ctrl('F'),
-            KeyPress::Enter,
-        ],
+        &[E::alt('-'), E::alt('2'), E::ctrl('F'), E::ENTER],
         ("", "Hi"),
     );
 }
@@ -94,24 +84,19 @@ fn ctrl_h() {
     assert_cursor(
         EditMode::Emacs,
         ("Hi", ""),
-        &[KeyPress::Ctrl('H'), KeyPress::Enter],
+        &[E::ctrl('H'), E::ENTER],
         ("H", ""),
     );
     assert_cursor(
         EditMode::Emacs,
         ("Hi", ""),
-        &[KeyPress::Meta('2'), KeyPress::Ctrl('H'), KeyPress::Enter],
+        &[E::alt('2'), E::ctrl('H'), E::ENTER],
         ("", ""),
     );
     assert_cursor(
         EditMode::Emacs,
         ("", "Hi"),
-        &[
-            KeyPress::Meta('-'),
-            KeyPress::Meta('2'),
-            KeyPress::Ctrl('H'),
-            KeyPress::Enter,
-        ],
+        &[E::alt('-'), E::alt('2'), E::ctrl('H'), E::ENTER],
         ("", ""),
     );
 }
@@ -121,19 +106,19 @@ fn backspace() {
     assert_cursor(
         EditMode::Emacs,
         ("", ""),
-        &[KeyPress::Backspace, KeyPress::Enter],
+        &[E::BACKSPACE, E::ENTER],
         ("", ""),
     );
     assert_cursor(
         EditMode::Emacs,
         ("Hi", ""),
-        &[KeyPress::Backspace, KeyPress::Enter],
+        &[E::BACKSPACE, E::ENTER],
         ("H", ""),
     );
     assert_cursor(
         EditMode::Emacs,
         ("", "Hi"),
-        &[KeyPress::Backspace, KeyPress::Enter],
+        &[E::BACKSPACE, E::ENTER],
         ("", "Hi"),
     );
 }
@@ -143,37 +128,37 @@ fn ctrl_k() {
     assert_cursor(
         EditMode::Emacs,
         ("Hi", ""),
-        &[KeyPress::Ctrl('K'), KeyPress::Enter],
+        &[E::ctrl('K'), E::ENTER],
         ("Hi", ""),
     );
     assert_cursor(
         EditMode::Emacs,
         ("", "Hi"),
-        &[KeyPress::Ctrl('K'), KeyPress::Enter],
+        &[E::ctrl('K'), E::ENTER],
         ("", ""),
     );
     assert_cursor(
         EditMode::Emacs,
         ("B", "ye"),
-        &[KeyPress::Ctrl('K'), KeyPress::Enter],
+        &[E::ctrl('K'), E::ENTER],
         ("B", ""),
     );
     assert_cursor(
         EditMode::Emacs,
         ("Hi", "foo\nbar"),
-        &[KeyPress::Ctrl('K'), KeyPress::Enter],
+        &[E::ctrl('K'), E::ENTER],
         ("Hi", "\nbar"),
     );
     assert_cursor(
         EditMode::Emacs,
         ("Hi", "\nbar"),
-        &[KeyPress::Ctrl('K'), KeyPress::Enter],
+        &[E::ctrl('K'), E::ENTER],
         ("Hi", "bar"),
     );
     assert_cursor(
         EditMode::Emacs,
         ("Hi", "bar"),
-        &[KeyPress::Ctrl('K'), KeyPress::Enter],
+        &[E::ctrl('K'), E::ENTER],
         ("Hi", ""),
     );
 }
@@ -183,37 +168,37 @@ fn ctrl_u() {
     assert_cursor(
         EditMode::Emacs,
         ("", "Hi"),
-        &[KeyPress::Ctrl('U'), KeyPress::Enter],
+        &[E::ctrl('U'), E::ENTER],
         ("", "Hi"),
     );
     assert_cursor(
         EditMode::Emacs,
         ("Hi", ""),
-        &[KeyPress::Ctrl('U'), KeyPress::Enter],
+        &[E::ctrl('U'), E::ENTER],
         ("", ""),
     );
     assert_cursor(
         EditMode::Emacs,
         ("B", "ye"),
-        &[KeyPress::Ctrl('U'), KeyPress::Enter],
+        &[E::ctrl('U'), E::ENTER],
         ("", "ye"),
     );
     assert_cursor(
         EditMode::Emacs,
         ("foo\nbar", "Hi"),
-        &[KeyPress::Ctrl('U'), KeyPress::Enter],
+        &[E::ctrl('U'), E::ENTER],
         ("foo\n", "Hi"),
     );
     assert_cursor(
         EditMode::Emacs,
         ("foo\n", "Hi"),
-        &[KeyPress::Ctrl('U'), KeyPress::Enter],
+        &[E::ctrl('U'), E::ENTER],
         ("foo", "Hi"),
     );
     assert_cursor(
         EditMode::Emacs,
         ("foo", "Hi"),
-        &[KeyPress::Ctrl('U'), KeyPress::Enter],
+        &[E::ctrl('U'), E::ENTER],
         ("", "Hi"),
     );
 }
@@ -222,12 +207,7 @@ fn ctrl_n() {
     assert_history(
         EditMode::Emacs,
         &["line1", "line2"],
-        &[
-            KeyPress::Ctrl('P'),
-            KeyPress::Ctrl('P'),
-            KeyPress::Ctrl('N'),
-            KeyPress::Enter,
-        ],
+        &[E::ctrl('P'), E::ctrl('P'), E::ctrl('N'), E::ENTER],
         "",
         ("line2", ""),
     );
@@ -238,7 +218,7 @@ fn ctrl_p() {
     assert_history(
         EditMode::Emacs,
         &["line1"],
-        &[KeyPress::Ctrl('P'), KeyPress::Enter],
+        &[E::ctrl('P'), E::ENTER],
         "",
         ("line1", ""),
     );
@@ -249,7 +229,7 @@ fn ctrl_t() {
     /* FIXME
     assert_cursor(
         ("ab", "cd"),
-        &[KeyPress::Meta('2'), KeyPress::Ctrl('T'), KeyPress::Enter],
+        &[E::alt('2'), E::ctrl('T'), E::ENTER],
         ("acdb", ""),
     );*/
 }
@@ -259,12 +239,7 @@ fn ctrl_x_ctrl_u() {
     assert_cursor(
         EditMode::Emacs,
         ("Hello, ", "world"),
-        &[
-            KeyPress::Ctrl('W'),
-            KeyPress::Ctrl('X'),
-            KeyPress::Ctrl('U'),
-            KeyPress::Enter,
-        ],
+        &[E::ctrl('W'), E::ctrl('X'), E::ctrl('U'), E::ENTER],
         ("Hello, ", "world"),
     );
 }
@@ -274,19 +249,19 @@ fn meta_b() {
     assert_cursor(
         EditMode::Emacs,
         ("Hello, world!", ""),
-        &[KeyPress::Meta('B'), KeyPress::Enter],
+        &[E::alt('B'), E::ENTER],
         ("Hello, ", "world!"),
     );
     assert_cursor(
         EditMode::Emacs,
         ("Hello, world!", ""),
-        &[KeyPress::Meta('2'), KeyPress::Meta('B'), KeyPress::Enter],
+        &[E::alt('2'), E::alt('B'), E::ENTER],
         ("", "Hello, world!"),
     );
     assert_cursor(
         EditMode::Emacs,
         ("", "Hello, world!"),
-        &[KeyPress::Meta('-'), KeyPress::Meta('B'), KeyPress::Enter],
+        &[E::alt('-'), E::alt('B'), E::ENTER],
         ("Hello", ", world!"),
     );
 }
@@ -296,19 +271,19 @@ fn meta_f() {
     assert_cursor(
         EditMode::Emacs,
         ("", "Hello, world!"),
-        &[KeyPress::Meta('F'), KeyPress::Enter],
+        &[E::alt('F'), E::ENTER],
         ("Hello", ", world!"),
     );
     assert_cursor(
         EditMode::Emacs,
         ("", "Hello, world!"),
-        &[KeyPress::Meta('2'), KeyPress::Meta('F'), KeyPress::Enter],
+        &[E::alt('2'), E::alt('F'), E::ENTER],
         ("Hello, world", "!"),
     );
     assert_cursor(
         EditMode::Emacs,
         ("Hello, world!", ""),
-        &[KeyPress::Meta('-'), KeyPress::Meta('F'), KeyPress::Enter],
+        &[E::alt('-'), E::alt('F'), E::ENTER],
         ("Hello, ", "world!"),
     );
 }
@@ -318,19 +293,19 @@ fn meta_c() {
     assert_cursor(
         EditMode::Emacs,
         ("hi", ""),
-        &[KeyPress::Meta('C'), KeyPress::Enter],
+        &[E::alt('C'), E::ENTER],
         ("hi", ""),
     );
     assert_cursor(
         EditMode::Emacs,
         ("", "hi"),
-        &[KeyPress::Meta('C'), KeyPress::Enter],
+        &[E::alt('C'), E::ENTER],
         ("Hi", ""),
     );
     /* FIXME
     assert_cursor(
         ("", "hi test"),
-        &[KeyPress::Meta('2'), KeyPress::Meta('C'), KeyPress::Enter],
+        &[E::alt('2'), E::alt('C'), E::ENTER],
         ("Hi Test", ""),
     );*/
 }
@@ -340,19 +315,19 @@ fn meta_l() {
     assert_cursor(
         EditMode::Emacs,
         ("Hi", ""),
-        &[KeyPress::Meta('L'), KeyPress::Enter],
+        &[E::alt('L'), E::ENTER],
         ("Hi", ""),
     );
     assert_cursor(
         EditMode::Emacs,
         ("", "HI"),
-        &[KeyPress::Meta('L'), KeyPress::Enter],
+        &[E::alt('L'), E::ENTER],
         ("hi", ""),
     );
     /* FIXME
     assert_cursor(
         ("", "HI TEST"),
-        &[KeyPress::Meta('2'), KeyPress::Meta('L'), KeyPress::Enter],
+        &[E::alt('2'), E::alt('L'), E::ENTER],
         ("hi test", ""),
     );*/
 }
@@ -362,19 +337,19 @@ fn meta_u() {
     assert_cursor(
         EditMode::Emacs,
         ("hi", ""),
-        &[KeyPress::Meta('U'), KeyPress::Enter],
+        &[E::alt('U'), E::ENTER],
         ("hi", ""),
     );
     assert_cursor(
         EditMode::Emacs,
         ("", "hi"),
-        &[KeyPress::Meta('U'), KeyPress::Enter],
+        &[E::alt('U'), E::ENTER],
         ("HI", ""),
     );
     /* FIXME
     assert_cursor(
         ("", "hi test"),
-        &[KeyPress::Meta('2'), KeyPress::Meta('U'), KeyPress::Enter],
+        &[E::alt('2'), E::alt('U'), E::ENTER],
         ("HI TEST", ""),
     );*/
 }
@@ -384,13 +359,13 @@ fn meta_d() {
     assert_cursor(
         EditMode::Emacs,
         ("Hello", ", world!"),
-        &[KeyPress::Meta('D'), KeyPress::Enter],
+        &[E::alt('D'), E::ENTER],
         ("Hello", "!"),
     );
     assert_cursor(
         EditMode::Emacs,
         ("Hello", ", world!"),
-        &[KeyPress::Meta('2'), KeyPress::Meta('D'), KeyPress::Enter],
+        &[E::alt('2'), E::alt('D'), E::ENTER],
         ("Hello", ""),
     );
 }
@@ -400,13 +375,13 @@ fn meta_t() {
     assert_cursor(
         EditMode::Emacs,
         ("Hello", ", world!"),
-        &[KeyPress::Meta('T'), KeyPress::Enter],
+        &[E::alt('T'), E::ENTER],
         ("world, Hello", "!"),
     );
     /* FIXME
     assert_cursor(
         ("One Two", " Three Four"),
-        &[KeyPress::Meta('T'), KeyPress::Enter],
+        &[E::alt('T'), E::ENTER],
         ("One Four Three Two", ""),
     );*/
 }
@@ -417,12 +392,12 @@ fn meta_y() {
         EditMode::Emacs,
         ("Hello, world", "!"),
         &[
-            KeyPress::Ctrl('W'),
-            KeyPress::Left,
-            KeyPress::Ctrl('W'),
-            KeyPress::Ctrl('Y'),
-            KeyPress::Meta('Y'),
-            KeyPress::Enter,
+            E::ctrl('W'),
+            E(K::Left, M::NONE),
+            E::ctrl('W'),
+            E::ctrl('Y'),
+            E::alt('Y'),
+            E::ENTER,
         ],
         ("world", " !"),
     );
@@ -433,7 +408,7 @@ fn meta_backspace() {
     assert_cursor(
         EditMode::Emacs,
         ("Hello, wor", "ld!"),
-        &[KeyPress::Meta('\x08'), KeyPress::Enter],
+        &[E(K::Backspace, M::ALT), E::ENTER],
         ("Hello, ", "ld!"),
     );
 }
@@ -443,7 +418,7 @@ fn meta_digit() {
     assert_cursor(
         EditMode::Emacs,
         ("", ""),
-        &[KeyPress::Meta('3'), KeyPress::Char('h'), KeyPress::Enter],
+        &[E::alt('3'), E::from('h'), E::ENTER],
         ("hhh", ""),
     );
 }
diff --git a/src/test/history.rs b/src/test/history.rs
index 88cb3b5..ab86924 100644
--- a/src/test/history.rs
+++ b/src/test/history.rs
@@ -1,7 +1,7 @@
 //! History related commands tests
 use super::assert_history;
 use crate::config::EditMode;
-use crate::keys::KeyPress;
+use crate::keys::{KeyCode as K, KeyEvent as E, Modifiers as M};
 
 #[test]
 fn down_key() {
@@ -9,14 +9,19 @@ fn down_key() {
         assert_history(
             *mode,
             &["line1"],
-            &[KeyPress::Down, KeyPress::Enter],
+            &[E(K::Down, M::NONE), E::ENTER],
             "",
             ("", ""),
         );
         assert_history(
             *mode,
             &["line1", "line2"],
-            &[KeyPress::Up, KeyPress::Up, KeyPress::Down, KeyPress::Enter],
+            &[
+                E(K::Up, M::NONE),
+                E(K::Up, M::NONE),
+                E(K::Down, M::NONE),
+                E::ENTER,
+            ],
             "",
             ("line2", ""),
         );
@@ -24,10 +29,10 @@ fn down_key() {
             *mode,
             &["line1"],
             &[
-                KeyPress::Char('a'),
-                KeyPress::Up,
-                KeyPress::Down, // restore original line
-                KeyPress::Enter,
+                E::from('a'),
+                E(K::Up, M::NONE),
+                E(K::Down, M::NONE), // restore original line
+                E::ENTER,
             ],
             "",
             ("a", ""),
@@ -36,9 +41,9 @@ fn down_key() {
             *mode,
             &["line1"],
             &[
-                KeyPress::Char('a'),
-                KeyPress::Down, // noop
-                KeyPress::Enter,
+                E::from('a'),
+                E(K::Down, M::NONE), // noop
+                E::ENTER,
             ],
             "",
             ("a", ""),
@@ -49,18 +54,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, &[], &[E(K::Up, M::NONE), E::ENTER], "", ("", ""));
         assert_history(
             *mode,
             &["line1"],
-            &[KeyPress::Up, KeyPress::Enter],
+            &[E(K::Up, M::NONE), E::ENTER],
             "",
             ("line1", ""),
         );
         assert_history(
             *mode,
             &["line1", "line2"],
-            &[KeyPress::Up, KeyPress::Up, KeyPress::Enter],
+            &[E(K::Up, M::NONE), E(K::Up, M::NONE), E::ENTER],
             "",
             ("line1", ""),
         );
@@ -73,7 +78,7 @@ fn ctrl_r() {
         assert_history(
             *mode,
             &[],
-            &[KeyPress::Ctrl('R'), KeyPress::Char('o'), KeyPress::Enter],
+            &[E::ctrl('R'), E::from('o'), E::ENTER],
             "",
             ("o", ""),
         );
@@ -81,10 +86,10 @@ fn ctrl_r() {
             *mode,
             &["rustc", "cargo"],
             &[
-                KeyPress::Ctrl('R'),
-                KeyPress::Char('o'),
-                KeyPress::Right, // just to assert cursor pos
-                KeyPress::Enter,
+                E::ctrl('R'),
+                E::from('o'),
+                E(K::Right, M::NONE), // just to assert cursor pos
+                E::ENTER,
             ],
             "",
             ("cargo", ""),
@@ -93,10 +98,10 @@ fn ctrl_r() {
             *mode,
             &["rustc", "cargo"],
             &[
-                KeyPress::Ctrl('R'),
-                KeyPress::Char('u'),
-                KeyPress::Right, // just to assert cursor pos
-                KeyPress::Enter,
+                E::ctrl('R'),
+                E::from('u'),
+                E(K::Right, M::NONE), // just to assert cursor pos
+                E::ENTER,
             ],
             "",
             ("ru", "stc"),
@@ -105,11 +110,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,
+                E::ctrl('R'),
+                E::from('r'),
+                E::from('u'),
+                E(K::Right, M::NONE), // just to assert cursor pos
+                E::ENTER,
             ],
             "",
             ("r", "ustc"),
@@ -118,11 +123,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,
+                E::ctrl('R'),
+                E::from('r'),
+                E::ctrl('R'),
+                E(K::Right, M::NONE), // just to assert cursor pos
+                E::ENTER,
             ],
             "",
             ("r", "ustc"),
@@ -131,11 +136,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,
+                E::ctrl('R'),
+                E::from('r'),
+                E::from('z'),         // no match
+                E(K::Right, M::NONE), // just to assert cursor pos
+                E::ENTER,
             ],
             "",
             ("car", "go"),
@@ -144,11 +149,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,
+                E::from('a'),
+                E::ctrl('R'),
+                E::from('r'),
+                E::ctrl('G'), // abort (FIXME: doesn't work with vi mode)
+                E::ENTER,
             ],
             "",
             ("a", ""),
@@ -162,7 +167,7 @@ fn ctrl_r_with_long_prompt() {
         assert_history(
             *mode,
             &["rustc", "cargo"],
-            &[KeyPress::Ctrl('R'), KeyPress::Char('o'), KeyPress::Enter],
+            &[E::ctrl('R'), E::from('o'), E::ENTER],
             ">>>>>>>>>>>>>>>>>>>>>>>>>>> ",
             ("cargo", ""),
         );
@@ -176,12 +181,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,
+                E::ctrl('R'),
+                E::from('r'),
+                E::ctrl('R'),
+                E::ctrl('S'),
+                E(K::Right, M::NONE), // just to assert cursor pos
+                E::ENTER,
             ],
             "",
             ("car", "go"),
@@ -194,14 +199,14 @@ fn meta_lt() {
     assert_history(
         EditMode::Emacs,
         &[""],
-        &[KeyPress::Meta('<'), KeyPress::Enter],
+        &[E::alt('<'), E::ENTER],
         "",
         ("", ""),
     );
     assert_history(
         EditMode::Emacs,
         &["rustc", "cargo"],
-        &[KeyPress::Meta('<'), KeyPress::Enter],
+        &[E::alt('<'), E::ENTER],
         "",
         ("rustc", ""),
     );
@@ -212,14 +217,14 @@ fn meta_gt() {
     assert_history(
         EditMode::Emacs,
         &[""],
-        &[KeyPress::Meta('>'), KeyPress::Enter],
+        &[E::alt('>'), E::ENTER],
         "",
         ("", ""),
     );
     assert_history(
         EditMode::Emacs,
         &["rustc", "cargo"],
-        &[KeyPress::Meta('<'), KeyPress::Meta('>'), KeyPress::Enter],
+        &[E::alt('<'), E::alt('>'), E::ENTER],
         "",
         ("", ""),
     );
@@ -227,10 +232,10 @@ fn meta_gt() {
         EditMode::Emacs,
         &["rustc", "cargo"],
         &[
-            KeyPress::Char('a'),
-            KeyPress::Meta('<'),
-            KeyPress::Meta('>'), // restore original line
-            KeyPress::Enter,
+            E::from('a'),
+            E::alt('<'),
+            E::alt('>'), // restore original line
+            E::ENTER,
         ],
         "",
         ("a", ""),
diff --git a/src/test/mod.rs b/src/test/mod.rs
index f89bcf5..a9d42bf 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::{KeyCode as K, KeyEvent, KeyEvent as E, Modifiers as M};
 use crate::tty::Sink;
 use crate::validate::Validator;
 use crate::{Context, Editor, Helper, Result};
@@ -19,7 +19,7 @@ mod history;
 mod vi_cmd;
 mod vi_insert;
 
-fn init_editor(mode: EditMode, keys: &[KeyPress]) -> Editor<()> {
+fn init_editor(mode: EditMode, keys: &[KeyEvent]) -> Editor<()> {
     let config = Config::builder().edit_mode(mode).build();
     let mut editor = Editor::<()>::with_config(config);
     editor.term.keys.extend(keys.iter().cloned());
@@ -59,8 +59,8 @@ 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 mut rdr: IntoIter<KeyPress> = keys.into_iter();
+    let keys = vec![E::ENTER];
+    let mut rdr: IntoIter<KeyEvent> = keys.into_iter();
     let cmd = super::complete_line(&mut rdr, &mut s, &mut input_state, &Config::default()).unwrap();
     assert_eq!(
         Some(Cmd::AcceptOrInsertLine {
@@ -74,7 +74,7 @@ fn complete_line() {
 
 // `keys`: keys to press
 // `expected_line`: line after enter key
-fn assert_line(mode: EditMode, keys: &[KeyPress], expected_line: &str) {
+fn assert_line(mode: EditMode, keys: &[KeyEvent], expected_line: &str) {
     let mut editor = init_editor(mode, keys);
     let actual_line = editor.readline(">>").unwrap();
     assert_eq!(expected_line, actual_line);
@@ -86,7 +86,7 @@ fn assert_line(mode: EditMode, keys: &[KeyPress], expected_line: &str) {
 fn assert_line_with_initial(
     mode: EditMode,
     initial: (&str, &str),
-    keys: &[KeyPress],
+    keys: &[KeyEvent],
     expected_line: &str,
 ) {
     let mut editor = init_editor(mode, keys);
@@ -97,7 +97,7 @@ fn assert_line_with_initial(
 // `initial`: line status before `keys` pressed: strings before and after cursor
 // `keys`: keys to press
 // `expected`: line status before enter key: strings before and after cursor
-fn assert_cursor(mode: EditMode, initial: (&str, &str), keys: &[KeyPress], expected: (&str, &str)) {
+fn assert_cursor(mode: EditMode, initial: (&str, &str), keys: &[KeyEvent], expected: (&str, &str)) {
     let mut editor = init_editor(mode, keys);
     let actual_line = editor.readline_with_initial("", initial).unwrap();
     assert_eq!(expected.0.to_owned() + expected.1, actual_line);
@@ -110,7 +110,7 @@ fn assert_cursor(mode: EditMode, initial: (&str, &str), keys: &[KeyPress], expec
 fn assert_history(
     mode: EditMode,
     entries: &[&str],
-    keys: &[KeyPress],
+    keys: &[KeyEvent],
     prompt: &str,
     expected: (&str, &str),
 ) {
@@ -128,7 +128,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, &[E(K::UnknownEscSeq, M::NONE), E::ENTER], "");
     }
 }
 
diff --git a/src/test/vi_cmd.rs b/src/test/vi_cmd.rs
index 02c5c09..ec5234d 100644
--- a/src/test/vi_cmd.rs
+++ b/src/test/vi_cmd.rs
@@ -1,14 +1,14 @@
 //! Vi command mode specific key bindings
 use super::{assert_cursor, assert_history};
 use crate::config::EditMode;
-use crate::keys::KeyPress;
+use crate::keys::KeyEvent as E;
 
 #[test]
 fn dollar() {
     assert_cursor(
         EditMode::Vi,
         ("", "Hi"),
-        &[KeyPress::Esc, KeyPress::Char('$'), KeyPress::Enter],
+        &[E::ESC, E::from('$'), E::ENTER],
         ("Hi", ""), // FIXME
     );
 }
@@ -23,13 +23,7 @@ fn semi_colon() {
     assert_cursor(
         EditMode::Vi,
         ("", "Hello, world!"),
-        &[
-            KeyPress::Esc,
-            KeyPress::Char('f'),
-            KeyPress::Char('o'),
-            KeyPress::Char(';'),
-            KeyPress::Enter,
-        ],
+        &[E::ESC, E::from('f'), E::from('o'), E::from(';'), E::ENTER],
         ("Hello, w", "orld!"),
     );
 }
@@ -39,13 +33,7 @@ fn comma() {
     assert_cursor(
         EditMode::Vi,
         ("Hello, w", "orld!"),
-        &[
-            KeyPress::Esc,
-            KeyPress::Char('f'),
-            KeyPress::Char('l'),
-            KeyPress::Char(','),
-            KeyPress::Enter,
-        ],
+        &[E::ESC, E::from('f'), E::from('l'), E::from(','), E::ENTER],
         ("Hel", "lo, world!"),
     );
 }
@@ -55,7 +43,7 @@ fn zero() {
     assert_cursor(
         EditMode::Vi,
         ("Hi", ""),
-        &[KeyPress::Esc, KeyPress::Char('0'), KeyPress::Enter],
+        &[E::ESC, E::from('0'), E::ENTER],
         ("", "Hi"),
     );
 }
@@ -65,7 +53,7 @@ fn caret() {
     assert_cursor(
         EditMode::Vi,
         (" Hi", ""),
-        &[KeyPress::Esc, KeyPress::Char('^'), KeyPress::Enter],
+        &[E::ESC, E::from('^'), E::ENTER],
         (" ", "Hi"),
     );
 }
@@ -75,12 +63,7 @@ fn a() {
     assert_cursor(
         EditMode::Vi,
         ("B", "e"),
-        &[
-            KeyPress::Esc,
-            KeyPress::Char('a'),
-            KeyPress::Char('y'),
-            KeyPress::Enter,
-        ],
+        &[E::ESC, E::from('a'), E::from('y'), E::ENTER],
         ("By", "e"),
     );
 }
@@ -90,12 +73,7 @@ fn uppercase_a() {
     assert_cursor(
         EditMode::Vi,
         ("", "By"),
-        &[
-            KeyPress::Esc,
-            KeyPress::Char('A'),
-            KeyPress::Char('e'),
-            KeyPress::Enter,
-        ],
+        &[E::ESC, E::from('A'), E::from('e'), E::ENTER],
         ("Bye", ""),
     );
 }
@@ -105,18 +83,13 @@ fn b() {
     assert_cursor(
         EditMode::Vi,
         ("Hello, world!", ""),
-        &[KeyPress::Esc, KeyPress::Char('b'), KeyPress::Enter],
+        &[E::ESC, E::from('b'), E::ENTER],
         ("Hello, ", "world!"),
     );
     assert_cursor(
         EditMode::Vi,
         ("Hello, world!", ""),
-        &[
-            KeyPress::Esc,
-            KeyPress::Char('2'),
-            KeyPress::Char('b'),
-            KeyPress::Enter,
-        ],
+        &[E::ESC, E::from('2'), E::from('b'), E::ENTER],
         ("Hello", ", world!"),
     );
 }
@@ -126,18 +99,13 @@ fn uppercase_b() {
     assert_cursor(
         EditMode::Vi,
         ("Hello, world!", ""),
-        &[KeyPress::Esc, KeyPress::Char('B'), KeyPress::Enter],
+        &[E::ESC, E::from('B'), E::ENTER],
         ("Hello, ", "world!"),
     );
     assert_cursor(
         EditMode::Vi,
         ("Hello, world!", ""),
-        &[
-            KeyPress::Esc,
-            KeyPress::Char('2'),
-            KeyPress::Char('B'),
-            KeyPress::Enter,
-        ],
+        &[E::ESC, E::from('2'), E::from('B'), E::ENTER],
         ("", "Hello, world!"),
     );
 }
@@ -147,35 +115,30 @@ fn uppercase_c() {
     assert_cursor(
         EditMode::Vi,
         ("Hello, w", "orld!"),
-        &[
-            KeyPress::Esc,
-            KeyPress::Char('C'),
-            KeyPress::Char('i'),
-            KeyPress::Enter,
-        ],
+        &[E::ESC, E::from('C'), E::from('i'), E::ENTER],
         ("Hello, i", ""),
     );
 }
 
 #[test]
 fn ctrl_k() {
-    for key in &[KeyPress::Char('D'), KeyPress::Ctrl('K')] {
+    for key in &[E::from('D'), E::ctrl('K')] {
         assert_cursor(
             EditMode::Vi,
             ("Hi", ""),
-            &[KeyPress::Esc, *key, KeyPress::Enter],
+            &[E::ESC, *key, E::ENTER],
             ("H", ""),
         );
         assert_cursor(
             EditMode::Vi,
             ("", "Hi"),
-            &[KeyPress::Esc, *key, KeyPress::Enter],
+            &[E::ESC, *key, E::ENTER],
             ("", ""),
         );
         assert_cursor(
             EditMode::Vi,
             ("By", "e"),
-            &[KeyPress::Esc, *key, KeyPress::Enter],
+            &[E::ESC, *key, E::ENTER],
             ("B", ""),
         );
     }
@@ -186,18 +149,13 @@ fn e() {
     assert_cursor(
         EditMode::Vi,
         ("", "Hello, world!"),
-        &[KeyPress::Esc, KeyPress::Char('e'), KeyPress::Enter],
+        &[E::ESC, E::from('e'), E::ENTER],
         ("Hell", "o, world!"),
     );
     assert_cursor(
         EditMode::Vi,
         ("", "Hello, world!"),
-        &[
-            KeyPress::Esc,
-            KeyPress::Char('2'),
-            KeyPress::Char('e'),
-            KeyPress::Enter,
-        ],
+        &[E::ESC, E::from('2'), E::from('e'), E::ENTER],
         ("Hello, worl", "d!"),
     );
 }
@@ -207,18 +165,13 @@ fn uppercase_e() {
     assert_cursor(
         EditMode::Vi,
         ("", "Hello, world!"),
-        &[KeyPress::Esc, KeyPress::Char('E'), KeyPress::Enter],
+        &[E::ESC, E::from('E'), E::ENTER],
         ("Hello", ", world!"),
     );
     assert_cursor(
         EditMode::Vi,
         ("", "Hello, world!"),
-        &[
-            KeyPress::Esc,
-            KeyPress::Char('2'),
-            KeyPress::Char('E'),
-            KeyPress::Enter,
-        ],
+        &[E::ESC, E::from('2'), E::from('E'), E::ENTER],
         ("Hello, world", "!"),
     );
 }
@@ -228,24 +181,13 @@ fn f() {
     assert_cursor(
         EditMode::Vi,
         ("", "Hello, world!"),
-        &[
-            KeyPress::Esc,
-            KeyPress::Char('f'),
-            KeyPress::Char('r'),
-            KeyPress::Enter,
-        ],
+        &[E::ESC, E::from('f'), E::from('r'), E::ENTER],
         ("Hello, wo", "rld!"),
     );
     assert_cursor(
         EditMode::Vi,
         ("", "Hello, world!"),
-        &[
-            KeyPress::Esc,
-            KeyPress::Char('3'),
-            KeyPress::Char('f'),
-            KeyPress::Char('l'),
-            KeyPress::Enter,
-        ],
+        &[E::ESC, E::from('3'), E::from('f'), E::from('l'), E::ENTER],
         ("Hello, wor", "ld!"),
     );
 }
@@ -255,24 +197,13 @@ fn uppercase_f() {
     assert_cursor(
         EditMode::Vi,
         ("Hello, world!", ""),
-        &[
-            KeyPress::Esc,
-            KeyPress::Char('F'),
-            KeyPress::Char('r'),
-            KeyPress::Enter,
-        ],
+        &[E::ESC, E::from('F'), E::from('r'), E::ENTER],
         ("Hello, wo", "rld!"),
     );
     assert_cursor(
         EditMode::Vi,
         ("Hello, world!", ""),
-        &[
-            KeyPress::Esc,
-            KeyPress::Char('3'),
-            KeyPress::Char('F'),
-            KeyPress::Char('l'),
-            KeyPress::Enter,
-        ],
+        &[E::ESC, E::from('3'), E::from('F'), E::from('l'), E::ENTER],
         ("He", "llo, world!"),
     );
 }
@@ -282,12 +213,7 @@ fn i() {
     assert_cursor(
         EditMode::Vi,
         ("Be", ""),
-        &[
-            KeyPress::Esc,
-            KeyPress::Char('i'),
-            KeyPress::Char('y'),
-            KeyPress::Enter,
-        ],
+        &[E::ESC, E::from('i'), E::from('y'), E::ENTER],
         ("By", "e"),
     );
 }
@@ -297,12 +223,7 @@ fn uppercase_i() {
     assert_cursor(
         EditMode::Vi,
         ("Be", ""),
-        &[
-            KeyPress::Esc,
-            KeyPress::Char('I'),
-            KeyPress::Char('y'),
-            KeyPress::Enter,
-        ],
+        &[E::ESC, E::from('I'), E::from('y'), E::ENTER],
         ("y", "Be"),
     );
 }
@@ -312,12 +233,7 @@ fn u() {
     assert_cursor(
         EditMode::Vi,
         ("Hello, ", "world"),
-        &[
-            KeyPress::Esc,
-            KeyPress::Ctrl('W'),
-            KeyPress::Char('u'),
-            KeyPress::Enter,
-        ],
+        &[E::ESC, E::ctrl('W'), E::from('u'), E::ENTER],
         ("Hello,", " world"),
     );
 }
@@ -327,18 +243,13 @@ fn w() {
     assert_cursor(
         EditMode::Vi,
         ("", "Hello, world!"),
-        &[KeyPress::Esc, KeyPress::Char('w'), KeyPress::Enter],
+        &[E::ESC, E::from('w'), E::ENTER],
         ("Hello", ", world!"),
     );
     assert_cursor(
         EditMode::Vi,
         ("", "Hello, world!"),
-        &[
-            KeyPress::Esc,
-            KeyPress::Char('2'),
-            KeyPress::Char('w'),
-            KeyPress::Enter,
-        ],
+        &[E::ESC, E::from('2'), E::from('w'), E::ENTER],
         ("Hello, ", "world!"),
     );
 }
@@ -348,18 +259,13 @@ fn uppercase_w() {
     assert_cursor(
         EditMode::Vi,
         ("", "Hello, world!"),
-        &[KeyPress::Esc, KeyPress::Char('W'), KeyPress::Enter],
+        &[E::ESC, E::from('W'), E::ENTER],
         ("Hello, ", "world!"),
     );
     assert_cursor(
         EditMode::Vi,
         ("", "Hello, world!"),
-        &[
-            KeyPress::Esc,
-            KeyPress::Char('2'),
-            KeyPress::Char('W'),
-            KeyPress::Enter,
-        ],
+        &[E::ESC, E::from('2'), E::from('W'), E::ENTER],
         ("Hello, world", "!"),
     );
 }
@@ -369,7 +275,7 @@ fn x() {
     assert_cursor(
         EditMode::Vi,
         ("", "a"),
-        &[KeyPress::Esc, KeyPress::Char('x'), KeyPress::Enter],
+        &[E::ESC, E::from('x'), E::ENTER],
         ("", ""),
     );
 }
@@ -379,28 +285,24 @@ fn uppercase_x() {
     assert_cursor(
         EditMode::Vi,
         ("Hi", ""),
-        &[KeyPress::Esc, KeyPress::Char('X'), KeyPress::Enter],
+        &[E::ESC, E::from('X'), E::ENTER],
         ("", "i"),
     );
 }
 
 #[test]
 fn h() {
-    for key in &[
-        KeyPress::Char('h'),
-        KeyPress::Ctrl('H'),
-        KeyPress::Backspace,
-    ] {
+    for key in &[E::from('h'), E::ctrl('H'), E::BACKSPACE] {
         assert_cursor(
             EditMode::Vi,
             ("Bye", ""),
-            &[KeyPress::Esc, *key, KeyPress::Enter],
+            &[E::ESC, *key, E::ENTER],
             ("B", "ye"),
         );
         assert_cursor(
             EditMode::Vi,
             ("Bye", ""),
-            &[KeyPress::Esc, KeyPress::Char('2'), *key, KeyPress::Enter],
+            &[E::ESC, E::from('2'), *key, E::ENTER],
             ("", "Bye"),
         );
     }
@@ -408,17 +310,17 @@ fn h() {
 
 #[test]
 fn l() {
-    for key in &[KeyPress::Char('l'), KeyPress::Char(' ')] {
+    for key in &[E::from('l'), E::from(' ')] {
         assert_cursor(
             EditMode::Vi,
             ("", "Hi"),
-            &[KeyPress::Esc, *key, KeyPress::Enter],
+            &[E::ESC, *key, E::ENTER],
             ("H", "i"),
         );
         assert_cursor(
             EditMode::Vi,
             ("", "Hi"),
-            &[KeyPress::Esc, KeyPress::Char('2'), *key, KeyPress::Enter],
+            &[E::ESC, E::from('2'), *key, E::ENTER],
             ("Hi", ""),
         );
     }
@@ -426,25 +328,25 @@ fn l() {
 
 #[test]
 fn j() {
-    for key in &[KeyPress::Char('j'), KeyPress::Char('+')] {
+    for key in &[E::from('j'), E::from('+')] {
         assert_cursor(
             EditMode::Vi,
             ("Hel", "lo,\nworld!"),
             // NOTE: escape moves backwards on char
-            &[KeyPress::Esc, *key, KeyPress::Enter],
+            &[E::ESC, *key, E::ENTER],
             ("Hello,\nwo", "rld!"),
         );
         assert_cursor(
             EditMode::Vi,
             ("", "One\nTwo\nThree"),
-            &[KeyPress::Esc, KeyPress::Char('2'), *key, KeyPress::Enter],
+            &[E::ESC, E::from('2'), *key, E::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],
+            &[E::ESC, E::from('7'), *key, E::ENTER],
             ("Hello,\nwo", "rld!"),
         );
     }
@@ -452,32 +354,32 @@ fn j() {
 
 #[test]
 fn k() {
-    for key in &[KeyPress::Char('k'), KeyPress::Char('-')] {
+    for key in &[E::from('k'), E::from('-')] {
         assert_cursor(
             EditMode::Vi,
             ("Hello,\nworl", "d!"),
             // NOTE: escape moves backwards on char
-            &[KeyPress::Esc, *key, KeyPress::Enter],
+            &[E::ESC, *key, E::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],
+            &[E::ESC, E::from('2'), *key, E::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],
+            &[E::ESC, E::from('5'), *key, E::ENTER],
             ("Hel", "lo,\nworld!"),
         );
         assert_cursor(
             EditMode::Vi,
             ("first line\nshort\nlong line", ""),
-            &[KeyPress::Esc, *key, KeyPress::Enter],
+            &[E::ESC, *key, E::ENTER],
             ("first line\nshort", "\nlong line"),
         );
     }
@@ -485,17 +387,11 @@ fn k() {
 
 #[test]
 fn ctrl_n() {
-    for key in &[KeyPress::Ctrl('N')] {
+    for key in &[E::ctrl('N')] {
         assert_history(
             EditMode::Vi,
             &["line1", "line2"],
-            &[
-                KeyPress::Esc,
-                KeyPress::Ctrl('P'),
-                KeyPress::Ctrl('P'),
-                *key,
-                KeyPress::Enter,
-            ],
+            &[E::ESC, E::ctrl('P'), E::ctrl('P'), *key, E::ENTER],
             "",
             ("line2", ""),
         );
@@ -504,11 +400,11 @@ fn ctrl_n() {
 
 #[test]
 fn ctrl_p() {
-    for key in &[KeyPress::Ctrl('P')] {
+    for key in &[E::ctrl('P')] {
         assert_history(
             EditMode::Vi,
             &["line1"],
-            &[KeyPress::Esc, *key, KeyPress::Enter],
+            &[E::ESC, *key, E::ENTER],
             "",
             ("line1", ""),
         );
@@ -520,12 +416,7 @@ fn p() {
     assert_cursor(
         EditMode::Vi,
         ("Hello, ", "world"),
-        &[
-            KeyPress::Esc,
-            KeyPress::Ctrl('W'),
-            KeyPress::Char('p'),
-            KeyPress::Enter,
-        ],
+        &[E::ESC, E::ctrl('W'), E::from('p'), E::ENTER],
         (" Hello", ",world"),
     );
 }
@@ -535,12 +426,7 @@ fn uppercase_p() {
     assert_cursor(
         EditMode::Vi,
         ("Hello, ", "world"),
-        &[
-            KeyPress::Esc,
-            KeyPress::Ctrl('W'),
-            KeyPress::Char('P'),
-            KeyPress::Enter,
-        ],
+        &[E::ESC, E::ctrl('W'), E::from('P'), E::ENTER],
         ("Hello", ", world"),
     );
 }
@@ -550,24 +436,13 @@ fn r() {
     assert_cursor(
         EditMode::Vi,
         ("Hi", ", world!"),
-        &[
-            KeyPress::Esc,
-            KeyPress::Char('r'),
-            KeyPress::Char('o'),
-            KeyPress::Enter,
-        ],
+        &[E::ESC, E::from('r'), E::from('o'), E::ENTER],
         ("H", "o, world!"),
     );
     assert_cursor(
         EditMode::Vi,
         ("He", "llo, world!"),
-        &[
-            KeyPress::Esc,
-            KeyPress::Char('4'),
-            KeyPress::Char('r'),
-            KeyPress::Char('i'),
-            KeyPress::Enter,
-        ],
+        &[E::ESC, E::from('4'), E::from('r'), E::from('i'), E::ENTER],
         ("Hiii", "i, world!"),
     );
 }
@@ -577,24 +452,13 @@ fn s() {
     assert_cursor(
         EditMode::Vi,
         ("Hi", ", world!"),
-        &[
-            KeyPress::Esc,
-            KeyPress::Char('s'),
-            KeyPress::Char('o'),
-            KeyPress::Enter,
-        ],
+        &[E::ESC, E::from('s'), E::from('o'), E::ENTER],
         ("Ho", ", world!"),
     );
     assert_cursor(
         EditMode::Vi,
         ("He", "llo, world!"),
-        &[
-            KeyPress::Esc,
-            KeyPress::Char('4'),
-            KeyPress::Char('s'),
-            KeyPress::Char('i'),
-            KeyPress::Enter,
-        ],
+        &[E::ESC, E::from('4'), E::from('s'), E::from('i'), E::ENTER],
         ("Hi", ", world!"),
     );
 }
@@ -604,7 +468,7 @@ fn uppercase_s() {
     assert_cursor(
         EditMode::Vi,
         ("Hello, ", "world"),
-        &[KeyPress::Esc, KeyPress::Char('S'), KeyPress::Enter],
+        &[E::ESC, E::from('S'), E::ENTER],
         ("", ""),
     );
 }
@@ -614,24 +478,13 @@ fn t() {
     assert_cursor(
         EditMode::Vi,
         ("", "Hello, world!"),
-        &[
-            KeyPress::Esc,
-            KeyPress::Char('t'),
-            KeyPress::Char('r'),
-            KeyPress::Enter,
-        ],
+        &[E::ESC, E::from('t'), E::from('r'), E::ENTER],
         ("Hello, w", "orld!"),
     );
     assert_cursor(
         EditMode::Vi,
         ("", "Hello, world!"),
-        &[
-            KeyPress::Esc,
-            KeyPress::Char('3'),
-            KeyPress::Char('t'),
-            KeyPress::Char('l'),
-            KeyPress::Enter,
-        ],
+        &[E::ESC, E::from('3'), E::from('t'), E::from('l'), E::ENTER],
         ("Hello, wo", "rld!"),
     );
 }
@@ -641,24 +494,13 @@ fn uppercase_t() {
     assert_cursor(
         EditMode::Vi,
         ("Hello, world!", ""),
-        &[
-            KeyPress::Esc,
-            KeyPress::Char('T'),
-            KeyPress::Char('r'),
-            KeyPress::Enter,
-        ],
+        &[E::ESC, E::from('T'), E::from('r'), E::ENTER],
         ("Hello, wor", "ld!"),
     );
     assert_cursor(
         EditMode::Vi,
         ("Hello, world!", ""),
-        &[
-            KeyPress::Esc,
-            KeyPress::Char('3'),
-            KeyPress::Char('T'),
-            KeyPress::Char('l'),
-            KeyPress::Enter,
-        ],
+        &[E::ESC, E::from('3'), E::from('T'), E::from('l'), E::ENTER],
         ("Hel", "lo, world!"),
     );
 }
diff --git a/src/test/vi_insert.rs b/src/test/vi_insert.rs
index 608e2ac..8a18529 100644
--- a/src/test/vi_insert.rs
+++ b/src/test/vi_insert.rs
@@ -1,16 +1,11 @@
 //! Vi insert mode specific key bindings
 use super::assert_cursor;
 use crate::config::EditMode;
-use crate::keys::KeyPress;
+use crate::keys::KeyEvent as E;
 
 #[test]
 fn insert_mode_by_default() {
-    assert_cursor(
-        EditMode::Vi,
-        ("", ""),
-        &[KeyPress::Char('a'), KeyPress::Enter],
-        ("a", ""),
-    );
+    assert_cursor(EditMode::Vi, ("", ""), &[E::from('a'), E::ENTER], ("a", ""));
 }
 
 #[test]
@@ -18,29 +13,24 @@ fn ctrl_h() {
     assert_cursor(
         EditMode::Vi,
         ("Hi", ""),
-        &[KeyPress::Ctrl('H'), KeyPress::Enter],
+        &[E::ctrl('H'), E::ENTER],
         ("H", ""),
     );
 }
 
 #[test]
 fn backspace() {
-    assert_cursor(
-        EditMode::Vi,
-        ("", ""),
-        &[KeyPress::Backspace, KeyPress::Enter],
-        ("", ""),
-    );
+    assert_cursor(EditMode::Vi, ("", ""), &[E::BACKSPACE, E::ENTER], ("", ""));
     assert_cursor(
         EditMode::Vi,
         ("Hi", ""),
-        &[KeyPress::Backspace, KeyPress::Enter],
+        &[E::BACKSPACE, E::ENTER],
         ("H", ""),
     );
     assert_cursor(
         EditMode::Vi,
         ("", "Hi"),
-        &[KeyPress::Backspace, KeyPress::Enter],
+        &[E::BACKSPACE, E::ENTER],
         ("", "Hi"),
     );
 }
@@ -50,7 +40,7 @@ fn esc() {
     assert_cursor(
         EditMode::Vi,
         ("", ""),
-        &[KeyPress::Char('a'), KeyPress::Esc, KeyPress::Enter],
+        &[E::from('a'), E::ESC, E::ENTER],
         ("", "a"),
     );
 }
diff --git a/src/tty/test.rs b/src/tty/test.rs
index f8dbb02..765d744 100644
--- a/src/tty/test.rs
+++ b/src/tty/test.rs
@@ -7,7 +7,7 @@ use super::{RawMode, RawReader, Renderer, Term};
 use crate::config::{BellStyle, ColorMode, Config, OutputStreamType};
 use crate::error::ReadlineError;
 use crate::highlight::Highlighter;
-use crate::keys::KeyPress;
+use crate::keys::KeyEvent;
 use crate::layout::{Layout, Position};
 use crate::line_buffer::LineBuffer;
 use crate::Result;
@@ -20,8 +20,8 @@ impl RawMode for Mode {
     }
 }
 
-impl<'a> RawReader for Iter<'a, KeyPress> {
-    fn next_key(&mut self, _: bool) -> Result<KeyPress> {
+impl<'a> RawReader for Iter<'a, KeyEvent> {
+    fn next_key(&mut self, _: bool) -> Result<KeyEvent> {
         match self.next() {
             Some(key) => Ok(*key),
             None => Err(ReadlineError::Eof),
@@ -38,8 +38,8 @@ impl<'a> RawReader for Iter<'a, KeyPress> {
     }
 }
 
-impl RawReader for IntoIter<KeyPress> {
-    fn next_key(&mut self, _: bool) -> Result<KeyPress> {
+impl RawReader for IntoIter<KeyEvent> {
+    fn next_key(&mut self, _: bool) -> Result<KeyEvent> {
         match self.next() {
             Some(key) => Ok(key),
             None => Err(ReadlineError::Eof),
@@ -48,8 +48,9 @@ impl RawReader for IntoIter<KeyPress> {
 
     #[cfg(unix)]
     fn next_char(&mut self) -> Result<char> {
+        use crate::keys::{KeyCode as K, KeyEvent as E, Modifiers as M};
         match self.next() {
-            Some(KeyPress::Char(c)) => Ok(c),
+            Some(E(K::Char(c), M::NONE)) => Ok(c),
             None => Err(ReadlineError::Eof),
             _ => unimplemented!(),
         }
@@ -69,7 +70,7 @@ impl Sink {
 }
 
 impl Renderer for Sink {
-    type Reader = IntoIter<KeyPress>;
+    type Reader = IntoIter<KeyEvent>;
 
     fn move_cursor(&mut self, _: Position, _: Position) -> Result<()> {
         Ok(())
@@ -123,7 +124,7 @@ impl Renderer for Sink {
         false
     }
 
-    fn move_cursor_at_leftmost(&mut self, _: &mut IntoIter<KeyPress>) -> Result<()> {
+    fn move_cursor_at_leftmost(&mut self, _: &mut IntoIter<KeyEvent>) -> Result<()> {
         Ok(())
     }
 }
@@ -132,7 +133,7 @@ pub type Terminal = DummyTerminal;
 
 #[derive(Clone, Debug)]
 pub struct DummyTerminal {
-    pub keys: Vec<KeyPress>,
+    pub keys: Vec<KeyEvent>,
     pub cursor: usize, // cursor position before last command
     pub color_mode: ColorMode,
     pub bell_style: BellStyle,
@@ -140,7 +141,7 @@ pub struct DummyTerminal {
 
 impl Term for DummyTerminal {
     type Mode = Mode;
-    type Reader = IntoIter<KeyPress>;
+    type Reader = IntoIter<KeyEvent>;
     type Writer = Sink;
 
     fn new(
@@ -183,7 +184,7 @@ impl Term for DummyTerminal {
         Ok(())
     }
 
-    fn create_reader(&self, _: &Config) -> Result<IntoIter<KeyPress>> {
+    fn create_reader(&self, _: &Config) -> Result<IntoIter<KeyEvent>> {
         Ok(self.keys.clone().into_iter())
     }