From c3c06a1b26ff938f0850680aa1b772751d9373e0 Mon Sep 17 00:00:00 2001 From: gwenn <gtreguier@gmail.com> Date: Sun, 30 Apr 2017 09:38:26 +0200 Subject: [PATCH] Implement overwrite mode --- src/keymap.rs | 57 +++++++++++++------ src/lib.rs | 17 ++++++ src/line_buffer.rs | 2 +- src/undo.rs | 133 +++++++++++++++++++++++---------------------- 4 files changed, 124 insertions(+), 85 deletions(-) diff --git a/src/keymap.rs b/src/keymap.rs index 63e11c92..b950057a 100644 --- a/src/keymap.rs +++ b/src/keymap.rs @@ -51,11 +51,13 @@ pub enum Cmd { /// next-history NextHistory, Noop, + /// vi-replace + Overwrite(char), /// previous-history PreviousHistory, /// quoted-insert QuotedInsert, - /// vi-change-char (TODO: vi-replace) + /// vi-change-char Replace(RepeatCount, char), /// reverse-search-history ReverseSearchHistory, @@ -225,12 +227,21 @@ impl Movement { } } +#[derive(PartialEq)] +enum InputMode { + /// Vi Command/Alternate + Command, + /// Insert/Input mode + Insert, + /// Overwrite mode + Replace, +} + /// Tranform key(s) to commands based on current input mode pub struct EditState { mode: EditMode, custom_bindings: Rc<RefCell<HashMap<KeyPress, Cmd>>>, - // Vi Command/Alternate, Insert/Input mode - insert: bool, // vi only ? + 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 @@ -243,7 +254,7 @@ impl EditState { EditState { mode: config.edit_mode(), custom_bindings: custom_bindings, - insert: true, + input_mode: InputMode::Insert, num_args: 0, last_cmd: Cmd::Noop, consecutive_insert: false, @@ -259,7 +270,7 @@ impl EditState { pub fn next_cmd<R: RawReader>(&mut self, rdr: &mut R) -> Result<Cmd> { match self.mode { EditMode::Emacs => self.emacs(rdr), - EditMode::Vi if self.insert => self.vi_insert(rdr), + EditMode::Vi if self.input_mode != InputMode::Command => self.vi_insert(rdr), EditMode::Vi => self.vi_command(rdr), } } @@ -453,25 +464,25 @@ impl EditState { KeyPress::Char('^') => Cmd::Move(Movement::ViFirstPrint), KeyPress::Char('a') => { // vi-append-mode: Vi enter insert mode after the cursor. - self.insert = true; + self.input_mode = InputMode::Insert; Cmd::Move(Movement::ForwardChar(n)) } KeyPress::Char('A') => { // vi-append-eol: Vi enter insert mode at end of line. - self.insert = true; + self.input_mode = InputMode::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') => { - self.insert = true; + self.input_mode = InputMode::Insert; match try!(self.vi_cmd_motion(rdr, key, n)) { Some(mvt) => Cmd::Kill(mvt), None => Cmd::Unknown, } } KeyPress::Char('C') => { - self.insert = true; + self.input_mode = InputMode::Insert; Cmd::Kill(Movement::EndOfLine) } KeyPress::Char('d') => { @@ -486,12 +497,12 @@ impl EditState { KeyPress::Char('E') => Cmd::Move(Movement::ForwardWord(n, At::BeforeEnd, Word::Big)), KeyPress::Char('i') => { // vi-insertion-mode - self.insert = true; + self.input_mode = InputMode::Insert; Cmd::Noop } KeyPress::Char('I') => { // vi-insert-beg - self.insert = true; + self.input_mode = InputMode::Insert; Cmd::Move(Movement::BeginningOfLine) } KeyPress::Char(c) if c == 'f' || c == 'F' || c == 't' || c == 'T' => { @@ -526,15 +537,19 @@ impl EditState { _ => Cmd::Unknown, } } - // TODO KeyPress::Char('R') => Cmd::???, vi-replace-mode: Vi enter replace mode. Replaces characters under the cursor. (overwrite-mode) + KeyPress::Char('R') => { + // vi-replace-mode: Vi enter replace mode. Replaces characters under the cursor. (overwrite-mode) + self.input_mode = InputMode::Replace; + Cmd::Noop + } KeyPress::Char('s') => { // vi-substitute-char: Vi replace character under the cursor and enter insert mode. - self.insert = true; + self.input_mode = InputMode::Insert; Cmd::Kill(Movement::ForwardChar(n)) } KeyPress::Char('S') => { // vi-substitute-line: Vi substitute entire line. - self.insert = true; + self.input_mode = InputMode::Insert; Cmd::Kill(Movement::WholeLine) } KeyPress::Char('u') => Cmd::Undo, @@ -564,11 +579,11 @@ impl EditState { KeyPress::Char('k') | // TODO: move to the start of the line. KeyPress::Ctrl('P') => Cmd::PreviousHistory, KeyPress::Ctrl('R') => { - self.insert = true; // TODO Validate + self.input_mode = InputMode::Insert; // TODO Validate Cmd::ReverseSearchHistory } KeyPress::Ctrl('S') => { - self.insert = true; // TODO Validate + self.input_mode = InputMode::Insert; // TODO Validate Cmd::ForwardSearchHistory } KeyPress::Esc => Cmd::Noop, @@ -592,13 +607,19 @@ impl EditState { }); } let cmd = match key { - KeyPress::Char(c) => Cmd::SelfInsert(1, c), + KeyPress::Char(c) => { + 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::Tab => Cmd::Complete, KeyPress::Esc => { // vi-movement-mode/vi-command-mode: Vi enter command mode (use alternative key bindings). - self.insert = false; + self.input_mode = InputMode::Command; Cmd::Move(Movement::BackwardChar(1)) } _ => self.common(key, 1, true), diff --git a/src/lib.rs b/src/lib.rs index c298b7ab..788ebebe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -221,6 +221,20 @@ fn edit_replace_char(s: &mut State, ch: char, n: RepeatCount) -> Result<()> { if succeed { s.refresh_line() } else { Ok(()) } } +/// Overwrite the character under the cursor (Vi mode) +fn edit_overwrite_char(s: &mut State, ch: char) -> Result<()> { + if let Some(end) = s.line.next_pos(1) { + { + let text = ch.encode_utf8(&mut s.byte_buffer); + let start = s.line.pos(); + s.line.replace(start..end, text); + } + s.refresh_line() + } else { + Ok(()) + } +} + // Yank/paste `text` at current position. fn edit_yank(s: &mut State, text: &str, anchor: Anchor, n: RepeatCount) -> Result<()> { if let Anchor::After = anchor { @@ -819,6 +833,9 @@ fn readline_edit<C: Completer>(prompt: &str, Cmd::Replace(n, c) => { try!(edit_replace_char(&mut s, c, n)); } + Cmd::Overwrite(c) => { + try!(edit_overwrite_char(&mut s, c)); + } Cmd::EndOfFile => { if !s.edit_state.is_emacs_mode() && !s.line.is_empty() { try!(edit_move_end(&mut s)); diff --git a/src/line_buffer.rs b/src/line_buffer.rs index 2b8f0f63..cf657152 100644 --- a/src/line_buffer.rs +++ b/src/line_buffer.rs @@ -152,7 +152,7 @@ impl LineBuffer { } } - fn next_pos(&self, n: RepeatCount) -> Option<usize> { + pub fn next_pos(&self, n: RepeatCount) -> Option<usize> { if self.pos == self.buf.len() { return None; } diff --git a/src/undo.rs b/src/undo.rs index e2febeb1..21e2e86a 100644 --- a/src/undo.rs +++ b/src/undo.rs @@ -78,6 +78,14 @@ impl Change { false } } + + fn replace_seq(&self, indx: usize) -> bool { + if let Change::Replace { idx, ref new, .. } = *self { + idx + new.len() == indx + } else { + false + } + } } pub struct Changeset { @@ -124,31 +132,18 @@ impl Changeset { pub fn insert(&mut self, idx: usize, c: char) { debug!(target: "rustyline", "Changeset::insert({}, {:?})", idx, c); self.redos.clear(); - if !c.is_alphanumeric() { + if !c.is_alphanumeric() || !self.undos.last().map_or(false, |lc| lc.insert_seq(idx)) { self.undos.push(Self::insert_char(idx, c)); return; } - let last_change = self.undos.pop(); - match last_change { - Some(last_change) => { - // merge consecutive char insertions when char is alphanumeric - if last_change.insert_seq(idx) { - let mut last_change = last_change; - if let Change::Insert { ref mut text, .. } = last_change { - text.push(c); - } else { - unreachable!(); - } - self.undos.push(last_change); - } else { - self.undos.push(last_change); - self.undos.push(Self::insert_char(idx, c)); - } - } - None => { - self.undos.push(Self::insert_char(idx, c)); - } - }; + // merge consecutive char insertions when char is alphanumeric + let mut last_change = self.undos.pop().unwrap(); + if let Change::Insert { ref mut text, .. } = last_change { + text.push(c); + } else { + unreachable!(); + } + self.undos.push(last_change); } pub fn insert_str<S: Into<String> + Debug>(&mut self, idx: usize, string: S) { @@ -165,7 +160,10 @@ impl Changeset { debug!(target: "rustyline", "Changeset::delete({}, {:?})", indx, string); self.redos.clear(); - if !Self::single_char(string.as_ref()) { + if !Self::single_char(string.as_ref()) || + !self.undos + .last() + .map_or(false, |lc| lc.delete_seq(indx, string.as_ref().len())) { self.undos .push(Change::Delete { idx: indx, @@ -173,43 +171,22 @@ impl Changeset { }); return; } - let last_change = self.undos.pop(); - match last_change { - Some(last_change) => { - // merge consecutive char deletions when char is alphanumeric - if last_change.delete_seq(indx, string.as_ref().len()) { - let mut last_change = last_change; - if let Change::Delete { - ref mut idx, - ref mut text, - } = last_change { - if *idx == indx { - text.push_str(string.as_ref()); - } else { - text.insert_str(0, string.as_ref()); - *idx = indx; - } - } else { - unreachable!(); - } - self.undos.push(last_change); - } else { - self.undos.push(last_change); - self.undos - .push(Change::Delete { - idx: indx, - text: string.into(), - }); - } - } - None => { - self.undos - .push(Change::Delete { - idx: indx, - text: string.into(), - }); + // merge consecutive char deletions when char is alphanumeric + let mut last_change = self.undos.pop().unwrap(); + if let Change::Delete { + ref mut idx, + ref mut text, + } = last_change { + if *idx == indx { + text.push_str(string.as_ref()); + } else { + text.insert_str(0, string.as_ref()); + *idx = indx; } - }; + } else { + unreachable!(); + } + self.undos.push(last_change); } fn single_char(s: &str) -> bool { @@ -220,14 +197,38 @@ impl Changeset { graphemes.next().is_none() } - pub fn replace<S: Into<String>>(&mut self, idx: usize, old: S, new: S) { + pub fn replace<S: AsRef<str> + Into<String> + Debug>(&mut self, + indx: usize, + old_: S, + new_: S) { + debug!(target: "rustyline", "Changeset::replace({}, {:?}, {:?})", indx, old_, new_); self.redos.clear(); - self.undos - .push(Change::Replace { - idx: idx, - old: old.into(), - new: new.into(), - }); + + if !self.undos + .last() + .map_or(false, |lc| lc.replace_seq(indx)) { + self.undos + .push(Change::Replace { + idx: indx, + old: old_.into(), + new: new_.into(), + }); + return; + } + + // merge consecutive char replacements + let mut last_change = self.undos.pop().unwrap(); + if let Change::Replace { + ref mut old, + ref mut new, + .. + } = last_change { + old.push_str(old_.as_ref()); + new.push_str(new_.as_ref()); + } else { + unreachable!(); + } + self.undos.push(last_change); } pub fn undo(&mut self, line: &mut LineBuffer) -> bool { -- GitLab