Repository: kkawakam/rustyline
Pull request #502 (2021-03-07 07:54:55)

Prepare release of version 8.0.0


Patch:
diff --git a/Cargo.toml b/Cargo.toml
index 86c548d..8c39b3c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "rustyline"
-version = "7.1.0"
+version = "8.0.0"
 authors = ["Katsu Kawakami <kkawa1570@gmail.com>"]
 edition = "2018"
 description = "Rustyline, a readline implementation based on Antirez's Linenoise"
@@ -20,20 +20,14 @@ members = ["rustyline-derive"]
 [dependencies]
 bitflags = "1.2"
 cfg-if = "1.0"
-# For file completion
 # https://rustsec.org/advisories/RUSTSEC-2020-0053.html
 dirs-next = { version = "2.0", optional = true }
-# For History
 fs2 = "0.4"
 libc = "0.2"
 log = "0.4"
 unicode-width = "0.1"
 unicode-segmentation = "1.0"
 memchr = "2.0"
-# For custom bindings
-# https://rustsec.org/advisories/RUSTSEC-2021-0003.html
-smallvec = "1.6.1"
-radix_trie = "0.2"
 
 [target.'cfg(unix)'.dependencies]
 nix = "0.20"
diff --git a/README.md b/README.md
index c94e94d..69fe27e 100644
--- a/README.md
+++ b/README.md
@@ -59,7 +59,7 @@ to your `Cargo.toml`:
 
 ```toml
 [dependencies]
-rustyline = "7.1.0"
+rustyline = "8.0.0"
 ```
 
 ## Features
diff --git a/examples/custom_key_bindings.rs b/examples/custom_key_bindings.rs
deleted file mode 100644
index 77957e3..0000000
--- a/examples/custom_key_bindings.rs
+++ /dev/null
@@ -1,110 +0,0 @@
-use smallvec::smallvec;
-use std::borrow::Cow::{self, Borrowed, Owned};
-
-use rustyline::highlight::Highlighter;
-use rustyline::hint::{Hinter, HistoryHinter};
-use rustyline::{
-    Cmd, ConditionalEventHandler, Context, Editor, Event, EventContext, EventHandler, KeyEvent,
-    RepeatCount,
-};
-use rustyline_derive::{Completer, Helper, Validator};
-
-#[derive(Completer, Helper, Validator)]
-struct MyHelper(HistoryHinter);
-
-impl Hinter for MyHelper {
-    type Hint = String;
-
-    fn hint(&self, line: &str, pos: usize, ctx: &Context<'_>) -> Option<String> {
-        self.0.hint(line, pos, ctx)
-    }
-}
-
-impl Highlighter for MyHelper {
-    fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
-        &'s self,
-        prompt: &'p str,
-        default: bool,
-    ) -> Cow<'b, str> {
-        if default {
-            Owned(format!("\x1b[1;32m{}\x1b[m", prompt))
-        } else {
-            Borrowed(prompt)
-        }
-    }
-
-    fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
-        Owned(format!("\x1b[1m{}\x1b[m", hint))
-    }
-}
-
-#[derive(Clone)]
-struct CompleteHintHandler;
-impl ConditionalEventHandler for CompleteHintHandler {
-    fn handle(&self, evt: &Event, _: RepeatCount, _: bool, ctx: &EventContext) -> Option<Cmd> {
-        if !ctx.has_hint() {
-            return None; // default
-        }
-        if let Some(k) = evt.get(0) {
-            #[allow(clippy::if_same_then_else)]
-            if *k == KeyEvent::ctrl('E') {
-                Some(Cmd::CompleteHint)
-            } else if *k == KeyEvent::alt('f') && ctx.line().len() == ctx.pos() {
-                Some(Cmd::CompleteHint) // TODO give access to hint
-            } else {
-                None
-            }
-        } else {
-            unreachable!()
-        }
-    }
-}
-
-struct TabEventHandler;
-impl ConditionalEventHandler for TabEventHandler {
-    fn handle(&self, evt: &Event, n: RepeatCount, _: bool, ctx: &EventContext) -> Option<Cmd> {
-        debug_assert_eq!(*evt, Event::from(KeyEvent::from('\t')));
-        if ctx.line()[..ctx.pos()]
-            .chars()
-            .rev()
-            .next()
-            .filter(|c| c.is_whitespace())
-            .is_some()
-        {
-            Some(Cmd::SelfInsert(n, '\t'))
-        } else {
-            None // default complete
-        }
-    }
-}
-
-fn main() {
-    let mut rl = Editor::<MyHelper>::new();
-    rl.set_helper(Some(MyHelper(HistoryHinter {})));
-
-    let ceh = Box::new(CompleteHintHandler);
-    rl.bind_sequence(KeyEvent::ctrl('E'), EventHandler::Conditional(ceh.clone()));
-    rl.bind_sequence(KeyEvent::alt('f'), EventHandler::Conditional(ceh));
-    rl.bind_sequence(
-        KeyEvent::from('\t'),
-        EventHandler::Conditional(Box::new(TabEventHandler)),
-    );
-    rl.bind_sequence(
-        Event::KeySeq(smallvec![KeyEvent::ctrl('X'), KeyEvent::ctrl('E')]),
-        EventHandler::Simple(Cmd::Suspend), // TODO external editor
-    );
-
-    loop {
-        let readline = rl.readline("> ");
-        match readline {
-            Ok(line) => {
-                rl.add_history_entry(line.as_str());
-                println!("Line: {}", line);
-            }
-            Err(err) => {
-                println!("Error: {:?}", err);
-                break;
-            }
-        }
-    }
-}
diff --git a/examples/example.rs b/examples/example.rs
index 7b3ccc8..c627471 100644
--- a/examples/example.rs
+++ b/examples/example.rs
@@ -97,8 +97,8 @@ fn main() -> rustyline::Result<()> {
     };
     let mut rl = Editor::with_config(config);
     rl.set_helper(Some(h));
-    rl.bind_sequence(KeyEvent::alt('n'), Cmd::HistorySearchForward);
-    rl.bind_sequence(KeyEvent::alt('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/examples/numeric_input.rs b/examples/numeric_input.rs
deleted file mode 100644
index 8071d2b..0000000
--- a/examples/numeric_input.rs
+++ /dev/null
@@ -1,41 +0,0 @@
-use rustyline::{
-    Cmd, ConditionalEventHandler, Editor, Event, EventContext, EventHandler, KeyCode, KeyEvent,
-    Modifiers, RepeatCount,
-};
-
-struct FilteringEventHandler;
-impl ConditionalEventHandler for FilteringEventHandler {
-    fn handle(&self, evt: &Event, _: RepeatCount, _: bool, _: &EventContext) -> Option<Cmd> {
-        if let Some(KeyEvent(KeyCode::Char(c), m)) = evt.get(0) {
-            if m.contains(Modifiers::CTRL) || m.contains(Modifiers::ALT) || c.is_ascii_digit() {
-                None
-            } else {
-                Some(Cmd::Noop) // filter out invalid input
-            }
-        } else {
-            None
-        }
-    }
-}
-
-fn main() {
-    let mut rl = Editor::<()>::new();
-
-    rl.bind_sequence(
-        Event::Any,
-        EventHandler::Conditional(Box::new(FilteringEventHandler)),
-    );
-
-    loop {
-        let readline = rl.readline("> ");
-        match readline {
-            Ok(line) => {
-                println!("Num: {}", line);
-            }
-            Err(err) => {
-                println!("Error: {:?}", err);
-                break;
-            }
-        }
-    }
-}
diff --git a/src/binding.rs b/src/binding.rs
deleted file mode 100644
index 8fde7be..0000000
--- a/src/binding.rs
+++ /dev/null
@@ -1,227 +0,0 @@
-/// Custom event handlers
-use crate::{
-    Cmd, EditMode, InputMode, InputState, KeyCode, KeyEvent, Modifiers, Refresher, RepeatCount,
-};
-
-use radix_trie::TrieKey;
-use smallvec::{smallvec, SmallVec};
-
-/// Input event
-#[derive(Debug, Clone, PartialEq, Eq, Hash)]
-pub enum Event {
-    /// Wildcard.
-    /// Useful if you want to filter out some keys.
-    Any,
-    /// Key sequence
-    // TODO Validate 2 ?
-    KeySeq(SmallVec<[KeyEvent; 2]>),
-    /// TODO Mouse event
-    Mouse(),
-}
-
-impl Event {
-    /// See [`KeyEvent::normalize`]
-    pub(crate) fn normalize(mut self) -> Self {
-        if let Event::KeySeq(ref mut keys) = self {
-            for key in keys.iter_mut() {
-                *key = KeyEvent::normalize(*key);
-            }
-        }
-        self
-    }
-
-    /// Return `i`th key event
-    pub fn get(&self, i: usize) -> Option<&KeyEvent> {
-        if let Event::KeySeq(ref ks) = self {
-            ks.get(i)
-        } else {
-            None
-        }
-    }
-}
-
-impl From<KeyEvent> for Event {
-    fn from(k: KeyEvent) -> Event {
-        Event::KeySeq(smallvec![k])
-    }
-}
-
-const BASE: u32 = 0x0010ffff + 1;
-const BASE_CONTROL: u32 = 0x02000000;
-const BASE_META: u32 = 0x04000000;
-const BASE_SHIFT: u32 = 0x01000000;
-const ESCAPE: u32 = 27;
-const PAGE_UP: u32 = BASE + 1;
-const PAGE_DOWN: u32 = PAGE_UP + 1;
-const DOWN: u32 = PAGE_DOWN + 1;
-const UP: u32 = DOWN + 1;
-const LEFT: u32 = UP + 1;
-const RIGHT: u32 = LEFT + 1;
-const HOME: u32 = RIGHT + 1;
-const END: u32 = HOME + 1;
-const DELETE: u32 = END + 1;
-const INSERT: u32 = DELETE + 1;
-//const F1: u32 = INSERT + 1;
-const MOUSE: u32 = /*F24 + 1*/ INSERT + 25;
-const PASTE_START: u32 = MOUSE + 1;
-const PASTE_FINISH: u32 = PASTE_START + 1;
-const ANY: u32 = PASTE_FINISH + 1;
-
-impl KeyEvent {
-    fn encode(&self) -> u32 {
-        let mut u = match self.0 {
-            KeyCode::UnknownEscSeq => 0,
-            KeyCode::Backspace => u32::from('H') | BASE_CONTROL,
-            KeyCode::BackTab => u32::from('I') | BASE_CONTROL | BASE_SHIFT,
-            KeyCode::BracketedPasteStart => PASTE_START,
-            KeyCode::BracketedPasteEnd => PASTE_FINISH,
-            KeyCode::Char(c) => u32::from(c),
-            KeyCode::Delete => DELETE,
-            KeyCode::Down => DOWN,
-            KeyCode::End => END,
-            KeyCode::Enter => u32::from('M') | BASE_CONTROL,
-            KeyCode::F(i) => INSERT + i as u32,
-            KeyCode::Esc => ESCAPE,
-            KeyCode::Home => HOME,
-            KeyCode::Insert => INSERT,
-            KeyCode::Left => LEFT,
-            KeyCode::Null => 0,
-            KeyCode::PageDown => PAGE_DOWN,
-            KeyCode::PageUp => PAGE_UP,
-            KeyCode::Right => RIGHT,
-            KeyCode::Tab => u32::from('I') | BASE_CONTROL,
-            KeyCode::Up => UP,
-        };
-        if self.1.contains(Modifiers::CTRL) {
-            u |= BASE_CONTROL;
-        }
-        if self.1.contains(Modifiers::ALT) {
-            u |= BASE_META;
-        }
-        if self.1.contains(Modifiers::SHIFT) {
-            u |= BASE_SHIFT;
-        }
-        u
-    }
-}
-
-impl TrieKey for Event {
-    fn encode_bytes(&self) -> Vec<u8> {
-        match self {
-            Event::Any => ANY.to_be_bytes().to_vec(),
-            Event::KeySeq(keys) => {
-                let mut dst = Vec::with_capacity(keys.len() * 4);
-                for key in keys {
-                    dst.extend_from_slice(&key.encode().to_be_bytes());
-                }
-                dst
-            }
-            Event::Mouse() => MOUSE.to_be_bytes().to_vec(),
-        }
-    }
-}
-
-/// Event handler
-pub enum EventHandler {
-    /// unconditional command
-    Simple(Cmd),
-    /// handler behaviour depends on input state
-    Conditional(Box<dyn ConditionalEventHandler>),
-    /* invoke multiple actions
-     * TODO Macro(), */
-}
-
-impl From<Cmd> for EventHandler {
-    fn from(c: Cmd) -> EventHandler {
-        EventHandler::Simple(c)
-    }
-}
-
-/// Give access to user input.
-pub struct EventContext<'r> {
-    mode: EditMode,
-    input_mode: InputMode,
-    wrt: &'r dyn Refresher,
-}
-
-impl<'r> EventContext<'r> {
-    pub(crate) fn new(is: &InputState, wrt: &'r dyn Refresher) -> Self {
-        EventContext {
-            mode: is.mode,
-            input_mode: is.input_mode,
-            wrt,
-        }
-    }
-
-    /// emacs or vi mode
-    pub fn mode(&self) -> EditMode {
-        self.mode
-    }
-
-    /// vi input mode
-    pub fn input_mode(&self) -> InputMode {
-        self.input_mode
-    }
-
-    /// Returns `true` if there is a hint displayed.
-    pub fn has_hint(&self) -> bool {
-        self.wrt.has_hint()
-    }
-
-    /// currently edited line
-    pub fn line(&self) -> &str {
-        self.wrt.line()
-    }
-
-    /// Current cursor position (byte position)
-    pub fn pos(&self) -> usize {
-        self.wrt.pos()
-    }
-}
-
-/// May behave differently depending on:
-///  * edit mode (emacs vs vi)
-///  * vi input mode (insert vs replace vs command modes)
-///  * empty line
-///  * cursor position
-///  * repeat count
-///  * original key pressed (when same command is bound to different key)
-///  * hint
-///  * ...
-pub trait ConditionalEventHandler: Send + Sync {
-    /// Takes the current input state and
-    /// returns the command to be performed or `None` to perform the default
-    /// one.
-    fn handle(
-        &self,
-        evt: &Event,
-        n: RepeatCount,
-        positive: bool,
-        ctx: &EventContext,
-    ) -> Option<Cmd>;
-}
-
-#[cfg(test)]
-mod test {
-    use super::{Event, EventHandler};
-    use crate::{Cmd, KeyEvent};
-    use radix_trie::Trie;
-    use smallvec::smallvec;
-
-    #[test]
-    fn encode() {
-        let mut trie = Trie::new();
-        let evt = Event::KeySeq(smallvec![KeyEvent::ctrl('X'), KeyEvent::ctrl('E')]);
-        trie.insert(evt.clone(), EventHandler::from(Cmd::Noop));
-        let prefix = Event::from(KeyEvent::ctrl('X'));
-        let subtrie = trie.get_raw_descendant(&prefix);
-        assert!(subtrie.is_some());
-        let subtrie = subtrie.unwrap();
-        let sub_result = subtrie.get(&evt);
-        assert!(sub_result.is_ok());
-        assert!(sub_result.unwrap().is_some());
-        let prefix = Event::from(KeyEvent::ctrl('O'));
-        let subtrie = trie.get_raw_descendant(&prefix);
-        assert!(subtrie.is_none())
-    }
-}
diff --git a/src/edit.rs b/src/edit.rs
index 540f907..578962f 100644
--- a/src/edit.rs
+++ b/src/edit.rs
@@ -277,14 +277,6 @@ impl<'out, 'prompt, H: Helper> Refresher for State<'out, 'prompt, H> {
     fn has_hint(&self) -> bool {
         self.hint.is_some()
     }
-
-    fn line(&self) -> &str {
-        self.line.as_str()
-    }
-
-    fn pos(&self) -> usize {
-        self.line.pos()
-    }
 }
 
 impl<'out, 'prompt, H: Helper> fmt::Debug for State<'out, 'prompt, H> {
diff --git a/src/keymap.rs b/src/keymap.rs
index c70dee3..d7714d5 100644
--- a/src/keymap.rs
+++ b/src/keymap.rs
@@ -1,13 +1,14 @@
 //! Bindings from keys to command for Emacs and Vi modes
+use std::collections::HashMap;
 use std::sync::{Arc, RwLock};
 
 use log::debug;
-use radix_trie::Trie;
 
 use super::Result;
+use crate::config::Config;
+use crate::config::EditMode;
 use crate::keys::{KeyCode as K, KeyEvent, KeyEvent as E, Modifiers as M};
 use crate::tty::{RawReader, Term, Terminal};
-use crate::{Config, EditMode, Event, EventContext, EventHandler};
 
 /// The number of times one command should be repeated.
 pub type RepeatCount = usize;
@@ -328,9 +329,8 @@ impl Movement {
     }
 }
 
-/// Vi input modes
-#[derive(Clone, Copy, PartialEq)]
-pub enum InputMode {
+#[derive(PartialEq)]
+enum InputMode {
     /// Vi Command/Alternate
     Command,
     /// Insert/Input mode
@@ -341,9 +341,9 @@ pub enum InputMode {
 
 /// Transform key(s) to commands based on current input mode
 pub struct InputState {
-    pub(crate) mode: EditMode,
-    custom_bindings: Arc<RwLock<Trie<Event, EventHandler>>>,
-    pub(crate) input_mode: InputMode, // vi only ?
+    mode: EditMode,
+    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,
     last_cmd: Cmd,                        // vi only
@@ -376,14 +376,10 @@ pub trait Refresher {
     fn is_cursor_at_end(&self) -> bool;
     /// Returns `true` if there is a hint displayed.
     fn has_hint(&self) -> bool;
-    /// currently edited line
-    fn line(&self) -> &str;
-    /// Current cursor position (byte position)
-    fn pos(&self) -> usize;
 }
 
 impl InputState {
-    pub fn new(config: &Config, custom_bindings: Arc<RwLock<Trie<Event, EventHandler>>>) -> Self {
+    pub fn new(config: &Config, custom_bindings: Arc<RwLock<HashMap<KeyEvent, Cmd>>>) -> Self {
         Self {
             mode: config.edit_mode(),
             custom_bindings,
@@ -423,61 +419,6 @@ impl InputState {
         }
     }
 
-    fn custom_binding(
-        &self,
-        wrt: &mut dyn Refresher,
-        evt: &Event,
-        n: RepeatCount,
-        positive: bool,
-    ) -> Option<Cmd> {
-        let bindings = self.custom_bindings.read().unwrap();
-        let handler = bindings.get(&evt).or_else(|| bindings.get(&Event::Any));
-        if let Some(handler) = handler {
-            match handler {
-                EventHandler::Simple(cmd) => Some(cmd.clone()),
-                EventHandler::Conditional(handler) => {
-                    let ctx = EventContext::new(self, wrt);
-                    handler.handle(&evt, n, positive, &ctx)
-                }
-            }
-        } else {
-            None
-        }
-    }
-
-    fn custom_seq_binding<R: RawReader>(
-        &self,
-        rdr: &mut R,
-        wrt: &mut dyn Refresher,
-        evt: &mut Event,
-        n: RepeatCount,
-        positive: bool,
-    ) -> Result<Option<Cmd>> {
-        let bindings = self.custom_bindings.read().unwrap();
-        while let Some(subtrie) = bindings.get_raw_descendant(&evt) {
-            let snd_key = rdr.next_key(true)?;
-            if let Event::KeySeq(ref mut key_seq) = evt {
-                key_seq.push(snd_key);
-            } else {
-                break;
-            }
-            let handler = subtrie.get(&evt).unwrap();
-            if let Some(handler) = handler {
-                let cmd = match handler {
-                    EventHandler::Simple(cmd) => Some(cmd.clone()),
-                    EventHandler::Conditional(handler) => {
-                        let ctx = EventContext::new(self, wrt);
-                        handler.handle(&evt, n, positive, &ctx)
-                    }
-                };
-                if cmd.is_some() {
-                    return Ok(cmd);
-                }
-            }
-        }
-        Ok(None)
-    }
-
     fn emacs_digit_argument<R: RawReader>(
         &mut self,
         rdr: &mut R,
@@ -531,14 +472,16 @@ impl InputState {
             key = self.emacs_digit_argument(rdr, wrt, digit)?;
         }
         let (n, positive) = self.emacs_num_args(); // consume them in all cases
-
-        let mut evt = key.into();
-        if let Some(cmd) = self.custom_binding(wrt, &evt, n, positive) {
-            return Ok(if cmd.is_repeatable() {
-                cmd.redo(Some(n), wrt)
-            } else {
-                cmd
-            });
+        {
+            let bindings = self.custom_bindings.read().unwrap();
+            if let Some(cmd) = bindings.get(&key) {
+                debug!(target: "rustyline", "Custom command: {:?}", cmd);
+                return Ok(if cmd.is_repeatable() {
+                    cmd.redo(Some(n), wrt)
+                } else {
+                    cmd.clone()
+                });
+            }
         }
         let cmd = match key {
             E(K::Char(c), M::NONE) => {
@@ -593,19 +536,11 @@ impl InputState {
             E(K::Char('N'), M::CTRL) => Cmd::NextHistory,
             E(K::Char('P'), M::CTRL) => Cmd::PreviousHistory,
             E(K::Char('X'), M::CTRL) => {
-                if let Some(cmd) = self.custom_seq_binding(rdr, wrt, &mut evt, n, positive)? {
-                    cmd
-                } else {
-                    let snd_key = match evt {
-                        // we may have already read the second key in custom_seq_binding
-                        Event::KeySeq(ref key_seq) if key_seq.len() > 1 => key_seq[1],
-                        _ => rdr.next_key(true)?,
-                    };
-                    match snd_key {
-                        E(K::Char('G'), M::CTRL) | E::ESC => Cmd::Abort,
-                        E(K::Char('U'), M::CTRL) => Cmd::Undo(n),
-                        _ => Cmd::Unknown,
-                    }
+                let snd_key = rdr.next_key(true)?;
+                match snd_key {
+                    E(K::Char('G'), M::CTRL) | E::ESC => Cmd::Abort,
+                    E(K::Char('U'), M::CTRL) => Cmd::Undo(n),
+                    _ => Cmd::Unknown,
                 }
             }
             E(K::Backspace, M::ALT) => {
@@ -644,7 +579,7 @@ impl InputState {
             // 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, wrt, evt, key, n, positive)?,
+            _ => self.common(rdr, key, n, positive)?,
         };
         debug!(target: "rustyline", "Emacs command: {:?}", cmd);
         Ok(cmd)
@@ -687,17 +622,20 @@ impl InputState {
         }
         let no_num_args = self.num_args == 0;
         let n = self.vi_num_args(); // consume them in all cases
-        let evt = key.into();
-        if let Some(cmd) = self.custom_binding(wrt, &evt, n, true) {
-            return Ok(if cmd.is_repeatable() {
-                if no_num_args {
-                    cmd.redo(None, wrt)
+        {
+            let bindings = self.custom_bindings.read().unwrap();
+            if let Some(cmd) = bindings.get(&key) {
+                debug!(target: "rustyline", "Custom command: {:?}", cmd);
+                return Ok(if cmd.is_repeatable() {
+                    if no_num_args {
+                        cmd.redo(None, wrt)
+                    } else {
+                        cmd.redo(Some(n), wrt)
+                    }
                 } else {
-                    cmd.redo(Some(n), wrt)
-                }
-            } else {
-                cmd
-            });
+                    cmd.clone()
+                });
+            }
         }
         let cmd = match key {
             E(K::Char('$'), M::NONE) | E(K::End, M::NONE) => Cmd::Move(Movement::EndOfLine),
@@ -847,7 +785,7 @@ impl InputState {
                 None => Cmd::Unknown,
             },
             E::ESC => Cmd::Noop,
-            _ => self.common(rdr, wrt, evt, key, n, true)?,
+            _ => self.common(rdr, key, n, true)?,
         };
         debug!(target: "rustyline", "Vi command: {:?}", cmd);
         if cmd.is_repeatable_change() {
@@ -862,13 +800,16 @@ impl InputState {
         wrt: &mut dyn Refresher,
         key: KeyEvent,
     ) -> Result<Cmd> {
-        let evt = key.into();
-        if let Some(cmd) = self.custom_binding(wrt, &evt, 0, true) {
-            return Ok(if cmd.is_repeatable() {
-                cmd.redo(None, wrt)
-            } else {
-                cmd
-            });
+        {
+            let bindings = self.custom_bindings.read().unwrap();
+            if let Some(cmd) = bindings.get(&key) {
+                debug!(target: "rustyline", "Custom command: {:?}", cmd);
+                return Ok(if cmd.is_repeatable() {
+                    cmd.redo(None, wrt)
+                } else {
+                    cmd.clone()
+                });
+            }
         }
         let cmd = match key {
             E(K::Char(c), M::NONE) => {
@@ -896,7 +837,7 @@ impl InputState {
                 wrt.done_inserting();
                 Cmd::Move(Movement::BackwardChar(1))
             }
-            _ => self.common(rdr, wrt, evt, key, 1, true)?,
+            _ => self.common(rdr, key, 1, true)?,
         };
         debug!(target: "rustyline", "Vi insert: {:?}", cmd);
         if cmd.is_repeatable_change() {
@@ -997,8 +938,6 @@ impl InputState {
     fn common<R: RawReader>(
         &mut self,
         rdr: &mut R,
-        wrt: &mut dyn Refresher,
-        mut evt: Event,
         key: KeyEvent,
         n: RepeatCount,
         positive: bool,
@@ -1068,9 +1007,7 @@ impl InputState {
                 let paste = rdr.read_pasted_text()?;
                 Cmd::Insert(1, paste)
             },
-            _ => {
-                self.custom_seq_binding(rdr, wrt, &mut evt, n, positive)?.unwrap_or(Cmd::Unknown)
-            },
+            _ => Cmd::Unknown,
         })
     }
 
diff --git a/src/keys.rs b/src/keys.rs
index 6a250ae..c147e49 100644
--- a/src/keys.rs
+++ b/src/keys.rs
@@ -187,11 +187,6 @@ mod tests {
         assert_eq!(E::ESC, E::new('\x1b', M::NONE));
     }
 
-    #[test]
-    fn from() {
-        assert_eq!(E(K::Tab, M::NONE), E::from('\t'));
-    }
-
     #[test]
     fn normalize() {
         assert_eq!(E::ctrl('A'), E::normalize(E(K::Char('\x01'), M::NONE)));
diff --git a/src/lib.rs b/src/lib.rs
index a79802c..343b70e 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -17,7 +17,6 @@
 //! ```
 #![warn(missing_docs)]
 
-mod binding;
 mod command;
 pub mod completion;
 pub mod config;
@@ -35,6 +34,7 @@ mod tty;
 mod undo;
 pub mod validate;
 
+use std::collections::HashMap;
 use std::fmt;
 use std::io::{self, Write};
 use std::path::Path;
@@ -42,12 +42,10 @@ use std::result;
 use std::sync::{Arc, Mutex, RwLock};
 
 use log::debug;
-use radix_trie::Trie;
 use unicode_width::UnicodeWidthStr;
 
 use crate::tty::{RawMode, Renderer, Term, Terminal};
 
-pub use crate::binding::{ConditionalEventHandler, Event, EventContext, EventHandler};
 use crate::completion::{longest_common_prefix, Candidate, Completer};
 pub use crate::config::{
     ColorMode, CompletionType, Config, EditMode, HistoryDuplicates, OutputStreamType,
@@ -56,7 +54,7 @@ use crate::edit::State;
 use crate::highlight::Highlighter;
 use crate::hint::Hinter;
 use crate::history::{Direction, History};
-pub use crate::keymap::{Anchor, At, CharSearch, Cmd, InputMode, Movement, RepeatCount, Word};
+pub use crate::keymap::{Anchor, At, CharSearch, Cmd, Movement, RepeatCount, Word};
 use crate::keymap::{InputState, Refresher};
 pub use crate::keys::{KeyCode, KeyEvent, Modifiers};
 use crate::kill_ring::KillRing;
@@ -637,7 +635,7 @@ pub struct Editor<H: Helper> {
     helper: Option<H>,
     kill_ring: Arc<Mutex<KillRing>>,
     config: Config,
-    custom_bindings: Arc<RwLock<Trie<Event, EventHandler>>>,
+    custom_bindings: Arc<RwLock<HashMap<KeyEvent, Cmd>>>,
 }
 
 #[allow(clippy::new_without_default)]
@@ -662,7 +660,7 @@ impl<H: Helper> Editor<H> {
             helper: None,
             kill_ring: Arc::new(Mutex::new(KillRing::new(60))),
             config,
-            custom_bindings: Arc::new(RwLock::new(Trie::new())),
+            custom_bindings: Arc::new(RwLock::new(HashMap::new())),
         }
     }
 
@@ -757,22 +755,18 @@ impl<H: Helper> Editor<H> {
     }
 
     /// Bind a sequence to a command.
-    pub fn bind_sequence<E: Into<Event>, R: Into<EventHandler>>(
-        &mut self,
-        key_seq: E,
-        handler: R,
-    ) -> Option<EventHandler> {
+    pub fn bind_sequence(&mut self, key_seq: KeyEvent, cmd: Cmd) -> Option<Cmd> {
         if let Ok(mut bindings) = self.custom_bindings.write() {
-            bindings.insert(Event::normalize(key_seq.into()), handler.into())
+            bindings.insert(KeyEvent::normalize(key_seq), cmd)
         } else {
             None
         }
     }
 
     /// Remove a binding for the given sequence.
-    pub fn unbind_sequence<E: Into<Event>>(&mut self, key_seq: E) -> Option<EventHandler> {
+    pub fn unbind_sequence(&mut self, key_seq: KeyEvent) -> Option<Cmd> {
         if let Ok(mut bindings) = self.custom_bindings.write() {
-            bindings.remove(&Event::normalize(key_seq.into()))
+            bindings.remove(&KeyEvent::normalize(key_seq))
         } else {
             None
         }
diff --git a/src/tty/windows.rs b/src/tty/windows.rs
index 12de168..01e3ec7 100644
--- a/src/tty/windows.rs
+++ b/src/tty/windows.rs
@@ -116,7 +116,9 @@ impl RawReader for ConsoleRawReader {
         let mut surrogate = 0;
         loop {
             // TODO GetNumberOfConsoleInputEvents
-            check(unsafe { consoleapi::ReadConsoleInputW(self.handle, &mut rec, 1, &mut count) })?;
+            check(unsafe {
+                consoleapi::ReadConsoleInputW(self.handle, &mut rec, 1, &mut count)
+            })?;
 
             if rec.EventType == wincon::WINDOW_BUFFER_SIZE_EVENT {
                 SIGWINCH.store(true, Ordering::SeqCst);
@@ -447,9 +449,7 @@ impl Renderer for ConsoleRenderer {
     }
 
     fn sigwinch(&self) -> bool {
-        SIGWINCH
-            .compare_exchange(true, false, Ordering::SeqCst, Ordering::SeqCst)
-            .unwrap_or(false)
+        SIGWINCH.compare_exchange(true, false, Ordering::SeqCst, Ordering::SeqCst).unwrap_or(false)
     }
 
     /// Try to get the number of columns in the current terminal,


Test patch:
diff --git a/src/test/mod.rs b/src/test/mod.rs
index 9b66b83..a9d42bf 100644
--- a/src/test/mod.rs
+++ b/src/test/mod.rs
@@ -1,8 +1,7 @@
+use std::collections::HashMap;
 use std::sync::{Arc, RwLock};
 use std::vec::IntoIter;
 
-use radix_trie::Trie;
-
 use crate::completion::Completer;
 use crate::config::{Config, EditMode};
 use crate::edit::init_state;
@@ -59,7 +58,7 @@ fn complete_line() {
     let helper = Some(SimpleCompleter);
     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(Trie::new())));
+    let mut input_state = InputState::new(&config, Arc::new(RwLock::new(HashMap::new())));
     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();