From b42683c6c81556ad00bb081376dc215fd10613bf Mon Sep 17 00:00:00 2001 From: gwenn <gtreguier@gmail.com> Date: Wed, 28 Dec 2016 21:29:48 +0100 Subject: [PATCH] Partial support to repeated commands --- src/keymap.rs | 217 +++++++++++++++++++++++++++++++++------------ src/lib.rs | 106 +++++++++++----------- src/line_buffer.rs | 154 +++++++++++++++++++------------- 3 files changed, 307 insertions(+), 170 deletions(-) diff --git a/src/keymap.rs b/src/keymap.rs index 8909e7d3..a59313a5 100644 --- a/src/keymap.rs +++ b/src/keymap.rs @@ -8,32 +8,32 @@ use super::Result; pub enum Cmd { Abort, // Miscellaneous Command AcceptLine, - BackwardChar(i32), - BackwardDeleteChar(i32), - BackwardKillWord(i32, Word), // Backward until start of word - BackwardWord(i32, Word), // Backward until start of word + BackwardChar(u16), + BackwardDeleteChar(u16), + BackwardKillWord(u16, Word), // Backward until start of word + BackwardWord(u16, Word), // Backward until start of word BeginningOfHistory, BeginningOfLine, CapitalizeWord, ClearScreen, Complete, - DeleteChar(i32), + DeleteChar(u16), DowncaseWord, EndOfFile, EndOfHistory, EndOfLine, - ForwardChar(i32), + ForwardChar(u16), ForwardSearchHistory, - ForwardWord(i32, At, Word), // Forward until start/end of word + ForwardWord(u16, At, Word), // Forward until start/end of word Interrupt, KillLine, KillWholeLine, - KillWord(i32, At, Word), // Forward until start/end of word + KillWord(u16, At, Word), // Forward until start/end of word NextHistory, Noop, PreviousHistory, QuotedInsert, - Replace(i32, char), // TODO DeleteChar + SelfInsert + Replace(u16, char), // TODO DeleteChar + SelfInsert ReverseSearchHistory, SelfInsert(char), Suspend, @@ -43,9 +43,9 @@ pub enum Cmd { UnixLikeDiscard, // UnixWordRubout, // = BackwardKillWord(Word::Big) UpcaseWord, - ViCharSearch(i32, CharSearch), - ViDeleteTo(i32, CharSearch), - Yank(i32, Anchor), + ViCharSearch(u16, CharSearch), + ViDeleteTo(u16, CharSearch), + Yank(u16, Anchor), YankPop, } @@ -86,7 +86,7 @@ pub struct EditState { // Vi Command/Alternate, Insert/Input mode insert: bool, // vi only ? // numeric arguments: http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC7 - num_args: i32, + num_args: i16, } impl EditState { @@ -117,7 +117,7 @@ impl EditState { -> Result<KeyPress> { match digit { '0'...'9' => { - self.num_args = digit.to_digit(10).unwrap() as i32; + self.num_args = digit.to_digit(10).unwrap() as i16; } '-' => { self.num_args = -1; @@ -129,7 +129,7 @@ impl EditState { match key { KeyPress::Char(digit @ '0'...'9') | KeyPress::Meta(digit @ '0'...'9') => { - self.num_args = self.num_args * 10 + digit.to_digit(10).unwrap() as i32; + self.num_args = self.num_args * 10 + digit.to_digit(10).unwrap() as i16; } _ => return Ok(key), }; @@ -146,26 +146,75 @@ impl EditState { let cmd = match key { KeyPress::Char(c) => Cmd::SelfInsert(c), KeyPress::Ctrl('A') => Cmd::BeginningOfLine, - KeyPress::Ctrl('B') => Cmd::BackwardChar(self.num_args()), + KeyPress::Ctrl('B') => { + let (count, positive) = self.emacs_num_args(); + if positive { + Cmd::BackwardChar(count) + } else { + Cmd::ForwardChar(count) + } + } KeyPress::Ctrl('E') => Cmd::EndOfLine, - KeyPress::Ctrl('F') => Cmd::ForwardChar(self.num_args()), + KeyPress::Ctrl('F') => { + let (count, positive) = self.emacs_num_args(); + if positive { + Cmd::ForwardChar(count) + } else { + Cmd::BackwardChar(count) + } + } KeyPress::Ctrl('G') | KeyPress::Esc => Cmd::Abort, KeyPress::Ctrl('H') | - KeyPress::Backspace => Cmd::BackwardDeleteChar(self.num_args()), + KeyPress::Backspace => { + let (count, positive) = self.emacs_num_args(); + if positive { + Cmd::BackwardDeleteChar(count) + } else { + Cmd::DeleteChar(count) + } + } KeyPress::Tab => Cmd::Complete, KeyPress::Ctrl('K') => Cmd::KillLine, KeyPress::Ctrl('L') => Cmd::ClearScreen, KeyPress::Ctrl('N') => Cmd::NextHistory, KeyPress::Ctrl('P') => Cmd::PreviousHistory, KeyPress::Meta('\x08') | - KeyPress::Meta('\x7f') => Cmd::BackwardKillWord(self.num_args(), Word::Emacs), + KeyPress::Meta('\x7f') => { + let (count, positive) = self.emacs_num_args(); + if positive { + Cmd::BackwardKillWord(count, Word::Emacs) + } else { + Cmd::KillWord(count, At::End, Word::Emacs) + } + } KeyPress::Meta('<') => Cmd::BeginningOfHistory, KeyPress::Meta('>') => Cmd::EndOfHistory, - KeyPress::Meta('B') => Cmd::BackwardWord(self.num_args(), Word::Emacs), + KeyPress::Meta('B') => { + let (count, positive) = self.emacs_num_args(); + if positive { + Cmd::BackwardWord(count, Word::Emacs) + } else { + Cmd::ForwardWord(count, At::End, Word::Emacs) + } + } KeyPress::Meta('C') => Cmd::CapitalizeWord, - KeyPress::Meta('D') => Cmd::KillWord(self.num_args(), At::End, Word::Emacs), - KeyPress::Meta('F') => Cmd::ForwardWord(self.num_args(), At::End, Word::Emacs), + KeyPress::Meta('D') => { + let (count, positive) = self.emacs_num_args(); + if positive { + Cmd::KillWord(count, At::End, Word::Emacs) + } else { + Cmd::BackwardKillWord(count, Word::Emacs) + } + } + KeyPress::Meta('F') => { + let (count, positive) = self.emacs_num_args(); + if positive { + Cmd::ForwardWord(count, At::End, Word::Emacs) + } else { + Cmd::BackwardWord(count, Word::Emacs) + } + } KeyPress::Meta('L') => Cmd::DowncaseWord, KeyPress::Meta('T') => Cmd::TransposeWords, KeyPress::Meta('U') => Cmd::UpcaseWord, @@ -180,12 +229,12 @@ impl EditState { config: &Config, digit: char) -> Result<KeyPress> { - self.num_args = digit.to_digit(10).unwrap() as i32; + self.num_args = digit.to_digit(10).unwrap() as i16; loop { let key = try!(rdr.next_key(config.keyseq_timeout())); match key { KeyPress::Char(digit @ '0'...'9') => { - self.num_args = self.num_args * 10 + digit.to_digit(10).unwrap() as i32; + self.num_args = self.num_args * 10 + digit.to_digit(10).unwrap() as i16; } _ => return Ok(key), }; @@ -206,15 +255,15 @@ impl EditState { KeyPress::Char('a') => { // vi-append-mode: Vi enter insert mode after the cursor. self.insert = true; - Cmd::ForwardChar(self.num_args()) + Cmd::ForwardChar(self.vi_num_args()) } KeyPress::Char('A') => { // vi-append-eol: Vi enter insert mode at end of line. self.insert = true; Cmd::EndOfLine } - KeyPress::Char('b') => Cmd::BackwardWord(self.num_args(), Word::Vi), // vi-prev-word - KeyPress::Char('B') => Cmd::BackwardWord(self.num_args(), Word::Big), + KeyPress::Char('b') => Cmd::BackwardWord(self.vi_num_args(), Word::Vi), // vi-prev-word + KeyPress::Char('B') => Cmd::BackwardWord(self.vi_num_args(), Word::Big), KeyPress::Char('c') => { self.insert = true; try!(self.vi_delete_motion(rdr, config, key)) @@ -226,8 +275,8 @@ impl EditState { KeyPress::Char('d') => try!(self.vi_delete_motion(rdr, config, key)), KeyPress::Char('D') | KeyPress::Ctrl('K') => Cmd::KillLine, - KeyPress::Char('e') => Cmd::ForwardWord(self.num_args(), At::End, Word::Vi), - KeyPress::Char('E') => Cmd::ForwardWord(self.num_args(), At::End, Word::Big), + KeyPress::Char('e') => Cmd::ForwardWord(self.vi_num_args(), At::End, Word::Vi), + KeyPress::Char('E') => Cmd::ForwardWord(self.vi_num_args(), At::End, Word::Big), KeyPress::Char('i') => { // vi-insertion-mode self.insert = true; @@ -242,18 +291,18 @@ impl EditState { // vi-char-search let cs = try!(self.vi_char_search(rdr, config, c)); match cs { - Some(cs) => Cmd::ViCharSearch(self.num_args(), cs), + Some(cs) => Cmd::ViCharSearch(self.vi_num_args(), cs), None => Cmd::Unknown, } } // TODO KeyPress::Char('G') => Cmd::???, Move to the history line n - KeyPress::Char('p') => Cmd::Yank(self.num_args(), Anchor::After), // vi-put - KeyPress::Char('P') => Cmd::Yank(self.num_args(), Anchor::Before), // vi-put + KeyPress::Char('p') => Cmd::Yank(self.vi_num_args(), Anchor::After), // vi-put, FIXME cursor position + KeyPress::Char('P') => Cmd::Yank(self.vi_num_args(), Anchor::Before), // vi-put, FIXME cursor position KeyPress::Char('r') => { // vi-replace-char: Vi replace character under the cursor with the next character typed. let ch = try!(rdr.next_key(config.keyseq_timeout())); match ch { - KeyPress::Char(c) => Cmd::Replace(self.num_args(), c), + KeyPress::Char(c) => Cmd::Replace(self.vi_num_args(), c), KeyPress::Esc => Cmd::Noop, _ => Cmd::Unknown, } @@ -262,7 +311,7 @@ impl EditState { KeyPress::Char('s') => { // vi-substitute-char: Vi replace character under the cursor and enter insert mode. self.insert = true; - Cmd::DeleteChar(self.num_args()) + Cmd::DeleteChar(self.vi_num_args()) } KeyPress::Char('S') => { // vi-substitute-line: Vi substitute entire line. @@ -270,18 +319,18 @@ impl EditState { Cmd::KillWholeLine } // KeyPress::Char('U') => Cmd::???, // revert-line - KeyPress::Char('w') => Cmd::ForwardWord(self.num_args(), At::Start, Word::Vi), // vi-next-word - KeyPress::Char('W') => Cmd::ForwardWord(self.num_args(), At::Start, Word::Big), // vi-next-word - KeyPress::Char('x') => Cmd::DeleteChar(self.num_args()), // vi-delete: TODO move backward if eol - KeyPress::Char('X') => Cmd::BackwardDeleteChar(self.num_args()), // vi-rubout + KeyPress::Char('w') => Cmd::ForwardWord(self.vi_num_args(), At::Start, Word::Vi), // vi-next-word + KeyPress::Char('W') => Cmd::ForwardWord(self.vi_num_args(), At::Start, Word::Big), // vi-next-word + KeyPress::Char('x') => Cmd::DeleteChar(self.vi_num_args()), // vi-delete: TODO move backward if eol + KeyPress::Char('X') => Cmd::BackwardDeleteChar(self.vi_num_args()), // vi-rubout // KeyPress::Char('y') => Cmd::???, // vi-yank-to // KeyPress::Char('Y') => Cmd::???, // vi-yank-to KeyPress::Char('h') | KeyPress::Ctrl('H') | - KeyPress::Backspace => Cmd::BackwardChar(self.num_args()), // TODO Validate + KeyPress::Backspace => Cmd::BackwardChar(self.vi_num_args()), // TODO Validate KeyPress::Ctrl('G') => Cmd::Abort, KeyPress::Char('l') | - KeyPress::Char(' ') => Cmd::ForwardChar(self.num_args()), + KeyPress::Char(' ') => Cmd::ForwardChar(self.vi_num_args()), KeyPress::Ctrl('L') => Cmd::ClearScreen, KeyPress::Char('+') | KeyPress::Char('j') | @@ -336,24 +385,24 @@ impl EditState { Ok(match mvt { KeyPress::Char('$') => Cmd::KillLine, // vi-change-to-eol: Vi change to end of line. KeyPress::Char('0') => Cmd::UnixLikeDiscard, // vi-kill-line-prev: Vi cut from beginning of line to cursor. - KeyPress::Char('b') => Cmd::BackwardKillWord(self.num_args(), Word::Vi), - KeyPress::Char('B') => Cmd::BackwardKillWord(self.num_args(), Word::Big), - KeyPress::Char('e') => Cmd::KillWord(self.num_args(), At::End, Word::Vi), - KeyPress::Char('E') => Cmd::KillWord(self.num_args(), At::End, Word::Big), + KeyPress::Char('b') => Cmd::BackwardKillWord(self.vi_num_args(), Word::Vi), + KeyPress::Char('B') => Cmd::BackwardKillWord(self.vi_num_args(), Word::Big), + KeyPress::Char('e') => Cmd::KillWord(self.vi_num_args(), At::End, Word::Vi), + KeyPress::Char('E') => Cmd::KillWord(self.vi_num_args(), At::End, Word::Big), KeyPress::Char(c) if c == 'f' || c == 'F' || c == 't' || c == 'T' => { let cs = try!(self.vi_char_search(rdr, config, c)); match cs { - Some(cs) => Cmd::ViDeleteTo(self.num_args(), cs), + Some(cs) => Cmd::ViDeleteTo(self.vi_num_args(), cs), None => Cmd::Unknown, } } KeyPress::Char('h') | KeyPress::Ctrl('H') | - KeyPress::Backspace => Cmd::BackwardDeleteChar(self.num_args()), // vi-delete-prev-char: Vi move to previous character (backspace). + KeyPress::Backspace => Cmd::BackwardDeleteChar(self.vi_num_args()), // vi-delete-prev-char: Vi move to previous character (backspace). KeyPress::Char('l') | - KeyPress::Char(' ') => Cmd::DeleteChar(self.num_args()), - KeyPress::Char('w') => Cmd::KillWord(self.num_args(), At::Start, Word::Vi), - KeyPress::Char('W') => Cmd::KillWord(self.num_args(), At::Start, Word::Big), + KeyPress::Char(' ') => Cmd::DeleteChar(self.vi_num_args()), + KeyPress::Char('w') => Cmd::KillWord(self.vi_num_args(), At::Start, Word::Vi), + KeyPress::Char('W') => Cmd::KillWord(self.vi_num_args(), At::Start, Word::Big), _ => Cmd::Unknown, }) } @@ -381,12 +430,33 @@ impl EditState { fn common(&mut self, key: KeyPress) -> Cmd { match key { KeyPress::Home => Cmd::BeginningOfLine, - KeyPress::Left => Cmd::BackwardChar(self.num_args()), + KeyPress::Left => { + let (count, positive) = self.emacs_num_args(); + if positive { + Cmd::BackwardChar(count) + } else { + Cmd::ForwardChar(count) + } + } KeyPress::Ctrl('C') => Cmd::Interrupt, KeyPress::Ctrl('D') => Cmd::EndOfFile, - KeyPress::Delete => Cmd::DeleteChar(self.num_args()), + KeyPress::Delete => { + let (count, positive) = self.emacs_num_args(); + if positive { + Cmd::DeleteChar(count) + } else { + Cmd::BackwardDeleteChar(count) + } + } KeyPress::End => Cmd::EndOfLine, - KeyPress::Right => Cmd::ForwardChar(self.num_args()), + KeyPress::Right => { + let (count, positive) = self.emacs_num_args(); + if positive { + Cmd::ForwardChar(count) + } else { + Cmd::BackwardChar(count) + } + } KeyPress::Ctrl('J') | KeyPress::Enter => Cmd::AcceptLine, KeyPress::Down => Cmd::NextHistory, @@ -397,15 +467,28 @@ impl EditState { KeyPress::Ctrl('U') => Cmd::UnixLikeDiscard, KeyPress::Ctrl('Q') | // most terminals override Ctrl+Q to resume execution KeyPress::Ctrl('V') => Cmd::QuotedInsert, - KeyPress::Ctrl('W') => Cmd::BackwardKillWord(self.num_args(), Word::Big), - KeyPress::Ctrl('Y') => Cmd::Yank(self.num_args(), Anchor::Before), + KeyPress::Ctrl('W') => { + let (count, positive) = self.emacs_num_args(); + if positive { + Cmd::BackwardKillWord(count, Word::Big) + } else { + Cmd::KillWord(count, At::End, Word::Big) + } + } + KeyPress::Ctrl('Y') => { + let (count, positive) = self.emacs_num_args(); + if positive { + Cmd::Yank(count, Anchor::Before) + } else { + Cmd::Unknown // TODO Validate + } + } KeyPress::Ctrl('Z') => Cmd::Suspend, KeyPress::UnknownEscSeq => Cmd::Noop, _ => Cmd::Unknown, } } - - fn num_args(&mut self) -> i32 { + fn num_args(&mut self) -> i16 { let num_args = match self.num_args { 0 => 1, _ => self.num_args, @@ -413,4 +496,26 @@ impl EditState { self.num_args = 0; num_args } + + fn emacs_num_args(&mut self) -> (u16, bool) { + let num_args = self.num_args(); + if num_args < 0 { + if let (count, false) = num_args.overflowing_abs() { + (count as u16, false) + } else { + (u16::max_value(), false) + } + } else { + (num_args as u16, true) + } + } + + fn vi_num_args(&mut self) -> u16 { + let num_args = self.num_args(); + if num_args < 0 { + unreachable!() + } else { + num_args.abs() as u16 + } + } } diff --git a/src/lib.rs b/src/lib.rs index 1362e886..6b20479b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -306,7 +306,7 @@ fn calculate_position(s: &str, orig: Position, cols: usize) -> Position { } /// Insert the character `ch` at cursor current position. -fn edit_insert(s: &mut State, ch: char) -> Result<()> { +fn edit_insert(s: &mut State, ch: char, count: u16) -> Result<()> { if let Some(push) = s.line.insert(ch) { if push { if s.cursor.col + unicode_width::UnicodeWidthChar::width(ch).unwrap_or(0) < s.cols { @@ -328,8 +328,8 @@ fn edit_insert(s: &mut State, ch: char) -> Result<()> { } // Yank/paste `text` at current position. -fn edit_yank(s: &mut State, text: &str, anchor: Anchor) -> Result<()> { - if s.line.yank(text, anchor).is_some() { +fn edit_yank(s: &mut State, text: &str, anchor: Anchor, count: u16) -> Result<()> { + if s.line.yank(text, anchor, count).is_some() { s.refresh_line() } else { Ok(()) @@ -339,12 +339,12 @@ fn edit_yank(s: &mut State, text: &str, anchor: Anchor) -> Result<()> { // Delete previously yanked text and yank/paste `text` at current position. fn edit_yank_pop(s: &mut State, yank_size: usize, text: &str) -> Result<()> { s.line.yank_pop(yank_size, text); - edit_yank(s, text, Anchor::Before) + edit_yank(s, text, Anchor::Before, 1) } /// Move cursor on the left. -fn edit_move_left(s: &mut State) -> Result<()> { - if s.line.move_left() { +fn edit_move_left(s: &mut State, count: u16) -> Result<()> { + if s.line.move_left(count) { s.refresh_line() } else { Ok(()) @@ -352,8 +352,8 @@ fn edit_move_left(s: &mut State) -> Result<()> { } /// Move cursor on the right. -fn edit_move_right(s: &mut State) -> Result<()> { - if s.line.move_right() { +fn edit_move_right(s: &mut State, count: u16) -> Result<()> { + if s.line.move_right(count) { s.refresh_line() } else { Ok(()) @@ -380,8 +380,8 @@ fn edit_move_end(s: &mut State) -> Result<()> { /// Delete the character at the right of the cursor without altering the cursor /// position. Basically this is what happens with the "Delete" keyboard key. -fn edit_delete(s: &mut State) -> Result<()> { - if s.line.delete() { +fn edit_delete(s: &mut State, count: u16) -> Result<()> { + if s.line.delete(count) { s.refresh_line() } else { Ok(()) @@ -389,8 +389,8 @@ fn edit_delete(s: &mut State) -> Result<()> { } /// Backspace implementation. -fn edit_backspace(s: &mut State) -> Result<()> { - if s.line.backspace() { +fn edit_backspace(s: &mut State, count: u16) -> Result<()> { + if s.line.backspace(count) { s.refresh_line() } else { Ok(()) @@ -426,8 +426,8 @@ fn edit_transpose_chars(s: &mut State) -> Result<()> { } } -fn edit_move_to_prev_word(s: &mut State, word_def: Word) -> Result<()> { - if s.line.move_to_prev_word(word_def) { +fn edit_move_to_prev_word(s: &mut State, word_def: Word, count: u16) -> Result<()> { + if s.line.move_to_prev_word(word_def, count) { s.refresh_line() } else { Ok(()) @@ -436,8 +436,8 @@ fn edit_move_to_prev_word(s: &mut State, word_def: Word) -> Result<()> { /// Delete the previous word, maintaining the cursor at the start of the /// current word. -fn edit_delete_prev_word(s: &mut State, word_def: Word) -> Result<Option<String>> { - if let Some(text) = s.line.delete_prev_word(word_def) { +fn edit_delete_prev_word(s: &mut State, word_def: Word, count: u16) -> Result<Option<String>> { + if let Some(text) = s.line.delete_prev_word(word_def, count) { try!(s.refresh_line()); Ok(Some(text)) } else { @@ -445,16 +445,16 @@ fn edit_delete_prev_word(s: &mut State, word_def: Word) -> Result<Option<String> } } -fn edit_move_to_next_word(s: &mut State, at: At, word_def: Word) -> Result<()> { - if s.line.move_to_next_word(at, word_def) { +fn edit_move_to_next_word(s: &mut State, at: At, word_def: Word, count: u16) -> Result<()> { + if s.line.move_to_next_word(at, word_def, count) { s.refresh_line() } else { Ok(()) } } -fn edit_move_to(s: &mut State, cs: CharSearch) -> Result<()> { - if s.line.move_to(cs) { +fn edit_move_to(s: &mut State, cs: CharSearch, count: u16) -> Result<()> { + if s.line.move_to(cs, count) { s.refresh_line() } else { Ok(()) @@ -462,8 +462,8 @@ fn edit_move_to(s: &mut State, cs: CharSearch) -> Result<()> { } /// Kill from the cursor to the end of the current word, or, if between words, to the end of the next word. -fn edit_delete_word(s: &mut State, at: At, word_def: Word) -> Result<Option<String>> { - if let Some(text) = s.line.delete_word(at, word_def) { +fn edit_delete_word(s: &mut State, at: At, word_def: Word, count: u16) -> Result<Option<String>> { + if let Some(text) = s.line.delete_word(at, word_def, count) { try!(s.refresh_line()); Ok(Some(text)) } else { @@ -471,8 +471,8 @@ fn edit_delete_word(s: &mut State, at: At, word_def: Word) -> Result<Option<Stri } } -fn edit_delete_to(s: &mut State, cs: CharSearch) -> Result<Option<String>> { - if let Some(text) = s.line.delete_to(cs) { +fn edit_delete_to(s: &mut State, cs: CharSearch, count: u16) -> Result<Option<String>> { + if let Some(text) = s.line.delete_to(cs, count) { try!(s.refresh_line()); Ok(Some(text)) } else { @@ -842,7 +842,7 @@ fn readline_edit<C: Completer>(prompt: &str, if let Cmd::SelfInsert(c) = cmd { editor.kill_ring.reset(); - try!(edit_insert(&mut s, c)); + try!(edit_insert(&mut s, c, 1)); continue; } @@ -863,28 +863,28 @@ fn readline_edit<C: Completer>(prompt: &str, // Move to the beginning of line. try!(edit_move_home(&mut s)) } - Cmd::BackwardChar(_) => { + Cmd::BackwardChar(count) => { editor.kill_ring.reset(); // Move back a character. - try!(edit_move_left(&mut s)) + try!(edit_move_left(&mut s, count)) } - Cmd::DeleteChar(_) => { + Cmd::DeleteChar(count) => { editor.kill_ring.reset(); // Delete (forward) one character at point. - try!(edit_delete(&mut s)) + try!(edit_delete(&mut s, count)) } - Cmd::Replace(_, c) => { + Cmd::Replace(count, c) => { editor.kill_ring.reset(); - try!(edit_delete(&mut s)); - try!(edit_insert(&mut s, c)); - try!(edit_move_left(&mut s)) + try!(edit_delete(&mut s, count)); + try!(edit_insert(&mut s, c, count)); + try!(edit_move_left(&mut s, 1)) } Cmd::EndOfFile => { editor.kill_ring.reset(); if !s.edit_state.is_emacs_mode() || s.line.is_empty() { return Err(error::ReadlineError::Eof); } else { - try!(edit_delete(&mut s)) + try!(edit_delete(&mut s, 1)) } } Cmd::EndOfLine => { @@ -892,15 +892,15 @@ fn readline_edit<C: Completer>(prompt: &str, // Move to the end of line. try!(edit_move_end(&mut s)) } - Cmd::ForwardChar(_) => { + Cmd::ForwardChar(count) => { editor.kill_ring.reset(); // Move forward a character. - try!(edit_move_right(&mut s)) + try!(edit_move_right(&mut s, count)) } - Cmd::BackwardDeleteChar(_) => { + Cmd::BackwardDeleteChar(count) => { editor.kill_ring.reset(); // Delete one character backward. - try!(edit_backspace(&mut s)) + try!(edit_backspace(&mut s, count)) } Cmd::KillLine => { // Kill the text from point to the end of the line. @@ -945,12 +945,12 @@ fn readline_edit<C: Completer>(prompt: &str, // Quoted insert editor.kill_ring.reset(); let c = try!(rdr.next_char()); - try!(edit_insert(&mut s, c)) // FIXME + try!(edit_insert(&mut s, c, 1)) // FIXME } - Cmd::Yank(_, anchor) => { + Cmd::Yank(count, anchor) => { // retrieve (yank) last item killed if let Some(text) = editor.kill_ring.yank() { - try!(edit_yank(&mut s, text, anchor)) + try!(edit_yank(&mut s, text, anchor, count)) } } // TODO CTRL-_ // undo @@ -960,9 +960,9 @@ fn readline_edit<C: Completer>(prompt: &str, try!(edit_move_end(&mut s)); break; } - Cmd::BackwardKillWord(_, word_def) => { + Cmd::BackwardKillWord(count, word_def) => { // kill one word backward - if let Some(text) = try!(edit_delete_prev_word(&mut s, word_def)) { + if let Some(text) = try!(edit_delete_prev_word(&mut s, word_def, count)) { editor.kill_ring.kill(&text, Mode::Prepend) } } @@ -976,26 +976,26 @@ fn readline_edit<C: Completer>(prompt: &str, editor.kill_ring.reset(); try!(edit_history(&mut s, &editor.history, false)) } - Cmd::BackwardWord(_, word_def) => { + Cmd::BackwardWord(count, word_def) => { // move backwards one word editor.kill_ring.reset(); - try!(edit_move_to_prev_word(&mut s, word_def)) + try!(edit_move_to_prev_word(&mut s, word_def, count)) } Cmd::CapitalizeWord => { // capitalize word after point editor.kill_ring.reset(); try!(edit_word(&mut s, WordAction::CAPITALIZE)) } - Cmd::KillWord(_, at, word_def) => { + Cmd::KillWord(count, at, word_def) => { // kill one word forward - if let Some(text) = try!(edit_delete_word(&mut s, at, word_def)) { + if let Some(text) = try!(edit_delete_word(&mut s, at, word_def, count)) { editor.kill_ring.kill(&text, Mode::Append) } } - Cmd::ForwardWord(_, at, word_def) => { + Cmd::ForwardWord(count, at, word_def) => { // move forwards one word editor.kill_ring.reset(); - try!(edit_move_to_next_word(&mut s, at, word_def)) + try!(edit_move_to_next_word(&mut s, at, word_def, count)) } Cmd::DowncaseWord => { // lowercase word after point @@ -1018,12 +1018,12 @@ fn readline_edit<C: Completer>(prompt: &str, try!(edit_yank_pop(&mut s, yank_size, text)) } } - Cmd::ViCharSearch(_, cs) => { + Cmd::ViCharSearch(count, cs) => { editor.kill_ring.reset(); - try!(edit_move_to(&mut s, cs)) + try!(edit_move_to(&mut s, cs, count)) } - Cmd::ViDeleteTo(_, cs) => { - if let Some(text) = try!(edit_delete_to(&mut s, cs)) { + Cmd::ViDeleteTo(count, cs) => { + if let Some(text) = try!(edit_delete_to(&mut s, cs, count)) { editor.kill_ring.kill(&text, Mode::Append) } } diff --git a/src/line_buffer.rs b/src/line_buffer.rs index aa81550f..47bd13c5 100644 --- a/src/line_buffer.rs +++ b/src/line_buffer.rs @@ -126,13 +126,13 @@ impl LineBuffer { /// Yank/paste `text` at current position. /// Return `None` when maximum buffer size has been reached, /// `true` when the character has been appended to the end of the line. - pub fn yank(&mut self, text: &str, anchor: Anchor) -> Option<bool> { + pub fn yank(&mut self, text: &str, anchor: Anchor, count: u16) -> Option<bool> { let shift = text.len(); if text.is_empty() || (self.buf.len() + shift) > self.buf.capacity() { return None; } if let Anchor::After = anchor { - self.move_right(); + self.move_right(1); } let pos = self.pos; let push = self.insert_str(pos, text); @@ -144,27 +144,35 @@ impl LineBuffer { pub fn yank_pop(&mut self, yank_size: usize, text: &str) -> Option<bool> { self.buf.drain((self.pos - yank_size)..self.pos); self.pos -= yank_size; - self.yank(text, Anchor::Before) + self.yank(text, Anchor::Before, 1) } /// Move cursor on the left. - pub fn move_left(&mut self) -> bool { - if let Some(ch) = self.char_before_cursor() { - self.pos -= ch.len_utf8(); - true - } else { - false + pub fn move_left(&mut self, count: u16) -> bool { + let mut moved = false; + for _ in 0..count { + if let Some(ch) = self.char_before_cursor() { + self.pos -= ch.len_utf8(); + moved = true + } else { + break; + } } + moved } /// Move cursor on the right. - pub fn move_right(&mut self) -> bool { - if let Some(ch) = self.char_at_cursor() { - self.pos += ch.len_utf8(); - true - } else { - false + pub fn move_right(&mut self, count: u16) -> bool { + let mut moved = false; + for _ in 0..count { + if let Some(ch) = self.char_at_cursor() { + self.pos += ch.len_utf8(); + moved = true + } else { + break; + } } + moved } /// Move cursor to the start of the line. @@ -189,30 +197,42 @@ impl LineBuffer { /// Replace a single character under the cursor (Vi mode) pub fn replace_char(&mut self, ch: char) -> Option<bool> { - if self.delete() { self.insert(ch) } else { None } + if self.delete(1) { + self.insert(ch) + } else { + None + } } /// Delete the character at the right of the cursor without altering the cursor /// position. Basically this is what happens with the "Delete" keyboard key. - pub fn delete(&mut self) -> bool { - if !self.buf.is_empty() && self.pos < self.buf.len() { - self.buf.remove(self.pos); - true - } else { - false + pub fn delete(&mut self, count: u16) -> bool { + let mut deleted = false; + for _ in 0..count { + if !self.buf.is_empty() && self.pos < self.buf.len() { + self.buf.remove(self.pos); + deleted = true + } else { + break; + } } + deleted } /// Delete the character at the left of the cursor. /// Basically that is what happens with the "Backspace" keyboard key. - pub fn backspace(&mut self) -> bool { - if let Some(ch) = self.char_before_cursor() { - self.pos -= ch.len_utf8(); - self.buf.remove(self.pos); - true - } else { - false + pub fn backspace(&mut self, count: u16) -> bool { + let mut deleted = false; + for _ in 0..count { + if let Some(ch) = self.char_before_cursor() { + self.pos -= ch.len_utf8(); + self.buf.remove(self.pos); + deleted = true + } else { + break; + } } + deleted } /// Kill all characters on the current line. @@ -248,7 +268,7 @@ impl LineBuffer { return false; } if self.pos == self.buf.len() { - self.move_left(); + self.move_left(1); } let ch = self.buf.remove(self.pos); let size = ch.len_utf8(); @@ -292,18 +312,22 @@ impl LineBuffer { } /// Moves the cursor to the beginning of previous word. - pub fn move_to_prev_word(&mut self, word_def: Word) -> bool { - if let Some(pos) = self.prev_word_pos(self.pos, word_def) { - self.pos = pos; - true - } else { - false + pub fn move_to_prev_word(&mut self, word_def: Word, count: u16) -> bool { + let mut moved = false; + for _ in 0..count { + if let Some(pos) = self.prev_word_pos(self.pos, word_def) { + self.pos = pos; + moved = true + } else { + break; + } } + moved } /// Delete the previous word, maintaining the cursor at the start of the /// current word. - pub fn delete_prev_word(&mut self, word_def: Word) -> Option<String> { + pub fn delete_prev_word(&mut self, word_def: Word, count: u16) -> Option<String> { if let Some(pos) = self.prev_word_pos(self.pos, word_def) { let word = self.buf.drain(pos..self.pos).collect(); self.pos = pos; @@ -378,13 +402,17 @@ impl LineBuffer { } /// Moves the cursor to the end of next word. - pub fn move_to_next_word(&mut self, at: At, word_def: Word) -> bool { - if let Some(pos) = self.next_pos(self.pos, at, word_def) { - self.pos = pos; - true - } else { - false + pub fn move_to_next_word(&mut self, at: At, word_def: Word, count: u16) -> bool { + let mut moved = false; + for _ in 0..count { + if let Some(pos) = self.next_pos(self.pos, at, word_def) { + self.pos = pos; + moved = true + } else { + break; + } } + moved } fn search_char_pos(&mut self, cs: &CharSearch) -> Option<usize> { @@ -427,17 +455,21 @@ impl LineBuffer { } } - pub fn move_to(&mut self, cs: CharSearch) -> bool { - if let Some(pos) = self.search_char_pos(&cs) { - self.pos = pos; - true - } else { - false + pub fn move_to(&mut self, cs: CharSearch, count: u16) -> bool { + let mut moved = false; + for _ in 0..count { + if let Some(pos) = self.search_char_pos(&cs) { + self.pos = pos; + moved = true + } else { + break; + } } + moved } /// Kill from the cursor to the end of the current word, or, if between words, to the end of the next word. - pub fn delete_word(&mut self, at: At, word_def: Word) -> Option<String> { + pub fn delete_word(&mut self, at: At, word_def: Word, count: u16) -> Option<String> { if let Some(pos) = self.next_pos(self.pos, at, word_def) { let word = self.buf.drain(self.pos..pos).collect(); Some(word) @@ -446,7 +478,7 @@ impl LineBuffer { } } - pub fn delete_to(&mut self, cs: CharSearch) -> Option<String> { + pub fn delete_to(&mut self, cs: CharSearch, count: u16) -> Option<String> { let search_result = match cs { CharSearch::ForwardBefore(c) => self.search_char_pos(&CharSearch::Forward(c)), _ => self.search_char_pos(&cs), @@ -607,12 +639,12 @@ mod test { #[test] fn moves() { let mut s = LineBuffer::init("αß", 4); - let ok = s.move_left(); + let ok = s.move_left(1); assert_eq!("αß", s.buf); assert_eq!(2, s.pos); assert_eq!(true, ok); - let ok = s.move_right(); + let ok = s.move_right(1); assert_eq!("αß", s.buf); assert_eq!(4, s.pos); assert_eq!(true, ok); @@ -631,12 +663,12 @@ mod test { #[test] fn delete() { let mut s = LineBuffer::init("αß", 2); - let ok = s.delete(); + let ok = s.delete(1); assert_eq!("α", s.buf); assert_eq!(2, s.pos); assert_eq!(true, ok); - let ok = s.backspace(); + let ok = s.backspace(1); assert_eq!("", s.buf); assert_eq!(0, s.pos); assert_eq!(true, ok); @@ -683,7 +715,7 @@ mod test { #[test] fn move_to_prev_word() { let mut s = LineBuffer::init("a ß c", 6); - let ok = s.move_to_prev_word(Word::Emacs); + let ok = s.move_to_prev_word(Word::Emacs, 1); assert_eq!("a ß c", s.buf); assert_eq!(2, s.pos); assert_eq!(true, ok); @@ -692,7 +724,7 @@ mod test { #[test] fn delete_prev_word() { let mut s = LineBuffer::init("a ß c", 6); - let text = s.delete_prev_word(Word::Big); + let text = s.delete_prev_word(Word::Big, 1); assert_eq!("a c", s.buf); assert_eq!(2, s.pos); assert_eq!(Some("ß ".to_string()), text); @@ -701,7 +733,7 @@ mod test { #[test] fn move_to_next_word() { let mut s = LineBuffer::init("a ß c", 1); - let ok = s.move_to_next_word(At::End, Word::Emacs); + let ok = s.move_to_next_word(At::End, Word::Emacs, 1); assert_eq!("a ß c", s.buf); assert_eq!(4, s.pos); assert_eq!(true, ok); @@ -710,7 +742,7 @@ mod test { #[test] fn move_to_start_of_word() { let mut s = LineBuffer::init("a ß c", 2); - let ok = s.move_to_next_word(At::Start, Word::Emacs); + let ok = s.move_to_next_word(At::Start, Word::Emacs, 1); assert_eq!("a ß c", s.buf); assert_eq!(6, s.pos); assert_eq!(true, ok); @@ -719,7 +751,7 @@ mod test { #[test] fn delete_word() { let mut s = LineBuffer::init("a ß c", 1); - let text = s.delete_word(At::End, Word::Emacs); + let text = s.delete_word(At::End, Word::Emacs, 1); assert_eq!("a c", s.buf); assert_eq!(1, s.pos); assert_eq!(Some(" ß".to_string()), text); @@ -728,7 +760,7 @@ mod test { #[test] fn delete_til_start_of_word() { let mut s = LineBuffer::init("a ß c", 2); - let text = s.delete_word(At::Start, Word::Emacs); + let text = s.delete_word(At::Start, Word::Emacs, 1); assert_eq!("a c", s.buf); assert_eq!(2, s.pos); assert_eq!(Some("ß ".to_string()), text); -- GitLab