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