From ace74cc1874e8871c5ad2806de6961c64de66746 Mon Sep 17 00:00:00 2001
From: gwenn <gtreguier@gmail.com>
Date: Sun, 26 Mar 2017 12:40:39 +0200
Subject: [PATCH] Make possible to customize key bindings (#115)

---
 src/consts.rs |  3 ++-
 src/keymap.rs | 41 ++++++++++++++++++++++++++++++++++++++++-
 src/lib.rs    | 31 ++++++++++++++++++++++++++-----
 3 files changed, 68 insertions(+), 7 deletions(-)

diff --git a/src/consts.rs b/src/consts.rs
index e0ee405c..a1fc550b 100644
--- a/src/consts.rs
+++ b/src/consts.rs
@@ -1,5 +1,6 @@
+//! Key constants
 
-#[derive(Debug, Clone, PartialEq, Copy)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
 pub enum KeyPress {
     UnknownEscSeq,
     Backspace,
diff --git a/src/keymap.rs b/src/keymap.rs
index 8c022c3f..e39db68f 100644
--- a/src/keymap.rs
+++ b/src/keymap.rs
@@ -1,3 +1,8 @@
+//! Bindings from keys to command for Emacs and Vi modes
+use std::cell::RefCell;
+use std::collections::HashMap;
+use std::rc::Rc;
+
 use config::Config;
 use config::EditMode;
 use consts::KeyPress;
@@ -52,6 +57,12 @@ impl Cmd {
             _ => false,
         }
     }
+    fn is_repeatable(&self) -> bool {
+        match *self {
+            Cmd::Move(_) => true,
+            _ => self.is_repeatable_change(),
+        }
+    }
 
     fn redo(&self, new: Option<RepeatCount>) -> Cmd {
         match *self {
@@ -59,6 +70,7 @@ impl Cmd {
                 Cmd::Insert(repeat_count(previous, new), text.clone())
             }
             Cmd::Kill(ref mvt) => Cmd::Kill(mvt.redo(new)),
+            Cmd::Move(ref mvt) => Cmd::Move(mvt.redo(new)),
             Cmd::Replace(previous, c) => Cmd::Replace(repeat_count(previous, new), c),
             Cmd::SelfInsert(previous, c) => Cmd::SelfInsert(repeat_count(previous, new), c),
             //Cmd::TransposeChars => Cmd::TransposeChars,
@@ -158,6 +170,7 @@ impl Movement {
 
 pub struct EditState {
     mode: EditMode,
+    custom_bindings: Rc<RefCell<HashMap<KeyPress, Cmd>>>,
     // Vi Command/Alternate, Insert/Input mode
     insert: bool, // vi only ?
     // numeric arguments: http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC7
@@ -168,9 +181,10 @@ pub struct EditState {
 }
 
 impl EditState {
-    pub fn new(config: &Config) -> EditState {
+    pub fn new(config: &Config, custom_bindings: Rc<RefCell<HashMap<KeyPress, Cmd>>>) -> EditState {
         EditState {
             mode: config.edit_mode(),
+            custom_bindings: custom_bindings,
             insert: true,
             num_args: 0,
             last_cmd: Cmd::Noop,
@@ -228,6 +242,13 @@ impl EditState {
             key = try!(self.emacs_digit_argument(rdr, digit));
         }
         let (n, positive) = self.emacs_num_args(); // consume them in all cases
+        if let Some(cmd) = self.custom_bindings.borrow().get(&key) {
+            return Ok(if cmd.is_repeatable() {
+                cmd.redo(Some(n))
+            } else {
+                cmd.clone()
+            });
+        }
         let cmd = match key {
             KeyPress::Char(c) => {
                 if positive {
@@ -337,6 +358,17 @@ impl EditState {
         }
         let no_num_args = self.num_args == 0;
         let n = self.vi_num_args(); // consume them in all cases
+        if let Some(cmd) = self.custom_bindings.borrow().get(&key) {
+            return Ok(if cmd.is_repeatable() {
+                if no_num_args {
+                    cmd.redo(None)
+                } else {
+                    cmd.redo(Some(n))
+                }
+            } else {
+                cmd.clone()
+            });
+        }
         let cmd = match key {
             KeyPress::Char('$') |
             KeyPress::End => Cmd::Move(Movement::EndOfLine),
@@ -481,6 +513,13 @@ impl EditState {
 
     fn vi_insert<R: RawReader>(&mut self, rdr: &mut R) -> Result<Cmd> {
         let key = try!(rdr.next_key());
+        if let Some(cmd) = self.custom_bindings.borrow().get(&key) {
+            return Ok(if cmd.is_repeatable() {
+                cmd.redo(None)
+            } else {
+                cmd.clone()
+            });
+        }
         let cmd = match key {
             KeyPress::Char(c) => Cmd::SelfInsert(1, c),
             KeyPress::Ctrl('H') |
diff --git a/src/lib.rs b/src/lib.rs
index 14057cb5..53851a28 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -42,10 +42,13 @@ pub mod config;
 
 mod tty;
 
+use std::cell::RefCell;
+use std::collections::HashMap;
 use std::fmt;
 use std::io::{self, Write};
 use std::mem;
 use std::path::Path;
+use std::rc::Rc;
 use std::result;
 use unicode_segmentation::UnicodeSegmentation;
 use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
@@ -56,9 +59,11 @@ use encode_unicode::CharExt;
 use completion::{Completer, longest_common_prefix};
 use history::{Direction, History};
 use line_buffer::{LineBuffer, MAX_LINE, WordAction};
-use keymap::{Anchor, At, CharSearch, Cmd, EditState, Movement, RepeatCount, Word};
+pub use keymap::{Anchor, At, CharSearch, Cmd, Movement, RepeatCount, Word};
+use keymap::EditState;
 use kill_ring::{Mode, KillRing};
 pub use config::{CompletionType, Config, EditMode, HistoryDuplicates};
+pub use consts::KeyPress;
 
 /// The error type for I/O and Linux Syscalls (Errno)
 pub type Result<T> = result::Result<T, error::ReadlineError>;
@@ -89,7 +94,8 @@ impl<'out, 'prompt> State<'out, 'prompt> {
            term: Terminal,
            config: &Config,
            prompt: &'prompt str,
-           history_index: usize)
+           history_index: usize,
+           custom_bindings: Rc<RefCell<HashMap<KeyPress, Cmd>>>)
            -> State<'out, 'prompt> {
         let capacity = MAX_LINE;
         let cols = term.get_columns();
@@ -105,7 +111,7 @@ impl<'out, 'prompt> State<'out, 'prompt> {
             history_index: history_index,
             snapshot: LineBuffer::with_capacity(capacity),
             term: term,
-            edit_state: EditState::new(config),
+            edit_state: EditState::new(config, custom_bindings),
         }
     }
 
@@ -847,7 +853,8 @@ fn readline_edit<C: Completer>(prompt: &str,
                            editor.term.clone(),
                            &editor.config,
                            prompt,
-                           editor.history.len());
+                           editor.history.len(),
+                           editor.custom_bindings.clone());
     try!(s.refresh_line());
 
     let mut rdr = try!(s.term.create_reader(&editor.config));
@@ -1128,6 +1135,7 @@ pub struct Editor<C: Completer> {
     completer: Option<C>,
     kill_ring: KillRing,
     config: Config,
+    custom_bindings: Rc<RefCell<HashMap<KeyPress, Cmd>>>,
 }
 
 impl<C: Completer> Editor<C> {
@@ -1143,6 +1151,7 @@ impl<C: Completer> Editor<C> {
             completer: None,
             kill_ring: KillRing::new(60),
             config: config,
+            custom_bindings: Rc::new(RefCell::new(HashMap::new())),
         }
     }
 
@@ -1190,6 +1199,15 @@ impl<C: Completer> Editor<C> {
         self.completer = completer;
     }
 
+    /// Bind a sequence to a command.
+    pub fn bind_sequence(&mut self, key_seq: KeyPress, cmd: Cmd) -> Option<Cmd> {
+        self.custom_bindings.borrow_mut().insert(key_seq, cmd)
+    }
+    /// Remove a binding for the given sequence.
+    pub fn unbind_sequence(&mut self, key_seq: KeyPress) -> Option<Cmd> {
+        self.custom_bindings.borrow_mut().remove(&key_seq)
+    }
+
     /// ```
     /// let mut rl = rustyline::Editor::<()>::new();
     /// for readline in rl.iter("> ") {
@@ -1246,7 +1264,10 @@ impl<'a, C: Completer> Iterator for Iter<'a, C> {
 
 #[cfg(test)]
 mod test {
+    use std::cell::RefCell;
+    use std::collections::HashMap;
     use std::io::Write;
+    use std::rc::Rc;
     use line_buffer::LineBuffer;
     use history::History;
     use completion::Completer;
@@ -1274,7 +1295,7 @@ mod test {
             history_index: 0,
             snapshot: LineBuffer::with_capacity(100),
             term: term,
-            edit_state: EditState::new(&config),
+            edit_state: EditState::new(&config, Rc::new(RefCell::new(HashMap::new()))),
         }
     }
 
-- 
GitLab