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