Make possible to customize key bindings (#115)
diff --git a/src/consts.rs b/src/consts.rs
index 93c2e73..ed38db6 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 17a502a..696532a 100644
--- a/src/keymap.rs
+++ b/src/keymap.rs
@@ -1,4 +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;
@@ -54,6 +58,12 @@
_ => false,
}
}
+ fn is_repeatable(&self) -> bool {
+ match *self {
+ Cmd::Move(_) => true,
+ _ => self.is_repeatable_change(),
+ }
+ }
fn redo(&self, new: Option<RepeatCount>) -> Cmd {
match *self {
@@ -61,6 +71,7 @@
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,
@@ -160,6 +171,7 @@
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
@@ -170,9 +182,10 @@
}
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,
@@ -230,6 +243,13 @@
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 {
@@ -347,6 +367,17 @@
}
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),
@@ -492,6 +523,13 @@
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 2350214..264ea10 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -44,6 +44,7 @@
mod tty;
use std::cell::RefCell;
+use std::collections::HashMap;
use std::fmt;
use std::io::{self, Write};
use std::mem;
@@ -58,10 +59,12 @@
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};
use undo::Changeset;
+pub use consts::KeyPress;
/// The error type for I/O and Linux Syscalls (Errno)
pub type Result<T> = result::Result<T, error::ReadlineError>;
@@ -94,7 +97,8 @@
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();
@@ -111,7 +115,7 @@
snapshot: LineBuffer::with_capacity(capacity),
term: term,
byte_buffer: [0; 4],
- edit_state: EditState::new(config),
+ edit_state: EditState::new(config, custom_bindings),
changes: Rc::new(RefCell::new(Changeset::new())),
}
}
@@ -851,7 +855,8 @@
editor.term.clone(),
&editor.config,
prompt,
- editor.history.len());
+ editor.history.len(),
+ editor.custom_bindings.clone());
s.line.bind(UNDOS_NAME, s.changes.clone());
try!(s.refresh_line());
@@ -1140,6 +1145,7 @@
completer: Option<C>,
kill_ring: Rc<RefCell<KillRing>>,
config: Config,
+ custom_bindings: Rc<RefCell<HashMap<KeyPress, Cmd>>>,
}
impl<C: Completer> Editor<C> {
@@ -1155,6 +1161,7 @@
completer: None,
kill_ring: Rc::new(RefCell::new(KillRing::new(60))),
config: config,
+ custom_bindings: Rc::new(RefCell::new(HashMap::new())),
}
}
@@ -1202,6 +1209,15 @@
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("> ") {
@@ -1263,6 +1279,7 @@
#[cfg(test)]
mod test {
use std::cell::RefCell;
+ use std::collections::HashMap;
use std::io::Write;
use std::rc::Rc;
use line_buffer::LineBuffer;
@@ -1294,7 +1311,7 @@
snapshot: LineBuffer::with_capacity(100),
term: term,
byte_buffer: [0; 4],
- edit_state: EditState::new(&config),
+ edit_state: EditState::new(&config, Rc::new(RefCell::new(HashMap::new()))),
changes: Rc::new(RefCell::new(Changeset::new())),
}
}