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