diff --git a/TODO.md b/TODO.md
index 62637d6d56ac195e8f358519abe1bdc5cb476e82..50efdb423c4589cd814741b67bcb030ed94ccbde 100644
--- a/TODO.md
+++ b/TODO.md
@@ -52,7 +52,7 @@ Movement
 - [ ] Move to the corresponding opening/closing bracket
 
 Redo
-- [ ] redo substitue
+- [X] redo substitue
 
 Repeat
 - [x] dynamic prompt (arg: ?)
diff --git a/src/edit.rs b/src/edit.rs
index 71cf81845f2c49e22c82ed708951d9a5f23a2e78..045ed3a062d5aa498360ab69404ed1d59d26b593 100644
--- a/src/edit.rs
+++ b/src/edit.rs
@@ -9,7 +9,7 @@ use unicode_width::UnicodeWidthChar;
 use super::Result;
 use hint::Hinter;
 use history::{Direction, History};
-use keymap::{Anchor, At, CharSearch, Cmd, RepeatCount, Word};
+use keymap::{Anchor, At, CharSearch, Cmd, Movement, RepeatCount, Word};
 use keymap::{InputState, Refresher};
 use line_buffer::{LineBuffer, WordAction, MAX_LINE};
 use tty::{Position, RawReader, Renderer};
@@ -69,6 +69,9 @@ impl<'out, 'prompt> State<'out, 'prompt> {
                 try!(self.refresh_line());
                 continue;
             }
+            if let Ok(Cmd::Replace(_, _)) = rc {
+                self.changes.borrow_mut().begin();
+            }
             return rc;
         }
     }
@@ -136,12 +139,12 @@ impl<'out, 'prompt> Refresher for State<'out, 'prompt> {
     fn doing_insert(&mut self) {
         self.changes.borrow_mut().begin();
     }
-    fn doing_replace(&mut self) {
-        self.changes.borrow_mut().begin();
-    }
     fn done_inserting(&mut self) {
         self.changes.borrow_mut().end();
     }
+    fn last_insert(&self) -> Option<String> {
+        self.changes.borrow().last_insert()
+    }
 }
 
 impl<'out, 'prompt> fmt::Debug for State<'out, 'prompt> {
@@ -291,38 +294,26 @@ impl<'out, 'prompt> State<'out, 'prompt> {
         }
     }
 
-    /// 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 edit_delete(&mut self, n: RepeatCount) -> Result<()> {
-        if self.line.delete(n).is_some() {
-            self.refresh_line()
-        } else {
-            Ok(())
-        }
-    }
-
-    /// Backspace implementation.
-    pub fn edit_backspace(&mut self, n: RepeatCount) -> Result<()> {
-        if self.line.backspace(n) {
+    pub fn edit_kill(&mut self, mvt: &Movement) -> Result<()> {
+        if self.line.kill(mvt) {
             self.refresh_line()
         } else {
             Ok(())
         }
     }
 
-    /// Kill the text from point to the end of the line.
-    pub fn edit_kill_line(&mut self) -> Result<()> {
-        if self.line.kill_line() {
+    pub fn edit_insert_text(&mut self, text: &str) -> Result<()> {
+        if !text.is_empty() {
+            let cursor = self.line.pos();
+            self.line.insert_str(cursor, text);
             self.refresh_line()
         } else {
             Ok(())
         }
     }
 
-    /// Kill backward from point to the beginning of the line.
-    pub fn edit_discard_line(&mut self) -> Result<()> {
-        if self.line.discard_line() {
+    pub fn edit_delete(&mut self, n: RepeatCount) -> Result<()> {
+        if self.line.delete(n).is_some() {
             self.refresh_line()
         } else {
             Ok(())
@@ -349,16 +340,6 @@ impl<'out, 'prompt> State<'out, 'prompt> {
         }
     }
 
-    /// Delete the previous word, maintaining the cursor at the start of the
-    /// current word.
-    pub fn edit_delete_prev_word(&mut self, word_def: Word, n: RepeatCount) -> Result<()> {
-        if self.line.delete_prev_word(word_def, n) {
-            self.refresh_line()
-        } else {
-            Ok(())
-        }
-    }
-
     pub fn edit_move_to_next_word(&mut self, at: At, word_def: Word, n: RepeatCount) -> Result<()> {
         if self.line.move_to_next_word(at, word_def, n) {
             self.move_cursor()
@@ -375,24 +356,6 @@ impl<'out, 'prompt> State<'out, 'prompt> {
         }
     }
 
-    /// Kill from the cursor to the end of the current word, or, if between
-    /// words, to the end of the next word.
-    pub fn edit_delete_word(&mut self, at: At, word_def: Word, n: RepeatCount) -> Result<()> {
-        if self.line.delete_word(at, word_def, n) {
-            self.refresh_line()
-        } else {
-            Ok(())
-        }
-    }
-
-    pub fn edit_delete_to(&mut self, cs: CharSearch, n: RepeatCount) -> Result<()> {
-        if self.line.delete_to(cs, n) {
-            self.refresh_line()
-        } else {
-            Ok(())
-        }
-    }
-
     pub fn edit_word(&mut self, a: WordAction) -> Result<()> {
         self.changes.borrow_mut().begin();
         let succeed = self.line.edit_word(a);
diff --git a/src/keymap.rs b/src/keymap.rs
index 450b511b1ba0bc857ddabcfe298e4e9f1c08f666..9656e3ac9ed2fb797fa660eeef1d3256c26a54ff 100644
--- a/src/keymap.rs
+++ b/src/keymap.rs
@@ -43,7 +43,7 @@ pub enum Cmd {
     Interrupt,
     /// backward-delete-char, backward-kill-line, backward-kill-word
     /// delete-char, kill-line, kill-word, unix-line-discard, unix-word-rubout,
-    /// vi-change-to, vi-delete, vi-delete-to, vi-rubout, vi-subst
+    /// vi-delete, vi-delete-to, vi-rubout
     Kill(Movement),
     /// backward-char, backward-word, beginning-of-line, end-of-line,
     /// forward-char, forward-word, vi-char-search, vi-end-word, vi-next-word,
@@ -59,7 +59,9 @@ pub enum Cmd {
     /// quoted-insert
     QuotedInsert,
     /// vi-change-char
-    Replace(RepeatCount, char),
+    ReplaceChar(RepeatCount, char),
+    /// vi-change-to, vi-substitute
+    Replace(Movement, Option<String>),
     /// reverse-search-history
     ReverseSearchHistory,
     /// self-insert
@@ -88,6 +90,7 @@ impl Cmd {
             Cmd::Kill(Movement::BackwardChar(_)) | Cmd::Kill(Movement::ForwardChar(_)) => true,
             Cmd::ClearScreen
             | Cmd::Kill(_)
+            | Cmd::Replace(_, _)
             | Cmd::Noop
             | Cmd::Suspend
             | Cmd::Yank(_, _)
@@ -100,6 +103,7 @@ impl Cmd {
         match *self {
             Cmd::Insert(_, _)
             | Cmd::Kill(_)
+            | Cmd::ReplaceChar(_, _)
             | Cmd::Replace(_, _)
             | Cmd::SelfInsert(_, _)
             | Cmd::ViYankTo(_)
@@ -115,15 +119,30 @@ impl Cmd {
         }
     }
 
-    fn redo(&self, new: Option<RepeatCount>) -> Cmd {
+    // Replay this command with a possible different `RepeatCount`.
+    fn redo(&self, new: Option<RepeatCount>, wrt: &Refresher) -> Cmd {
         match *self {
             Cmd::Insert(previous, ref text) => {
                 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::ReplaceChar(previous, c) => Cmd::ReplaceChar(repeat_count(previous, new), c),
+            Cmd::Replace(ref mvt, ref text) => {
+                if text.is_none() {
+                    Cmd::Replace(mvt.redo(new), wrt.last_insert())
+                } else {
+                    Cmd::Replace(mvt.redo(new), text.clone())
+                }
+            }
+            Cmd::SelfInsert(previous, c) => {
+                // consecutive char inserts are repeatable not only the last one...
+                if let Some(text) = wrt.last_insert() {
+                    Cmd::Insert(repeat_count(previous, new), text)
+                } else {
+                    Cmd::SelfInsert(repeat_count(previous, new), c)
+                }
+            }
             // Cmd::TransposeChars => Cmd::TransposeChars,
             Cmd::ViYankTo(ref mvt) => Cmd::ViYankTo(mvt.redo(new)),
             Cmd::Yank(previous, anchor) => Cmd::Yank(repeat_count(previous, new), anchor),
@@ -210,6 +229,7 @@ pub enum Movement {
 }
 
 impl Movement {
+    // Replay this movement with a possible different `RepeatCount`.
     fn redo(&self, new: Option<RepeatCount>) -> Movement {
         match *self {
             Movement::WholeLine => Movement::WholeLine,
@@ -248,8 +268,7 @@ pub struct InputState {
     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
-    consecutive_insert: bool,
+    last_cmd: Cmd,                        // vi only
     last_char_search: Option<CharSearch>, // vi only
 }
 
@@ -261,10 +280,10 @@ pub trait Refresher {
     fn refresh_prompt_and_line(&mut self, prompt: &str) -> Result<()>;
     /// Vi only, switch to insert mode.
     fn doing_insert(&mut self);
-    /// Vi only, start replacing text.
-    fn doing_replace(&mut self);
     /// Vi only, switch to command mode.
     fn done_inserting(&mut self);
+    /// Vi only, last text inserted.
+    fn last_insert(&self) -> Option<String>;
 }
 
 impl InputState {
@@ -278,7 +297,6 @@ impl InputState {
             input_mode: InputMode::Insert,
             num_args: 0,
             last_cmd: Cmd::Noop,
-            consecutive_insert: false,
             last_char_search: None,
         }
     }
@@ -359,7 +377,7 @@ impl InputState {
         if let Some(cmd) = self.custom_bindings.borrow().get(&key) {
             debug!(target: "rustyline", "Custom command: {:?}", cmd);
             return Ok(if cmd.is_repeatable() {
-                cmd.redo(Some(n))
+                cmd.redo(Some(n), wrt)
             } else {
                 cmd.clone()
             });
@@ -477,9 +495,9 @@ impl InputState {
             debug!(target: "rustyline", "Custom command: {:?}", cmd);
             return Ok(if cmd.is_repeatable() {
                 if no_num_args {
-                    cmd.redo(None)
+                    cmd.redo(None, wrt)
                 } else {
-                    cmd.redo(Some(n))
+                    cmd.redo(Some(n), wrt)
                 }
             } else {
                 cmd.clone()
@@ -490,9 +508,9 @@ impl InputState {
             KeyPress::End => Cmd::Move(Movement::EndOfLine),
             KeyPress::Char('.') => { // vi-redo (repeat last command)
                 if no_num_args {
-                    self.last_cmd.redo(None)
+                    self.last_cmd.redo(None, wrt)
                 } else {
-                    self.last_cmd.redo(Some(n))
+                    self.last_cmd.redo(Some(n), wrt)
                 }
             },
             // TODO KeyPress::Char('%') => Cmd::???, Move to the corresponding opening/closing bracket
@@ -514,16 +532,14 @@ impl InputState {
             KeyPress::Char('B') => Cmd::Move(Movement::BackwardWord(n, Word::Big)),
             KeyPress::Char('c') => {
                 self.input_mode = InputMode::Insert;
-                wrt.doing_replace();
                 match try!(self.vi_cmd_motion(rdr, wrt, key, n)) {
-                    Some(mvt) => Cmd::Kill(mvt),
+                    Some(mvt) => Cmd::Replace(mvt, None),
                     None => Cmd::Unknown,
                 }
             }
             KeyPress::Char('C') => {
                 self.input_mode = InputMode::Insert;
-                wrt.doing_replace();
-                Cmd::Kill(Movement::EndOfLine)
+                Cmd::Replace(Movement::EndOfLine, None)
             }
             KeyPress::Char('d') => {
                 match try!(self.vi_cmd_motion(rdr, wrt, key, n)) {
@@ -574,7 +590,7 @@ impl InputState {
                 // vi-replace-char:
                 let ch = try!(rdr.next_key(false));
                 match ch {
-                    KeyPress::Char(c) => Cmd::Replace(n, c),
+                    KeyPress::Char(c) => Cmd::ReplaceChar(n, c),
                     KeyPress::Esc => Cmd::Noop,
                     _ => Cmd::Unknown,
                 }
@@ -582,20 +598,17 @@ impl InputState {
             KeyPress::Char('R') => {
                 //  vi-replace-mode (overwrite-mode)
                 self.input_mode = InputMode::Replace;
-                wrt.doing_replace();
-                Cmd::Noop
+                Cmd::Noop // FIXME no redo possible
             }
             KeyPress::Char('s') => {
                 // vi-substitute-char:
                 self.input_mode = InputMode::Insert;
-                wrt.doing_replace();
-                Cmd::Kill(Movement::ForwardChar(n))
+                Cmd::Replace(Movement::ForwardChar(n), None)
             }
             KeyPress::Char('S') => {
                 // vi-substitute-line:
                 self.input_mode = InputMode::Insert;
-                wrt.doing_replace();
-                Cmd::Kill(Movement::WholeLine)
+                Cmd::Replace(Movement::WholeLine, None)
             }
             KeyPress::Char('u') => Cmd::Undo(n),
             // KeyPress::Char('U') => Cmd::???, // revert-line
@@ -636,7 +649,7 @@ impl InputState {
         };
         debug!(target: "rustyline", "Vi command: {:?}", cmd);
         if cmd.is_repeatable_change() {
-            self.update_last_cmd(cmd.clone());
+            self.last_cmd = cmd.clone();
         }
         Ok(cmd)
     }
@@ -646,7 +659,7 @@ impl InputState {
         if let Some(cmd) = self.custom_bindings.borrow().get(&key) {
             debug!(target: "rustyline", "Custom command: {:?}", cmd);
             return Ok(if cmd.is_repeatable() {
-                cmd.redo(None)
+                cmd.redo(None, wrt)
             } else {
                 cmd.clone()
             });
@@ -669,12 +682,12 @@ impl InputState {
         };
         debug!(target: "rustyline", "Vi insert: {:?}", cmd);
         if cmd.is_repeatable_change() {
-            self.update_last_cmd(cmd.clone());
+            if let (Cmd::Replace(_, _), Cmd::SelfInsert(_, _)) = (&self.last_cmd, &cmd) {
+                // replacing...
+            } else {
+                self.last_cmd = cmd.clone();
+            }
         }
-        self.consecutive_insert = match cmd {
-            Cmd::SelfInsert(_, _) => true,
-            _ => false,
-        };
         Ok(cmd)
     }
 
@@ -858,26 +871,4 @@ impl InputState {
             num_args.abs() as RepeatCount
         }
     }
-
-    fn update_last_cmd(&mut self, new: Cmd) {
-        // consecutive char inserts are repeatable not only the last one...
-        if !self.consecutive_insert {
-            self.last_cmd = new;
-        } else if let Cmd::SelfInsert(_, c) = new {
-            match self.last_cmd {
-                Cmd::SelfInsert(_, pc) => {
-                    let mut text = String::new();
-                    text.push(pc);
-                    text.push(c);
-                    self.last_cmd = Cmd::Insert(1, text);
-                }
-                Cmd::Insert(_, ref mut text) => {
-                    text.push(c);
-                }
-                _ => self.last_cmd = new,
-            }
-        } else {
-            self.last_cmd = new;
-        }
-    }
 }
diff --git a/src/kill_ring.rs b/src/kill_ring.rs
index b587b81d81edae72691d9fd5d26b56c9cc0674b4..15b66494eb944b1dd3d0d48fc853b9111f04e794 100644
--- a/src/kill_ring.rs
+++ b/src/kill_ring.rs
@@ -34,13 +34,6 @@ impl KillRing {
         }
     }
 
-    pub fn start_killing(&mut self) {
-        self.killing = true;
-    }
-    pub fn stop_killing(&mut self) {
-        self.killing = false;
-    }
-
     /// Reset `last_action` state.
     pub fn reset(&mut self) {
         self.last_action = Action::Other;
@@ -113,6 +106,9 @@ impl KillRing {
 }
 
 impl DeleteListener for KillRing {
+    fn start_killing(&mut self) {
+        self.killing = true;
+    }
     fn delete(&mut self, _: usize, string: &str, dir: Direction) {
         if !self.killing {
             return;
@@ -123,6 +119,9 @@ impl DeleteListener for KillRing {
         };
         self.kill(string, mode);
     }
+    fn stop_killing(&mut self) {
+        self.killing = false;
+    }
 }
 
 #[cfg(test)]
diff --git a/src/lib.rs b/src/lib.rs
index c7d610bfdcc21bc908a6489dee627b546b989734..e898b343c3cbd39ade3ed683070c71e247dbe980 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -422,12 +422,12 @@ fn readline_edit<H: Helper>(
                 // Move back a character.
                 try!(s.edit_move_backward(n))
             }
-            Cmd::Kill(Movement::ForwardChar(n)) => {
-                // Delete (forward) one character at point.
-                try!(s.edit_delete(n))
-            }
-            Cmd::Replace(n, c) => {
-                try!(s.edit_replace_char(c, n));
+            Cmd::ReplaceChar(n, c) => try!(s.edit_replace_char(c, n)),
+            Cmd::Replace(mvt, text) => {
+                try!(s.edit_kill(&mvt));
+                if let Some(text) = text {
+                    try!(s.edit_insert_text(&text))
+                }
             }
             Cmd::Overwrite(c) => {
                 try!(s.edit_overwrite_char(c));
@@ -448,22 +448,6 @@ fn readline_edit<H: Helper>(
                 // Move forward a character.
                 try!(s.edit_move_forward(n))
             }
-            Cmd::Kill(Movement::BackwardChar(n)) => {
-                // Delete one character backward.
-                try!(s.edit_backspace(n))
-            }
-            Cmd::Kill(Movement::EndOfLine) => {
-                // Kill the text from point to the end of the line.
-                editor.kill_ring.borrow_mut().start_killing();
-                try!(s.edit_kill_line());
-                editor.kill_ring.borrow_mut().stop_killing();
-            }
-            Cmd::Kill(Movement::WholeLine) => {
-                try!(s.edit_move_home());
-                editor.kill_ring.borrow_mut().start_killing();
-                try!(s.edit_kill_line());
-                editor.kill_ring.borrow_mut().stop_killing();
-            }
             Cmd::ClearScreen => {
                 // Clear the screen leaving the current line at the top of the screen.
                 try!(s.out.clear_screen());
@@ -487,12 +471,6 @@ fn readline_edit<H: Helper>(
                 // Exchange the char before cursor with the character at cursor.
                 try!(s.edit_transpose_chars())
             }
-            Cmd::Kill(Movement::BeginningOfLine) => {
-                // Kill backward from point to the beginning of the line.
-                editor.kill_ring.borrow_mut().start_killing();
-                try!(s.edit_discard_line());
-                editor.kill_ring.borrow_mut().stop_killing();
-            }
             #[cfg(unix)]
             Cmd::QuotedInsert => {
                 // Quoted insert
@@ -505,7 +483,7 @@ fn readline_edit<H: Helper>(
                     try!(s.edit_yank(&input_state, text, anchor, n))
                 }
             }
-            Cmd::ViYankTo(mvt) => if let Some(text) = s.line.copy(mvt) {
+            Cmd::ViYankTo(ref mvt) => if let Some(text) = s.line.copy(mvt) {
                 editor.kill_ring.borrow_mut().kill(&text, Mode::Append)
             },
             // TODO CTRL-_ // undo
@@ -520,12 +498,6 @@ fn readline_edit<H: Helper>(
                 }
                 break;
             }
-            Cmd::Kill(Movement::BackwardWord(n, word_def)) => {
-                // kill one word backward (until start of word)
-                editor.kill_ring.borrow_mut().start_killing();
-                try!(s.edit_delete_prev_word(word_def, n));
-                editor.kill_ring.borrow_mut().stop_killing();
-            }
             Cmd::BeginningOfHistory => {
                 // move to first entry in history
                 try!(s.edit_history(&editor.history, true))
@@ -542,11 +514,8 @@ fn readline_edit<H: Helper>(
                 // capitalize word after point
                 try!(s.edit_word(WordAction::CAPITALIZE))
             }
-            Cmd::Kill(Movement::ForwardWord(n, at, word_def)) => {
-                // kill one word forward (until start/end of word)
-                editor.kill_ring.borrow_mut().start_killing();
-                try!(s.edit_delete_word(at, word_def, n));
-                editor.kill_ring.borrow_mut().stop_killing();
+            Cmd::Kill(ref mvt) => {
+                try!(s.edit_kill(mvt));
             }
             Cmd::Move(Movement::ForwardWord(n, at, word_def)) => {
                 // move forwards one word
@@ -571,11 +540,6 @@ fn readline_edit<H: Helper>(
                 }
             }
             Cmd::Move(Movement::ViCharSearch(n, cs)) => try!(s.edit_move_to(cs, n)),
-            Cmd::Kill(Movement::ViCharSearch(n, cs)) => {
-                editor.kill_ring.borrow_mut().start_killing();
-                try!(s.edit_delete_to(cs, n));
-                editor.kill_ring.borrow_mut().stop_killing();
-            }
             Cmd::Undo(n) => {
                 s.line.remove_change_listener();
                 if s.changes.borrow_mut().undo(&mut s.line, n) {
diff --git a/src/line_buffer.rs b/src/line_buffer.rs
index af7a4b3b8c0710c710312b43db3836c8635863a9..31fd672a33d21f908dbfe19423e1da71b4f194d4 100644
--- a/src/line_buffer.rs
+++ b/src/line_buffer.rs
@@ -34,7 +34,9 @@ impl Default for Direction {
 
 /// Listener to be notified when some text is deleted.
 pub trait DeleteListener {
+    fn start_killing(&mut self);
     fn delete(&mut self, idx: usize, string: &str, dir: Direction);
+    fn stop_killing(&mut self);
 }
 
 /// Listener to be notified when the line is modified.
@@ -275,8 +277,8 @@ impl LineBuffer {
     }
 
     /// Delete the character at the right of the cursor without altering the
-    /// cursor
-    /// position. Basically this is what happens with the "Delete" keyboard key.
+    /// cursor position. Basically this is what happens with the "Delete"
+    /// keyboard key.
     /// Return the number of characters deleted.
     pub fn delete(&mut self, n: RepeatCount) -> Option<String> {
         match self.next_pos(n) {
@@ -689,11 +691,11 @@ impl LineBuffer {
 
     /// Return the content between current cursor position and `mvt` position.
     /// Return `None` when the buffer is empty or when the movement fails.
-    pub fn copy(&self, mvt: Movement) -> Option<String> {
+    pub fn copy(&self, mvt: &Movement) -> Option<String> {
         if self.is_empty() {
             return None;
         }
-        match mvt {
+        match *mvt {
             Movement::WholeLine => Some(self.buf.clone()),
             Movement::BeginningOfLine => if self.pos == 0 {
                 None
@@ -757,6 +759,59 @@ impl LineBuffer {
             },
         }
     }
+
+    pub fn kill(&mut self, mvt: &Movement) -> bool {
+        let notify = match *mvt {
+            Movement::ForwardChar(_) => false,
+            Movement::BackwardChar(_) => false,
+            _ => true,
+        };
+        if notify {
+            if let Some(dl) = self.dl.as_ref() {
+                dl.borrow_mut().start_killing()
+            }
+        }
+        let killed = match *mvt {
+            Movement::ForwardChar(n) => {
+                // Delete (forward) `n` characters at point.
+                self.delete(n).is_some()
+            }
+            Movement::BackwardChar(n) => {
+                // Delete `n` characters backward.
+                self.backspace(n)
+            }
+            Movement::EndOfLine => {
+                // Kill the text from point to the end of the line.
+                self.kill_line()
+            }
+            Movement::WholeLine => {
+                self.move_home();
+                self.kill_line()
+            }
+            Movement::BeginningOfLine => {
+                // Kill backward from point to the beginning of the line.
+                self.discard_line()
+            }
+            Movement::BackwardWord(n, word_def) => {
+                // kill `n` words backward (until start of word)
+                self.delete_prev_word(word_def, n)
+            }
+            Movement::ForwardWord(n, at, word_def) => {
+                // kill `n` words forward (until start/end of word)
+                self.delete_word(at, word_def, n)
+            }
+            Movement::ViCharSearch(n, cs) => self.delete_to(cs, n),
+            Movement::ViFirstPrint => {
+                false // TODO
+            }
+        };
+        if notify {
+            if let Some(dl) = self.dl.as_ref() {
+                dl.borrow_mut().stop_killing()
+            }
+        }
+        killed
+    }
 }
 
 impl Deref for LineBuffer {
@@ -814,9 +869,11 @@ mod test {
     }
 
     impl DeleteListener for Listener {
+        fn start_killing(&mut self) {}
         fn delete(&mut self, _: usize, string: &str, _: Direction) {
             self.deleted_str = Some(string.to_owned());
         }
+        fn stop_killing(&mut self) {}
     }
     impl ChangeListener for Listener {
         fn insert_char(&mut self, _: usize, _: char) {}
diff --git a/src/tty/unix.rs b/src/tty/unix.rs
index aaba241653cd22b26ff50e8beed5883905cba0fd..b1d5b7889ef627f86e9007dd567327d853f717d1 100644
--- a/src/tty/unix.rs
+++ b/src/tty/unix.rs
@@ -263,6 +263,7 @@ impl PosixRawReader {
 }
 
 // https://tools.ietf.org/html/rfc3629
+#[cfg_attr(rustfmt, rustfmt_skip)]
 static UTF8_CHAR_WIDTH: [u8; 256] = [
 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x1F
diff --git a/src/undo.rs b/src/undo.rs
index a0a2553a58282c16cf4c720ba59de6895064c40a..8e865b8320f5e2fa9baec7ef03f608ee81f2e5c1 100644
--- a/src/undo.rs
+++ b/src/undo.rs
@@ -310,12 +310,29 @@ impl Changeset {
         }
         redone
     }
+
+    pub fn last_insert(&self) -> Option<String> {
+        for change in self.undos.iter().rev() {
+            match change {
+                Change::Insert { ref text, .. } => return Some(text.clone()),
+                Change::End => {
+                    continue;
+                }
+                _ => {
+                    return None;
+                }
+            }
+        }
+        None
+    }
 }
 
 impl DeleteListener for Changeset {
+    fn start_killing(&mut self) {}
     fn delete(&mut self, idx: usize, string: &str, _: Direction) {
         self.delete(idx, string);
     }
+    fn stop_killing(&mut self) {}
 }
 impl ChangeListener for Changeset {
     fn insert_char(&mut self, idx: usize, c: char) {
@@ -436,4 +453,15 @@ mod tests {
         cs.redo(&mut buf);
         assert_eq!(buf.as_str(), "Hi, world!");
     }
+
+    #[test]
+    fn test_last_insert() {
+        let mut cs = Changeset::new();
+        cs.begin();
+        cs.delete(0, "Hello");
+        cs.insert_str(0, "Bye");
+        cs.end();
+        let insert = cs.last_insert();
+        assert_eq!(Some("Bye".to_owned()), insert);
+    }
 }