Merge pull request #132 from kkawakam/2.0

2.0.0-alpha
diff --git a/.travis.yml b/.travis.yml
index af46809..711713e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,16 +1,5 @@
+sudo: false
 language: rust
-rust:
-  - stable
-  - beta
-  - nightly
 script:
   - cargo build --verbose
   - cargo test --verbose
-  - cargo doc
-after_success: |
-  [ $TRAVIS_BRANCH = master ] &&
-  [ $TRAVIS_PULL_REQUEST = false ] &&
-  bash deploy-docs.sh
-env:
-  global:
-    secure: "XxaPXHiVplTwMaAytYC0VQR/nNnm7SJVzXiUuaVEjssHip0Uje/4f3vGqtJjnD70FfxwNWQKiSYOcbYjWPlsJeANRt4ZoCsRt5eLGUZ+wH79n1fOkp5EIpFT/isjCB51A4n8PRUvuWfQ2OtNNeGLL6akMxt19sHdXoiQkLOe338="
diff --git a/Cargo.toml b/Cargo.toml
index ec6586a..9b940d8 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "rustyline"
-version = "1.0.0"
+version = "2.0.0-alpha"
 authors = ["Katsu Kawakami <kkawa1570@gmail.com>"]
 description = "Rustyline, a readline implementation based on Antirez's Linenoise"
 documentation = "http://docs.rs/rustyline"
@@ -15,18 +15,17 @@
 appveyor = { repository = "kkawakam/rustyline" }
 
 [dependencies]
-libc = "0.2.7"
-log = "0.3"
-unicode-width = "0.1.3"
+libc = "0.2"
+log = "0.4"
+unicode-width = "0.1"
 unicode-segmentation = "1.0"
-encode_unicode = "0.1.3"
 
 [target.'cfg(unix)'.dependencies]
-nix = "0.8"
+nix = "0.11"
 
 [target.'cfg(windows)'.dependencies]
-winapi = "0.2"
-kernel32-sys = "0.2"
+winapi = { version = "0.3", features = ["consoleapi", "handleapi", "minwindef", "processenv", "winbase", "wincon", "winuser"] }
 
 [dev-dependencies]
-tempdir = "0.3.4"
+tempdir = "0.3"
+assert_matches = "1.2"
diff --git a/README.md b/README.md
index f031d1b..99f345b 100644
--- a/README.md
+++ b/README.md
@@ -1,28 +1,21 @@
 # RustyLine
 [![Build Status](https://travis-ci.org/kkawakam/rustyline.svg?branch=master)](https://travis-ci.org/kkawakam/rustyline)
-[![Build status](https://ci.appveyor.com/api/projects/status/ls7sty8nt25rdfkq/branch/master?svg=true)](https://ci.appveyor.com/project/kkawakam/rustyline/branch/master)
-[![Clippy Linting Result](https://clippy.bashy.io/github/kkawakam/rustyline/master/badge.svg)](https://clippy.bashy.io/github/kkawakam/rustyline/master/log)
+[![Build Status](https://ci.appveyor.com/api/projects/status/github/kkawakam/rustyline?branch=master&svg=true)](https://ci.appveyor.com/project/kkawakam/rustyline/branch/master)
+[![dependency status](https://deps.rs/repo/github/kkawakam/rustyline/status.svg)](https://deps.rs/repo/github/kkawakam/rustyline)
 [![](http://meritbadge.herokuapp.com/rustyline)](https://crates.io/crates/rustyline)
+[![Docs](https://docs.rs/rustyline/badge.svg)](https://docs.rs/rustyline)
 
 Readline implementation in Rust that is based on [Antirez' Linenoise](https://github.com/antirez/linenoise)
 
-[Documentation (Releases)](https://docs.rs/rustyline)
-
-[Documentation (Master)](https://kkawakam.github.io/rustyline/rustyline/)
-
 **Supported Platforms**
-* Linux
+* Unix (tested on FreeBSD, Linux and macOS)
 * Windows
    * cmd.exe
    * Powershell
 
-**Note**: Powershell ISE is not supported, check [issue #56](https://github.com/kkawakam/rustyline/issues/56)
-
-## Build
-This project uses Cargo and Rust stable
-```bash
-cargo build --release
-```
+**Note**:
+* Powershell ISE is not supported, check [issue #56](https://github.com/kkawakam/rustyline/issues/56)
+* Mintty (Cygwin/Mingw) is not supported
 
 ## Example
 ```rust
@@ -34,14 +27,14 @@
 fn main() {
     // `()` can be used when no completer is required
     let mut rl = Editor::<()>::new();
-    if let Err(_) = rl.load_history("history.txt") {
+    if rl.load_history("history.txt").is_err() {
         println!("No previous history.");
     }
     loop {
         let readline = rl.readline(">> ");
         match readline {
             Ok(line) => {
-                rl.add_history_entry(&line);
+                rl.add_history_entry(line.as_ref());
                 println!("Line: {}", line);
             },
             Err(ReadlineError::Interrupted) => {
@@ -78,8 +71,9 @@
  - Filename completion
  - History search ([Searching for Commands in the History](http://cnswww.cns.cwru.edu/php/chet/readline/readline.html#SEC8))
  - Kill ring ([Killing Commands](http://cnswww.cns.cwru.edu/php/chet/readline/readline.html#IDX3))
- - Multi line mode
+ - Multi line mode (line wrapping)
  - Word commands
+ - Hints
 
 ## Actions
 
@@ -102,6 +96,7 @@
 Ctrl-W       | Delete word leading up to cursor (using white space as a word boundary)
 Ctrl-Y       | Paste from Yank buffer
 Ctrl-Z       | Suspend (unix only)
+Ctrl-_       | Undo
 
 ### Emacs mode (default mode)
 
@@ -117,6 +112,7 @@
 Ctrl-L       | Clear screen
 Ctrl-N, Down | Next match from history
 Ctrl-P, Up   | Previous match from history
+Ctrl-X Ctrl-U | Undo
 Ctrl-Y       | Paste from Yank buffer (Meta-Y to paste next yank instead)
 Meta-<       | Move to first entry in history
 Meta->       | Move to last entry in history
@@ -169,6 +165,7 @@
 S            | Change current line (equivalent to 0c$)
 t<char>      | Move right to the next occurance of `char`, then one char backward
 T<char>      | Move left to the previous occurance of `char`, then one char forward
+u            | Undo
 w            | Move one word or token right
 W            | Move one non-blank word right
 x            | Delete a single character under the cursor
@@ -187,12 +184,6 @@
 
 [Terminal codes (ANSI/VT100)](http://wiki.bash-hackers.org/scripting/terminalcodes)
 
-## ToDo
-
- - Undos
- - Read input with timeout to properly handle single ESC key
- - expose an API callable from C
-
 ## Wine
 
 ```sh
@@ -214,10 +205,24 @@
 
 ## Similar projects
 
- - [copperline](https://github.com/srijs/rust-copperline) (Rust)
- - [linefeed](https://github.com/murarth/linefeed) (Rust)
- - [liner](https://github.com/MovingtoMars/liner) (Rust)
- - [linenoise-ng](https://github.com/arangodb/linenoise-ng) (C++)
- - [liner](https://github.com/peterh/liner) (Go)
- - [readline](https://github.com/chzyer/readline) (Go)
- - [haskeline](https://github.com/judah/haskeline) (Haskell)
+Library            | Lang    | OS     | Term  | Unicode | History       | Completion | Keymap        | Kill Ring | Undo | Colors     | Hint/Auto suggest |
+--------           | ----    | --     | ----  | ------- | -------       | ---------- | -------       | --------- | ---- | ------     | ----------------- |
+[Haskeline][]      | Haskell | Ux/Win | Any   | Yes     | Yes           | any        | Emacs/Vi/conf | Yes       | Yes  | ?          | ?                 |
+[Linenoise][]      | C       | Ux     | ANSI  | No      | Yes           | only line  | Emacs         | No        | No   | Ux         | Yes               |
+[Linenoise-ng][]   | C       | Ux/Win | ANSI  | Yes     | Yes           | only line  | Emacs         | Yes       | No   | ?          | ?                 |
+[Linefeed][]       | Rust    | Ux/Win | Any   |         | Yes           | any        | Emacs/conf    | Yes       | No   | ?          | No                |
+[Liner][]          | Rust    | Ux     | ANSI  |         | No inc search | only word  | Emacs/Vi/prog | No        | Yes  | Ux         | History based     |
+[Prompt-toolkit][] | Python  | Ux/Win | ANSI  | Yes     | Yes           | any        | Emacs/Vi/conf | Yes       | Yes  | Ux/Win     | Yes               |
+[Rb-readline][]    | Ruby    | Ux/Win | ANSI  | Yes     | Yes           | only word  | Emacs/Vi/conf | Yes       | Yes  | ?          | No                |
+[Replxx][]         | C/C++   | Ux/Win | ANSI  | Yes     | Yes           | only line  | Emacs         | Yes       | No   | Ux/Win     | Yes               |
+Rustyline          | Rust    | Ux/Win | ANSI  | Yes     | Yes           | any        | Emacs/Vi/bind | Yes       | Yes  | Ux/Win 10+ | Yes               |
+
+[Haskeline]: https://github.com/judah/haskeline
+[Linefeed]: https://github.com/murarth/linefeed
+[Linenoise]: https://github.com/antirez/linenoise
+[Linenoise-ng]: https://github.com/arangodb/linenoise-ng
+[Liner]: https://github.com/MovingtoMars/liner
+[Prompt-toolkit]: https://github.com/jonathanslenders/python-prompt-toolkit
+[Rb-readline]: https://github.com/ConnorAtherton/rb-readline
+[Replxx]: https://github.com/AmokHuginnsson/replxx
+
diff --git a/TODO.md b/TODO.md
new file mode 100644
index 0000000..948d42b
--- /dev/null
+++ b/TODO.md
@@ -0,0 +1,79 @@
+API
+- [ ] expose an API callable from C
+
+Async (#126)
+
+Bell
+- [ ] bell-style
+
+Color
+- [x] ANSI Colors & Windows 10+
+- [ ] ANSI Colors & Windows <10 (https://docs.rs/console/0.6.1/console/fn.strip_ansi_codes.html ?)
+- [ ] Syntax highlighting
+- [ ] clicolors spec (https://docs.rs/console/0.6.1/console/fn.colors_enabled.html)
+
+Completion
+- [X] Quoted path
+- [ ] Windows escape/unescape space in path
+- [ ] file completion & escape/unescape (#106)
+- [ ] file completion & tilde (#62)
+- [ ] display versus replacement
+- [ ] composite/alternate completer (if the current completer returns nothing, try the next one)
+
+Config
+- [ ] Maximum buffer size for the line read
+
+Cursor
+- [ ] insert versus overwrite versus command mode
+- [ ] In Vi command mode, prevent user from going to end of line. (#94)
+
+Grapheme
+- [ ] grapheme & input auto-wrap are buggy
+
+Hints Callback
+- [x] Not implemented on windows
+
+History
+- [ ] Move to the history line n
+- [ ] historyFile: Where to read/write the history at the start and end of
+each line input session.
+- [ ] append_history
+- [ ] history_truncate_file
+
+Input
+- [ ] Password input (#58)
+- [ ] quoted insert (#65)
+- [ ] Overwrite mode (em-toggle-overwrite, vi-replace-mode, rl_insert_mode)
+- [ ] Encoding
+
+Mouse
+- [ ] Mouse support
+
+Movement
+- [ ] Move to the corresponding opening/closing bracket
+
+Redo
+- [X] redo substitue
+
+Repeat
+- [x] dynamic prompt (arg: ?)
+- [ ] transpose chars
+
+Syntax
+- [ ] syntax specific tokenizer/parser
+- [ ] highlighting
+
+Undo
+- [ ] Merge consecutive Replace
+- [X] Undo group
+- [ ] Undo all changes made to this line.
+- [X] Kill+Insert (substitute/replace)
+- [X] Repeated undo `Undo(RepeatCount)`
+
+Unix
+- [ ] Terminfo (https://github.com/Stebalien/term)
+
+Windows
+- [ ] is_atty is not working with cygwin/msys (https://github.com/softprops/atty works but then how to make `enable_raw_mode` works ?)
+- [X] UTF-16 surrogate pair
+- [ ] handle ansi escape code (https://docs.rs/console/0.6.1/console/fn.strip_ansi_codes.html ?)
diff --git a/appveyor.yml b/appveyor.yml
index 8934d30..4506f3a 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,14 +1,9 @@
 environment:
-  matrix:
-  - TARGET: 1.16.0-x86_64-pc-windows-msvc
-  - TARGET: 1.16.0-x86_64-pc-windows-gnu
-  - TARGET: beta-x86_64-pc-windows-msvc
-  - TARGET: beta-x86_64-pc-windows-gnu
+  TARGET: x86_64-pc-windows-msvc
 install:
-  - ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-${env:TARGET}.exe"
-  - rust-%TARGET%.exe /VERYSILENT /NORESTART /DIR="C:\Program Files (x86)\Rust"
-  - SET PATH=%PATH%;C:\Program Files (x86)\Rust\bin
-  - SET PATH=%PATH%;C:\MinGW\bin
+  - appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
+  - rustup-init -yv --default-toolchain stable --default-host %TARGET%
+  - set PATH=%PATH%;%USERPROFILE%\.cargo\bin
   - rustc -V
   - cargo -V
 
diff --git a/deploy-docs.sh b/deploy-docs.sh
deleted file mode 100755
index efd5a56..0000000
--- a/deploy-docs.sh
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/bin/bash
-
-rev=$(git rev-parse --short HEAD)
-
-cd target/doc
-
-echo '<meta http-equiv=refresh content=0;url=rustyline/index.html>' > index.html
-
-git init
-git config user.name "Katsu Kawakami"
-git config user.email "kkawa1570@gmail.com"
-
-git remote add upstream "https://$GH_TOKEN@github.com/kkawakam/rustyline.git"
-git fetch upstream
-git push upstream --delete gh-pages > /dev/null 2>&1
-
-touch .
-
-git add -A .
-git commit -m "rebuild pages at ${rev}"
-git push -q upstream HEAD:gh-pages > /dev/null 2>&1
diff --git a/examples/example.rs b/examples/example.rs
index 8f02337..4e9c407 100644
--- a/examples/example.rs
+++ b/examples/example.rs
@@ -1,12 +1,12 @@
 extern crate log;
 extern crate rustyline;
 
-use std::io::{self, Write};
-use log::{LogRecord, LogLevel, LogLevelFilter, LogMetadata, SetLoggerError};
+use log::{Level, LevelFilter, Metadata, Record, SetLoggerError};
 
 use rustyline::completion::FilenameCompleter;
 use rustyline::error::ReadlineError;
-use rustyline::{Cmd, Config, CompletionType, Editor, EditMode, KeyPress};
+use rustyline::hint::Hinter;
+use rustyline::{Cmd, CompletionType, Config, EditMode, Editor, KeyPress};
 
 // On unix platforms you can use ANSI escape sequences
 #[cfg(unix)]
@@ -17,6 +17,22 @@
 #[cfg(windows)]
 static PROMPT: &'static str = ">> ";
 
+struct Hints {}
+
+impl Hinter for Hints {
+    fn hint(&self, line: &str, _pos: usize) -> Option<String> {
+        if line == "hello" {
+            if cfg!(target_os = "windows") {
+                Some(" World".to_owned())
+            } else {
+                Some(" \x1b[1mWorld\x1b[m".to_owned())
+            }
+        } else {
+            None
+        }
+    }
+}
+
 fn main() {
     init_logger().is_ok();
     let config = Config::builder()
@@ -26,7 +42,7 @@
         .build();
     let c = FilenameCompleter::new();
     let mut rl = Editor::with_config(config);
-    rl.set_completer(Some(c));
+    rl.set_helper(Some((c, Hints {})));
     rl.bind_sequence(KeyPress::Meta('N'), Cmd::HistorySearchForward);
     rl.bind_sequence(KeyPress::Meta('P'), Cmd::HistorySearchBackward);
     if rl.load_history("history.txt").is_err() {
@@ -56,23 +72,25 @@
     rl.save_history("history.txt").unwrap();
 }
 
+static LOGGER: Logger = Logger;
 struct Logger;
 
 impl log::Log for Logger {
-    fn enabled(&self, metadata: &LogMetadata) -> bool {
-        metadata.level() <= LogLevel::Debug
+    fn enabled(&self, metadata: &Metadata) -> bool {
+        metadata.level() <= Level::Debug
     }
 
-    fn log(&self, record: &LogRecord) {
+    fn log(&self, record: &Record) {
         if self.enabled(record.metadata()) {
-            writeln!(io::stderr(), "{} - {}", record.level(), record.args()).unwrap();
+            eprintln!("{} - {}", record.level(), record.args());
         }
     }
+
+    fn flush(&self) {}
 }
 
 fn init_logger() -> Result<(), SetLoggerError> {
-    log::set_logger(|max_log_level| {
-                        max_log_level.set(LogLevelFilter::Info);
-                        Box::new(Logger)
-                    })
+    try!(log::set_logger(&LOGGER));
+    log::set_max_level(LevelFilter::Info);
+    Ok(())
 }
diff --git a/index.html b/index.html
deleted file mode 100644
index 269d4c6..0000000
--- a/index.html
+++ /dev/null
@@ -1 +0,0 @@
-<meta http-equiv=refresh content=0;url=rustyline/index.html>
diff --git a/rustfmt.toml b/rustfmt.toml
index 6496add..34977e4 100644
--- a/rustfmt.toml
+++ b/rustfmt.toml
@@ -1,3 +1,3 @@
-reorder_imports = false
-normalise_comments = false
-write_mode = "Overwrite"
\ No newline at end of file
+wrap_comments = true
+format_strings = true
+error_on_unformatted = false
diff --git a/src/char_iter.rs b/src/char_iter.rs
deleted file mode 100644
index 1866dbd..0000000
--- a/src/char_iter.rs
+++ /dev/null
@@ -1,111 +0,0 @@
-//! An iterator over the `char`s of a reader.
-//!
-//! A copy of the unstable code from the stdlib's std::io::Read::chars.
-//! TODO: Remove this once [Read::chars](https://github.com/rust-lang/rust/issues/27802) has been stabilized
-
-use std::error;
-use std::fmt;
-use std::io;
-use std::io::Read;
-use std::str;
-
-pub fn chars<R: Read>(read: R) -> Chars<R>
-    where R: Sized
-{
-    Chars { inner: read }
-}
-
-// https://tools.ietf.org/html/rfc3629
-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
-    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, // 0x3F
-    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, // 0x5F
-    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, // 0x7F
-    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 0x9F
-    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 0xBF
-    0,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
-    2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // 0xDF
-    3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, // 0xEF
-    4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0, // 0xFF
-];
-
-/// Given a first byte, determine how many bytes are in this UTF-8 character
-#[inline]
-fn utf8_char_width(b: u8) -> usize {
-    return UTF8_CHAR_WIDTH[b as usize] as usize;
-}
-
-pub struct Chars<R> {
-    inner: R,
-}
-
-#[derive(Debug)]
-pub enum CharsError {
-    NotUtf8,
-    Other(io::Error),
-}
-
-impl<R: Read> Iterator for Chars<R> {
-    type Item = Result<char, CharsError>;
-
-    fn next(&mut self) -> Option<Result<char, CharsError>> {
-        let mut buf = [0];
-        let first_byte = match self.inner.read(&mut buf) {
-            Ok(0) => return None,
-            Ok(..) => buf[0],
-            Err(e) => return Some(Err(CharsError::Other(e))),
-        };
-        let width = utf8_char_width(first_byte);
-        if width == 1 {
-            return Some(Ok(first_byte as char));
-        }
-        if width == 0 {
-            return Some(Err(CharsError::NotUtf8));
-        }
-        let mut buf = [first_byte, 0, 0, 0];
-        {
-            let mut start = 1;
-            while start < width {
-                match self.inner.read(&mut buf[start..width]) {
-                    Ok(0) => return Some(Err(CharsError::NotUtf8)),
-                    Ok(n) => start += n,
-                    Err(e) => return Some(Err(CharsError::Other(e))),
-                }
-            }
-        }
-        Some(match str::from_utf8(&buf[..width]).ok() {
-                 Some(s) => Ok(s.chars().next().unwrap()),
-                 None => Err(CharsError::NotUtf8),
-             })
-    }
-}
-
-impl error::Error for CharsError {
-    fn description(&self) -> &str {
-        match *self {
-            CharsError::NotUtf8 => "invalid utf8 encoding",
-            CharsError::Other(ref e) => error::Error::description(e),
-        }
-    }
-    fn cause(&self) -> Option<&error::Error> {
-        match *self {
-            CharsError::NotUtf8 => None,
-            CharsError::Other(ref e) => e.cause(),
-        }
-    }
-}
-
-impl fmt::Display for CharsError {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match *self {
-            CharsError::NotUtf8 => "byte stream did not contain valid utf8".fmt(f),
-            CharsError::Other(ref e) => e.fmt(f),
-        }
-    }
-}
diff --git a/src/completion.rs b/src/completion.rs
index 9c07c4d..c6e65ed 100644
--- a/src/completion.rs
+++ b/src/completion.rs
@@ -8,14 +8,17 @@
 use line_buffer::LineBuffer;
 
 // TODO: let the implementers choose/find word boudaries ???
-// (line, pos) is like (rl_line_buffer, rl_point) to make contextual completion ("select t.na| from tbl as t")
-// TOOD: make &self &mut self ???
+// (line, pos) is like (rl_line_buffer, rl_point) to make contextual completion
+// ("select t.na| from tbl as t")
+// TODO: make &self &mut self ???
 
 /// To be called for tab-completion.
 pub trait Completer {
     /// Takes the currently edited `line` with the cursor `pos`ition and
-    /// returns the start position and the completion candidates for the partial word to be completed.
-    /// "ls /usr/loc" => Ok((3, vec!["/usr/local/"]))
+    /// returns the start position and the completion candidates for the
+    /// partial word to be completed.
+    ///
+    /// ("ls /usr/loc", 11) => Ok((3, vec!["/usr/local/"]))
     fn complete(&self, line: &str, pos: usize) -> Result<(usize, Vec<String>)>;
     /// Updates the edited `line` with the `elected` candidate.
     fn update(&self, line: &mut LineBuffer, start: usize, elected: &str) {
@@ -26,7 +29,7 @@
 
 impl Completer for () {
     fn complete(&self, _line: &str, _pos: usize) -> Result<(usize, Vec<String>)> {
-        Ok((0, Vec::new()))
+        Ok((0, Vec::with_capacity(0)))
     }
     fn update(&self, _line: &mut LineBuffer, _start: usize, _elected: &str) {
         unreachable!()
@@ -63,23 +66,34 @@
 /// A `Completer` for file and folder names.
 pub struct FilenameCompleter {
     break_chars: BTreeSet<char>,
+    double_quotes_special_chars: BTreeSet<char>,
 }
 
+// rl_basic_word_break_characters, rl_completer_word_break_characters
 #[cfg(unix)]
-static DEFAULT_BREAK_CHARS: [char; 18] = [' ', '\t', '\n', '"', '\\', '\'', '`', '@', '$', '>',
-                                          '<', '=', ';', '|', '&', '{', '(', '\0'];
+static DEFAULT_BREAK_CHARS: [char; 18] = [
+    ' ', '\t', '\n', '"', '\\', '\'', '`', '@', '$', '>', '<', '=', ';', '|', '&', '{', '(', '\0',
+];
 #[cfg(unix)]
 static ESCAPE_CHAR: Option<char> = Some('\\');
 // Remove \ to make file completion works on windows
 #[cfg(windows)]
-static DEFAULT_BREAK_CHARS: [char; 17] = [' ', '\t', '\n', '"', '\'', '`', '@', '$', '>', '<',
-                                          '=', ';', '|', '&', '{', '(', '\0'];
+static DEFAULT_BREAK_CHARS: [char; 17] = [
+    ' ', '\t', '\n', '"', '\'', '`', '@', '$', '>', '<', '=', ';', '|', '&', '{', '(', '\0',
+];
 #[cfg(windows)]
 static ESCAPE_CHAR: Option<char> = None;
 
+// In double quotes, not all break_chars need to be escaped
+// https://www.gnu.org/software/bash/manual/html_node/Double-Quotes.html
+static DOUBLE_QUOTES_SPECIAL_CHARS: [char; 4] = ['"', '$', '\\', '`'];
+
 impl FilenameCompleter {
     pub fn new() -> FilenameCompleter {
-        FilenameCompleter { break_chars: DEFAULT_BREAK_CHARS.iter().cloned().collect() }
+        FilenameCompleter {
+            break_chars: DEFAULT_BREAK_CHARS.iter().cloned().collect(),
+            double_quotes_special_chars: DOUBLE_QUOTES_SPECIAL_CHARS.iter().cloned().collect(),
+        }
     }
 }
 
@@ -91,9 +105,25 @@
 
 impl Completer for FilenameCompleter {
     fn complete(&self, line: &str, pos: usize) -> Result<(usize, Vec<String>)> {
-        let (start, path) = extract_word(line, pos, ESCAPE_CHAR, &self.break_chars);
-        let path = unescape(path, ESCAPE_CHAR);
-        let matches = try!(filename_complete(&path, ESCAPE_CHAR, &self.break_chars));
+        let (start, path, esc_char, break_chars) =
+            if let Some((idx, double_quote)) = find_unclosed_quote(&line[..pos]) {
+                let start = idx + 1;
+                if double_quote {
+                    (
+                        start,
+                        unescape(&line[start..pos], ESCAPE_CHAR),
+                        ESCAPE_CHAR,
+                        &self.double_quotes_special_chars,
+                    )
+                } else {
+                    (start, Borrowed(&line[start..pos]), None, &self.break_chars)
+                }
+            } else {
+                let (start, path) = extract_word(line, pos, ESCAPE_CHAR, &self.break_chars);
+                let path = unescape(path, ESCAPE_CHAR);
+                (start, path, ESCAPE_CHAR, &self.break_chars)
+            };
+        let matches = try!(filename_complete(&path, esc_char, break_chars));
         Ok((start, matches))
     }
 }
@@ -124,16 +154,13 @@
 
 /// Escape any `break_chars` in `input` string with `esc_char`.
 /// For example, '/User Information' becomes '/User\ Information'
-/// when space is a breaking char and '\' the escape char.
+/// when space is a breaking char and '\\' the escape char.
 pub fn escape(input: String, esc_char: Option<char>, break_chars: &BTreeSet<char>) -> String {
     if esc_char.is_none() {
         return input;
     }
     let esc_char = esc_char.unwrap();
-    let n = input
-        .chars()
-        .filter(|c| break_chars.contains(c))
-        .count();
+    let n = input.chars().filter(|c| break_chars.contains(c)).count();
     if n == 0 {
         return input;
     }
@@ -148,10 +175,11 @@
     result
 }
 
-fn filename_complete(path: &str,
-                     esc_char: Option<char>,
-                     break_chars: &BTreeSet<char>)
-                     -> Result<Vec<String>> {
+fn filename_complete(
+    path: &str,
+    esc_char: Option<char>,
+    break_chars: &BTreeSet<char>,
+) -> Result<Vec<String>> {
     use std::env::{current_dir, home_dir};
 
     let sep = path::MAIN_SEPARATOR;
@@ -202,11 +230,12 @@
 /// try to find backward the start of a word.
 /// Return (0, `line[..pos]`) if no break char has been found.
 /// Return the word and its start position (idx, `line[idx..pos]`) otherwise.
-pub fn extract_word<'l>(line: &'l str,
-                        pos: usize,
-                        esc_char: Option<char>,
-                        break_chars: &BTreeSet<char>)
-                        -> (usize, &'l str) {
+pub fn extract_word<'l>(
+    line: &'l str,
+    pos: usize,
+    esc_char: Option<char>,
+    break_chars: &BTreeSet<char>,
+) -> (usize, &'l str) {
     let line = &line[..pos];
     if line.is_empty() {
         return (0, line);
@@ -247,8 +276,10 @@
         for (i, c1) in candidates.iter().enumerate().take(candidates.len() - 1) {
             let b1 = c1.as_bytes();
             let b2 = candidates[i + 1].as_bytes();
-            if b1.len() <= longest_common_prefix || b2.len() <= longest_common_prefix ||
-               b1[longest_common_prefix] != b2[longest_common_prefix] {
+            if b1.len() <= longest_common_prefix
+                || b2.len() <= longest_common_prefix
+                || b1[longest_common_prefix] != b2[longest_common_prefix]
+            {
                 break 'o;
             }
         }
@@ -263,6 +294,61 @@
     Some(&candidates[0][0..longest_common_prefix])
 }
 
+#[derive(PartialEq)]
+enum ScanMode {
+    DoubleQuote,
+    Escape,
+    EscapeInDoubleQuote,
+    Normal,
+    SingleQuote,
+}
+
+/// try to find an unclosed single/double quote in `s`.
+/// Return `None` if no unclosed quote is found.
+/// Return the unclosed quote position and if it is a double quote.
+fn find_unclosed_quote(s: &str) -> Option<(usize, bool)> {
+    let char_indices = s.char_indices();
+    let mut mode = ScanMode::Normal;
+    let mut quote_index = 0;
+    for (index, char) in char_indices {
+        match mode {
+            ScanMode::DoubleQuote => {
+                if char == '"' {
+                    mode = ScanMode::Normal;
+                } else if char == '\\' {
+                    mode = ScanMode::EscapeInDoubleQuote;
+                }
+            }
+            ScanMode::Escape => {
+                mode = ScanMode::Normal;
+            }
+            ScanMode::EscapeInDoubleQuote => {
+                mode = ScanMode::DoubleQuote;
+            }
+            ScanMode::Normal => {
+                if char == '"' {
+                    mode = ScanMode::DoubleQuote;
+                    quote_index = index;
+                } else if char == '\\' && cfg!(not(windows)) {
+                    mode = ScanMode::Escape;
+                } else if char == '\'' && cfg!(not(windows)) {
+                    mode = ScanMode::SingleQuote;
+                    quote_index = index;
+                }
+            }
+            ScanMode::SingleQuote => {
+                if char == '\'' {
+                    mode = ScanMode::Normal;
+                } // no escape in single quotes
+            }
+        };
+    }
+    if ScanMode::DoubleQuote == mode || ScanMode::SingleQuote == mode {
+        return Some((quote_index, ScanMode::DoubleQuote == mode));
+    }
+    None
+}
+
 #[cfg(test)]
 mod tests {
     use std::collections::BTreeSet;
@@ -271,11 +357,15 @@
     pub fn extract_word() {
         let break_chars: BTreeSet<char> = super::DEFAULT_BREAK_CHARS.iter().cloned().collect();
         let line = "ls '/usr/local/b";
-        assert_eq!((4, "/usr/local/b"),
-                   super::extract_word(line, line.len(), Some('\\'), &break_chars));
+        assert_eq!(
+            (4, "/usr/local/b"),
+            super::extract_word(line, line.len(), Some('\\'), &break_chars)
+        );
         let line = "ls /User\\ Information";
-        assert_eq!((3, "/User\\ Information"),
-                   super::extract_word(line, line.len(), Some('\\'), &break_chars));
+        assert_eq!(
+            (3, "/User\\ Information"),
+            super::extract_word(line, line.len(), Some('\\'), &break_chars)
+        );
     }
 
     #[test]
@@ -292,8 +382,10 @@
     pub fn escape() {
         let break_chars: BTreeSet<char> = super::DEFAULT_BREAK_CHARS.iter().cloned().collect();
         let input = String::from("/usr/local/b");
-        assert_eq!(input.clone(),
-                   super::escape(input, Some('\\'), &break_chars));
+        assert_eq!(
+            input.clone(),
+            super::escape(input, Some('\\'), &break_chars)
+        );
         let input = String::from("/User Information");
         let result = String::from("/User\\ Information");
         assert_eq!(result, super::escape(input, Some('\\'), &break_chars));
@@ -333,4 +425,17 @@
         let lcp = super::longest_common_prefix(&candidates);
         assert_eq!(Some("f"), lcp);
     }
+
+    #[test]
+    pub fn find_unclosed_quote() {
+        assert_eq!(None, super::find_unclosed_quote("ls /etc"));
+        assert_eq!(
+            Some((3, true)),
+            super::find_unclosed_quote("ls \"User Information")
+        );
+        assert_eq!(
+            None,
+            super::find_unclosed_quote("ls \"/User Information\" /etc")
+        );
+    }
 }
diff --git a/src/config.rs b/src/config.rs
index 3be614c..d1dbc42 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -1,20 +1,25 @@
 //! Customize line editor
 use std::default::Default;
 
+/// User preferences
 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
 pub struct Config {
     /// Maximum number of entries in History.
-    max_history_size: usize,
+    max_history_size: usize, // history_max_entries
     history_duplicates: HistoryDuplicates,
     history_ignore_space: bool,
     completion_type: CompletionType,
     /// When listing completion alternatives, only display
     /// one screen of possibilities at a time.
     completion_prompt_limit: usize,
-    /// Duration (milliseconds) Rustyline will wait for a character when reading an ambiguous key sequence.
+    /// Duration (milliseconds) Rustyline will wait for a character when
+    /// reading an ambiguous key sequence.
     keyseq_timeout: i32,
-    // Emacs or Vi mode
+    /// Emacs or Vi mode
     edit_mode: EditMode,
+    /// If true, each nonblank line returned by `readline` will be
+    /// automatically added to the history.
+    auto_add_history: bool,
 }
 
 impl Config {
@@ -27,13 +32,17 @@
         self.max_history_size
     }
 
-    /// Tell if lines which match the previous history entry are saved or not in the history list.
+    /// Tell if lines which match the previous history entry are saved or not
+    /// in the history list.
+    ///
     /// By default, they are ignored.
     pub fn history_duplicates(&self) -> HistoryDuplicates {
         self.history_duplicates
     }
 
-    /// Tell if lines which begin with a space character are saved or not in the history list.
+    /// Tell if lines which begin with a space character are saved or not in
+    /// the history list.
+    ///
     /// By default, they are saved.
     pub fn history_ignore_space(&self) -> bool {
         self.history_ignore_space
@@ -54,6 +63,13 @@
     pub fn edit_mode(&self) -> EditMode {
         self.edit_mode
     }
+
+    /// Tell if lines are automatically added to the history.
+    ///
+    /// By default, they are not.
+    pub fn auto_add_history(&self) -> bool {
+        self.auto_add_history
+    }
 }
 
 impl Default for Config {
@@ -64,8 +80,9 @@
             history_ignore_space: false,
             completion_type: CompletionType::Circular, // TODO Validate
             completion_prompt_limit: 100,
-            keyseq_timeout: 500,
+            keyseq_timeout: -1,
             edit_mode: EditMode::Emacs,
+            auto_add_history: false,
         }
     }
 }
@@ -73,6 +90,7 @@
 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
 pub enum HistoryDuplicates {
     AlwaysAdd,
+    /// a line will not be added to the history if it matches the previous entry
     IgnoreConsecutive,
 }
 
@@ -86,12 +104,14 @@
     List,
 }
 
+/// Style of editing / Standard keymaps
 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
 pub enum EditMode {
     Emacs,
     Vi,
 }
 
+/// Configuration builder
 #[derive(Debug, Default)]
 pub struct Builder {
     p: Config,
@@ -99,7 +119,9 @@
 
 impl Builder {
     pub fn new() -> Builder {
-        Builder { p: Config::default() }
+        Builder {
+            p: Config::default(),
+        }
     }
 
     /// Set the maximum length for the history.
@@ -108,7 +130,9 @@
         self
     }
 
-    /// Tell if lines which match the previous history entry are saved or not in the history list.
+    /// Tell if lines which match the previous history entry are saved or not
+    /// in the history list.
+    ///
     /// By default, they are ignored.
     pub fn history_ignore_dups(mut self, yes: bool) -> Builder {
         self.p.history_duplicates = if yes {
@@ -119,7 +143,9 @@
         self
     }
 
-    /// Tell if lines which begin with a space character are saved or not in the history list.
+    /// Tell if lines which begin with a space character are saved or not in
+    /// the history list.
+    ///
     /// By default, they are saved.
     pub fn history_ignore_space(mut self, yes: bool) -> Builder {
         self.p.history_ignore_space = yes;
@@ -132,16 +158,18 @@
         self
     }
 
-    /// The number of possible completions that determines when the user is asked
-    /// whether the list of possibilities should be displayed.
+    /// The number of possible completions that determines when the user is
+    /// asked whether the list of possibilities should be displayed.
     pub fn completion_prompt_limit(mut self, completion_prompt_limit: usize) -> Builder {
         self.p.completion_prompt_limit = completion_prompt_limit;
         self
     }
 
     /// Timeout for ambiguous key sequences in milliseconds.
-    /// Currently, it is used only to distinguish a single ESC from an ESC sequence.
-    /// After seeing an ESC key, wait at most `keyseq_timeout_ms` for another byte.
+    /// Currently, it is used only to distinguish a single ESC from an ESC
+    /// sequence.
+    /// After seeing an ESC key, wait at most `keyseq_timeout_ms` for another
+    /// byte.
     pub fn keyseq_timeout(mut self, keyseq_timeout_ms: i32) -> Builder {
         self.p.keyseq_timeout = keyseq_timeout_ms;
         self
@@ -150,6 +178,18 @@
     /// Choose between Emacs or Vi mode.
     pub fn edit_mode(mut self, edit_mode: EditMode) -> Builder {
         self.p.edit_mode = edit_mode;
+        match edit_mode {
+            EditMode::Emacs => self.p.keyseq_timeout = -1, // no timeout
+            EditMode::Vi => self.p.keyseq_timeout = 500,
+        };
+        self
+    }
+
+    /// Tell if lines are automatically added to the history.
+    ///
+    /// By default, they are not.
+    pub fn auto_add_history(mut self, yes: bool) -> Builder {
+        self.p.auto_add_history = yes;
         self
     }
 
diff --git a/src/consts.rs b/src/consts.rs
index aadac84..5841016 100644
--- a/src/consts.rs
+++ b/src/consts.rs
@@ -5,19 +5,29 @@
     UnknownEscSeq,
     Backspace,
     Char(char),
+    ControlDown,
+    ControlLeft,
+    ControlRight,
+    ControlUp,
     Ctrl(char),
     Delete,
     Down,
     End,
     Enter, // Ctrl('M')
     Esc,
+    F(u8),
     Home,
+    Insert,
     Left,
     Meta(char),
     Null,
     PageDown,
     PageUp,
     Right,
+    ShiftDown,
+    ShiftLeft,
+    ShiftRight,
+    ShiftUp,
     Tab, // Ctrl('I')
     Up,
 }
@@ -28,7 +38,7 @@
         return KeyPress::Char(c);
     }
     match c {
-        '\x00' => KeyPress::Null,
+        '\x00' => KeyPress::Ctrl(' '),
         '\x01' => KeyPress::Ctrl('A'),
         '\x02' => KeyPress::Ctrl('B'),
         '\x03' => KeyPress::Ctrl('C'),
@@ -37,12 +47,13 @@
         '\x06' => KeyPress::Ctrl('F'),
         '\x07' => KeyPress::Ctrl('G'),
         '\x08' => KeyPress::Backspace, // '\b'
-        '\x09' => KeyPress::Tab,
+        '\x09' => KeyPress::Tab,       // '\t'
         '\x0a' => KeyPress::Ctrl('J'), // '\n' (10)
         '\x0b' => KeyPress::Ctrl('K'),
         '\x0c' => KeyPress::Ctrl('L'),
         '\x0d' => KeyPress::Enter, // '\r' (13)
         '\x0e' => KeyPress::Ctrl('N'),
+        '\x0f' => KeyPress::Ctrl('O'),
         '\x10' => KeyPress::Ctrl('P'),
         '\x12' => KeyPress::Ctrl('R'),
         '\x13' => KeyPress::Ctrl('S'),
@@ -50,10 +61,15 @@
         '\x15' => KeyPress::Ctrl('U'),
         '\x16' => KeyPress::Ctrl('V'),
         '\x17' => KeyPress::Ctrl('W'),
+        '\x18' => KeyPress::Ctrl('X'),
         '\x19' => KeyPress::Ctrl('Y'),
         '\x1a' => KeyPress::Ctrl('Z'),
-        '\x1b' => KeyPress::Esc,
-        '\x7f' => KeyPress::Backspace,
+        '\x1b' => KeyPress::Esc, // Ctrl-[
+        '\x1c' => KeyPress::Ctrl('\\'),
+        '\x1d' => KeyPress::Ctrl(']'),
+        '\x1e' => KeyPress::Ctrl('^'),
+        '\x1f' => KeyPress::Ctrl('_'),
+        '\x7f' => KeyPress::Backspace, // Rubout
         _ => KeyPress::Null,
     }
 }
diff --git a/src/edit.rs b/src/edit.rs
new file mode 100644
index 0000000..7dae766
--- /dev/null
+++ b/src/edit.rs
@@ -0,0 +1,535 @@
+//! Command processor
+
+use std::cell::RefCell;
+use std::fmt;
+use std::rc::Rc;
+use unicode_segmentation::UnicodeSegmentation;
+use unicode_width::UnicodeWidthChar;
+
+use super::Result;
+use hint::Hinter;
+use history::{Direction, History};
+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};
+use undo::Changeset;
+
+/// Represent the state during line editing.
+/// Implement rendering.
+pub struct State<'out, 'prompt> {
+    pub out: &'out mut Renderer,
+    prompt: &'prompt str,  // Prompt to display (rl_prompt)
+    prompt_size: Position, // Prompt Unicode/visible width and height
+    pub line: LineBuffer,  // Edited line buffer
+    pub cursor: Position,  /* Cursor position (relative to the start of the prompt
+                            * for `row`) */
+    pub old_rows: usize, // Number of rows used so far (from start of prompt to end of input)
+    history_index: usize, // The history index we are currently editing
+    saved_line_for_history: LineBuffer, // Current edited line before history browsing
+    byte_buffer: [u8; 4],
+    pub changes: Rc<RefCell<Changeset>>, // changes to line, for undo/redo
+    pub hinter: Option<&'out Hinter>,
+}
+
+impl<'out, 'prompt> State<'out, 'prompt> {
+    pub fn new(
+        out: &'out mut Renderer,
+        prompt: &'prompt str,
+        history_index: usize,
+        hinter: Option<&'out Hinter>,
+    ) -> State<'out, 'prompt> {
+        let capacity = MAX_LINE;
+        let prompt_size = out.calculate_position(prompt, Position::default());
+        State {
+            out,
+            prompt,
+            prompt_size,
+            line: LineBuffer::with_capacity(capacity),
+            cursor: prompt_size,
+            old_rows: 0,
+            history_index,
+            saved_line_for_history: LineBuffer::with_capacity(capacity),
+            byte_buffer: [0; 4],
+            changes: Rc::new(RefCell::new(Changeset::new())),
+            hinter,
+        }
+    }
+
+    pub fn next_cmd<R: RawReader>(
+        &mut self,
+        input_state: &mut InputState,
+        rdr: &mut R,
+        single_esc_abort: bool,
+    ) -> Result<Cmd> {
+        loop {
+            let rc = input_state.next_cmd(rdr, self, single_esc_abort);
+            if rc.is_err() && self.out.sigwinch() {
+                self.out.update_size();
+                try!(self.refresh_line());
+                continue;
+            }
+            if let Ok(Cmd::Replace(_, _)) = rc {
+                self.changes.borrow_mut().begin();
+            }
+            return rc;
+        }
+    }
+
+    pub fn backup(&mut self) {
+        self.saved_line_for_history
+            .update(self.line.as_str(), self.line.pos());
+    }
+    pub fn restore(&mut self) {
+        self.line.update(
+            self.saved_line_for_history.as_str(),
+            self.saved_line_for_history.pos(),
+        );
+    }
+
+    pub fn move_cursor(&mut self) -> Result<()> {
+        // calculate the desired position of the cursor
+        let cursor = self
+            .out
+            .calculate_position(&self.line[..self.line.pos()], self.prompt_size);
+        if self.cursor == cursor {
+            return Ok(());
+        }
+        try!(self.out.move_cursor(self.cursor, cursor));
+        self.cursor = cursor;
+        Ok(())
+    }
+
+    fn refresh(&mut self, prompt: &str, prompt_size: Position, hint: Option<String>) -> Result<()> {
+        let (cursor, end_pos) = try!(self.out.refresh_line(
+            prompt,
+            prompt_size,
+            &self.line,
+            hint,
+            self.cursor.row,
+            self.old_rows,
+        ));
+
+        self.cursor = cursor;
+        self.old_rows = end_pos.row;
+        Ok(())
+    }
+
+    fn hint(&self) -> Option<String> {
+        if let Some(hinter) = self.hinter {
+            hinter.hint(self.line.as_str(), self.line.pos())
+        } else {
+            None
+        }
+    }
+}
+
+impl<'out, 'prompt> Refresher for State<'out, 'prompt> {
+    fn refresh_line(&mut self) -> Result<()> {
+        let prompt_size = self.prompt_size;
+        let hint = self.hint();
+        self.refresh(self.prompt, prompt_size, hint)
+    }
+
+    fn refresh_prompt_and_line(&mut self, prompt: &str) -> Result<()> {
+        let prompt_size = self.out.calculate_position(prompt, Position::default());
+        let hint = self.hint();
+        self.refresh(prompt, prompt_size, hint)
+    }
+    fn doing_insert(&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> {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        f.debug_struct("State")
+            .field("prompt", &self.prompt)
+            .field("prompt_size", &self.prompt_size)
+            .field("buf", &self.line)
+            .field("cursor", &self.cursor)
+            .field("cols", &self.out.get_columns())
+            .field("old_rows", &self.old_rows)
+            .field("history_index", &self.history_index)
+            .field("saved_line_for_history", &self.saved_line_for_history)
+            .finish()
+    }
+}
+
+impl<'out, 'prompt> State<'out, 'prompt> {
+    /// Insert the character `ch` at cursor current position.
+    pub fn edit_insert(&mut self, ch: char, n: RepeatCount) -> Result<()> {
+        if let Some(push) = self.line.insert(ch, n) {
+            if push {
+                let prompt_size = self.prompt_size;
+                let hint = self.hint();
+                if n == 1
+                    && self.cursor.col + ch.width().unwrap_or(0) < self.out.get_columns()
+                    && hint.is_none()
+                {
+                    // Avoid a full update of the line in the trivial case.
+                    let cursor = self
+                        .out
+                        .calculate_position(&self.line[..self.line.pos()], self.prompt_size);
+                    self.cursor = cursor;
+                    let bits = ch.encode_utf8(&mut self.byte_buffer);
+                    let bits = bits.as_bytes();
+                    self.out.write_and_flush(bits)
+                } else {
+                    self.refresh(self.prompt, prompt_size, hint)
+                }
+            } else {
+                self.refresh_line()
+            }
+        } else {
+            Ok(())
+        }
+    }
+
+    /// Replace a single (or n) character(s) under the cursor (Vi mode)
+    pub fn edit_replace_char(&mut self, ch: char, n: RepeatCount) -> Result<()> {
+        self.changes.borrow_mut().begin();
+        let succeed = if let Some(chars) = self.line.delete(n) {
+            let count = chars.graphemes(true).count();
+            self.line.insert(ch, count);
+            self.line.move_backward(1);
+            true
+        } else {
+            false
+        };
+        self.changes.borrow_mut().end();
+        if succeed {
+            self.refresh_line()
+        } else {
+            Ok(())
+        }
+    }
+
+    /// Overwrite the character under the cursor (Vi mode)
+    pub fn edit_overwrite_char(&mut self, ch: char) -> Result<()> {
+        if let Some(end) = self.line.next_pos(1) {
+            {
+                let text = ch.encode_utf8(&mut self.byte_buffer);
+                let start = self.line.pos();
+                self.line.replace(start..end, text);
+            }
+            self.refresh_line()
+        } else {
+            Ok(())
+        }
+    }
+
+    // Yank/paste `text` at current position.
+    pub fn edit_yank(
+        &mut self,
+        input_state: &InputState,
+        text: &str,
+        anchor: Anchor,
+        n: RepeatCount,
+    ) -> Result<()> {
+        if let Anchor::After = anchor {
+            self.line.move_forward(1);
+        }
+        if self.line.yank(text, n).is_some() {
+            if !input_state.is_emacs_mode() {
+                self.line.move_backward(1);
+            }
+            self.refresh_line()
+        } else {
+            Ok(())
+        }
+    }
+
+    // Delete previously yanked text and yank/paste `text` at current position.
+    pub fn edit_yank_pop(&mut self, yank_size: usize, text: &str) -> Result<()> {
+        self.changes.borrow_mut().begin();
+        let result = if self.line.yank_pop(yank_size, text).is_some() {
+            self.refresh_line()
+        } else {
+            Ok(())
+        };
+        self.changes.borrow_mut().end();
+        result
+    }
+
+    /// Move cursor on the left.
+    pub fn edit_move_backward(&mut self, n: RepeatCount) -> Result<()> {
+        if self.line.move_backward(n) {
+            self.move_cursor()
+        } else {
+            Ok(())
+        }
+    }
+
+    /// Move cursor on the right.
+    pub fn edit_move_forward(&mut self, n: RepeatCount) -> Result<()> {
+        if self.line.move_forward(n) {
+            self.move_cursor()
+        } else {
+            Ok(())
+        }
+    }
+
+    /// Move cursor to the start of the line.
+    pub fn edit_move_home(&mut self) -> Result<()> {
+        if self.line.move_home() {
+            self.move_cursor()
+        } else {
+            Ok(())
+        }
+    }
+
+    /// Move cursor to the end of the line.
+    pub fn edit_move_end(&mut self) -> Result<()> {
+        if self.line.move_end() {
+            self.move_cursor()
+        } else {
+            Ok(())
+        }
+    }
+
+    pub fn edit_kill(&mut self, mvt: &Movement) -> Result<()> {
+        if self.line.kill(mvt) {
+            self.refresh_line()
+        } else {
+            Ok(())
+        }
+    }
+
+    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(())
+        }
+    }
+
+    pub fn edit_delete(&mut self, n: RepeatCount) -> Result<()> {
+        if self.line.delete(n).is_some() {
+            self.refresh_line()
+        } else {
+            Ok(())
+        }
+    }
+
+    /// Exchange the char before cursor with the character at cursor.
+    pub fn edit_transpose_chars(&mut self) -> Result<()> {
+        self.changes.borrow_mut().begin();
+        let succeed = self.line.transpose_chars();
+        self.changes.borrow_mut().end();
+        if succeed {
+            self.refresh_line()
+        } else {
+            Ok(())
+        }
+    }
+
+    pub fn edit_move_to_prev_word(&mut self, word_def: Word, n: RepeatCount) -> Result<()> {
+        if self.line.move_to_prev_word(word_def, n) {
+            self.move_cursor()
+        } 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()
+        } else {
+            Ok(())
+        }
+    }
+
+    pub fn edit_move_to(&mut self, cs: CharSearch, n: RepeatCount) -> Result<()> {
+        if self.line.move_to(cs, n) {
+            self.move_cursor()
+        } else {
+            Ok(())
+        }
+    }
+
+    pub fn edit_word(&mut self, a: WordAction) -> Result<()> {
+        self.changes.borrow_mut().begin();
+        let succeed = self.line.edit_word(a);
+        self.changes.borrow_mut().end();
+        if succeed {
+            self.refresh_line()
+        } else {
+            Ok(())
+        }
+    }
+
+    pub fn edit_transpose_words(&mut self, n: RepeatCount) -> Result<()> {
+        self.changes.borrow_mut().begin();
+        let succeed = self.line.transpose_words(n);
+        self.changes.borrow_mut().end();
+        if succeed {
+            self.refresh_line()
+        } else {
+            Ok(())
+        }
+    }
+
+    /// Substitute the currently edited line with the next or previous history
+    /// entry.
+    pub fn edit_history_next(&mut self, history: &History, prev: bool) -> Result<()> {
+        if history.is_empty() {
+            return Ok(());
+        }
+        if self.history_index == history.len() {
+            if prev {
+                // Save the current edited line before overwriting it
+                self.backup();
+            } else {
+                return Ok(());
+            }
+        } else if self.history_index == 0 && prev {
+            return Ok(());
+        }
+        if prev {
+            self.history_index -= 1;
+        } else {
+            self.history_index += 1;
+        }
+        if self.history_index < history.len() {
+            let buf = history.get(self.history_index).unwrap();
+            self.changes.borrow_mut().begin();
+            self.line.update(buf, buf.len());
+            self.changes.borrow_mut().end();
+        } else {
+            // Restore current edited line
+            self.restore();
+        }
+        self.refresh_line()
+    }
+
+    // Non-incremental, anchored search
+    pub fn edit_history_search(&mut self, history: &History, dir: Direction) -> Result<()> {
+        if history.is_empty() {
+            return self.out.beep();
+        }
+        if self.history_index == history.len() && dir == Direction::Forward
+            || self.history_index == 0 && dir == Direction::Reverse
+        {
+            return self.out.beep();
+        }
+        if dir == Direction::Reverse {
+            self.history_index -= 1;
+        } else {
+            self.history_index += 1;
+        }
+        if let Some(history_index) = history.starts_with(
+            &self.line.as_str()[..self.line.pos()],
+            self.history_index,
+            dir,
+        ) {
+            self.history_index = history_index;
+            let buf = history.get(history_index).unwrap();
+            self.changes.borrow_mut().begin();
+            self.line.update(buf, buf.len());
+            self.changes.borrow_mut().end();
+            self.refresh_line()
+        } else {
+            self.out.beep()
+        }
+    }
+
+    /// Substitute the currently edited line with the first/last history entry.
+    pub fn edit_history(&mut self, history: &History, first: bool) -> Result<()> {
+        if history.is_empty() {
+            return Ok(());
+        }
+        if self.history_index == history.len() {
+            if first {
+                // Save the current edited line before overwriting it
+                self.backup();
+            } else {
+                return Ok(());
+            }
+        } else if self.history_index == 0 && first {
+            return Ok(());
+        }
+        if first {
+            self.history_index = 0;
+            let buf = history.get(self.history_index).unwrap();
+            self.changes.borrow_mut().begin();
+            self.line.update(buf, buf.len());
+            self.changes.borrow_mut().end();
+        } else {
+            self.history_index = history.len();
+            // Restore current edited line
+            self.restore();
+        }
+        self.refresh_line()
+    }
+}
+
+#[cfg(test)]
+pub fn init_state<'out>(out: &'out mut Renderer, line: &str, pos: usize) -> State<'out, 'static> {
+    State {
+        out,
+        prompt: "",
+        prompt_size: Position::default(),
+        line: LineBuffer::init(line, pos, None),
+        cursor: Position::default(),
+        old_rows: 0,
+        history_index: 0,
+        saved_line_for_history: LineBuffer::with_capacity(100),
+        byte_buffer: [0; 4],
+        changes: Rc::new(RefCell::new(Changeset::new())),
+        hinter: None,
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::init_state;
+    use history::History;
+    use tty::Sink;
+
+    #[test]
+    fn edit_history_next() {
+        let mut out = Sink::new();
+        let line = "current edited line";
+        let mut s = init_state(&mut out, line, 6);
+        let mut history = History::new();
+        history.add("line0");
+        history.add("line1");
+        s.history_index = history.len();
+
+        for _ in 0..2 {
+            s.edit_history_next(&history, false).unwrap();
+            assert_eq!(line, s.line.as_str());
+        }
+
+        s.edit_history_next(&history, true).unwrap();
+        assert_eq!(line, s.saved_line_for_history.as_str());
+        assert_eq!(1, s.history_index);
+        assert_eq!("line1", s.line.as_str());
+
+        for _ in 0..2 {
+            s.edit_history_next(&history, true).unwrap();
+            assert_eq!(line, s.saved_line_for_history.as_str());
+            assert_eq!(0, s.history_index);
+            assert_eq!("line0", s.line.as_str());
+        }
+
+        s.edit_history_next(&history, false).unwrap();
+        assert_eq!(line, s.saved_line_for_history.as_str());
+        assert_eq!(1, s.history_index);
+        assert_eq!("line1", s.line.as_str());
+
+        s.edit_history_next(&history, false).unwrap();
+        // assert_eq!(line, s.saved_line_for_history);
+        assert_eq!(2, s.history_index);
+        assert_eq!(line, s.line.as_str());
+    }
+}
diff --git a/src/error.rs b/src/error.rs
index 009ec65..d9cfdb8 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -1,14 +1,12 @@
 //! Contains error type for handling I/O and Errno errors
-#[cfg(windows)]
-use std::char;
-use std::io;
-use std::error;
-use std::fmt;
 #[cfg(unix)]
 use nix;
-
-#[cfg(unix)]
-use char_iter;
+#[cfg(windows)]
+use std::char;
+use std::error;
+use std::fmt;
+use std::io;
+use std::str;
 
 /// The error type for Rustyline errors that can arise from
 /// I/O related errors or Errno when using the nix-rust library
@@ -22,7 +20,7 @@
     Interrupted,
     /// Chars Error
     #[cfg(unix)]
-    Char(char_iter::CharsError),
+    Char(str::Utf8Error),
     /// Unix Error from syscall
     #[cfg(unix)]
     Errno(nix::Error),
@@ -41,7 +39,7 @@
             #[cfg(unix)]
             ReadlineError::Char(ref err) => err.fmt(f),
             #[cfg(unix)]
-            ReadlineError::Errno(ref err) => write!(f, "Errno: {}", err.errno().desc()),
+            ReadlineError::Errno(ref err) => err.fmt(f),
             #[cfg(windows)]
             ReadlineError::WindowResize => write!(f, "WindowResize"),
             #[cfg(windows)]
@@ -59,7 +57,7 @@
             #[cfg(unix)]
             ReadlineError::Char(ref err) => err.description(),
             #[cfg(unix)]
-            ReadlineError::Errno(ref err) => err.errno().desc(),
+            ReadlineError::Errno(ref err) => err.description(),
             #[cfg(windows)]
             ReadlineError::WindowResize => "WindowResize",
             #[cfg(windows)]
@@ -82,8 +80,8 @@
 }
 
 #[cfg(unix)]
-impl From<char_iter::CharsError> for ReadlineError {
-    fn from(err: char_iter::CharsError) -> ReadlineError {
+impl From<str::Utf8Error> for ReadlineError {
+    fn from(err: str::Utf8Error) -> ReadlineError {
         ReadlineError::Char(err)
     }
 }
diff --git a/src/hint.rs b/src/hint.rs
new file mode 100644
index 0000000..c15db9c
--- /dev/null
+++ b/src/hint.rs
@@ -0,0 +1,15 @@
+//! Hints (suggestions at the right of the prompt as you type).
+
+/// Hints provider
+pub trait Hinter {
+    /// Takes the currently edited `line` with the cursor `pos`ition and
+    /// returns the string that should be displayed or `None`
+    /// if no hint is available for the text the user currently typed.
+    fn hint(&self, line: &str, pos: usize) -> Option<String>;
+}
+
+impl Hinter for () {
+    fn hint(&self, _line: &str, _pos: usize) -> Option<String> {
+        None
+    }
+}
diff --git a/src/history.rs b/src/history.rs
index e283cb4..d4e0c94 100644
--- a/src/history.rs
+++ b/src/history.rs
@@ -1,17 +1,18 @@
 //! History API
 
-use std::collections::VecDeque;
+#[cfg(unix)]
+use libc;
 use std::collections::vec_deque;
+use std::collections::VecDeque;
 use std::fs::File;
 use std::iter::DoubleEndedIterator;
 use std::ops::Index;
 use std::path::Path;
-#[cfg(unix)]
-use libc;
 
 use super::Result;
 use config::{Config, HistoryDuplicates};
 
+/// Search direction
 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
 pub enum Direction {
     Forward,
@@ -55,12 +56,14 @@
         if self.max_len == 0 {
             return false;
         }
-        if line.as_ref().is_empty() ||
-           (self.ignore_space &&
-            line.as_ref()
-                .chars()
-                .next()
-                .map_or(true, |c| c.is_whitespace())) {
+        if line.as_ref().is_empty()
+            || (self.ignore_space
+                && line
+                    .as_ref()
+                    .chars()
+                    .next()
+                    .map_or(true, |c| c.is_whitespace()))
+        {
             return false;
         }
         if self.ignore_dups {
@@ -77,19 +80,22 @@
         true
     }
 
-    /// Returns the number of entries in the history.
+    /// Return the number of entries in the history.
     pub fn len(&self) -> usize {
         self.entries.len()
     }
-    /// Returns true if the history has no entry.
+    /// Return true if the history has no entry.
     pub fn is_empty(&self) -> bool {
         self.entries.is_empty()
     }
 
     /// Set the maximum length for the history. This function can be called even
     /// if there is already some history, the function will make sure to retain
-    /// just the latest `len` elements if the new history length value is smaller
-    /// than the amount of items already inside the history.
+    /// just the latest `len` elements if the new history length value is
+    /// smaller than the amount of items already inside the history.
+    ///
+    /// Like [stifle_history](http://cnswww.cns.cwru.
+    /// edu/php/chet/readline/history.html#IDX11).
     pub fn set_max_len(&mut self, len: usize) {
         self.max_len = len;
         if len == 0 {
@@ -105,6 +111,10 @@
     }
 
     /// Save the history in the specified file.
+    // TODO append_history
+    // http://cnswww.cns.cwru.edu/php/chet/readline/history.html#IDX30
+    // TODO history_truncate_file
+    // http://cnswww.cns.cwru.edu/php/chet/readline/history.html#IDX31
     pub fn save<P: AsRef<Path> + ?Sized>(&self, path: &P) -> Result<()> {
         use std::io::{BufWriter, Write};
 
@@ -121,13 +131,15 @@
             try!(wtr.write_all(entry.as_bytes()));
             try!(wtr.write_all(b"\n"));
         }
+        // https://github.com/rust-lang/rust/issues/32677#issuecomment-204833485
+        try!(wtr.flush());
         Ok(())
     }
 
     /// Load the history from the specified file.
     ///
-    /// # Failure
-    /// Will return `Err` if path does not already exist.
+    /// # Errors
+    /// Will return `Err` if path does not already exist or could not be read.
     pub fn load<P: AsRef<Path> + ?Sized>(&mut self, path: &P) -> Result<()> {
         use std::io::{BufRead, BufReader};
 
@@ -144,29 +156,36 @@
         self.entries.clear()
     }
 
-    /// Search history (start position inclusive [0, len-1])
-    /// Return the absolute index of the nearest history entry that matches `term`.
-    /// Return None if no entry contains `term` between [start, len -1] for forward search
+    /// Search history (start position inclusive [0, len-1]).
+    ///
+    /// Return the absolute index of the nearest history entry that matches
+    /// `term`.
+    ///
+    /// Return None if no entry contains `term` between [start, len -1] for
+    /// forward search
     /// or between [0, start] for reverse search.
     pub fn search(&self, term: &str, start: usize, dir: Direction) -> Option<usize> {
         let test = |entry: &String| entry.contains(term);
         self.search_match(term, start, dir, test)
     }
 
+    /// Anchored search
     pub fn starts_with(&self, term: &str, start: usize, dir: Direction) -> Option<usize> {
         let test = |entry: &String| entry.starts_with(term);
         self.search_match(term, start, dir, test)
     }
 
     fn search_match<F>(&self, term: &str, start: usize, dir: Direction, test: F) -> Option<usize>
-        where F: Fn(&String) -> bool
+    where
+        F: Fn(&String) -> bool,
     {
         if term.is_empty() || start >= self.len() {
             return None;
         }
         match dir {
             Direction::Reverse => {
-                let index = self.entries
+                let index = self
+                    .entries
                     .iter()
                     .rev()
                     .skip(self.entries.len() - 1 - start)
@@ -254,9 +273,9 @@
 #[cfg(test)]
 mod tests {
     extern crate tempdir;
-    use std::path::Path;
     use super::{Direction, History};
     use config::Config;
+    use std::path::Path;
 
     fn init() -> History {
         let mut history = History::new();
@@ -289,7 +308,7 @@
         let mut history = init();
         history.set_max_len(1);
         assert_eq!(1, history.entries.len());
-        assert_eq!(Some(&"line3".to_string()), history.last());
+        assert_eq!(Some(&"line3".to_owned()), history.last());
     }
 
     #[test]
diff --git a/src/keymap.rs b/src/keymap.rs
index 7141066..3652493 100644
--- a/src/keymap.rs
+++ b/src/keymap.rs
@@ -1,71 +1,113 @@
 //! Bindings from keys to command for Emacs and Vi modes
-use std::cell::RefCell;
 use std::collections::HashMap;
-use std::rc::Rc;
+use std::sync::{Arc, RwLock};
 
+use super::Result;
 use config::Config;
 use config::EditMode;
 use consts::KeyPress;
 use tty::RawReader;
-use super::Result;
 
+/// The number of times one command should be repeated.
 pub type RepeatCount = usize;
 
+/// Commands
 #[derive(Debug, Clone, PartialEq)]
 pub enum Cmd {
+    /// abort
     Abort, // Miscellaneous Command
+    /// accept-line
     AcceptLine,
+    /// beginning-of-history
     BeginningOfHistory,
+    /// capitalize-word
     CapitalizeWord,
+    /// clear-screen
     ClearScreen,
+    /// complete
     Complete,
+    /// downcase-word
     DowncaseWord,
+    /// vi-eof-maybe
     EndOfFile,
+    /// end-of-history
     EndOfHistory,
+    /// forward-search-history
     ForwardSearchHistory,
+    /// history-search-backward
     HistorySearchBackward,
+    /// history-search-forward
     HistorySearchForward,
     Insert(RepeatCount, String),
     Interrupt,
+    /// backward-delete-char, backward-kill-line, backward-kill-word
+    /// delete-char, kill-line, kill-word, unix-line-discard, unix-word-rubout,
+    /// 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,
+    /// vi-prev-word
     Move(Movement),
+    /// next-history
     NextHistory,
     Noop,
+    /// vi-replace
+    Overwrite(char),
+    /// previous-history
     PreviousHistory,
+    /// quoted-insert
     QuotedInsert,
-    Replace(RepeatCount, char),
+    /// vi-change-char
+    ReplaceChar(RepeatCount, char),
+    /// vi-change-to, vi-substitute
+    Replace(Movement, Option<String>),
+    /// reverse-search-history
     ReverseSearchHistory,
+    /// self-insert
     SelfInsert(RepeatCount, char),
     Suspend,
+    /// transpose-chars
     TransposeChars,
+    /// transpose-words
     TransposeWords(RepeatCount),
+    /// undo
+    Undo(RepeatCount),
     Unknown,
+    /// upcase-word
     UpcaseWord,
+    /// vi-yank-to
     ViYankTo(Movement),
+    /// yank, vi-put
     Yank(RepeatCount, Anchor),
+    /// yank-pop
     YankPop,
 }
 
 impl Cmd {
     pub fn should_reset_kill_ring(&self) -> bool {
         match *self {
-            Cmd::Kill(Movement::BackwardChar(_)) |
-            Cmd::Kill(Movement::ForwardChar(_)) => true,
-            Cmd::ClearScreen | Cmd::Kill(_) | Cmd::Noop | Cmd::Suspend | Cmd::Yank(_, _) |
-            Cmd::YankPop => false,
+            Cmd::Kill(Movement::BackwardChar(_)) | Cmd::Kill(Movement::ForwardChar(_)) => true,
+            Cmd::ClearScreen
+            | Cmd::Kill(_)
+            | Cmd::Replace(_, _)
+            | Cmd::Noop
+            | Cmd::Suspend
+            | Cmd::Yank(_, _)
+            | Cmd::YankPop => false,
             _ => true,
         }
     }
 
     fn is_repeatable_change(&self) -> bool {
         match *self {
-            Cmd::Insert(_, _) => true,
-            Cmd::Kill(_) => true,
-            Cmd::Replace(_, _) => true,
-            Cmd::SelfInsert(_, _) => true,
+            Cmd::Insert(_, _)
+            | Cmd::Kill(_)
+            | Cmd::ReplaceChar(_, _)
+            | Cmd::Replace(_, _)
+            | Cmd::SelfInsert(_, _)
+            | Cmd::ViYankTo(_)
+            | Cmd::Yank(_, _) => true,
             Cmd::TransposeChars => false, // TODO Validate
-            Cmd::ViYankTo(_) => true,
-            Cmd::Yank(_, _) => true,
             _ => false,
         }
     }
@@ -76,16 +118,41 @@
         }
     }
 
-    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::TransposeChars => Cmd::TransposeChars,
+            Cmd::ReplaceChar(previous, c) => Cmd::ReplaceChar(repeat_count(previous, new), c),
+            Cmd::Replace(ref mvt, ref text) => {
+                if text.is_none() {
+                    let last_insert = wrt.last_insert();
+                    if let Movement::ForwardChar(0) = mvt {
+                        Cmd::Replace(
+                            Movement::ForwardChar(
+                                last_insert.as_ref().map_or(0, |text| text.len()),
+                            ),
+                            last_insert,
+                        )
+                    } else {
+                        Cmd::Replace(mvt.redo(new), 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),
             _ => unreachable!(),
@@ -100,16 +167,18 @@
     }
 }
 
+/// Different word definitions
 #[derive(Debug, Clone, PartialEq, Copy)]
 pub enum Word {
-    // non-blanks characters
+    /// non-blanks characters
     Big,
-    // alphanumeric characters
+    /// alphanumeric characters
     Emacs,
-    // alphanumeric (and '_') characters
+    /// alphanumeric (and '_') characters
     Vi,
 }
 
+/// Where to move with respect to word boundary
 #[derive(Debug, Clone, PartialEq, Copy)]
 pub enum At {
     Start,
@@ -117,13 +186,15 @@
     AfterEnd,
 }
 
+/// Where to paste (relative to cursor position)
 #[derive(Debug, Clone, PartialEq, Copy)]
 pub enum Anchor {
     After,
     Before,
 }
 
-#[derive(Debug, Clone, PartialEq)]
+/// Vi character search
+#[derive(Debug, Clone, PartialEq, Copy)]
 pub enum CharSearch {
     Forward(char),
     // until
@@ -144,21 +215,30 @@
     }
 }
 
-
+/// Where to move
 #[derive(Debug, Clone, PartialEq)]
 pub enum Movement {
     WholeLine, // not really a movement
+    /// beginning-of-line
     BeginningOfLine,
+    /// end-of-line
     EndOfLine,
+    /// backward-word, vi-prev-word
     BackwardWord(RepeatCount, Word), // Backward until start of word
+    /// forward-word, vi-end-word, vi-next-word
     ForwardWord(RepeatCount, At, Word), // Forward until start/end of word
+    /// vi-char-search
     ViCharSearch(RepeatCount, CharSearch),
+    /// vi-first-print
     ViFirstPrint,
+    /// backward-char
     BackwardChar(RepeatCount),
+    /// forward-char
     ForwardChar(RepeatCount),
 }
 
 impl Movement {
+    // Replay this movement with a possible different `RepeatCount`.
     fn redo(&self, new: Option<RepeatCount>) -> Movement {
         match *self {
             Movement::WholeLine => Movement::WholeLine,
@@ -171,8 +251,8 @@
             Movement::ForwardWord(previous, at, word) => {
                 Movement::ForwardWord(repeat_count(previous, new), at, word)
             }
-            Movement::ViCharSearch(previous, ref char_search) => {
-                Movement::ViCharSearch(repeat_count(previous, new), char_search.clone())
+            Movement::ViCharSearch(previous, char_search) => {
+                Movement::ViCharSearch(repeat_count(previous, new), char_search)
             }
             Movement::BackwardChar(previous) => Movement::BackwardChar(repeat_count(previous, new)),
             Movement::ForwardChar(previous) => Movement::ForwardChar(repeat_count(previous, new)),
@@ -180,27 +260,52 @@
     }
 }
 
-pub struct EditState {
+#[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 InputState {
     mode: EditMode,
-    custom_bindings: Rc<RefCell<HashMap<KeyPress, Cmd>>>,
-    // Vi Command/Alternate, Insert/Input mode
-    insert: bool, // vi only ?
+    custom_bindings: Arc<RwLock<HashMap<KeyPress, Cmd>>>,
+    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
 }
 
-impl EditState {
-    pub fn new(config: &Config, custom_bindings: Rc<RefCell<HashMap<KeyPress, Cmd>>>) -> EditState {
-        EditState {
+pub trait Refresher {
+    /// Rewrite the currently edited line accordingly to the buffer content,
+    /// cursor position, and number of columns of the terminal.
+    fn refresh_line(&mut self) -> Result<()>;
+    /// Same as `refresh_line` but with a dynamic prompt.
+    fn refresh_prompt_and_line(&mut self, prompt: &str) -> Result<()>;
+    /// Vi only, switch to insert mode.
+    fn doing_insert(&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 {
+    pub fn new(
+        config: &Config,
+        custom_bindings: Arc<RwLock<HashMap<KeyPress, Cmd>>>,
+    ) -> InputState {
+        InputState {
             mode: config.edit_mode(),
-            custom_bindings: custom_bindings,
-            insert: true,
+            custom_bindings,
+            input_mode: InputMode::Insert,
             num_args: 0,
             last_cmd: Cmd::Noop,
-            consecutive_insert: false,
             last_char_search: None,
         }
     }
@@ -209,16 +314,29 @@
         self.mode == EditMode::Emacs
     }
 
-    pub fn next_cmd<R: RawReader>(&mut self, rdr: &mut R) -> Result<Cmd> {
+    /// Parse user input into one command
+    /// `single_esc_abort` is used in emacs mode on unix platform when a single
+    /// esc key is expected to abort current action.
+    pub fn next_cmd<R: RawReader>(
+        &mut self,
+        rdr: &mut R,
+        wrt: &mut Refresher,
+        single_esc_abort: bool,
+    ) -> Result<Cmd> {
         match self.mode {
-            EditMode::Emacs => self.emacs(rdr),
-            EditMode::Vi if self.insert => self.vi_insert(rdr),
-            EditMode::Vi => self.vi_command(rdr),
+            EditMode::Emacs => self.emacs(rdr, wrt, single_esc_abort),
+            EditMode::Vi if self.input_mode != InputMode::Command => self.vi_insert(rdr, wrt),
+            EditMode::Vi => self.vi_command(rdr, wrt),
         }
     }
 
     // TODO dynamic prompt (arg: ?)
-    fn emacs_digit_argument<R: RawReader>(&mut self, rdr: &mut R, digit: char) -> Result<KeyPress> {
+    fn emacs_digit_argument<R: RawReader>(
+        &mut self,
+        rdr: &mut R,
+        wrt: &mut Refresher,
+        digit: char,
+    ) -> Result<KeyPress> {
         match digit {
             '0'...'9' => {
                 self.num_args = digit.to_digit(10).unwrap() as i16;
@@ -229,198 +347,217 @@
             _ => unreachable!(),
         }
         loop {
-            let key = try!(rdr.next_key());
+            try!(wrt.refresh_prompt_and_line(&format!("(arg: {}) ", self.num_args)));
+            let key = try!(rdr.next_key(true));
             match key {
-                KeyPress::Char(digit @ '0'...'9') |
-                KeyPress::Meta(digit @ '0'...'9') => {
+                KeyPress::Char(digit @ '0'...'9') | KeyPress::Meta(digit @ '0'...'9') => {
                     if self.num_args == -1 {
                         self.num_args *= digit.to_digit(10).unwrap() as i16;
-                    } else {
-                        self.num_args = self.num_args
+                    } else if self.num_args.abs() < 1000 {
+                        // shouldn't ever need more than 4 digits
+                        self.num_args = self
+                            .num_args
                             .saturating_mul(10)
                             .saturating_add(digit.to_digit(10).unwrap() as i16);
                     }
                 }
-                _ => return Ok(key),
+                KeyPress::Char('-') | KeyPress::Meta('-') => {}
+                _ => {
+                    try!(wrt.refresh_line());
+                    return Ok(key);
+                }
             };
         }
     }
 
-    fn emacs<R: RawReader>(&mut self, rdr: &mut R) -> Result<Cmd> {
-        let mut key = try!(rdr.next_key());
+    fn emacs<R: RawReader>(
+        &mut self,
+        rdr: &mut R,
+        wrt: &mut Refresher,
+        single_esc_abort: bool,
+    ) -> Result<Cmd> {
+        let mut key = try!(rdr.next_key(single_esc_abort));
         if let KeyPress::Meta(digit @ '-') = key {
-            key = try!(self.emacs_digit_argument(rdr, digit));
+            key = try!(self.emacs_digit_argument(rdr, wrt, digit));
         } else if let KeyPress::Meta(digit @ '0'...'9') = key {
-            key = try!(self.emacs_digit_argument(rdr, digit));
+            key = try!(self.emacs_digit_argument(rdr, wrt, digit));
         }
         let (n, positive) = self.emacs_num_args(); // consume them in all cases
-        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))
-                      } else {
-                          cmd.clone()
-                      });
+        {
+            let bindings = self.custom_bindings.read().unwrap();
+            if let Some(cmd) = bindings.get(&key) {
+                debug!(target: "rustyline", "Custom command: {:?}", cmd);
+                return Ok(if cmd.is_repeatable() {
+                    cmd.redo(Some(n), wrt)
+                } else {
+                    cmd.clone()
+                });
+            }
         }
         let cmd = match key {
-            KeyPress::Char(c) => {
-                if positive {
-                    Cmd::SelfInsert(n, c)
-                } else {
-                    Cmd::Unknown
-                }
-            }
+            KeyPress::Char(c) => if positive {
+                Cmd::SelfInsert(n, c)
+            } else {
+                Cmd::Unknown
+            },
             KeyPress::Ctrl('A') => Cmd::Move(Movement::BeginningOfLine),
-            KeyPress::Ctrl('B') => {
-                if positive {
-                    Cmd::Move(Movement::BackwardChar(n))
-                } else {
-                    Cmd::Move(Movement::ForwardChar(n))
-                }
-            }
+            KeyPress::Ctrl('B') => if positive {
+                Cmd::Move(Movement::BackwardChar(n))
+            } else {
+                Cmd::Move(Movement::ForwardChar(n))
+            },
             KeyPress::Ctrl('E') => Cmd::Move(Movement::EndOfLine),
-            KeyPress::Ctrl('F') => {
-                if positive {
-                    Cmd::Move(Movement::ForwardChar(n))
-                } else {
-                    Cmd::Move(Movement::BackwardChar(n))
-                }
-            }
-            KeyPress::Ctrl('G') |
-            KeyPress::Esc => Cmd::Abort,
-            KeyPress::Ctrl('H') |
-            KeyPress::Backspace => {
-                if positive {
-                    Cmd::Kill(Movement::BackwardChar(n))
-                } else {
-                    Cmd::Kill(Movement::ForwardChar(n))
-                }
-            }
+            KeyPress::Ctrl('F') => if positive {
+                Cmd::Move(Movement::ForwardChar(n))
+            } else {
+                Cmd::Move(Movement::BackwardChar(n))
+            },
+            KeyPress::Ctrl('G') | KeyPress::Esc | KeyPress::Meta('\x07') => Cmd::Abort,
+            KeyPress::Ctrl('H') | KeyPress::Backspace => if positive {
+                Cmd::Kill(Movement::BackwardChar(n))
+            } else {
+                Cmd::Kill(Movement::ForwardChar(n))
+            },
             KeyPress::Tab => Cmd::Complete,
-            KeyPress::Ctrl('K') => {
-                if positive {
-                    Cmd::Kill(Movement::EndOfLine)
-                } else {
-                    Cmd::Kill(Movement::BeginningOfLine)
-                }
-            }
+            KeyPress::Ctrl('K') => if positive {
+                Cmd::Kill(Movement::EndOfLine)
+            } else {
+                Cmd::Kill(Movement::BeginningOfLine)
+            },
             KeyPress::Ctrl('L') => Cmd::ClearScreen,
             KeyPress::Ctrl('N') => Cmd::NextHistory,
             KeyPress::Ctrl('P') => Cmd::PreviousHistory,
-            KeyPress::Meta('\x08') |
-            KeyPress::Meta('\x7f') => {
-                if positive {
-                    Cmd::Kill(Movement::BackwardWord(n, Word::Emacs))
-                } else {
-                    Cmd::Kill(Movement::ForwardWord(n, At::AfterEnd, Word::Emacs))
+            KeyPress::Ctrl('X') => {
+                let snd_key = try!(rdr.next_key(true));
+                match snd_key {
+                    KeyPress::Ctrl('G') | KeyPress::Esc => Cmd::Abort,
+                    KeyPress::Ctrl('U') => Cmd::Undo(n),
+                    _ => Cmd::Unknown,
                 }
             }
+            KeyPress::Meta('\x08') | KeyPress::Meta('\x7f') => if positive {
+                Cmd::Kill(Movement::BackwardWord(n, Word::Emacs))
+            } else {
+                Cmd::Kill(Movement::ForwardWord(n, At::AfterEnd, Word::Emacs))
+            },
             KeyPress::Meta('<') => Cmd::BeginningOfHistory,
             KeyPress::Meta('>') => Cmd::EndOfHistory,
-            KeyPress::Meta('B') => {
-                if positive {
-                    Cmd::Move(Movement::BackwardWord(n, Word::Emacs))
-                } else {
-                    Cmd::Move(Movement::ForwardWord(n, At::AfterEnd, Word::Emacs))
-                }
-            }
-            KeyPress::Meta('C') => Cmd::CapitalizeWord,
-            KeyPress::Meta('D') => {
-                if positive {
-                    Cmd::Kill(Movement::ForwardWord(n, At::AfterEnd, Word::Emacs))
-                } else {
-                    Cmd::Kill(Movement::BackwardWord(n, Word::Emacs))
-                }
-            }
-            KeyPress::Meta('F') => {
-                if positive {
-                    Cmd::Move(Movement::ForwardWord(n, At::AfterEnd, Word::Emacs))
-                } else {
-                    Cmd::Move(Movement::BackwardWord(n, Word::Emacs))
-                }
-            }
-            KeyPress::Meta('L') => Cmd::DowncaseWord,
-            KeyPress::Meta('T') => Cmd::TransposeWords(n),
-            KeyPress::Meta('U') => Cmd::UpcaseWord,
-            KeyPress::Meta('Y') => Cmd::YankPop,
+            KeyPress::Meta('B') | KeyPress::Meta('b') => if positive {
+                Cmd::Move(Movement::BackwardWord(n, Word::Emacs))
+            } else {
+                Cmd::Move(Movement::ForwardWord(n, At::AfterEnd, Word::Emacs))
+            },
+            KeyPress::Meta('C') | KeyPress::Meta('c') => Cmd::CapitalizeWord,
+            KeyPress::Meta('D') | KeyPress::Meta('d') => if positive {
+                Cmd::Kill(Movement::ForwardWord(n, At::AfterEnd, Word::Emacs))
+            } else {
+                Cmd::Kill(Movement::BackwardWord(n, Word::Emacs))
+            },
+            KeyPress::Meta('F') | KeyPress::Meta('f') => if positive {
+                Cmd::Move(Movement::ForwardWord(n, At::AfterEnd, Word::Emacs))
+            } else {
+                Cmd::Move(Movement::BackwardWord(n, Word::Emacs))
+            },
+            KeyPress::Meta('L') | KeyPress::Meta('l') => Cmd::DowncaseWord,
+            KeyPress::Meta('T') | KeyPress::Meta('t') => Cmd::TransposeWords(n),
+            KeyPress::Meta('U') | KeyPress::Meta('u') => Cmd::UpcaseWord,
+            KeyPress::Meta('Y') | KeyPress::Meta('y') => Cmd::YankPop,
             _ => self.common(key, n, positive),
         };
         debug!(target: "rustyline", "Emacs command: {:?}", cmd);
         Ok(cmd)
     }
 
-    fn vi_arg_digit<R: RawReader>(&mut self, rdr: &mut R, digit: char) -> Result<KeyPress> {
+    fn vi_arg_digit<R: RawReader>(
+        &mut self,
+        rdr: &mut R,
+        wrt: &mut Refresher,
+        digit: char,
+    ) -> Result<KeyPress> {
         self.num_args = digit.to_digit(10).unwrap() as i16;
         loop {
-            let key = try!(rdr.next_key());
+            try!(wrt.refresh_prompt_and_line(&format!("(arg: {}) ", self.num_args)));
+            let key = try!(rdr.next_key(false));
             match key {
                 KeyPress::Char(digit @ '0'...'9') => {
-                    self.num_args = self.num_args
-                        .saturating_mul(10)
-                        .saturating_add(digit.to_digit(10).unwrap() as i16);
+                    if self.num_args.abs() < 1000 {
+                        // shouldn't ever need more than 4 digits
+                        self.num_args = self
+                            .num_args
+                            .saturating_mul(10)
+                            .saturating_add(digit.to_digit(10).unwrap() as i16);
+                    }
                 }
-                _ => return Ok(key),
+                _ => {
+                    try!(wrt.refresh_line());
+                    return Ok(key);
+                }
             };
         }
     }
 
-    fn vi_command<R: RawReader>(&mut self, rdr: &mut R) -> Result<Cmd> {
-        let mut key = try!(rdr.next_key());
+    fn vi_command<R: RawReader>(&mut self, rdr: &mut R, wrt: &mut Refresher) -> Result<Cmd> {
+        let mut key = try!(rdr.next_key(false));
         if let KeyPress::Char(digit @ '1'...'9') = key {
-            key = try!(self.vi_arg_digit(rdr, digit));
+            key = try!(self.vi_arg_digit(rdr, wrt, digit));
         }
         let no_num_args = self.num_args == 0;
         let n = self.vi_num_args(); // consume them in all cases
-        if let Some(cmd) = self.custom_bindings.borrow().get(&key) {
-            debug!(target: "rustyline", "Custom command: {:?}", cmd);
-            return Ok(if cmd.is_repeatable() {
-                          if no_num_args {
-                              cmd.redo(None)
-                          } else {
-                              cmd.redo(Some(n))
-                          }
-                      } else {
-                          cmd.clone()
-                      });
+        {
+            let bindings = self.custom_bindings.read().unwrap();
+            if let Some(cmd) = bindings.get(&key) {
+                debug!(target: "rustyline", "Custom command: {:?}", cmd);
+                return Ok(if cmd.is_repeatable() {
+                    if no_num_args {
+                        cmd.redo(None, wrt)
+                    } else {
+                        cmd.redo(Some(n), wrt)
+                    }
+                } else {
+                    cmd.clone()
+                });
+            }
         }
         let cmd = match key {
             KeyPress::Char('$') |
             KeyPress::End => Cmd::Move(Movement::EndOfLine),
-            KeyPress::Char('.') => { // vi-redo
+            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
             KeyPress::Char('0') => Cmd::Move(Movement::BeginningOfLine),
             KeyPress::Char('^') => Cmd::Move(Movement::ViFirstPrint),
             KeyPress::Char('a') => {
-                // vi-append-mode: Vi enter insert mode after the cursor.
-                self.insert = true;
+                // vi-append-mode
+                self.input_mode = InputMode::Insert;
+                wrt.doing_insert();
                 Cmd::Move(Movement::ForwardChar(n))
             }
             KeyPress::Char('A') => {
-                // vi-append-eol: Vi enter insert mode at end of line.
-                self.insert = true;
+                // vi-append-eol
+                self.input_mode = InputMode::Insert;
+                wrt.doing_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;
-                match try!(self.vi_cmd_motion(rdr, key, n)) {
-                    Some(mvt) => Cmd::Kill(mvt),
+                self.input_mode = InputMode::Insert;
+                match try!(self.vi_cmd_motion(rdr, wrt, key, n)) {
+                    Some(mvt) => Cmd::Replace(mvt, None),
                     None => Cmd::Unknown,
                 }
             }
             KeyPress::Char('C') => {
-                self.insert = true;
-                Cmd::Kill(Movement::EndOfLine)
+                self.input_mode = InputMode::Insert;
+                Cmd::Replace(Movement::EndOfLine, None)
             }
             KeyPress::Char('d') => {
-                match try!(self.vi_cmd_motion(rdr, key, n)) {
+                match try!(self.vi_cmd_motion(rdr, wrt, key, n)) {
                     Some(mvt) => Cmd::Kill(mvt),
                     None => Cmd::Unknown,
                 }
@@ -431,12 +568,14 @@
             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;
+                wrt.doing_insert();
                 Cmd::Noop
             }
             KeyPress::Char('I') => {
                 // vi-insert-beg
-                self.insert = true;
+                self.input_mode = InputMode::Insert;
+                wrt.doing_insert();
                 Cmd::Move(Movement::BeginningOfLine)
             }
             KeyPress::Char(c) if c == 'f' || c == 'F' || c == 't' || c == 'T' => {
@@ -449,7 +588,7 @@
             }
             KeyPress::Char(';') => {
                 match self.last_char_search {
-                    Some(ref cs) => Cmd::Move(Movement::ViCharSearch(n, cs.clone())),
+                    Some(cs) => Cmd::Move(Movement::ViCharSearch(n, cs)),
                     None => Cmd::Noop,
                 }
             }
@@ -463,32 +602,37 @@
             KeyPress::Char('p') => Cmd::Yank(n, Anchor::After), // vi-put
             KeyPress::Char('P') => Cmd::Yank(n, Anchor::Before), // vi-put
             KeyPress::Char('r') => {
-                // vi-replace-char: Vi replace character under the cursor with the next character typed.
-                let ch = try!(rdr.next_key());
+                // 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,
                 }
             }
-            // 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 (overwrite-mode)
+                self.input_mode = InputMode::Replace;
+                Cmd::Replace(Movement::ForwardChar(0), None)
+            }
             KeyPress::Char('s') => {
-                // vi-substitute-char: Vi replace character under the cursor and enter insert mode.
-                self.insert = true;
-                Cmd::Kill(Movement::ForwardChar(n))
+                // vi-substitute-char:
+                self.input_mode = InputMode::Insert;
+                Cmd::Replace(Movement::ForwardChar(n), None)
             }
             KeyPress::Char('S') => {
-                // vi-substitute-line: Vi substitute entire line.
-                self.insert = true;
-                Cmd::Kill(Movement::WholeLine)
+                // vi-substitute-line:
+                self.input_mode = InputMode::Insert;
+                Cmd::Replace(Movement::WholeLine, None)
             }
+            KeyPress::Char('u') => Cmd::Undo(n),
             // KeyPress::Char('U') => Cmd::???, // revert-line
             KeyPress::Char('w') => Cmd::Move(Movement::ForwardWord(n, At::Start, Word::Vi)), // vi-next-word
             KeyPress::Char('W') => Cmd::Move(Movement::ForwardWord(n, At::Start, Word::Big)), // vi-next-word
             KeyPress::Char('x') => Cmd::Kill(Movement::ForwardChar(n)), // vi-delete: TODO move backward if eol
             KeyPress::Char('X') => Cmd::Kill(Movement::BackwardChar(n)), // vi-rubout
             KeyPress::Char('y') => {
-                match try!(self.vi_cmd_motion(rdr, key, n)) {
+                match try!(self.vi_cmd_motion(rdr, wrt, key, n)) {
                     Some(mvt) => Cmd::ViYankTo(mvt),
                     None => Cmd::Unknown,
                 }
@@ -508,11 +652,11 @@
             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,
@@ -520,130 +664,137 @@
         };
         debug!(target: "rustyline", "Vi command: {:?}", cmd);
         if cmd.is_repeatable_change() {
-            self.update_last_cmd(cmd.clone());
+            self.last_cmd = cmd.clone();
         }
         Ok(cmd)
     }
 
-    fn vi_insert<R: RawReader>(&mut self, rdr: &mut R) -> Result<Cmd> {
-        let key = try!(rdr.next_key());
-        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)
-                      } else {
-                          cmd.clone()
-                      });
+    fn vi_insert<R: RawReader>(&mut self, rdr: &mut R, wrt: &mut Refresher) -> Result<Cmd> {
+        let key = try!(rdr.next_key(false));
+        {
+            let bindings = self.custom_bindings.read().unwrap();
+            if let Some(cmd) = bindings.get(&key) {
+                debug!(target: "rustyline", "Custom command: {:?}", cmd);
+                return Ok(if cmd.is_repeatable() {
+                    cmd.redo(None, wrt)
+                } else {
+                    cmd.clone()
+                });
+            }
         }
         let cmd = match key {
-            KeyPress::Char(c) => Cmd::SelfInsert(1, c),
-            KeyPress::Ctrl('H') |
-            KeyPress::Backspace => Cmd::Kill(Movement::BackwardChar(1)),
+            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;
+                // vi-movement-mode/vi-command-mode
+                self.input_mode = InputMode::Command;
+                wrt.done_inserting();
                 Cmd::Move(Movement::BackwardChar(1))
             }
             _ => self.common(key, 1, true),
         };
         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 if let (Cmd::SelfInsert(_, _), Cmd::SelfInsert(_, _)) = (&self.last_cmd, &cmd) {
+                // inserting...
+            } else {
+                self.last_cmd = cmd.clone();
+            }
         }
-        self.consecutive_insert = match cmd {
-            Cmd::SelfInsert(_, _) => true,
-            _ => false,
-        };
         Ok(cmd)
     }
 
-    fn vi_cmd_motion<R: RawReader>(&mut self,
-                                   rdr: &mut R,
-                                   key: KeyPress,
-                                   n: RepeatCount)
-                                   -> Result<Option<Movement>> {
-        let mut mvt = try!(rdr.next_key());
+    fn vi_cmd_motion<R: RawReader>(
+        &mut self,
+        rdr: &mut R,
+        wrt: &mut Refresher,
+        key: KeyPress,
+        n: RepeatCount,
+    ) -> Result<Option<Movement>> {
+        let mut mvt = try!(rdr.next_key(false));
         if mvt == key {
             return Ok(Some(Movement::WholeLine));
         }
         let mut n = n;
         if let KeyPress::Char(digit @ '1'...'9') = mvt {
             // vi-arg-digit
-            mvt = try!(self.vi_arg_digit(rdr, digit));
+            mvt = try!(self.vi_arg_digit(rdr, wrt, digit));
             n = self.vi_num_args().saturating_mul(n);
         }
         Ok(match mvt {
-               KeyPress::Char('$') => Some(Movement::EndOfLine), // vi-change-to-eol: Vi change to end of line.
-               KeyPress::Char('0') => Some(Movement::BeginningOfLine), // vi-kill-line-prev: Vi cut from beginning of line to cursor.
-               KeyPress::Char('^') => Some(Movement::ViFirstPrint),
-               KeyPress::Char('b') => Some(Movement::BackwardWord(n, Word::Vi)),
-               KeyPress::Char('B') => Some(Movement::BackwardWord(n, Word::Big)),
-               KeyPress::Char('e') => Some(Movement::ForwardWord(n, At::AfterEnd, Word::Vi)),
-               KeyPress::Char('E') => Some(Movement::ForwardWord(n, At::AfterEnd, Word::Big)),
-               KeyPress::Char(c) if c == 'f' || c == 'F' || c == 't' || c == 'T' => {
-            let cs = try!(self.vi_char_search(rdr, c));
-            match cs {
+            KeyPress::Char('$') => Some(Movement::EndOfLine),
+            KeyPress::Char('0') => Some(Movement::BeginningOfLine),
+            KeyPress::Char('^') => Some(Movement::ViFirstPrint),
+            KeyPress::Char('b') => Some(Movement::BackwardWord(n, Word::Vi)),
+            KeyPress::Char('B') => Some(Movement::BackwardWord(n, Word::Big)),
+            KeyPress::Char('e') => Some(Movement::ForwardWord(n, At::AfterEnd, Word::Vi)),
+            KeyPress::Char('E') => Some(Movement::ForwardWord(n, At::AfterEnd, Word::Big)),
+            KeyPress::Char(c) if c == 'f' || c == 'F' || c == 't' || c == 'T' => {
+                let cs = try!(self.vi_char_search(rdr, c));
+                match cs {
+                    Some(cs) => Some(Movement::ViCharSearch(n, cs)),
+                    None => None,
+                }
+            }
+            KeyPress::Char(';') => match self.last_char_search {
                 Some(cs) => Some(Movement::ViCharSearch(n, cs)),
                 None => None,
+            },
+            KeyPress::Char(',') => match self.last_char_search {
+                Some(ref cs) => Some(Movement::ViCharSearch(n, cs.opposite())),
+                None => None,
+            },
+            KeyPress::Char('h') | KeyPress::Ctrl('H') | KeyPress::Backspace => {
+                Some(Movement::BackwardChar(n))
             }
-        }
-               KeyPress::Char(';') => {
-                   match self.last_char_search {
-                       Some(ref cs) => Some(Movement::ViCharSearch(n, cs.clone())),
-                       None => None,
-                   }
-               }
-               KeyPress::Char(',') => {
-                   match self.last_char_search {
-                       Some(ref cs) => Some(Movement::ViCharSearch(n, cs.opposite())),
-                       None => None,
-                   }
-               }
-               KeyPress::Char('h') |
-               KeyPress::Ctrl('H') |
-               KeyPress::Backspace => Some(Movement::BackwardChar(n)), // vi-delete-prev-char: Vi move to previous character (backspace).
-               KeyPress::Char('l') |
-               KeyPress::Char(' ') => Some(Movement::ForwardChar(n)),
-               KeyPress::Char('w') => {
-            // 'cw' is 'ce'
-            if key == KeyPress::Char('c') {
-                Some(Movement::ForwardWord(n, At::AfterEnd, Word::Vi))
-            } else {
-                Some(Movement::ForwardWord(n, At::Start, Word::Vi))
+            KeyPress::Char('l') | KeyPress::Char(' ') => Some(Movement::ForwardChar(n)),
+            KeyPress::Char('w') => {
+                // 'cw' is 'ce'
+                if key == KeyPress::Char('c') {
+                    Some(Movement::ForwardWord(n, At::AfterEnd, Word::Vi))
+                } else {
+                    Some(Movement::ForwardWord(n, At::Start, Word::Vi))
+                }
             }
-        }
-               KeyPress::Char('W') => {
-            // 'cW' is 'cE'
-            if key == KeyPress::Char('c') {
-                Some(Movement::ForwardWord(n, At::AfterEnd, Word::Big))
-            } else {
-                Some(Movement::ForwardWord(n, At::Start, Word::Big))
+            KeyPress::Char('W') => {
+                // 'cW' is 'cE'
+                if key == KeyPress::Char('c') {
+                    Some(Movement::ForwardWord(n, At::AfterEnd, Word::Big))
+                } else {
+                    Some(Movement::ForwardWord(n, At::Start, Word::Big))
+                }
             }
-        }
-               _ => None,
-           })
+            _ => None,
+        })
     }
 
-    fn vi_char_search<R: RawReader>(&mut self,
-                                    rdr: &mut R,
-                                    cmd: char)
-                                    -> Result<Option<CharSearch>> {
-        let ch = try!(rdr.next_key());
+    fn vi_char_search<R: RawReader>(
+        &mut self,
+        rdr: &mut R,
+        cmd: char,
+    ) -> Result<Option<CharSearch>> {
+        let ch = try!(rdr.next_key(false));
         Ok(match ch {
-               KeyPress::Char(ch) => {
-            let cs = match cmd {
-                'f' => CharSearch::Forward(ch),
-                't' => CharSearch::ForwardBefore(ch),
-                'F' => CharSearch::Backward(ch),
-                'T' => CharSearch::BackwardAfter(ch),
-                _ => unreachable!(),
-            };
-            self.last_char_search = Some(cs.clone());
-            Some(cs)
-        }
-               _ => None,
-           })
+            KeyPress::Char(ch) => {
+                let cs = match cmd {
+                    'f' => CharSearch::Forward(ch),
+                    't' => CharSearch::ForwardBefore(ch),
+                    'F' => CharSearch::Backward(ch),
+                    'T' => CharSearch::BackwardAfter(ch),
+                    _ => unreachable!(),
+                };
+                self.last_char_search = Some(cs);
+                Some(cs)
+            }
+            _ => None,
+        })
     }
 
     fn common(&mut self, key: KeyPress, n: RepeatCount, positive: bool) -> Cmd {
@@ -682,9 +833,9 @@
             KeyPress::Ctrl('T') => Cmd::TransposeChars,
             KeyPress::Ctrl('U') => {
                 if positive {
-                Cmd::Kill(Movement::BeginningOfLine)
+                    Cmd::Kill(Movement::BeginningOfLine)
                 } else {
-                Cmd::Kill(Movement::EndOfLine)
+                    Cmd::Kill(Movement::EndOfLine)
                 }
             },
             KeyPress::Ctrl('Q') | // most terminals override Ctrl+Q to resume execution
@@ -704,6 +855,7 @@
                 }
             }
             KeyPress::Ctrl('Z') => Cmd::Suspend,
+            KeyPress::Ctrl('_') => Cmd::Undo(n),
             KeyPress::UnknownEscSeq => Cmd::Noop,
             _ => Cmd::Unknown,
         }
@@ -739,26 +891,4 @@
             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 635cb4f..15b6649 100644
--- a/src/kill_ring.rs
+++ b/src/kill_ring.rs
@@ -1,4 +1,5 @@
-//! Kill Ring
+//! Kill Ring management
+use line_buffer::{DeleteListener, Direction};
 
 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
 enum Action {
@@ -19,6 +20,7 @@
     index: usize,
     // whether or not the last command was a kill or a yank
     last_action: Action,
+    killing: bool,
 }
 
 impl KillRing {
@@ -28,6 +30,7 @@
             slots: Vec::with_capacity(size),
             index: 0,
             last_action: Action::Other,
+            killing: false,
         }
     }
 
@@ -102,9 +105,28 @@
     }
 }
 
+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;
+        }
+        let mode = match dir {
+            Direction::Forward => Mode::Append,
+            Direction::Backward => Mode::Prepend,
+        };
+        self.kill(string, mode);
+    }
+    fn stop_killing(&mut self) {
+        self.killing = false;
+    }
+}
+
 #[cfg(test)]
 mod tests {
-    use super::{Action, Mode, KillRing};
+    use super::{Action, KillRing, Mode};
 
     #[test]
     fn disabled() {
@@ -187,9 +209,9 @@
         kill_ring.reset();
         kill_ring.kill("word2", Mode::Append);
 
-        assert_eq!(Some(&"word2".to_string()), kill_ring.yank());
+        assert_eq!(Some(&"word2".to_owned()), kill_ring.yank());
         assert_eq!(Action::Yank(5), kill_ring.last_action);
-        assert_eq!(Some(&"word2".to_string()), kill_ring.yank());
+        assert_eq!(Some(&"word2".to_owned()), kill_ring.yank());
         assert_eq!(Action::Yank(5), kill_ring.last_action);
     }
 
@@ -202,8 +224,8 @@
 
         assert_eq!(None, kill_ring.yank_pop());
         kill_ring.yank();
-        assert_eq!(Some((9, &"word1".to_string())), kill_ring.yank_pop());
-        assert_eq!(Some((5, &"longword2".to_string())), kill_ring.yank_pop());
-        assert_eq!(Some((9, &"word1".to_string())), kill_ring.yank_pop());
+        assert_eq!(Some((9, &"word1".to_owned())), kill_ring.yank_pop());
+        assert_eq!(Some((5, &"longword2".to_owned())), kill_ring.yank_pop());
+        assert_eq!(Some((9, &"word1".to_owned())), kill_ring.yank_pop());
     }
 }
diff --git a/src/lib.rs b/src/lib.rs
index 24829e0..3fb9a2a 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,6 +1,7 @@
 //! Readline for Rust
 //!
-//! This implementation is based on [Antirez's Linenoise](https://github.com/antirez/linenoise)
+//! This implementation is based on [Antirez's
+//! Linenoise](https://github.com/antirez/linenoise)
 //!
 //! # Example
 //!
@@ -10,625 +11,79 @@
 //! let mut rl = rustyline::Editor::<()>::new();
 //! let readline = rl.readline(">> ");
 //! match readline {
-//!     Ok(line) => println!("Line: {:?}",line),
-//!     Err(_)   => println!("No input"),
+//!     Ok(line) => println!("Line: {:?}", line),
+//!     Err(_) => println!("No input"),
 //! }
 //! ```
 #![allow(unknown_lints)]
 
 extern crate libc;
-extern crate encode_unicode;
 #[macro_use]
 extern crate log;
-extern crate unicode_segmentation;
-extern crate unicode_width;
 #[cfg(unix)]
 extern crate nix;
+extern crate unicode_segmentation;
+extern crate unicode_width;
 #[cfg(windows)]
 extern crate winapi;
-#[cfg(windows)]
-extern crate kernel32;
 
 pub mod completion;
+pub mod config;
 mod consts;
+mod edit;
 pub mod error;
+pub mod hint;
 pub mod history;
 mod keymap;
 mod kill_ring;
 pub mod line_buffer;
-#[cfg(unix)]
-mod char_iter;
-pub mod config;
+mod undo;
 
 mod tty;
 
-use std::cell::RefCell;
 use std::collections::HashMap;
 use std::fmt;
 use std::io::{self, Write};
-use std::mem;
 use std::path::Path;
-use std::rc::Rc;
 use std::result;
-use unicode_segmentation::UnicodeSegmentation;
-use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
+use std::sync::{Arc, Mutex, RwLock};
+use unicode_width::UnicodeWidthStr;
 
-use tty::{RawMode, RawReader, Terminal, Term};
+use tty::{RawMode, RawReader, Renderer, Term, Terminal};
 
-use encode_unicode::CharExt;
-use completion::{Completer, longest_common_prefix};
-use history::{Direction, History};
-use line_buffer::{LineBuffer, MAX_LINE, WordAction};
-pub use keymap::{Anchor, At, CharSearch, Cmd, Movement, RepeatCount, Word};
-use keymap::EditState;
-use kill_ring::{Mode, KillRing};
+use completion::{longest_common_prefix, Completer};
 pub use config::{CompletionType, Config, EditMode, HistoryDuplicates};
 pub use consts::KeyPress;
+use edit::State;
+use hint::Hinter;
+use history::{Direction, History};
+pub use keymap::{Anchor, At, CharSearch, Cmd, Movement, RepeatCount, Word};
+use keymap::{InputState, Refresher};
+use kill_ring::{KillRing, Mode};
+use line_buffer::WordAction;
 
 /// The error type for I/O and Linux Syscalls (Errno)
 pub type Result<T> = result::Result<T, error::ReadlineError>;
 
-// Represent the state during line editing.
-struct State<'out, 'prompt> {
-    out: &'out mut Write,
-    prompt: &'prompt str, // Prompt to display
-    prompt_size: Position, // Prompt Unicode width and height
-    line: LineBuffer, // Edited line buffer
-    cursor: Position, // Cursor position (relative to the start of the prompt for `row`)
-    cols: usize, // Number of columns in terminal
-    old_rows: usize, // Number of rows used so far (from start of prompt to end of input)
-    history_index: usize, // The history index we are currently editing
-    snapshot: LineBuffer, // Current edited line before history browsing/completion
-    term: Terminal, // terminal
-    edit_state: EditState,
-}
-
-#[derive(Copy, Clone, Debug, Default)]
-struct Position {
-    col: usize,
-    row: usize,
-}
-
-impl<'out, 'prompt> State<'out, 'prompt> {
-    fn new(out: &'out mut Write,
-           term: Terminal,
-           config: &Config,
-           prompt: &'prompt str,
-           history_index: usize,
-           custom_bindings: Rc<RefCell<HashMap<KeyPress, Cmd>>>)
-           -> State<'out, 'prompt> {
-        let capacity = MAX_LINE;
-        let cols = term.get_columns();
-        let prompt_size = calculate_position(prompt, Position::default(), cols);
-        State {
-            out: out,
-            prompt: prompt,
-            prompt_size: prompt_size,
-            line: LineBuffer::with_capacity(capacity),
-            cursor: prompt_size,
-            cols: cols,
-            old_rows: prompt_size.row,
-            history_index: history_index,
-            snapshot: LineBuffer::with_capacity(capacity),
-            term: term,
-            edit_state: EditState::new(config, custom_bindings),
-        }
-    }
-
-    fn next_cmd<R: RawReader>(&mut self, rdr: &mut R) -> Result<Cmd> {
-        loop {
-            let rc = self.edit_state.next_cmd(rdr);
-            if rc.is_err() && self.term.sigwinch() {
-                self.update_columns();
-                try!(self.refresh_line());
-                continue;
-            }
-            return rc;
-        }
-    }
-
-    fn snapshot(&mut self) {
-        mem::swap(&mut self.line, &mut self.snapshot);
-    }
-
-    fn backup(&mut self) {
-        self.snapshot.backup(&self.line);
-    }
-
-    /// Rewrite the currently edited line accordingly to the buffer content,
-    /// cursor position, and number of columns of the terminal.
-    fn refresh_line(&mut self) -> Result<()> {
-        let prompt_size = self.prompt_size;
-        self.refresh(self.prompt, prompt_size)
-    }
-
-    fn refresh_prompt_and_line(&mut self, prompt: &str) -> Result<()> {
-        let prompt_size = calculate_position(prompt, Position::default(), self.cols);
-        self.refresh(prompt, prompt_size)
-    }
-
-    #[cfg(unix)]
-    fn refresh(&mut self, prompt: &str, prompt_size: Position) -> Result<()> {
-        use std::fmt::Write;
-
-        // calculate the position of the end of the input line
-        let end_pos = calculate_position(&self.line, prompt_size, self.cols);
-        // calculate the desired position of the cursor
-        let cursor = calculate_position(&self.line[..self.line.pos()], prompt_size, self.cols);
-
-        let mut ab = String::new();
-
-        let cursor_row_movement = self.old_rows - self.cursor.row;
-        // move the cursor down as required
-        if cursor_row_movement > 0 {
-            write!(ab, "\x1b[{}B", cursor_row_movement).unwrap();
-        }
-        // clear old rows
-        for _ in 0..self.old_rows {
-            ab.push_str("\r\x1b[0K\x1b[1A");
-        }
-        // clear the line
-        ab.push_str("\r\x1b[0K");
-
-        // display the prompt
-        ab.push_str(prompt);
-        // display the input line
-        ab.push_str(&self.line);
-        // we have to generate our own newline on line wrap
-        if end_pos.col == 0 && end_pos.row > 0 {
-            ab.push_str("\n");
-        }
-        // position the cursor
-        let cursor_row_movement = end_pos.row - cursor.row;
-        // move the cursor up as required
-        if cursor_row_movement > 0 {
-            write!(ab, "\x1b[{}A", cursor_row_movement).unwrap();
-        }
-        // position the cursor within the line
-        if cursor.col > 0 {
-            write!(ab, "\r\x1b[{}C", cursor.col).unwrap();
-        } else {
-            ab.push('\r');
-        }
-
-        self.cursor = cursor;
-        self.old_rows = end_pos.row;
-
-        write_and_flush(self.out, ab.as_bytes())
-    }
-
-    #[cfg(windows)]
-    fn refresh(&mut self, prompt: &str, prompt_size: Position) -> Result<()> {
-        // calculate the position of the end of the input line
-        let end_pos = calculate_position(&self.line, prompt_size, self.cols);
-        // calculate the desired position of the cursor
-        let cursor = calculate_position(&self.line[..self.line.pos()], prompt_size, self.cols);
-
-        // position at the start of the prompt, clear to end of previous input
-        let mut info = try!(self.term.get_console_screen_buffer_info());
-        info.dwCursorPosition.X = 0;
-        info.dwCursorPosition.Y -= self.cursor.row as i16;
-        try!(self.term
-                 .set_console_cursor_position(info.dwCursorPosition));
-        let mut _count = 0;
-        try!(self.term
-                 .fill_console_output_character((info.dwSize.X * (self.old_rows as i16 + 1)) as
-                                                u32,
-                                                info.dwCursorPosition));
-        let mut ab = String::new();
-        // display the prompt
-        ab.push_str(prompt); // TODO handle ansi escape code (SetConsoleTextAttribute)
-        // display the input line
-        ab.push_str(&self.line);
-        try!(write_and_flush(self.out, ab.as_bytes()));
-
-        // position the cursor
-        let mut info = try!(self.term.get_console_screen_buffer_info());
-        info.dwCursorPosition.X = cursor.col as i16;
-        info.dwCursorPosition.Y -= (end_pos.row - cursor.row) as i16;
-        try!(self.term
-                 .set_console_cursor_position(info.dwCursorPosition));
-
-        self.cursor = cursor;
-        self.old_rows = end_pos.row;
-
-        Ok(())
-    }
-
-    fn update_columns(&mut self) {
-        self.cols = self.term.get_columns();
-    }
-}
-
-impl<'out, 'prompt> fmt::Debug for State<'out, 'prompt> {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        f.debug_struct("State")
-            .field("prompt", &self.prompt)
-            .field("prompt_size", &self.prompt_size)
-            .field("buf", &self.line)
-            .field("cursor", &self.cursor)
-            .field("cols", &self.cols)
-            .field("old_rows", &self.old_rows)
-            .field("history_index", &self.history_index)
-            .field("snapshot", &self.snapshot)
-            .finish()
-    }
-}
-
-fn write_and_flush(w: &mut Write, buf: &[u8]) -> Result<()> {
-    try!(w.write_all(buf));
-    try!(w.flush());
-    Ok(())
-}
-
-/// Beep, used for completion when there is nothing to complete or when all
-/// the choices were already shown.
-fn beep() -> Result<()> {
-    write_and_flush(&mut io::stderr(), b"\x07") // TODO bell-style
-}
-
-/// Calculate the number of columns and rows used to display `s` on a `cols` width terminal
-/// starting at `orig`.
-/// Control characters are treated as having zero width.
-/// Characters with 2 column width are correctly handled (not splitted).
-#[allow(if_same_then_else)]
-fn calculate_position(s: &str, orig: Position, cols: usize) -> Position {
-    let mut pos = orig;
-    let mut esc_seq = 0;
-    for c in s.chars() {
-        let cw = if esc_seq == 1 {
-            if c == '[' {
-                // CSI
-                esc_seq = 2;
-            } else {
-                // two-character sequence
-                esc_seq = 0;
-            }
-            None
-        } else if esc_seq == 2 {
-            if c == ';' || (c >= '0' && c <= '9') {
-            } else if c == 'm' {
-                // last
-                esc_seq = 0;
-            } else {
-                // not supported
-                esc_seq = 0;
-            }
-            None
-        } else if c == '\x1b' {
-            esc_seq = 1;
-            None
-        } else if c == '\n' {
-            pos.col = 0;
-            pos.row += 1;
-            None
-        } else {
-            c.width()
-        };
-        if let Some(cw) = cw {
-            pos.col += cw;
-            if pos.col > cols {
-                pos.row += 1;
-                pos.col = cw;
-            }
-        }
-    }
-    if pos.col == cols {
-        pos.col = 0;
-        pos.row += 1;
-    }
-    pos
-}
-
-/// Insert the character `ch` at cursor current position.
-fn edit_insert(s: &mut State, ch: char, n: RepeatCount) -> Result<()> {
-    if let Some(push) = s.line.insert(ch, n) {
-        if push {
-            if n == 1 && s.cursor.col + ch.width().unwrap_or(0) < s.cols {
-                // Avoid a full update of the line in the trivial case.
-                let cursor = calculate_position(&s.line[..s.line.pos()], s.prompt_size, s.cols);
-                s.cursor = cursor;
-                write_and_flush(s.out, ch.to_utf8().as_bytes())
-            } else {
-                s.refresh_line()
-            }
-        } else {
-            s.refresh_line()
-        }
-    } else {
-        Ok(())
-    }
-}
-
-/// Replace a single (or n) character(s) under the cursor (Vi mode)
-fn edit_replace_char(s: &mut State, ch: char, n: RepeatCount) -> Result<()> {
-    if let Some(chars) = s.line.delete(n) {
-        let count = chars.graphemes(true).count();
-        s.line.insert(ch, count);
-        s.line.move_backward(1);
-        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 {
-        s.line.move_forward(1);
-    }
-    if s.line.yank(text, n).is_some() {
-        if !s.edit_state.is_emacs_mode() {
-            s.line.move_backward(1);
-        }
-        s.refresh_line()
-    } else {
-        Ok(())
-    }
-}
-
-// 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, 1)
-}
-
-/// Move cursor on the left.
-fn edit_move_backward(s: &mut State, n: RepeatCount) -> Result<()> {
-    if s.line.move_backward(n) {
-        s.refresh_line()
-    } else {
-        Ok(())
-    }
-}
-
-/// Move cursor on the right.
-fn edit_move_forward(s: &mut State, n: RepeatCount) -> Result<()> {
-    if s.line.move_forward(n) {
-        s.refresh_line()
-    } else {
-        Ok(())
-    }
-}
-
-/// Move cursor to the start of the line.
-fn edit_move_home(s: &mut State) -> Result<()> {
-    if s.line.move_home() {
-        s.refresh_line()
-    } else {
-        Ok(())
-    }
-}
-
-/// Move cursor to the end of the line.
-fn edit_move_end(s: &mut State) -> Result<()> {
-    if s.line.move_end() {
-        s.refresh_line()
-    } else {
-        Ok(())
-    }
-}
-
-/// 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, n: RepeatCount) -> Result<()> {
-    if s.line.delete(n).is_some() {
-        s.refresh_line()
-    } else {
-        Ok(())
-    }
-}
-
-/// Backspace implementation.
-fn edit_backspace(s: &mut State, n: RepeatCount) -> Result<()> {
-    if s.line.backspace(n).is_some() {
-        s.refresh_line()
-    } else {
-        Ok(())
-    }
-}
-
-/// Kill the text from point to the end of the line.
-fn edit_kill_line(s: &mut State) -> Result<Option<String>> {
-    if let Some(text) = s.line.kill_line() {
-        try!(s.refresh_line());
-        Ok(Some(text))
-    } else {
-        Ok(None)
-    }
-}
-
-/// Kill backward from point to the beginning of the line.
-fn edit_discard_line(s: &mut State) -> Result<Option<String>> {
-    if let Some(text) = s.line.discard_line() {
-        try!(s.refresh_line());
-        Ok(Some(text))
-    } else {
-        Ok(None)
-    }
-}
-
-/// Exchange the char before cursor with the character at cursor.
-fn edit_transpose_chars(s: &mut State) -> Result<()> {
-    if s.line.transpose_chars() {
-        s.refresh_line()
-    } else {
-        Ok(())
-    }
-}
-
-fn edit_move_to_prev_word(s: &mut State, word_def: Word, n: RepeatCount) -> Result<()> {
-    if s.line.move_to_prev_word(word_def, n) {
-        s.refresh_line()
-    } else {
-        Ok(())
-    }
-}
-
-/// 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, n: RepeatCount) -> Result<Option<String>> {
-    if let Some(text) = s.line.delete_prev_word(word_def, n) {
-        try!(s.refresh_line());
-        Ok(Some(text))
-    } else {
-        Ok(None)
-    }
-}
-
-fn edit_move_to_next_word(s: &mut State, at: At, word_def: Word, n: RepeatCount) -> Result<()> {
-    if s.line.move_to_next_word(at, word_def, n) {
-        s.refresh_line()
-    } else {
-        Ok(())
-    }
-}
-
-fn edit_move_to(s: &mut State, cs: CharSearch, n: RepeatCount) -> Result<()> {
-    if s.line.move_to(cs, n) {
-        s.refresh_line()
-    } else {
-        Ok(())
-    }
-}
-
-/// 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,
-                    n: RepeatCount)
-                    -> Result<Option<String>> {
-    if let Some(text) = s.line.delete_word(at, word_def, n) {
-        try!(s.refresh_line());
-        Ok(Some(text))
-    } else {
-        Ok(None)
-    }
-}
-
-fn edit_delete_to(s: &mut State, cs: CharSearch, n: RepeatCount) -> Result<Option<String>> {
-    if let Some(text) = s.line.delete_to(cs, n) {
-        try!(s.refresh_line());
-        Ok(Some(text))
-    } else {
-        Ok(None)
-    }
-}
-
-fn edit_word(s: &mut State, a: WordAction) -> Result<()> {
-    if s.line.edit_word(a) {
-        s.refresh_line()
-    } else {
-        Ok(())
-    }
-}
-
-fn edit_transpose_words(s: &mut State, n: RepeatCount) -> Result<()> {
-    if s.line.transpose_words(n) {
-        s.refresh_line()
-    } else {
-        Ok(())
-    }
-}
-
-/// Substitute the currently edited line with the next or previous history
-/// entry.
-fn edit_history_next(s: &mut State, history: &History, prev: bool) -> Result<()> {
-    if history.is_empty() {
-        return Ok(());
-    }
-    if s.history_index == history.len() {
-        if prev {
-            // Save the current edited line before to overwrite it
-            s.snapshot();
-        } else {
-            return Ok(());
-        }
-    } else if s.history_index == 0 && prev {
-        return Ok(());
-    }
-    if prev {
-        s.history_index -= 1;
-    } else {
-        s.history_index += 1;
-    }
-    if s.history_index < history.len() {
-        let buf = history.get(s.history_index).unwrap();
-        s.line.update(buf, buf.len());
-    } else {
-        // Restore current edited line
-        s.snapshot();
-    }
-    s.refresh_line()
-}
-
-fn edit_history_search(s: &mut State, history: &History, dir: Direction) -> Result<()> {
-    if history.is_empty() {
-        return beep();
-    }
-    if s.history_index == history.len() && dir == Direction::Forward {
-        return beep();
-    } else if s.history_index == 0 && dir == Direction::Reverse {
-        return beep();
-    }
-    if dir == Direction::Reverse {
-        s.history_index -= 1;
-    } else {
-        s.history_index += 1;
-    }
-    if let Some(history_index) =
-        history.starts_with(&s.line.as_str()[..s.line.pos()], s.history_index, dir) {
-        s.history_index = history_index;
-        let buf = history.get(history_index).unwrap();
-        s.line.update(buf, buf.len());
-        s.refresh_line()
-    } else {
-        beep()
-    }
-}
-
-/// Substitute the currently edited line with the first/last history entry.
-fn edit_history(s: &mut State, history: &History, first: bool) -> Result<()> {
-    if history.is_empty() {
-        return Ok(());
-    }
-    if s.history_index == history.len() {
-        if first {
-            // Save the current edited line before to overwrite it
-            s.snapshot();
-        } else {
-            return Ok(());
-        }
-    } else if s.history_index == 0 && first {
-        return Ok(());
-    }
-    if first {
-        s.history_index = 0;
-        let buf = history.get(s.history_index).unwrap();
-        s.line.update(buf, buf.len());
-    } else {
-        s.history_index = history.len();
-        // Restore current edited line
-        s.snapshot();
-    }
-    s.refresh_line()
-}
-
 /// Completes the line/word
-fn complete_line<R: RawReader>(rdr: &mut R,
-                               s: &mut State,
-                               completer: &Completer,
-                               config: &Config)
-                               -> Result<Option<Cmd>> {
+fn complete_line<R: RawReader, C: Completer>(
+    rdr: &mut R,
+    s: &mut State,
+    input_state: &mut InputState,
+    completer: &C,
+    config: &Config,
+) -> Result<Option<Cmd>> {
     // get a list of completions
     let (start, candidates) = try!(completer.complete(&s.line, s.line.pos()));
     // if no completions, we are done
     if candidates.is_empty() {
-        try!(beep());
+        try!(s.out.beep());
         Ok(None)
     } else if CompletionType::Circular == config.completion_type() {
-        // Save the current edited line before to overwrite it
-        s.backup();
+        let mark = s.changes.borrow_mut().begin();
+        // Save the current edited line before overwriting it
+        let backup = s.line.as_str().to_owned();
+        let backup_pos = s.line.pos();
         let mut cmd;
         let mut i = 0;
         loop {
@@ -638,31 +93,29 @@
                 try!(s.refresh_line());
             } else {
                 // Restore current edited line
-                s.snapshot();
+                s.line.update(&backup, backup_pos);
                 try!(s.refresh_line());
-                s.snapshot();
             }
 
-            cmd = try!(s.next_cmd(rdr));
+            cmd = try!(s.next_cmd(input_state, rdr, true));
             match cmd {
                 Cmd::Complete => {
                     i = (i + 1) % (candidates.len() + 1); // Circular
                     if i == candidates.len() {
-                        try!(beep());
+                        try!(s.out.beep());
                     }
                 }
                 Cmd::Abort => {
                     // Re-show original buffer
-                    s.snapshot();
                     if i < candidates.len() {
+                        s.line.update(&backup, backup_pos);
                         try!(s.refresh_line());
                     }
+                    s.changes.borrow_mut().truncate(mark);
                     return Ok(None);
                 }
                 _ => {
-                    if i == candidates.len() {
-                        s.snapshot();
-                    }
+                    s.changes.borrow_mut().end();
                     break;
                 }
             }
@@ -671,7 +124,7 @@
     } else if CompletionType::List == config.completion_type() {
         // beep if ambiguous
         if candidates.len() > 1 {
-            try!(beep());
+            try!(s.out.beep());
         }
         if let Some(lcp) = longest_common_prefix(&candidates) {
             // if we can extend the item, extend it and return to main loop
@@ -682,36 +135,37 @@
             }
         }
         // we can't complete any further, wait for second tab
-        let mut cmd = try!(s.next_cmd(rdr));
+        let mut cmd = try!(s.next_cmd(input_state, rdr, true));
         // if any character other than tab, pass it to the main loop
         if cmd != Cmd::Complete {
             return Ok(Some(cmd));
         }
         // move cursor to EOL to avoid overwriting the command line
         let save_pos = s.line.pos();
-        try!(edit_move_end(s));
+        try!(s.edit_move_end());
         s.line.set_pos(save_pos);
         // we got a second tab, maybe show list of possible completions
         let show_completions = if candidates.len() > config.completion_prompt_limit() {
             let msg = format!("\nDisplay all {} possibilities? (y or n)", candidates.len());
-            try!(write_and_flush(s.out, msg.as_bytes()));
+            try!(s.out.write_and_flush(msg.as_bytes()));
             s.old_rows += 1;
-            while cmd != Cmd::SelfInsert(1, 'y') && cmd != Cmd::SelfInsert(1, 'Y') &&
-                  cmd != Cmd::SelfInsert(1, 'n') &&
-                  cmd != Cmd::SelfInsert(1, 'N') &&
-                  cmd != Cmd::Kill(Movement::BackwardChar(1)) {
-                cmd = try!(s.next_cmd(rdr));
+            while cmd != Cmd::SelfInsert(1, 'y')
+                && cmd != Cmd::SelfInsert(1, 'Y')
+                && cmd != Cmd::SelfInsert(1, 'n')
+                && cmd != Cmd::SelfInsert(1, 'N')
+                && cmd != Cmd::Kill(Movement::BackwardChar(1))
+            {
+                cmd = try!(s.next_cmd(input_state, rdr, false));
             }
             match cmd {
-                Cmd::SelfInsert(1, 'y') |
-                Cmd::SelfInsert(1, 'Y') => true,
+                Cmd::SelfInsert(1, 'y') | Cmd::SelfInsert(1, 'Y') => true,
                 _ => false,
             }
         } else {
             true
         };
         if show_completions {
-            page_completions(rdr, s, &candidates)
+            page_completions(rdr, s, input_state, &candidates)
         } else {
             try!(s.refresh_line());
             Ok(None)
@@ -721,52 +175,57 @@
     }
 }
 
-fn page_completions<R: RawReader>(rdr: &mut R,
-                                  s: &mut State,
-                                  candidates: &[String])
-                                  -> Result<Option<Cmd>> {
+fn page_completions<R: RawReader>(
+    rdr: &mut R,
+    s: &mut State,
+    input_state: &mut InputState,
+    candidates: &[String],
+) -> Result<Option<Cmd>> {
     use std::cmp;
 
     let min_col_pad = 2;
-    let max_width = cmp::min(s.cols,
-                             candidates
-                                 .into_iter()
-                                 .map(|s| s.as_str().width())
-                                 .max()
-                                 .unwrap() + min_col_pad);
-    let num_cols = s.cols / max_width;
+    let cols = s.out.get_columns();
+    let max_width = cmp::min(
+        cols,
+        candidates
+            .into_iter()
+            .map(|s| s.as_str().width())
+            .max()
+            .unwrap() + min_col_pad,
+    );
+    let num_cols = cols / max_width;
 
-    let mut pause_row = s.term.get_rows() - 1;
+    let mut pause_row = s.out.get_rows() - 1;
     let num_rows = (candidates.len() + num_cols - 1) / num_cols;
     let mut ab = String::new();
     for row in 0..num_rows {
         if row == pause_row {
-            try!(write_and_flush(s.out, b"\n--More--"));
+            try!(s.out.write_and_flush(b"\n--More--"));
             let mut cmd = Cmd::Noop;
-            while cmd != Cmd::SelfInsert(1, 'y') && cmd != Cmd::SelfInsert(1_, 'Y') &&
-                  cmd != Cmd::SelfInsert(1, 'n') &&
-                  cmd != Cmd::SelfInsert(1_, 'N') &&
-                  cmd != Cmd::SelfInsert(1, 'q') &&
-                  cmd != Cmd::SelfInsert(1, 'Q') &&
-                  cmd != Cmd::SelfInsert(1, ' ') &&
-                  cmd != Cmd::Kill(Movement::BackwardChar(1)) &&
-                  cmd != Cmd::AcceptLine {
-                cmd = try!(s.next_cmd(rdr));
+            while cmd != Cmd::SelfInsert(1, 'y')
+                && cmd != Cmd::SelfInsert(1, 'Y')
+                && cmd != Cmd::SelfInsert(1, 'n')
+                && cmd != Cmd::SelfInsert(1, 'N')
+                && cmd != Cmd::SelfInsert(1, 'q')
+                && cmd != Cmd::SelfInsert(1, 'Q')
+                && cmd != Cmd::SelfInsert(1, ' ')
+                && cmd != Cmd::Kill(Movement::BackwardChar(1))
+                && cmd != Cmd::AcceptLine
+            {
+                cmd = try!(s.next_cmd(input_state, rdr, false));
             }
             match cmd {
-                Cmd::SelfInsert(1, 'y') |
-                Cmd::SelfInsert(1, 'Y') |
-                Cmd::SelfInsert(1, ' ') => {
-                    pause_row += s.term.get_rows() - 1;
+                Cmd::SelfInsert(1, 'y') | Cmd::SelfInsert(1, 'Y') | Cmd::SelfInsert(1, ' ') => {
+                    pause_row += s.out.get_rows() - 1;
                 }
                 Cmd::AcceptLine => {
                     pause_row += 1;
                 }
                 _ => break,
             }
-            try!(write_and_flush(s.out, b"\n"));
+            try!(s.out.write_and_flush(b"\n"));
         } else {
-            try!(write_and_flush(s.out, b"\n"));
+            try!(s.out.write_and_flush(b"\n"));
         }
         ab.clear();
         for col in 0..num_cols {
@@ -782,23 +241,27 @@
                 }
             }
         }
-        try!(write_and_flush(s.out, ab.as_bytes()));
+        try!(s.out.write_and_flush(ab.as_bytes()));
     }
-    try!(write_and_flush(s.out, b"\n"));
+    try!(s.out.write_and_flush(b"\n"));
     try!(s.refresh_line());
     Ok(None)
 }
 
 /// Incremental search
-fn reverse_incremental_search<R: RawReader>(rdr: &mut R,
-                                            s: &mut State,
-                                            history: &History)
-                                            -> Result<Option<Cmd>> {
+fn reverse_incremental_search<R: RawReader>(
+    rdr: &mut R,
+    s: &mut State,
+    input_state: &mut InputState,
+    history: &History,
+) -> Result<Option<Cmd>> {
     if history.is_empty() {
         return Ok(None);
     }
-    // Save the current edited line (and cursor position) before to overwrite it
-    s.snapshot();
+    let mark = s.changes.borrow_mut().begin();
+    // Save the current edited line (and cursor position) before overwriting it
+    let backup = s.line.as_str().to_owned();
+    let backup_pos = s.line.pos();
 
     let mut search_buf = String::new();
     let mut history_idx = history.len() - 1;
@@ -815,7 +278,7 @@
         };
         try!(s.refresh_prompt_and_line(&prompt));
 
-        cmd = try!(s.next_cmd(rdr));
+        cmd = try!(s.next_cmd(input_state, rdr, true));
         if let Cmd::SelfInsert(_, c) = cmd {
             search_buf.push(c);
         } else {
@@ -844,10 +307,15 @@
                 }
                 Cmd::Abort => {
                     // Restore current edited line (before search)
-                    s.snapshot();
+                    s.line.update(&backup, backup_pos);
                     try!(s.refresh_line());
+                    s.changes.borrow_mut().truncate(mark);
                     return Ok(None);
                 }
+                Cmd::Move(_) => {
+                    try!(s.refresh_line()); // restore prompt
+                    break;
+                }
                 _ => break,
             }
         }
@@ -862,6 +330,7 @@
             _ => false,
         };
     }
+    s.changes.borrow_mut().end();
     Ok(Some(cmd))
 }
 
@@ -869,36 +338,50 @@
 /// It will also handle special inputs in an appropriate fashion
 /// (e.g., C-c will exit readline)
 #[allow(let_unit_value)]
-fn readline_edit<C: Completer>(prompt: &str,
-                               editor: &mut Editor<C>,
-                               original_mode: tty::Mode)
-                               -> Result<String> {
-    let completer = editor.completer.as_ref().map(|c| c as &Completer);
+fn readline_edit<H: Helper>(
+    prompt: &str,
+    initial: Option<(&str, &str)>,
+    editor: &mut Editor<H>,
+    original_mode: &tty::Mode,
+) -> Result<String> {
+    let completer = editor.helper.as_ref().map(|h| h.completer());
+    let hinter = editor.helper.as_ref().map(|h| h.hinter() as &Hinter);
 
     let mut stdout = editor.term.create_writer();
 
-    editor.kill_ring.reset();
-    let mut s = State::new(&mut stdout,
-                           editor.term.clone(),
-                           &editor.config,
-                           prompt,
-                           editor.history.len(),
-                           editor.custom_bindings.clone());
+    editor.reset_kill_ring(); // TODO recreate a new kill ring vs Arc<Mutex<KillRing>>
+    let mut s = State::new(&mut stdout, prompt, editor.history.len(), hinter);
+    let mut input_state = InputState::new(&editor.config, Arc::clone(&editor.custom_bindings));
+
+    s.line.set_delete_listener(editor.kill_ring.clone());
+    s.line.set_change_listener(s.changes.clone());
+
+    if let Some((left, right)) = initial {
+        s.line
+            .update((left.to_owned() + right).as_ref(), left.len());
+    }
+
     try!(s.refresh_line());
 
-    let mut rdr = try!(s.term.create_reader(&editor.config));
+    let mut rdr = try!(editor.term.create_reader(&editor.config));
 
     loop {
-        let rc = s.next_cmd(&mut rdr);
+        let rc = s.next_cmd(&mut input_state, &mut rdr, false);
         let mut cmd = try!(rc);
 
         if cmd.should_reset_kill_ring() {
-            editor.kill_ring.reset();
+            editor.reset_kill_ring();
         }
 
         // autocomplete
         if cmd == Cmd::Complete && completer.is_some() {
-            let next = try!(complete_line(&mut rdr, &mut s, completer.unwrap(), &editor.config));
+            let next = try!(complete_line(
+                &mut rdr,
+                &mut s,
+                &mut input_state,
+                completer.unwrap(),
+                &editor.config,
+            ));
             if next.is_some() {
                 cmd = next.unwrap();
             } else {
@@ -907,16 +390,21 @@
         }
 
         if let Cmd::SelfInsert(n, c) = cmd {
-            try!(edit_insert(&mut s, c, n));
+            try!(s.edit_insert(c, n));
             continue;
         } else if let Cmd::Insert(n, text) = cmd {
-            try!(edit_yank(&mut s, &text, Anchor::Before, n));
+            try!(s.edit_yank(&input_state, &text, Anchor::Before, n));
             continue;
         }
 
         if cmd == Cmd::ReverseSearchHistory {
             // Search history backward
-            let next = try!(reverse_incremental_search(&mut rdr, &mut s, &editor.history));
+            let next = try!(reverse_incremental_search(
+                &mut rdr,
+                &mut s,
+                &mut input_state,
+                &editor.history,
+            ));
             if next.is_some() {
                 cmd = next.unwrap();
             } else {
@@ -927,164 +415,147 @@
         match cmd {
             Cmd::Move(Movement::BeginningOfLine) => {
                 // Move to the beginning of line.
-                try!(edit_move_home(&mut s))
+                try!(s.edit_move_home())
             }
             Cmd::Move(Movement::ViFirstPrint) => {
-                try!(edit_move_home(&mut s));
-                try!(edit_move_to_next_word(&mut s, At::Start, Word::Big, 1))
+                try!(s.edit_move_home());
+                try!(s.edit_move_to_next_word(At::Start, Word::Big, 1))
             }
             Cmd::Move(Movement::BackwardChar(n)) => {
                 // Move back a character.
-                try!(edit_move_backward(&mut s, n))
+                try!(s.edit_move_backward(n))
             }
-            Cmd::Kill(Movement::ForwardChar(n)) => {
-                // Delete (forward) one character at point.
-                try!(edit_delete(&mut s, n))
-            }
-            Cmd::Replace(n, c) => {
-                try!(edit_replace_char(&mut s, c, n));
-            }
-            Cmd::EndOfFile => {
-                if !s.edit_state.is_emacs_mode() && !s.line.is_empty() {
-                    try!(edit_move_end(&mut s));
-                    break;
-                } else if s.line.is_empty() {
-                    return Err(error::ReadlineError::Eof);
-                } else {
-                    try!(edit_delete(&mut s, 1))
+            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));
+            }
+            Cmd::EndOfFile => if !input_state.is_emacs_mode() && !s.line.is_empty() {
+                try!(s.edit_move_end());
+                break;
+            } else if s.line.is_empty() {
+                return Err(error::ReadlineError::Eof);
+            } else {
+                try!(s.edit_delete(1))
+            },
             Cmd::Move(Movement::EndOfLine) => {
                 // Move to the end of line.
-                try!(edit_move_end(&mut s))
+                try!(s.edit_move_end())
             }
             Cmd::Move(Movement::ForwardChar(n)) => {
                 // Move forward a character.
-                try!(edit_move_forward(&mut s, n))
-            }
-            Cmd::Kill(Movement::BackwardChar(n)) => {
-                // Delete one character backward.
-                try!(edit_backspace(&mut s, n))
-            }
-            Cmd::Kill(Movement::EndOfLine) => {
-                // Kill the text from point to the end of the line.
-                if let Some(text) = try!(edit_kill_line(&mut s)) {
-                    editor.kill_ring.kill(&text, Mode::Append)
-                }
-            }
-            Cmd::Kill(Movement::WholeLine) => {
-                try!(edit_move_home(&mut s));
-                if let Some(text) = try!(edit_kill_line(&mut s)) {
-                    editor.kill_ring.kill(&text, Mode::Append)
-                }
+                try!(s.edit_move_forward(n))
             }
             Cmd::ClearScreen => {
                 // Clear the screen leaving the current line at the top of the screen.
-                try!(s.term.clear_screen(&mut s.out));
+                try!(s.out.clear_screen());
                 try!(s.refresh_line())
             }
             Cmd::NextHistory => {
                 // Fetch the next command from the history list.
-                try!(edit_history_next(&mut s, &editor.history, false))
+                try!(s.edit_history_next(&editor.history, false))
             }
             Cmd::PreviousHistory => {
                 // Fetch the previous command from the history list.
-                try!(edit_history_next(&mut s, &editor.history, true))
+                try!(s.edit_history_next(&editor.history, true))
             }
             Cmd::HistorySearchBackward => {
-                try!(edit_history_search(&mut s, &editor.history, Direction::Reverse))
+                try!(s.edit_history_search(&editor.history, Direction::Reverse))
             }
             Cmd::HistorySearchForward => {
-                try!(edit_history_search(&mut s, &editor.history, Direction::Forward))
+                try!(s.edit_history_search(&editor.history, Direction::Forward))
             }
             Cmd::TransposeChars => {
                 // Exchange the char before cursor with the character at cursor.
-                try!(edit_transpose_chars(&mut s))
-            }
-            Cmd::Kill(Movement::BeginningOfLine) => {
-                // Kill backward from point to the beginning of the line.
-                if let Some(text) = try!(edit_discard_line(&mut s)) {
-                    editor.kill_ring.kill(&text, Mode::Prepend)
-                }
+                try!(s.edit_transpose_chars())
             }
             #[cfg(unix)]
             Cmd::QuotedInsert => {
                 // Quoted insert
                 let c = try!(rdr.next_char());
-                try!(edit_insert(&mut s, c, 1)) // FIXME
+                try!(s.edit_insert(c, 1)) // FIXME
             }
             Cmd::Yank(n, anchor) => {
                 // retrieve (yank) last item killed
-                if let Some(text) = editor.kill_ring.yank() {
-                    try!(edit_yank(&mut s, text, anchor, n))
+                let mut kill_ring = editor.kill_ring.lock().unwrap();
+                if let Some(text) = kill_ring.yank() {
+                    try!(s.edit_yank(&input_state, text, anchor, n))
                 }
             }
-            Cmd::ViYankTo(mvt) => {
-                if let Some(text) = s.line.copy(mvt) {
-                    editor.kill_ring.kill(&text, Mode::Append)
-                }
-            }
+            Cmd::ViYankTo(ref mvt) => if let Some(text) = s.line.copy(mvt) {
+                let mut kill_ring = editor.kill_ring.lock().unwrap();
+                kill_ring.kill(&text, Mode::Append)
+            },
             // TODO CTRL-_ // undo
             Cmd::AcceptLine => {
-                // Accept the line regardless of where the cursor is.
-                try!(edit_move_end(&mut s));
-                break;
-            }
-            Cmd::Kill(Movement::BackwardWord(n, word_def)) => {
-                // kill one word backward (until start of word)
-                if let Some(text) = try!(edit_delete_prev_word(&mut s, word_def, n)) {
-                    editor.kill_ring.kill(&text, Mode::Prepend)
+                #[cfg(test)]
+                {
+                    editor.term.cursor = s.cursor.col;
                 }
+                // Accept the line regardless of where the cursor is.
+                try!(s.edit_move_end());
+                if s.hinter.is_some() {
+                    // Force a refresh without hints to leave the previous
+                    // line as the user typed it after a newline.
+                    s.hinter = None;
+                    try!(s.refresh_line());
+                }
+                break;
             }
             Cmd::BeginningOfHistory => {
                 // move to first entry in history
-                try!(edit_history(&mut s, &editor.history, true))
+                try!(s.edit_history(&editor.history, true))
             }
             Cmd::EndOfHistory => {
                 // move to last entry in history
-                try!(edit_history(&mut s, &editor.history, false))
+                try!(s.edit_history(&editor.history, false))
             }
             Cmd::Move(Movement::BackwardWord(n, word_def)) => {
                 // move backwards one word
-                try!(edit_move_to_prev_word(&mut s, word_def, n))
+                try!(s.edit_move_to_prev_word(word_def, n))
             }
             Cmd::CapitalizeWord => {
                 // capitalize word after point
-                try!(edit_word(&mut s, WordAction::CAPITALIZE))
+                try!(s.edit_word(WordAction::CAPITALIZE))
             }
-            Cmd::Kill(Movement::ForwardWord(n, at, word_def)) => {
-                // kill one word forward (until start/end of word)
-                if let Some(text) = try!(edit_delete_word(&mut s, at, word_def, n)) {
-                    editor.kill_ring.kill(&text, Mode::Append)
-                }
+            Cmd::Kill(ref mvt) => {
+                try!(s.edit_kill(mvt));
             }
             Cmd::Move(Movement::ForwardWord(n, at, word_def)) => {
                 // move forwards one word
-                try!(edit_move_to_next_word(&mut s, at, word_def, n))
+                try!(s.edit_move_to_next_word(at, word_def, n))
             }
             Cmd::DowncaseWord => {
                 // lowercase word after point
-                try!(edit_word(&mut s, WordAction::LOWERCASE))
+                try!(s.edit_word(WordAction::LOWERCASE))
             }
             Cmd::TransposeWords(n) => {
                 // transpose words
-                try!(edit_transpose_words(&mut s, n))
+                try!(s.edit_transpose_words(n))
             }
             Cmd::UpcaseWord => {
                 // uppercase word after point
-                try!(edit_word(&mut s, WordAction::UPPERCASE))
+                try!(s.edit_word(WordAction::UPPERCASE))
             }
             Cmd::YankPop => {
                 // yank-pop
-                if let Some((yank_size, text)) = editor.kill_ring.yank_pop() {
-                    try!(edit_yank_pop(&mut s, yank_size, text))
+                let mut kill_ring = editor.kill_ring.lock().unwrap();
+                if let Some((yank_size, text)) = kill_ring.yank_pop() {
+                    try!(s.edit_yank_pop(yank_size, text))
                 }
             }
-            Cmd::Move(Movement::ViCharSearch(n, cs)) => try!(edit_move_to(&mut s, cs, n)),
-            Cmd::Kill(Movement::ViCharSearch(n, cs)) => {
-                if let Some(text) = try!(edit_delete_to(&mut s, cs, n)) {
-                    editor.kill_ring.kill(&text, Mode::Append)
+            Cmd::Move(Movement::ViCharSearch(n, cs)) => try!(s.edit_move_to(cs, n)),
+            Cmd::Undo(n) => {
+                s.line.remove_change_listener();
+                if s.changes.borrow_mut().undo(&mut s.line, n) {
+                    try!(s.refresh_line());
                 }
+                s.line.set_change_listener(s.changes.clone());
             }
             Cmd::Interrupt => {
                 return Err(error::ReadlineError::Interrupted);
@@ -1093,7 +564,7 @@
             Cmd::Suspend => {
                 try!(original_mode.disable_raw_mode());
                 try!(tty::suspend());
-                try!(s.term.enable_raw_mode()); // TODO original_mode may have changed
+                try!(editor.term.enable_raw_mode()); // TODO original_mode may have changed
                 try!(s.refresh_line());
                 continue;
             }
@@ -1106,10 +577,10 @@
     Ok(s.line.into_string())
 }
 
-struct Guard(tty::Mode);
+struct Guard<'m>(&'m tty::Mode);
 
 #[allow(unused_must_use)]
-impl Drop for Guard {
+impl<'m> Drop for Guard<'m> {
     fn drop(&mut self) {
         let Guard(mode) = *self;
         mode.disable_raw_mode();
@@ -1118,12 +589,21 @@
 
 /// Readline method that will enable RAW mode, call the `readline_edit()`
 /// method and disable raw mode
-fn readline_raw<C: Completer>(prompt: &str, editor: &mut Editor<C>) -> Result<String> {
+fn readline_raw<H: Helper>(
+    prompt: &str,
+    initial: Option<(&str, &str)>,
+    editor: &mut Editor<H>,
+) -> Result<String> {
     let original_mode = try!(editor.term.enable_raw_mode());
-    let guard = Guard(original_mode);
-    let user_input = readline_edit(prompt, editor, original_mode);
+    let guard = Guard(&original_mode);
+    let user_input = readline_edit(prompt, initial, editor, &original_mode);
+    if editor.config.auto_add_history() {
+        if let Ok(ref line) = user_input {
+            editor.add_history_entry(line.as_ref());
+        }
+    }
     drop(guard); // try!(disable_raw_mode(original_mode));
-    println!("");
+    println!();
     user_input
 }
 
@@ -1136,40 +616,96 @@
     }
 }
 
-/// Line editor
-pub struct Editor<C: Completer> {
-    term: Terminal,
-    history: History,
-    completer: Option<C>,
-    kill_ring: KillRing,
-    config: Config,
-    custom_bindings: Rc<RefCell<HashMap<KeyPress, Cmd>>>,
+/// Syntax specific helper.
+///
+/// TODO Tokenizer/parser used for both completion, suggestion, highlighting
+pub trait Helper {
+    type Completer: Completer;
+    type Hinter: Hinter;
+
+    fn completer(&self) -> &Self::Completer;
+    fn hinter(&self) -> &Self::Hinter;
 }
 
-impl<C: Completer> Editor<C> {
-    pub fn new() -> Editor<C> {
+impl<C: Completer, H: Hinter> Helper for (C, H) {
+    type Completer = C;
+    type Hinter = H;
+
+    fn completer(&self) -> &C {
+        &self.0
+    }
+    fn hinter(&self) -> &H {
+        &self.1
+    }
+}
+impl<C: Completer> Helper for C {
+    type Completer = C;
+    type Hinter = ();
+
+    fn completer(&self) -> &C {
+        self
+    }
+    fn hinter(&self) -> &() {
+        &()
+    }
+}
+
+/// Line editor
+pub struct Editor<H: Helper> {
+    term: Terminal,
+    history: History,
+    helper: Option<H>,
+    kill_ring: Arc<Mutex<KillRing>>,
+    config: Config,
+    custom_bindings: Arc<RwLock<HashMap<KeyPress, Cmd>>>,
+}
+
+impl<H: Helper> Editor<H> {
+    /// Create an editor with the default configuration
+    pub fn new() -> Editor<H> {
         Self::with_config(Config::default())
     }
 
-    pub fn with_config(config: Config) -> Editor<C> {
+    /// Create an editor with a specific configuration.
+    pub fn with_config(config: Config) -> Editor<H> {
         let term = Terminal::new();
         Editor {
-            term: term,
+            term,
             history: History::with_config(config),
-            completer: None,
-            kill_ring: KillRing::new(60),
-            config: config,
-            custom_bindings: Rc::new(RefCell::new(HashMap::new())),
+            helper: None,
+            kill_ring: Arc::new(Mutex::new(KillRing::new(60))),
+            config,
+            custom_bindings: Arc::new(RwLock::new(HashMap::new())),
         }
     }
 
-    /// This method will read a line from STDIN and will display a `prompt`
+    /// This method will read a line from STDIN and will display a `prompt`.
+    ///
+    /// It uses terminal-style interaction if `stdin` is connected to a
+    /// terminal.
+    /// Otherwise (e.g., if `stdin` is a pipe or the terminal is not supported),
+    /// it uses file-style interaction.
     pub fn readline(&mut self, prompt: &str) -> Result<String> {
+        self.readline_with(prompt, None)
+    }
+    /// This function behaves in the exact same manner as `readline`, except
+    /// that it pre-populates the input area.
+    ///
+    /// The text that resides in the input area is given as a 2-tuple.
+    /// The string on the left of the tuple is what will appear to the left of
+    /// the cursor and the string on the right is what will appear to the
+    /// right of the cursor.
+    pub fn readline_with_initial(&mut self, prompt: &str, initial: (&str, &str)) -> Result<String> {
+        self.readline_with(prompt, Some(initial))
+    }
+
+    fn readline_with(&mut self, prompt: &str, initial: Option<(&str, &str)>) -> Result<String> {
         if self.term.is_unsupported() {
             debug!(target: "rustyline", "unsupported terminal");
             // Write prompt and flush it to stdout
             let mut stdout = io::stdout();
-            try!(write_and_flush(&mut stdout, prompt.as_bytes()));
+            try!(stdout.write_all(prompt.as_bytes()));
+            try!(stdout.flush());
 
             readline_direct()
         } else if !self.term.is_stdin_tty() {
@@ -1177,7 +713,7 @@
             // Not a tty: read from file / pipe.
             readline_direct()
         } else {
-            readline_raw(prompt, self)
+            readline_raw(prompt, initial, self)
         }
     }
 
@@ -1206,18 +742,26 @@
         &self.history
     }
 
-    /// Register a callback function to be called for tab-completion.
-    pub fn set_completer(&mut self, completer: Option<C>) {
-        self.completer = completer;
+    /// Register a callback function to be called for tab-completion
+    /// or to show hints to the user at the right of the prompt.
+    pub fn set_helper(&mut self, helper: Option<H>) {
+        self.helper = helper;
+    }
+
+    #[deprecated(since = "2.0.0", note = "Use set_helper instead")]
+    pub fn set_completer(&mut self, completer: Option<H>) {
+        self.helper = completer;
     }
 
     /// Bind a sequence to a command.
     pub fn bind_sequence(&mut self, key_seq: KeyPress, cmd: Cmd) -> Option<Cmd> {
-        self.custom_bindings.borrow_mut().insert(key_seq, cmd)
+        let mut bindings = self.custom_bindings.write().unwrap();
+        bindings.insert(key_seq, cmd)
     }
     /// Remove a binding for the given sequence.
     pub fn unbind_sequence(&mut self, key_seq: KeyPress) -> Option<Cmd> {
-        self.custom_bindings.borrow_mut().remove(&key_seq)
+        let mut bindings = self.custom_bindings.write().unwrap();
+        bindings.remove(&key_seq)
     }
 
     /// ```
@@ -1226,23 +770,28 @@
     ///     match readline {
     ///         Ok(line) => {
     ///             println!("Line: {}", line);
-    ///         },
+    ///         }
     ///         Err(err) => {
     ///             println!("Error: {:?}", err);
-    ///             break
+    ///             break;
     ///         }
     ///     }
     /// }
     /// ```
-    pub fn iter<'a>(&'a mut self, prompt: &'a str) -> Iter<C> {
+    pub fn iter<'a>(&'a mut self, prompt: &'a str) -> Iter<H> {
         Iter {
             editor: self,
-            prompt: prompt,
+            prompt,
         }
     }
+
+    fn reset_kill_ring(&self) {
+        let mut kill_ring = self.kill_ring.lock().unwrap();
+        kill_ring.reset();
+    }
 }
 
-impl<C: Completer> fmt::Debug for Editor<C> {
+impl<H: Helper> fmt::Debug for Editor<H> {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         f.debug_struct("Editor")
             .field("term", &self.term)
@@ -1251,23 +800,22 @@
     }
 }
 
-pub struct Iter<'a, C: Completer>
-    where C: 'a
+/// Edited lines iterator
+pub struct Iter<'a, H: Helper>
+where
+    H: 'a,
 {
-    editor: &'a mut Editor<C>,
+    editor: &'a mut Editor<H>,
     prompt: &'a str,
 }
 
-impl<'a, C: Completer> Iterator for Iter<'a, C> {
+impl<'a, H: Helper> Iterator for Iter<'a, H> {
     type Item = Result<String>;
 
     fn next(&mut self) -> Option<Result<String>> {
         let readline = self.editor.readline(self.prompt);
         match readline {
-            Ok(l) => {
-                self.editor.add_history_entry(l.as_ref()); // TODO Validate
-                Some(Ok(l))
-            }
+            Ok(l) => Some(Ok(l)),
             Err(error::ReadlineError::Eof) => None,
             e @ Err(_) => Some(e),
         }
@@ -1275,177 +823,7 @@
 }
 
 #[cfg(test)]
-mod test {
-    use std::cell::RefCell;
-    use std::collections::HashMap;
-    use std::io::Write;
-    use std::rc::Rc;
-    use line_buffer::LineBuffer;
-    use history::History;
-    use completion::Completer;
-    use config::Config;
-    use consts::KeyPress;
-    use keymap::{Cmd, EditState};
-    use super::{Editor, Position, Result, State};
-    use tty::{Terminal, Term};
-
-    fn init_state<'out>(out: &'out mut Write,
-                        line: &str,
-                        pos: usize,
-                        cols: usize)
-                        -> State<'out, 'static> {
-        let term = Terminal::new();
-        let config = Config::default();
-        State {
-            out: out,
-            prompt: "",
-            prompt_size: Position::default(),
-            line: LineBuffer::init(line, pos),
-            cursor: Position::default(),
-            cols: cols,
-            old_rows: 0,
-            history_index: 0,
-            snapshot: LineBuffer::with_capacity(100),
-            term: term,
-            edit_state: EditState::new(&config, Rc::new(RefCell::new(HashMap::new()))),
-        }
-    }
-
-    fn init_editor(keys: &[KeyPress]) -> Editor<()> {
-        let mut editor = Editor::<()>::new();
-        editor.term.keys.extend(keys.iter().cloned());
-        editor
-    }
-
-    #[test]
-    fn edit_history_next() {
-        let mut out = ::std::io::sink();
-        let line = "current edited line";
-        let mut s = init_state(&mut out, line, 6, 80);
-        let mut history = History::new();
-        history.add("line0");
-        history.add("line1");
-        s.history_index = history.len();
-
-        for _ in 0..2 {
-            super::edit_history_next(&mut s, &history, false).unwrap();
-            assert_eq!(line, s.line.as_str());
-        }
-
-        super::edit_history_next(&mut s, &history, true).unwrap();
-        assert_eq!(line, s.snapshot.as_str());
-        assert_eq!(1, s.history_index);
-        assert_eq!("line1", s.line.as_str());
-
-        for _ in 0..2 {
-            super::edit_history_next(&mut s, &history, true).unwrap();
-            assert_eq!(line, s.snapshot.as_str());
-            assert_eq!(0, s.history_index);
-            assert_eq!("line0", s.line.as_str());
-        }
-
-        super::edit_history_next(&mut s, &history, false).unwrap();
-        assert_eq!(line, s.snapshot.as_str());
-        assert_eq!(1, s.history_index);
-        assert_eq!("line1", s.line.as_str());
-
-        super::edit_history_next(&mut s, &history, false).unwrap();
-        // assert_eq!(line, s.snapshot);
-        assert_eq!(2, s.history_index);
-        assert_eq!(line, s.line.as_str());
-    }
-
-    struct SimpleCompleter;
-    impl Completer for SimpleCompleter {
-        fn complete(&self, line: &str, _pos: usize) -> Result<(usize, Vec<String>)> {
-            Ok((0, vec![line.to_string() + "t"]))
-        }
-    }
-
-    #[test]
-    fn complete_line() {
-        let mut out = ::std::io::sink();
-        let mut s = init_state(&mut out, "rus", 3, 80);
-        let keys = &[KeyPress::Enter];
-        let mut rdr = keys.iter();
-        let completer = SimpleCompleter;
-        let cmd = super::complete_line(&mut rdr, &mut s, &completer, &Config::default()).unwrap();
-        assert_eq!(Some(Cmd::AcceptLine), cmd);
-        assert_eq!("rust", s.line.as_str());
-        assert_eq!(4, s.line.pos());
-    }
-
-    #[test]
-    fn prompt_with_ansi_escape_codes() {
-        let pos = super::calculate_position("\x1b[1;32m>>\x1b[0m ", Position::default(), 80);
-        assert_eq!(3, pos.col);
-        assert_eq!(0, pos.row);
-    }
-
-    fn assert_line(keys: &[KeyPress], expected_line: &str) {
-        let mut editor = init_editor(keys);
-        let actual_line = editor.readline(&">>").unwrap();
-        assert_eq!(expected_line, actual_line);
-    }
-
-    #[test]
-    fn delete_key() {
-        assert_line(&[KeyPress::Char('a'), KeyPress::Delete, KeyPress::Enter],
-                    "a");
-        assert_line(&[KeyPress::Char('a'),
-                      KeyPress::Left,
-                      KeyPress::Delete,
-                      KeyPress::Enter],
-                    "");
-    }
-
-    #[test]
-    fn down_key() {
-        assert_line(&[KeyPress::Down, KeyPress::Enter], "");
-    }
-
-    #[test]
-    fn end_key() {
-        assert_line(&[KeyPress::End, KeyPress::Enter], "");
-    }
-
-    #[test]
-    fn home_key() {
-        assert_line(&[KeyPress::Home, KeyPress::Enter], "");
-    }
-
-    #[test]
-    fn left_key() {
-        assert_line(&[KeyPress::Left, KeyPress::Enter], "");
-    }
-
-    #[test]
-    fn meta_backspace_key() {
-        assert_line(&[KeyPress::Meta('\x08'), KeyPress::Enter], "");
-    }
-
-    #[test]
-    fn page_down_key() {
-        assert_line(&[KeyPress::PageDown, KeyPress::Enter], "");
-    }
-
-    #[test]
-    fn page_up_key() {
-        assert_line(&[KeyPress::PageUp, KeyPress::Enter], "");
-    }
-
-    #[test]
-    fn right_key() {
-        assert_line(&[KeyPress::Right, KeyPress::Enter], "");
-    }
-
-    #[test]
-    fn up_key() {
-        assert_line(&[KeyPress::Up, KeyPress::Enter], "");
-    }
-
-    #[test]
-    fn unknown_esc_key() {
-        assert_line(&[KeyPress::UnknownEscSeq, KeyPress::Enter], "");
-    }
-}
+#[macro_use]
+extern crate assert_matches;
+#[cfg(test)]
+mod test;
diff --git a/src/line_buffer.rs b/src/line_buffer.rs
index 80699e2..7c16bb1 100644
--- a/src/line_buffer.rs
+++ b/src/line_buffer.rs
@@ -1,22 +1,69 @@
 //! Line buffer with current cursor position
-use std::iter;
-use std::ops::{Deref, Range};
-use unicode_segmentation::UnicodeSegmentation;
 use keymap::{At, CharSearch, Movement, RepeatCount, Word};
+use std::cell::RefCell;
+use std::fmt;
+use std::iter;
+use std::ops::{Deref, Index, Range};
+use std::rc::Rc;
+use std::string::Drain;
+use std::sync::{Arc, Mutex};
+use unicode_segmentation::UnicodeSegmentation;
 
 /// Maximum buffer size for the line read
 pub static MAX_LINE: usize = 4096;
 
+/// Word's case change
+#[derive(Clone, Copy)]
 pub enum WordAction {
     CAPITALIZE,
     LOWERCASE,
     UPPERCASE,
 }
 
-#[derive(Debug)]
+/// Delete (kill) direction
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Direction {
+    Forward,
+    Backward,
+}
+
+impl Default for Direction {
+    fn default() -> Direction {
+        Direction::Forward
+    }
+}
+
+/// 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.
+pub trait ChangeListener: DeleteListener {
+    fn insert_char(&mut self, idx: usize, c: char);
+    fn insert_str(&mut self, idx: usize, string: &str);
+    fn replace(&mut self, idx: usize, old: &str, new: &str);
+}
+
+/// Represent the current input (text and cursor position).
+///
+/// The methods do text manipulations or/and cursor movements.
 pub struct LineBuffer {
-    buf: String, // Edited line buffer
-    pos: usize, // Current cursor position (byte position)
+    buf: String, // Edited line buffer (rl_line_buffer)
+    pos: usize,  // Current cursor position (byte position) (rl_point)
+    dl: Option<Arc<Mutex<DeleteListener>>>,
+    cl: Option<Rc<RefCell<ChangeListener>>>,
+}
+
+impl fmt::Debug for LineBuffer {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        f.debug_struct("LineBuffer")
+            .field("buf", &self.buf)
+            .field("pos", &self.pos)
+            .finish()
+    }
 }
 
 impl LineBuffer {
@@ -25,17 +72,33 @@
         LineBuffer {
             buf: String::with_capacity(capacity),
             pos: 0,
+            dl: None,
+            cl: None,
         }
     }
 
     #[cfg(test)]
-    pub fn init(line: &str, pos: usize) -> LineBuffer {
+    pub fn init(line: &str, pos: usize, cl: Option<Rc<RefCell<ChangeListener>>>) -> LineBuffer {
         let mut lb = Self::with_capacity(MAX_LINE);
         assert!(lb.insert_str(0, line));
         lb.set_pos(pos);
+        lb.cl = cl;
         lb
     }
 
+    pub fn set_delete_listener(&mut self, dl: Arc<Mutex<DeleteListener>>) {
+        self.dl = Some(dl);
+    }
+    pub fn remove_delete_listener(&mut self) {
+        self.dl = None;
+    }
+    pub fn set_change_listener(&mut self, dl: Rc<RefCell<ChangeListener>>) {
+        self.cl = Some(dl);
+    }
+    pub fn remove_change_listener(&mut self) {
+        self.cl = None;
+    }
+
     /// Extracts a string slice containing the entire buffer.
     pub fn as_str(&self) -> &str {
         &self.buf
@@ -50,6 +113,7 @@
     pub fn pos(&self) -> usize {
         self.pos
     }
+    /// Set cursor position (byte position)
     pub fn set_pos(&mut self, pos: usize) {
         assert!(pos <= self.buf.len());
         self.pos = pos;
@@ -67,28 +131,22 @@
     /// Set line content (`buf`) and cursor position (`pos`).
     pub fn update(&mut self, buf: &str, pos: usize) {
         assert!(pos <= buf.len());
-        self.buf.clear();
+        let end = self.len();
+        self.drain(0..end, Direction::default());
         let max = self.buf.capacity();
         if buf.len() > max {
-            self.buf.push_str(&buf[..max]);
+            self.insert_str(0, &buf[..max]);
             if pos > max {
                 self.pos = max;
             } else {
                 self.pos = pos;
             }
         } else {
-            self.buf.push_str(buf);
+            self.insert_str(0, buf);
             self.pos = pos;
         }
     }
 
-    /// Backup `src`
-    pub fn backup(&mut self, src: &LineBuffer) {
-        self.buf.clear();
-        self.buf.push_str(&src.buf);
-        self.pos = src.pos;
-    }
-
     /// Returns the character at current cursor position.
     fn grapheme_at_cursor(&self) -> Option<&str> {
         if self.pos == self.buf.len() {
@@ -98,7 +156,9 @@
         }
     }
 
-    fn next_pos(&self, n: RepeatCount) -> Option<usize> {
+    /// Returns the position of the character just after the current cursor
+    /// position.
+    pub fn next_pos(&self, n: RepeatCount) -> Option<usize> {
         if self.pos == self.buf.len() {
             return None;
         }
@@ -108,7 +168,8 @@
             .last()
             .map(|(i, s)| i + self.pos + s.len())
     }
-    /// Returns the position of the character just before the current cursor position.
+    /// Returns the position of the character just before the current cursor
+    /// position.
     fn prev_pos(&self, n: RepeatCount) -> Option<usize> {
         if self.pos == 0 {
             return None;
@@ -131,13 +192,11 @@
             return None;
         }
         let push = self.pos == self.buf.len();
-        if push {
-            self.buf.reserve(shift);
-            for _ in 0..n {
-                self.buf.push(ch);
-            }
-        } else if n == 1 {
+        if n == 1 {
             self.buf.insert(self.pos, ch);
+            for cl in &self.cl {
+                cl.borrow_mut().insert_char(self.pos, ch);
+            }
         } else {
             let text = iter::repeat(ch).take(n).collect::<String>();
             let pos = self.pos;
@@ -156,14 +215,11 @@
             return None;
         }
         let push = self.pos == self.buf.len();
-        if push {
-            self.buf.reserve(shift);
-            for _ in 0..n {
-                self.buf.push_str(text);
-            }
+        let pos = self.pos;
+        if n == 1 {
+            self.insert_str(pos, text);
         } else {
             let text = iter::repeat(text).take(n).collect::<String>();
-            let pos = self.pos;
             self.insert_str(pos, &text);
         }
         self.pos += shift;
@@ -172,7 +228,9 @@
 
     /// Delete previously yanked text and yank/paste `text` at current position.
     pub fn yank_pop(&mut self, yank_size: usize, text: &str) -> Option<bool> {
-        self.buf.drain((self.pos - yank_size)..self.pos);
+        let end = self.pos;
+        let start = end - yank_size;
+        self.drain(start..end, Direction::default());
         self.pos -= yank_size;
         self.yank(text, 1)
     }
@@ -219,13 +277,17 @@
         }
     }
 
-    /// Delete the character at the right of the cursor without altering the cursor
-    /// position. Basically this is what happens with the "Delete" keyboard key.
+    /// Delete the character at the right of the cursor without altering the
+    /// 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) {
             Some(pos) => {
-                let chars = self.buf.drain(self.pos..pos).collect::<String>();
+                let start = self.pos;
+                let chars = self
+                    .drain(start..pos, Direction::Forward)
+                    .collect::<String>();
                 Some(chars)
             }
             None => None,
@@ -234,35 +296,39 @@
 
     /// Delete the character at the left of the cursor.
     /// Basically that is what happens with the "Backspace" keyboard key.
-    pub fn backspace(&mut self, n: RepeatCount) -> Option<String> {
+    pub fn backspace(&mut self, n: RepeatCount) -> bool {
         match self.prev_pos(n) {
             Some(pos) => {
-                let chars = self.buf.drain(pos..self.pos).collect::<String>();
+                let end = self.pos;
+                self.drain(pos..end, Direction::Backward);
                 self.pos = pos;
-                Some(chars)
+                true
             }
-            None => None,
+            None => false,
         }
     }
 
     /// Kill the text from point to the end of the line.
-    pub fn kill_line(&mut self) -> Option<String> {
+    pub fn kill_line(&mut self) -> bool {
         if !self.buf.is_empty() && self.pos < self.buf.len() {
-            let text = self.buf.drain(self.pos..).collect();
-            Some(text)
+            let start = self.pos;
+            let end = self.buf.len();
+            self.drain(start..end, Direction::Forward);
+            true
         } else {
-            None
+            false
         }
     }
 
     /// Kill backward from point to the beginning of the line.
-    pub fn discard_line(&mut self) -> Option<String> {
+    pub fn discard_line(&mut self) -> bool {
         if self.pos > 0 && !self.buf.is_empty() {
-            let text = self.buf.drain(..self.pos).collect();
+            let end = self.pos;
+            self.drain(0..end, Direction::Backward);
             self.pos = 0;
-            Some(text)
+            true
         } else {
-            None
+            false
         }
     }
 
@@ -289,6 +355,7 @@
         let mut sow = 0;
         let mut gis = self.buf[..pos].grapheme_indices(true).rev();
         'outer: for _ in 0..n {
+            sow = 0;
             let mut gj = gis.next();
             'inner: loop {
                 match gj {
@@ -328,13 +395,14 @@
 
     /// Delete the previous word, maintaining the cursor at the start of the
     /// current word.
-    pub fn delete_prev_word(&mut self, word_def: Word, n: RepeatCount) -> Option<String> {
+    pub fn delete_prev_word(&mut self, word_def: Word, n: RepeatCount) -> bool {
         if let Some(pos) = self.prev_word_pos(self.pos, word_def, n) {
-            let word = self.buf.drain(pos..self.pos).collect();
+            let end = self.pos;
+            self.drain(pos..end, Direction::Backward);
             self.pos = pos;
-            Some(word)
+            true
         } else {
-            None
+            false
         }
     }
 
@@ -344,13 +412,14 @@
         }
         let mut wp = 0;
         let mut gis = self.buf[pos..].grapheme_indices(true);
-        let mut gi = if at != At::Start {
+        let mut gi = if at == At::BeforeEnd {
             // TODO Validate
             gis.next()
         } else {
             None
         };
         'outer: for _ in 0..n {
+            wp = 0;
             gi = gis.next();
             'inner: loop {
                 match gi {
@@ -409,18 +478,14 @@
     fn search_char_pos(&self, cs: &CharSearch, n: RepeatCount) -> Option<usize> {
         let mut shift = 0;
         let search_result = match *cs {
-            CharSearch::Backward(c) |
-            CharSearch::BackwardAfter(c) => {
-                self.buf[..self.pos]
-                    .char_indices()
-                    .rev()
-                    .filter(|&(_, ch)| ch == c)
-                    .take(n)
-                    .last()
-                    .map(|(i, _)| i)
-            }
-            CharSearch::Forward(c) |
-            CharSearch::ForwardBefore(c) => {
+            CharSearch::Backward(c) | CharSearch::BackwardAfter(c) => self.buf[..self.pos]
+                .char_indices()
+                .rev()
+                .filter(|&(_, ch)| ch == c)
+                .take(n)
+                .last()
+                .map(|(i, _)| i),
+            CharSearch::Forward(c) | CharSearch::ForwardBefore(c) => {
                 if let Some(cc) = self.grapheme_at_cursor() {
                     shift = self.pos + cc.len();
                     if shift < self.buf.len() {
@@ -440,23 +505,25 @@
         };
         if let Some(pos) = search_result {
             Some(match *cs {
-                     CharSearch::Backward(_) => pos,
-                     CharSearch::BackwardAfter(c) => pos + c.len_utf8(),
-                     CharSearch::Forward(_) => shift + pos,
-                     CharSearch::ForwardBefore(_) => {
-                         shift + pos -
-                         self.buf[..shift + pos]
-                             .chars()
-                             .next_back()
-                             .unwrap()
-                             .len_utf8()
-                     }
-                 })
+                CharSearch::Backward(_) => pos,
+                CharSearch::BackwardAfter(c) => pos + c.len_utf8(),
+                CharSearch::Forward(_) => shift + pos,
+                CharSearch::ForwardBefore(_) => {
+                    shift + pos
+                        - self.buf[..shift + pos]
+                            .chars()
+                            .next_back()
+                            .unwrap()
+                            .len_utf8()
+                }
+            })
         } else {
             None
         }
     }
 
+    /// Move cursor to the matching character position.
+    /// Return `true` when the search succeeds.
     pub fn move_to(&mut self, cs: CharSearch, n: RepeatCount) -> bool {
         if let Some(pos) = self.search_char_pos(&cs, n) {
             self.pos = pos;
@@ -468,34 +535,40 @@
 
     /// 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, n: RepeatCount) -> Option<String> {
+    pub fn delete_word(&mut self, at: At, word_def: Word, n: RepeatCount) -> bool {
         if let Some(pos) = self.next_word_pos(self.pos, at, word_def, n) {
-            let word = self.buf.drain(self.pos..pos).collect();
-            Some(word)
+            let start = self.pos;
+            self.drain(start..pos, Direction::Forward);
+            true
         } else {
-            None
+            false
         }
     }
 
-    pub fn delete_to(&mut self, cs: CharSearch, n: RepeatCount) -> Option<String> {
+    pub fn delete_to(&mut self, cs: CharSearch, n: RepeatCount) -> bool {
         let search_result = match cs {
             CharSearch::ForwardBefore(c) => self.search_char_pos(&CharSearch::Forward(c), n),
             _ => self.search_char_pos(&cs, n),
         };
         if let Some(pos) = search_result {
-            let chunk = match cs {
-                CharSearch::Backward(_) |
-                CharSearch::BackwardAfter(_) => {
+            match cs {
+                CharSearch::Backward(_) | CharSearch::BackwardAfter(_) => {
                     let end = self.pos;
                     self.pos = pos;
-                    self.buf.drain(pos..end).collect()
+                    self.drain(pos..end, Direction::Backward);
                 }
-                CharSearch::ForwardBefore(_) => self.buf.drain(self.pos..pos).collect(),
-                CharSearch::Forward(c) => self.buf.drain(self.pos..pos + c.len_utf8()).collect(),
+                CharSearch::ForwardBefore(_) => {
+                    let start = self.pos;
+                    self.drain(start..pos, Direction::Forward);
+                }
+                CharSearch::Forward(c) => {
+                    let start = self.pos;
+                    self.drain(start..pos + c.len_utf8(), Direction::Forward);
+                }
             };
-            Some(chunk)
+            true
         } else {
-            None
+            false
         }
     }
 
@@ -517,7 +590,9 @@
                 if start == end {
                     return false;
                 }
-                let word = self.buf.drain(start..end).collect::<String>();
+                let word = self
+                    .drain(start..end, Direction::default())
+                    .collect::<String>();
                 let result = match a {
                     WordAction::CAPITALIZE => {
                         let ch = (&word).graphemes(true).next().unwrap();
@@ -550,12 +625,14 @@
             return false;
         }
 
-        let w1 = self.buf[w1_beg..w1_end].to_string();
+        let w1 = self.buf[w1_beg..w1_end].to_owned();
 
-        let w2 = self.buf.drain(w2_beg..w2_end).collect::<String>();
+        let w2 = self
+            .drain(w2_beg..w2_end, Direction::default())
+            .collect::<String>();
         self.insert_str(w2_beg, &w1);
 
-        self.buf.drain(w1_beg..w1_end);
+        self.drain(w1_beg..w1_end, Direction::default());
         self.insert_str(w1_beg, &w2);
 
         self.pos = w2_end;
@@ -566,60 +643,89 @@
     /// and positions the cursor to the end of text.
     pub fn replace(&mut self, range: Range<usize>, text: &str) {
         let start = range.start;
+        for cl in &self.cl {
+            cl.borrow_mut()
+                .replace(start, self.buf.index(range.clone()), text);
+        }
         self.buf.drain(range);
-        self.insert_str(start, text);
+        if start == self.buf.len() {
+            self.buf.push_str(text);
+        } else {
+            self.buf.insert_str(start, text);
+        }
         self.pos = start + text.len();
     }
 
-    fn insert_str(&mut self, idx: usize, s: &str) -> bool {
+    /// Insert the `s`tring at the specified position.
+    /// Return `true` if the text has been inserted at the end of the line.
+    pub fn insert_str(&mut self, idx: usize, s: &str) -> bool {
+        for cl in &self.cl {
+            cl.borrow_mut().insert_str(idx, s);
+        }
         if idx == self.buf.len() {
             self.buf.push_str(s);
             true
         } else {
-            insert_str(&mut self.buf, idx, s);
+            self.buf.insert_str(idx, s);
             false
         }
     }
 
-    pub fn copy(&self, mvt: Movement) -> Option<String> {
+    /// Remove the specified `range` in the line.
+    pub fn delete_range(&mut self, range: Range<usize>) {
+        self.set_pos(range.start);
+        self.drain(range, Direction::default());
+    }
+
+    fn drain(&mut self, range: Range<usize>, dir: Direction) -> Drain {
+        for dl in &self.dl {
+            let mut lock = dl.try_lock();
+            if let Ok(mut dl) = lock {
+                dl.delete(range.start, &self.buf[range.start..range.end], dir);
+            }
+        }
+        for cl in &self.cl {
+            cl.borrow_mut()
+                .delete(range.start, &self.buf[range.start..range.end], dir);
+        }
+        self.buf.drain(range)
+    }
+
+    /// 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> {
         if self.is_empty() {
             return None;
         }
-        match mvt {
+        match *mvt {
             Movement::WholeLine => Some(self.buf.clone()),
-            Movement::BeginningOfLine => {
-                if self.pos == 0 {
-                    None
-                } else {
-                    Some(self.buf[..self.pos].to_string())
-                }
-            }
-            Movement::ViFirstPrint => {
-                if self.pos == 0 {
-                    None
-                } else if let Some(pos) = self.next_word_pos(0, At::Start, Word::Big, 1) {
-                    Some(self.buf[pos..self.pos].to_owned())
-                } else {
-                    None
-                }
-            }
-            Movement::EndOfLine => {
-                if self.pos == self.buf.len() {
-                    None
-                } else {
-                    Some(self.buf[self.pos..].to_string())
-                }
-            }
+            Movement::BeginningOfLine => if self.pos == 0 {
+                None
+            } else {
+                Some(self.buf[..self.pos].to_owned())
+            },
+            Movement::ViFirstPrint => if self.pos == 0 {
+                None
+            } else if let Some(pos) = self.next_word_pos(0, At::Start, Word::Big, 1) {
+                Some(self.buf[pos..self.pos].to_owned())
+            } else {
+                None
+            },
+            Movement::EndOfLine => if self.pos == self.buf.len() {
+                None
+            } else {
+                Some(self.buf[self.pos..].to_owned())
+            },
             Movement::BackwardWord(n, word_def) => {
                 if let Some(pos) = self.prev_word_pos(self.pos, word_def, n) {
-                    Some(self.buf[pos..self.pos].to_string())
+                    Some(self.buf[pos..self.pos].to_owned())
                 } else {
                     None
                 }
             }
             Movement::ForwardWord(n, at, word_def) => {
                 if let Some(pos) = self.next_word_pos(self.pos, at, word_def, n) {
-                    Some(self.buf[self.pos..pos].to_string())
+                    Some(self.buf[self.pos..pos].to_owned())
                 } else {
                     None
                 }
@@ -633,32 +739,82 @@
                 };
                 if let Some(pos) = search_result {
                     Some(match cs {
-                             CharSearch::Backward(_) |
-                             CharSearch::BackwardAfter(_) => self.buf[pos..self.pos].to_string(),
-                             CharSearch::ForwardBefore(_) => self.buf[self.pos..pos].to_string(),
-                             CharSearch::Forward(c) => {
-                                 self.buf[self.pos..pos + c.len_utf8()].to_string()
-                             }
-                         })
+                        CharSearch::Backward(_) | CharSearch::BackwardAfter(_) => {
+                            self.buf[pos..self.pos].to_owned()
+                        }
+                        CharSearch::ForwardBefore(_) => self.buf[self.pos..pos].to_owned(),
+                        CharSearch::Forward(c) => self.buf[self.pos..pos + c.len_utf8()].to_owned(),
+                    })
                 } else {
                     None
                 }
             }
-            Movement::BackwardChar(n) => {
-                if let Some(pos) = self.prev_pos(n) {
-                    Some(self.buf[pos..self.pos].to_string())
-                } else {
-                    None
-                }
-            }
-            Movement::ForwardChar(n) => {
-                if let Some(pos) = self.next_pos(n) {
-                    Some(self.buf[self.pos..pos].to_string())
-                } else {
-                    None
-                }
+            Movement::BackwardChar(n) => if let Some(pos) = self.prev_pos(n) {
+                Some(self.buf[pos..self.pos].to_owned())
+            } else {
+                None
+            },
+            Movement::ForwardChar(n) => if let Some(pos) = self.next_pos(n) {
+                Some(self.buf[self.pos..pos].to_owned())
+            } else {
+                None
+            },
+        }
+    }
+
+    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() {
+                let mut dl = dl.lock().unwrap();
+                dl.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() {
+                let mut dl = dl.lock().unwrap();
+                dl.stop_killing()
+            }
+        }
+        killed
     }
 }
 
@@ -670,73 +826,85 @@
     }
 }
 
-fn insert_str(buf: &mut String, idx: usize, s: &str) {
-    use std::ptr;
-
-    let len = buf.len();
-    assert!(idx <= len);
-    assert!(buf.is_char_boundary(idx));
-    let amt = s.len();
-    buf.reserve(amt);
-
-    unsafe {
-        let v = buf.as_mut_vec();
-        ptr::copy(v.as_ptr().offset(idx as isize),
-                  v.as_mut_ptr().offset((idx + amt) as isize),
-                  len - idx);
-        ptr::copy_nonoverlapping(s.as_ptr(), v.as_mut_ptr().offset(idx as isize), amt);
-        v.set_len(len + amt);
-    }
-}
-
 fn is_start_of_word(word_def: Word, previous: &str, grapheme: &str) -> bool {
-    (!is_word_char(word_def, previous) && is_word_char(word_def, grapheme)) ||
-    (word_def == Word::Vi && !is_other_char(previous) && is_other_char(grapheme))
+    (!is_word_char(word_def, previous) && is_word_char(word_def, grapheme))
+        || (word_def == Word::Vi && !is_other_char(previous) && is_other_char(grapheme))
 }
 fn is_end_of_word(word_def: Word, grapheme: &str, next: &str) -> bool {
-    (!is_word_char(word_def, next) && is_word_char(word_def, grapheme)) ||
-    (word_def == Word::Vi && !is_other_char(next) && is_other_char(grapheme))
+    (!is_word_char(word_def, next) && is_word_char(word_def, grapheme))
+        || (word_def == Word::Vi && !is_other_char(next) && is_other_char(grapheme))
 }
 
 fn is_word_char(word_def: Word, grapheme: &str) -> bool {
     match word_def {
         Word::Emacs => grapheme.chars().all(|c| c.is_alphanumeric()),
         Word::Vi => is_vi_word_char(grapheme),
-        Word::Big => !grapheme.chars().all(|c| c.is_whitespace()),
+        Word::Big => !grapheme.chars().any(|c| c.is_whitespace()),
     }
 }
 fn is_vi_word_char(grapheme: &str) -> bool {
     grapheme.chars().all(|c| c.is_alphanumeric()) || grapheme == "_"
 }
 fn is_other_char(grapheme: &str) -> bool {
-    !(grapheme.chars().all(|c| c.is_whitespace()) || is_vi_word_char(grapheme))
+    !(grapheme.chars().any(|c| c.is_whitespace()) || is_vi_word_char(grapheme))
 }
 
 #[cfg(test)]
 mod test {
+    use super::{ChangeListener, DeleteListener, Direction, LineBuffer, WordAction, MAX_LINE};
     use keymap::{At, CharSearch, Word};
-    use super::{LineBuffer, MAX_LINE, WordAction};
+    use std::cell::RefCell;
+    use std::rc::Rc;
+
+    struct Listener {
+        deleted_str: Option<String>,
+    }
+
+    impl Listener {
+        fn new() -> Rc<RefCell<Listener>> {
+            let l = Listener { deleted_str: None };
+            Rc::new(RefCell::new(l))
+        }
+
+        fn assert_deleted_str_eq(&self, expected: &str) {
+            let actual = self.deleted_str.as_ref().expect("no deleted string");
+            assert_eq!(expected, actual)
+        }
+    }
+
+    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) {}
+        fn insert_str(&mut self, _: usize, _: &str) {}
+        fn replace(&mut self, _: usize, _: &str, _: &str) {}
+    }
 
     #[test]
     fn next_pos() {
-        let s = LineBuffer::init("ö̲g̈", 0);
+        let s = LineBuffer::init("ö̲g̈", 0, None);
         assert_eq!(7, s.len());
         let pos = s.next_pos(1);
         assert_eq!(Some(4), pos);
 
-        let s = LineBuffer::init("ö̲g̈", 4);
+        let s = LineBuffer::init("ö̲g̈", 4, None);
         let pos = s.next_pos(1);
         assert_eq!(Some(7), pos);
     }
 
     #[test]
     fn prev_pos() {
-        let s = LineBuffer::init("ö̲g̈", 4);
+        let s = LineBuffer::init("ö̲g̈", 4, None);
         assert_eq!(7, s.len());
         let pos = s.prev_pos(1);
         assert_eq!(Some(0), pos);
 
-        let s = LineBuffer::init("ö̲g̈", 7);
+        let s = LineBuffer::init("ö̲g̈", 7, None);
         let pos = s.prev_pos(1);
         assert_eq!(Some(4), pos);
     }
@@ -763,7 +931,7 @@
 
     #[test]
     fn yank_after() {
-        let mut s = LineBuffer::init("αß", 2);
+        let mut s = LineBuffer::init("αß", 2, None);
         s.move_forward(1);
         let ok = s.yank("γδε", 1);
         assert_eq!(Some(true), ok);
@@ -773,7 +941,7 @@
 
     #[test]
     fn yank_before() {
-        let mut s = LineBuffer::init("αε", 2);
+        let mut s = LineBuffer::init("αε", 2, None);
         let ok = s.yank("ßγδ", 1);
         assert_eq!(Some(false), ok);
         assert_eq!("αßγδε", s.buf);
@@ -782,7 +950,7 @@
 
     #[test]
     fn moves() {
-        let mut s = LineBuffer::init("αß", 4);
+        let mut s = LineBuffer::init("αß", 4, None);
         let ok = s.move_backward(1);
         assert_eq!("αß", s.buf);
         assert_eq!(2, s.pos);
@@ -806,7 +974,7 @@
 
     #[test]
     fn move_grapheme() {
-        let mut s = LineBuffer::init("ag̈", 4);
+        let mut s = LineBuffer::init("ag̈", 4, None);
         assert_eq!(4, s.len());
         let ok = s.move_backward(1);
         assert_eq!(true, ok);
@@ -819,36 +987,41 @@
 
     #[test]
     fn delete() {
-        let mut s = LineBuffer::init("αß", 2);
+        let cl = Listener::new();
+        let mut s = LineBuffer::init("αß", 2, Some(cl.clone()));
         let chars = s.delete(1);
         assert_eq!("α", s.buf);
         assert_eq!(2, s.pos);
-        assert_eq!(Some("ß".to_string()), chars);
+        assert_eq!(Some("ß".to_owned()), chars);
 
-        let chars = s.backspace(1);
+        let ok = s.backspace(1);
         assert_eq!("", s.buf);
         assert_eq!(0, s.pos);
-        assert_eq!(Some("α".to_string()), chars);
+        assert_eq!(true, ok);
+        cl.borrow().assert_deleted_str_eq("α");
     }
 
     #[test]
     fn kill() {
-        let mut s = LineBuffer::init("αßγδε", 6);
-        let text = s.kill_line();
+        let cl = Listener::new();
+        let mut s = LineBuffer::init("αßγδε", 6, Some(cl.clone()));
+        let ok = s.kill_line();
         assert_eq!("αßγ", s.buf);
         assert_eq!(6, s.pos);
-        assert_eq!(Some("δε".to_string()), text);
+        assert_eq!(true, ok);
+        cl.borrow().assert_deleted_str_eq("δε");
 
         s.pos = 4;
-        let text = s.discard_line();
+        let ok = s.discard_line();
         assert_eq!("γ", s.buf);
         assert_eq!(0, s.pos);
-        assert_eq!(Some("αß".to_string()), text);
+        assert_eq!(true, ok);
+        cl.borrow().assert_deleted_str_eq("αß");
     }
 
     #[test]
     fn transpose() {
-        let mut s = LineBuffer::init("aßc", 1);
+        let mut s = LineBuffer::init("aßc", 1, None);
         let ok = s.transpose_chars();
         assert_eq!("ßac", s.buf);
         assert_eq!(3, s.pos);
@@ -871,16 +1044,26 @@
 
     #[test]
     fn move_to_prev_word() {
-        let mut s = LineBuffer::init("a ß  c", 6);
+        let mut s = LineBuffer::init("a ß  c", 6, None); // before 'c'
         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);
+        assert_eq!(2, s.pos); // before 'ß'
+        assert!(true, ok);
+
+        assert!(s.move_end()); // after 'c'
+        assert_eq!(7, s.pos);
+        let ok = s.move_to_prev_word(Word::Emacs, 1);
+        assert!(true, ok);
+        assert_eq!(6, s.pos); // before 'c'
+
+        let ok = s.move_to_prev_word(Word::Emacs, 2);
+        assert!(true, ok);
+        assert_eq!(0, s.pos);
     }
 
     #[test]
     fn move_to_prev_vi_word() {
-        let mut s = LineBuffer::init("alpha ,beta/rho; mu", 19);
+        let mut s = LineBuffer::init("alpha ,beta/rho; mu", 19, None);
         let ok = s.move_to_prev_word(Word::Vi, 1);
         assert!(ok);
         assert_eq!(17, s.pos);
@@ -908,7 +1091,7 @@
 
     #[test]
     fn move_to_prev_big_word() {
-        let mut s = LineBuffer::init("alpha ,beta/rho; mu", 19);
+        let mut s = LineBuffer::init("alpha ,beta/rho; mu", 19, None);
         let ok = s.move_to_prev_word(Word::Big, 1);
         assert!(ok);
         assert_eq!(17, s.pos);
@@ -924,17 +1107,17 @@
 
     #[test]
     fn move_to_forward() {
-        let mut s = LineBuffer::init("αßγδε", 2);
+        let mut s = LineBuffer::init("αßγδε", 2, None);
         let ok = s.move_to(CharSearch::ForwardBefore('ε'), 1);
         assert_eq!(true, ok);
         assert_eq!(6, s.pos);
 
-        let mut s = LineBuffer::init("αßγδε", 2);
+        let mut s = LineBuffer::init("αßγδε", 2, None);
         let ok = s.move_to(CharSearch::Forward('ε'), 1);
         assert_eq!(true, ok);
         assert_eq!(8, s.pos);
 
-        let mut s = LineBuffer::init("αßγδε", 2);
+        let mut s = LineBuffer::init("αßγδε", 2, None);
         let ok = s.move_to(CharSearch::Forward('ε'), 10);
         assert_eq!(true, ok);
         assert_eq!(8, s.pos);
@@ -942,12 +1125,12 @@
 
     #[test]
     fn move_to_backward() {
-        let mut s = LineBuffer::init("αßγδε", 8);
+        let mut s = LineBuffer::init("αßγδε", 8, None);
         let ok = s.move_to(CharSearch::BackwardAfter('ß'), 1);
         assert_eq!(true, ok);
         assert_eq!(4, s.pos);
 
-        let mut s = LineBuffer::init("αßγδε", 8);
+        let mut s = LineBuffer::init("αßγδε", 8, None);
         let ok = s.move_to(CharSearch::Backward('ß'), 1);
         assert_eq!(true, ok);
         assert_eq!(2, s.pos);
@@ -955,25 +1138,40 @@
 
     #[test]
     fn delete_prev_word() {
-        let mut s = LineBuffer::init("a ß  c", 6);
-        let text = s.delete_prev_word(Word::Big, 1);
+        let cl = Listener::new();
+        let mut s = LineBuffer::init("a ß  c", 6, Some(cl.clone()));
+        let ok = s.delete_prev_word(Word::Big, 1);
         assert_eq!("a c", s.buf);
         assert_eq!(2, s.pos);
-        assert_eq!(Some("ß  ".to_string()), text);
+        assert_eq!(true, ok);
+        cl.borrow().assert_deleted_str_eq("ß  ");
     }
 
     #[test]
     fn move_to_next_word() {
-        let mut s = LineBuffer::init("a ß  c", 1);
+        let mut s = LineBuffer::init("a ß  c", 1, None); // after 'a'
         let ok = s.move_to_next_word(At::AfterEnd, Word::Emacs, 1);
         assert_eq!("a ß  c", s.buf);
-        assert_eq!(4, s.pos);
         assert_eq!(true, ok);
+        assert_eq!(4, s.pos); // after 'ß'
+
+        let ok = s.move_to_next_word(At::AfterEnd, Word::Emacs, 1);
+        assert_eq!(true, ok);
+        assert_eq!(7, s.pos); // after 'c'
+
+        s.move_home();
+        let ok = s.move_to_next_word(At::AfterEnd, Word::Emacs, 1);
+        assert_eq!(true, ok);
+        assert_eq!(1, s.pos); // after 'a'
+
+        let ok = s.move_to_next_word(At::AfterEnd, Word::Emacs, 2);
+        assert_eq!(true, ok);
+        assert_eq!(7, s.pos); // after 'c'
     }
 
     #[test]
     fn move_to_end_of_word() {
-        let mut s = LineBuffer::init("a ßeta  c", 1);
+        let mut s = LineBuffer::init("a ßeta  c", 1, None);
         let ok = s.move_to_next_word(At::BeforeEnd, Word::Vi, 1);
         assert_eq!("a ßeta  c", s.buf);
         assert_eq!(6, s.pos);
@@ -982,7 +1180,7 @@
 
     #[test]
     fn move_to_end_of_vi_word() {
-        let mut s = LineBuffer::init("alpha ,beta/rho; mu", 0);
+        let mut s = LineBuffer::init("alpha ,beta/rho; mu", 0, None);
         let ok = s.move_to_next_word(At::BeforeEnd, Word::Vi, 1);
         assert!(ok);
         assert_eq!(4, s.pos);
@@ -1010,7 +1208,7 @@
 
     #[test]
     fn move_to_end_of_big_word() {
-        let mut s = LineBuffer::init("alpha ,beta/rho; mu", 0);
+        let mut s = LineBuffer::init("alpha ,beta/rho; mu", 0, None);
         let ok = s.move_to_next_word(At::BeforeEnd, Word::Big, 1);
         assert!(ok);
         assert_eq!(4, s.pos);
@@ -1026,7 +1224,7 @@
 
     #[test]
     fn move_to_start_of_word() {
-        let mut s = LineBuffer::init("a ß  c", 2);
+        let mut s = LineBuffer::init("a ß  c", 2, None);
         let ok = s.move_to_next_word(At::Start, Word::Emacs, 1);
         assert_eq!("a ß  c", s.buf);
         assert_eq!(6, s.pos);
@@ -1035,7 +1233,7 @@
 
     #[test]
     fn move_to_start_of_vi_word() {
-        let mut s = LineBuffer::init("alpha ,beta/rho; mu", 0);
+        let mut s = LineBuffer::init("alpha ,beta/rho; mu", 0, None);
         let ok = s.move_to_next_word(At::Start, Word::Vi, 1);
         assert!(ok);
         assert_eq!(6, s.pos);
@@ -1063,7 +1261,7 @@
 
     #[test]
     fn move_to_start_of_big_word() {
-        let mut s = LineBuffer::init("alpha ,beta/rho; mu", 0);
+        let mut s = LineBuffer::init("alpha ,beta/rho; mu", 0, None);
         let ok = s.move_to_next_word(At::Start, Word::Big, 1);
         assert!(ok);
         assert_eq!(6, s.pos);
@@ -1079,76 +1277,87 @@
 
     #[test]
     fn delete_word() {
-        let mut s = LineBuffer::init("a ß  c", 1);
-        let text = s.delete_word(At::AfterEnd, Word::Emacs, 1);
+        let cl = Listener::new();
+        let mut s = LineBuffer::init("a ß  c", 1, Some(cl.clone()));
+        let ok = s.delete_word(At::AfterEnd, Word::Emacs, 1);
         assert_eq!("a  c", s.buf);
         assert_eq!(1, s.pos);
-        assert_eq!(Some(" ß".to_string()), text);
+        assert_eq!(true, ok);
+        cl.borrow().assert_deleted_str_eq(" ß");
 
-        let mut s = LineBuffer::init("test", 0);
-        let text = s.delete_word(At::AfterEnd, Word::Vi, 1);
+        let mut s = LineBuffer::init("test", 0, Some(cl.clone()));
+        let ok = s.delete_word(At::AfterEnd, Word::Vi, 1);
         assert_eq!("", s.buf);
         assert_eq!(0, s.pos);
-        assert_eq!(Some("test".to_string()), text);
+        assert_eq!(true, ok);
+        cl.borrow().assert_deleted_str_eq("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, 1);
+        let cl = Listener::new();
+        let mut s = LineBuffer::init("a ß  c", 2, Some(cl.clone()));
+        let ok = 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);
+        assert_eq!(true, ok);
+        cl.borrow().assert_deleted_str_eq("ß  ");
     }
 
     #[test]
     fn delete_to_forward() {
-        let mut s = LineBuffer::init("αßγδε", 2);
-        let text = s.delete_to(CharSearch::ForwardBefore('ε'), 1);
-        assert_eq!(Some("ßγδ".to_string()), text);
+        let cl = Listener::new();
+        let mut s = LineBuffer::init("αßγδε", 2, Some(cl.clone()));
+        let ok = s.delete_to(CharSearch::ForwardBefore('ε'), 1);
+        assert_eq!(true, ok);
+        cl.borrow().assert_deleted_str_eq("ßγδ");
         assert_eq!("αε", s.buf);
         assert_eq!(2, s.pos);
 
-        let mut s = LineBuffer::init("αßγδε", 2);
-        let text = s.delete_to(CharSearch::Forward('ε'), 1);
-        assert_eq!(Some("ßγδε".to_string()), text);
+        let mut s = LineBuffer::init("αßγδε", 2, Some(cl.clone()));
+        let ok = s.delete_to(CharSearch::Forward('ε'), 1);
+        assert_eq!(true, ok);
+        cl.borrow().assert_deleted_str_eq("ßγδε");
         assert_eq!("α", s.buf);
         assert_eq!(2, s.pos);
     }
 
     #[test]
     fn delete_to_backward() {
-        let mut s = LineBuffer::init("αßγδε", 8);
-        let text = s.delete_to(CharSearch::BackwardAfter('α'), 1);
-        assert_eq!(Some("ßγδ".to_string()), text);
+        let cl = Listener::new();
+        let mut s = LineBuffer::init("αßγδε", 8, Some(cl.clone()));
+        let ok = s.delete_to(CharSearch::BackwardAfter('α'), 1);
+        assert_eq!(true, ok);
+        cl.borrow().assert_deleted_str_eq("ßγδ");
         assert_eq!("αε", s.buf);
         assert_eq!(2, s.pos);
 
-        let mut s = LineBuffer::init("αßγδε", 8);
-        let text = s.delete_to(CharSearch::Backward('ß'), 1);
-        assert_eq!(Some("ßγδ".to_string()), text);
+        let mut s = LineBuffer::init("αßγδε", 8, Some(cl.clone()));
+        let ok = s.delete_to(CharSearch::Backward('ß'), 1);
+        assert_eq!(true, ok);
+        cl.borrow().assert_deleted_str_eq("ßγδ");
         assert_eq!("αε", s.buf);
         assert_eq!(2, s.pos);
     }
 
     #[test]
     fn edit_word() {
-        let mut s = LineBuffer::init("a ßeta  c", 1);
+        let mut s = LineBuffer::init("a ßeta  c", 1, None);
         assert!(s.edit_word(WordAction::UPPERCASE));
         assert_eq!("a SSETA  c", s.buf);
         assert_eq!(7, s.pos);
 
-        let mut s = LineBuffer::init("a ßetA  c", 1);
+        let mut s = LineBuffer::init("a ßetA  c", 1, None);
         assert!(s.edit_word(WordAction::LOWERCASE));
         assert_eq!("a ßeta  c", s.buf);
         assert_eq!(7, s.pos);
 
-        let mut s = LineBuffer::init("a ßETA  c", 1);
+        let mut s = LineBuffer::init("a ßETA  c", 1, None);
         assert!(s.edit_word(WordAction::CAPITALIZE));
         assert_eq!("a SSeta  c", s.buf);
         assert_eq!(7, s.pos);
 
-        let mut s = LineBuffer::init("test", 1);
+        let mut s = LineBuffer::init("test", 1, None);
         assert!(s.edit_word(WordAction::CAPITALIZE));
         assert_eq!("tEst", s.buf);
         assert_eq!(4, s.pos);
@@ -1156,20 +1365,20 @@
 
     #[test]
     fn transpose_words() {
-        let mut s = LineBuffer::init("ßeta / δelta__", 15);
+        let mut s = LineBuffer::init("ßeta / δelta__", 15, None);
         assert!(s.transpose_words(1));
         assert_eq!("δelta__ / ßeta", s.buf);
         assert_eq!(16, s.pos);
 
-        let mut s = LineBuffer::init("ßeta / δelta", 14);
+        let mut s = LineBuffer::init("ßeta / δelta", 14, None);
         assert!(s.transpose_words(1));
         assert_eq!("δelta / ßeta", s.buf);
         assert_eq!(14, s.pos);
 
-        let mut s = LineBuffer::init(" / δelta", 8);
+        let mut s = LineBuffer::init(" / δelta", 8, None);
         assert!(!s.transpose_words(1));
 
-        let mut s = LineBuffer::init("ßeta / __", 9);
+        let mut s = LineBuffer::init("ßeta / __", 9, None);
         assert!(!s.transpose_words(1));
     }
 }
diff --git a/src/test/common.rs b/src/test/common.rs
new file mode 100644
index 0000000..7fe8b17
--- /dev/null
+++ b/src/test/common.rs
@@ -0,0 +1,401 @@
+///! Basic commands tests.
+use super::{assert_cursor, assert_line, assert_line_with_initial, init_editor};
+use config::EditMode;
+use consts::KeyPress;
+use error::ReadlineError;
+
+#[test]
+fn home_key() {
+    for mode in &[EditMode::Emacs, EditMode::Vi] {
+        assert_cursor(
+            *mode,
+            ("", ""),
+            &[KeyPress::Home, KeyPress::Enter],
+            ("", ""),
+        );
+        assert_cursor(
+            *mode,
+            ("Hi", ""),
+            &[KeyPress::Home, KeyPress::Enter],
+            ("", "Hi"),
+        );
+        if *mode == EditMode::Vi {
+            // vi command mode
+            assert_cursor(
+                *mode,
+                ("Hi", ""),
+                &[KeyPress::Esc, KeyPress::Home, KeyPress::Enter],
+                ("", "Hi"),
+            );
+        }
+    }
+}
+
+#[test]
+fn end_key() {
+    for mode in &[EditMode::Emacs, EditMode::Vi] {
+        assert_cursor(*mode, ("", ""), &[KeyPress::End, KeyPress::Enter], ("", ""));
+        assert_cursor(
+            *mode,
+            ("H", "i"),
+            &[KeyPress::End, KeyPress::Enter],
+            ("Hi", ""),
+        );
+        assert_cursor(
+            *mode,
+            ("", "Hi"),
+            &[KeyPress::End, KeyPress::Enter],
+            ("Hi", ""),
+        );
+        if *mode == EditMode::Vi {
+            // vi command mode
+            assert_cursor(
+                *mode,
+                ("", "Hi"),
+                &[KeyPress::Esc, KeyPress::End, KeyPress::Enter],
+                ("Hi", ""),
+            );
+        }
+    }
+}
+
+#[test]
+fn left_key() {
+    for mode in &[EditMode::Emacs, EditMode::Vi] {
+        assert_cursor(
+            *mode,
+            ("Hi", ""),
+            &[KeyPress::Left, KeyPress::Enter],
+            ("H", "i"),
+        );
+        assert_cursor(
+            *mode,
+            ("H", "i"),
+            &[KeyPress::Left, KeyPress::Enter],
+            ("", "Hi"),
+        );
+        assert_cursor(
+            *mode,
+            ("", "Hi"),
+            &[KeyPress::Left, KeyPress::Enter],
+            ("", "Hi"),
+        );
+        if *mode == EditMode::Vi {
+            // vi command mode
+            assert_cursor(
+                *mode,
+                ("Bye", ""),
+                &[KeyPress::Esc, KeyPress::Left, KeyPress::Enter],
+                ("B", "ye"),
+            );
+        }
+    }
+}
+
+#[test]
+fn right_key() {
+    for mode in &[EditMode::Emacs, EditMode::Vi] {
+        assert_cursor(
+            *mode,
+            ("", ""),
+            &[KeyPress::Right, KeyPress::Enter],
+            ("", ""),
+        );
+        assert_cursor(
+            *mode,
+            ("", "Hi"),
+            &[KeyPress::Right, KeyPress::Enter],
+            ("H", "i"),
+        );
+        assert_cursor(
+            *mode,
+            ("B", "ye"),
+            &[KeyPress::Right, KeyPress::Enter],
+            ("By", "e"),
+        );
+        assert_cursor(
+            *mode,
+            ("H", "i"),
+            &[KeyPress::Right, KeyPress::Enter],
+            ("Hi", ""),
+        );
+        if *mode == EditMode::Vi {
+            // vi command mode
+            assert_cursor(
+                *mode,
+                ("", "Hi"),
+                &[KeyPress::Esc, KeyPress::Right, KeyPress::Enter],
+                ("H", "i"),
+            );
+        }
+    }
+}
+
+#[test]
+fn enter_key() {
+    for mode in &[EditMode::Emacs, EditMode::Vi] {
+        assert_line(*mode, &[KeyPress::Enter], "");
+        assert_line(*mode, &[KeyPress::Char('a'), KeyPress::Enter], "a");
+        assert_line_with_initial(*mode, ("Hi", ""), &[KeyPress::Enter], "Hi");
+        assert_line_with_initial(*mode, ("", "Hi"), &[KeyPress::Enter], "Hi");
+        assert_line_with_initial(*mode, ("H", "i"), &[KeyPress::Enter], "Hi");
+        if *mode == EditMode::Vi {
+            // vi command mode
+            assert_line(*mode, &[KeyPress::Esc, KeyPress::Enter], "");
+            assert_line(
+                *mode,
+                &[KeyPress::Char('a'), KeyPress::Esc, KeyPress::Enter],
+                "a",
+            );
+            assert_line_with_initial(*mode, ("Hi", ""), &[KeyPress::Esc, KeyPress::Enter], "Hi");
+            assert_line_with_initial(*mode, ("", "Hi"), &[KeyPress::Esc, KeyPress::Enter], "Hi");
+            assert_line_with_initial(*mode, ("H", "i"), &[KeyPress::Esc, KeyPress::Enter], "Hi");
+        }
+    }
+}
+
+#[test]
+fn newline_key() {
+    for mode in &[EditMode::Emacs, EditMode::Vi] {
+        assert_line(*mode, &[KeyPress::Ctrl('J')], "");
+        assert_line(*mode, &[KeyPress::Char('a'), KeyPress::Ctrl('J')], "a");
+        if *mode == EditMode::Vi {
+            // vi command mode
+            assert_line(*mode, &[KeyPress::Esc, KeyPress::Ctrl('J')], "");
+            assert_line(
+                *mode,
+                &[KeyPress::Char('a'), KeyPress::Esc, KeyPress::Ctrl('J')],
+                "a",
+            );
+        }
+    }
+}
+
+#[test]
+fn eof_key() {
+    for mode in &[EditMode::Emacs, EditMode::Vi] {
+        let mut editor = init_editor(*mode, &[KeyPress::Ctrl('D')]);
+        let err = editor.readline(">>");
+        assert_matches!(err, Err(ReadlineError::Eof));
+    }
+    assert_line(
+        EditMode::Emacs,
+        &[KeyPress::Char('a'), KeyPress::Ctrl('D'), KeyPress::Enter],
+        "a",
+    );
+    assert_line(
+        EditMode::Vi,
+        &[KeyPress::Char('a'), KeyPress::Ctrl('D')],
+        "a",
+    );
+    assert_line(
+        EditMode::Vi,
+        &[KeyPress::Char('a'), KeyPress::Esc, KeyPress::Ctrl('D')],
+        "a",
+    );
+    assert_line_with_initial(
+        EditMode::Emacs,
+        ("", "Hi"),
+        &[KeyPress::Ctrl('D'), KeyPress::Enter],
+        "i",
+    );
+    assert_line_with_initial(EditMode::Vi, ("", "Hi"), &[KeyPress::Ctrl('D')], "Hi");
+    assert_line_with_initial(
+        EditMode::Vi,
+        ("", "Hi"),
+        &[KeyPress::Esc, KeyPress::Ctrl('D')],
+        "Hi",
+    );
+}
+
+#[test]
+fn interrupt_key() {
+    for mode in &[EditMode::Emacs, EditMode::Vi] {
+        let mut editor = init_editor(*mode, &[KeyPress::Ctrl('C')]);
+        let err = editor.readline(">>");
+        assert_matches!(err, Err(ReadlineError::Interrupted));
+
+        let mut editor = init_editor(*mode, &[KeyPress::Ctrl('C')]);
+        let err = editor.readline_with_initial(">>", ("Hi", ""));
+        assert_matches!(err, Err(ReadlineError::Interrupted));
+        if *mode == EditMode::Vi {
+            // vi command mode
+            let mut editor = init_editor(*mode, &[KeyPress::Esc, KeyPress::Ctrl('C')]);
+            let err = editor.readline_with_initial(">>", ("Hi", ""));
+            assert_matches!(err, Err(ReadlineError::Interrupted));
+        }
+    }
+}
+
+#[test]
+fn delete_key() {
+    for mode in &[EditMode::Emacs, EditMode::Vi] {
+        assert_cursor(
+            *mode,
+            ("a", ""),
+            &[KeyPress::Delete, KeyPress::Enter],
+            ("a", ""),
+        );
+        assert_cursor(
+            *mode,
+            ("", "a"),
+            &[KeyPress::Delete, KeyPress::Enter],
+            ("", ""),
+        );
+        if *mode == EditMode::Vi {
+            // vi command mode
+            assert_cursor(
+                *mode,
+                ("", "a"),
+                &[KeyPress::Esc, KeyPress::Delete, KeyPress::Enter],
+                ("", ""),
+            );
+        }
+    }
+}
+
+#[test]
+fn ctrl_t() {
+    for mode in &[EditMode::Emacs, EditMode::Vi] {
+        assert_cursor(
+            *mode,
+            ("a", "b"),
+            &[KeyPress::Ctrl('T'), KeyPress::Enter],
+            ("ba", ""),
+        );
+        assert_cursor(
+            *mode,
+            ("ab", "cd"),
+            &[KeyPress::Ctrl('T'), KeyPress::Enter],
+            ("acb", "d"),
+        );
+        if *mode == EditMode::Vi {
+            // vi command mode
+            assert_cursor(
+                *mode,
+                ("ab", ""),
+                &[KeyPress::Esc, KeyPress::Ctrl('T'), KeyPress::Enter],
+                ("ba", ""),
+            );
+        }
+    }
+}
+
+#[test]
+fn ctrl_u() {
+    for mode in &[EditMode::Emacs, EditMode::Vi] {
+        assert_cursor(
+            *mode,
+            ("start of line ", "end"),
+            &[KeyPress::Ctrl('U'), KeyPress::Enter],
+            ("", "end"),
+        );
+        assert_cursor(
+            *mode,
+            ("", "end"),
+            &[KeyPress::Ctrl('U'), KeyPress::Enter],
+            ("", "end"),
+        );
+        if *mode == EditMode::Vi {
+            // vi command mode
+            assert_cursor(
+                *mode,
+                ("start of line ", "end"),
+                &[KeyPress::Esc, KeyPress::Ctrl('U'), KeyPress::Enter],
+                ("", " end"),
+            );
+        }
+    }
+}
+
+#[cfg(unix)]
+#[test]
+fn ctrl_v() {
+    for mode in &[EditMode::Emacs, EditMode::Vi] {
+        assert_cursor(
+            *mode,
+            ("", ""),
+            &[KeyPress::Ctrl('V'), KeyPress::Char('\t'), KeyPress::Enter],
+            ("\t", ""),
+        );
+        if *mode == EditMode::Vi {
+            // vi command mode
+            assert_cursor(
+                *mode,
+                ("", ""),
+                &[
+                    KeyPress::Esc,
+                    KeyPress::Ctrl('V'),
+                    KeyPress::Char('\t'),
+                    KeyPress::Enter,
+                ],
+                ("\t", ""),
+            );
+        }
+    }
+}
+
+#[test]
+fn ctrl_w() {
+    for mode in &[EditMode::Emacs, EditMode::Vi] {
+        assert_cursor(
+            *mode,
+            ("Hello, ", "world"),
+            &[KeyPress::Ctrl('W'), KeyPress::Enter],
+            ("", "world"),
+        );
+        assert_cursor(
+            *mode,
+            ("Hello, world.", ""),
+            &[KeyPress::Ctrl('W'), KeyPress::Enter],
+            ("Hello, ", ""),
+        );
+        if *mode == EditMode::Vi {
+            // vi command mode
+            assert_cursor(
+                *mode,
+                ("Hello, world.", ""),
+                &[KeyPress::Esc, KeyPress::Ctrl('W'), KeyPress::Enter],
+                ("Hello, ", "."),
+            );
+        }
+    }
+}
+
+#[test]
+fn ctrl_y() {
+    for mode in &[EditMode::Emacs /* FIXME, EditMode::Vi */] {
+        assert_cursor(
+            *mode,
+            ("Hello, ", "world"),
+            &[KeyPress::Ctrl('W'), KeyPress::Ctrl('Y'), KeyPress::Enter],
+            ("Hello, ", "world"),
+        );
+    }
+}
+
+#[test]
+fn ctrl__() {
+    for mode in &[EditMode::Emacs, EditMode::Vi] {
+        assert_cursor(
+            *mode,
+            ("Hello, ", "world"),
+            &[KeyPress::Ctrl('W'), KeyPress::Ctrl('_'), KeyPress::Enter],
+            ("Hello, ", "world"),
+        );
+        if *mode == EditMode::Vi {
+            // vi command mode
+            assert_cursor(
+                *mode,
+                ("Hello, ", "world"),
+                &[
+                    KeyPress::Esc,
+                    KeyPress::Ctrl('W'),
+                    KeyPress::Ctrl('_'),
+                    KeyPress::Enter,
+                ],
+                ("Hello,", " world"),
+            );
+        }
+    }
+}
diff --git a/src/test/emacs.rs b/src/test/emacs.rs
new file mode 100644
index 0000000..67a1b10
--- /dev/null
+++ b/src/test/emacs.rs
@@ -0,0 +1,378 @@
+//! Emacs specific key bindings
+use super::{assert_cursor, assert_history};
+use config::EditMode;
+use consts::KeyPress;
+
+#[test]
+fn ctrl_a() {
+    assert_cursor(
+        EditMode::Emacs,
+        ("Hi", ""),
+        &[KeyPress::Ctrl('A'), KeyPress::Enter],
+        ("", "Hi"),
+    );
+}
+
+#[test]
+fn ctrl_e() {
+    assert_cursor(
+        EditMode::Emacs,
+        ("", "Hi"),
+        &[KeyPress::Ctrl('E'), KeyPress::Enter],
+        ("Hi", ""),
+    );
+}
+
+#[test]
+fn ctrl_b() {
+    assert_cursor(
+        EditMode::Emacs,
+        ("Hi", ""),
+        &[KeyPress::Ctrl('B'), KeyPress::Enter],
+        ("H", "i"),
+    );
+    assert_cursor(
+        EditMode::Emacs,
+        ("Hi", ""),
+        &[KeyPress::Meta('2'), KeyPress::Ctrl('B'), KeyPress::Enter],
+        ("", "Hi"),
+    );
+    assert_cursor(
+        EditMode::Emacs,
+        ("", "Hi"),
+        &[
+            KeyPress::Meta('-'),
+            KeyPress::Meta('2'),
+            KeyPress::Ctrl('B'),
+            KeyPress::Enter,
+        ],
+        ("Hi", ""),
+    );
+}
+
+#[test]
+fn ctrl_f() {
+    assert_cursor(
+        EditMode::Emacs,
+        ("", "Hi"),
+        &[KeyPress::Ctrl('F'), KeyPress::Enter],
+        ("H", "i"),
+    );
+    assert_cursor(
+        EditMode::Emacs,
+        ("", "Hi"),
+        &[KeyPress::Meta('2'), KeyPress::Ctrl('F'), KeyPress::Enter],
+        ("Hi", ""),
+    );
+    assert_cursor(
+        EditMode::Emacs,
+        ("Hi", ""),
+        &[
+            KeyPress::Meta('-'),
+            KeyPress::Meta('2'),
+            KeyPress::Ctrl('F'),
+            KeyPress::Enter,
+        ],
+        ("", "Hi"),
+    );
+}
+
+#[test]
+fn ctrl_h() {
+    assert_cursor(
+        EditMode::Emacs,
+        ("Hi", ""),
+        &[KeyPress::Ctrl('H'), KeyPress::Enter],
+        ("H", ""),
+    );
+    assert_cursor(
+        EditMode::Emacs,
+        ("Hi", ""),
+        &[KeyPress::Meta('2'), KeyPress::Ctrl('H'), KeyPress::Enter],
+        ("", ""),
+    );
+    assert_cursor(
+        EditMode::Emacs,
+        ("", "Hi"),
+        &[
+            KeyPress::Meta('-'),
+            KeyPress::Meta('2'),
+            KeyPress::Ctrl('H'),
+            KeyPress::Enter,
+        ],
+        ("", ""),
+    );
+}
+
+#[test]
+fn backspace() {
+    assert_cursor(
+        EditMode::Emacs,
+        ("", ""),
+        &[KeyPress::Backspace, KeyPress::Enter],
+        ("", ""),
+    );
+    assert_cursor(
+        EditMode::Emacs,
+        ("Hi", ""),
+        &[KeyPress::Backspace, KeyPress::Enter],
+        ("H", ""),
+    );
+    assert_cursor(
+        EditMode::Emacs,
+        ("", "Hi"),
+        &[KeyPress::Backspace, KeyPress::Enter],
+        ("", "Hi"),
+    );
+}
+
+#[test]
+fn ctrl_k() {
+    assert_cursor(
+        EditMode::Emacs,
+        ("Hi", ""),
+        &[KeyPress::Ctrl('K'), KeyPress::Enter],
+        ("Hi", ""),
+    );
+    assert_cursor(
+        EditMode::Emacs,
+        ("", "Hi"),
+        &[KeyPress::Ctrl('K'), KeyPress::Enter],
+        ("", ""),
+    );
+    assert_cursor(
+        EditMode::Emacs,
+        ("B", "ye"),
+        &[KeyPress::Ctrl('K'), KeyPress::Enter],
+        ("B", ""),
+    );
+}
+
+#[test]
+fn ctrl_n() {
+    assert_history(
+        EditMode::Emacs,
+        &["line1", "line2"],
+        &[
+            KeyPress::Ctrl('P'),
+            KeyPress::Ctrl('P'),
+            KeyPress::Ctrl('N'),
+            KeyPress::Enter,
+        ],
+        ("line2", ""),
+    );
+}
+
+#[test]
+fn ctrl_p() {
+    assert_history(
+        EditMode::Emacs,
+        &["line1"],
+        &[KeyPress::Ctrl('P'), KeyPress::Enter],
+        ("line1", ""),
+    );
+}
+
+#[test]
+fn ctrl_t() {
+    /* FIXME
+    assert_cursor(
+        ("ab", "cd"),
+        &[KeyPress::Meta('2'), KeyPress::Ctrl('T'), KeyPress::Enter],
+        ("acdb", ""),
+    );*/
+}
+
+#[test]
+fn ctrl_x_ctrl_u() {
+    assert_cursor(
+        EditMode::Emacs,
+        ("Hello, ", "world"),
+        &[
+            KeyPress::Ctrl('W'),
+            KeyPress::Ctrl('X'),
+            KeyPress::Ctrl('U'),
+            KeyPress::Enter,
+        ],
+        ("Hello, ", "world"),
+    );
+}
+
+#[test]
+fn meta_b() {
+    assert_cursor(
+        EditMode::Emacs,
+        ("Hello, world!", ""),
+        &[KeyPress::Meta('B'), KeyPress::Enter],
+        ("Hello, ", "world!"),
+    );
+    assert_cursor(
+        EditMode::Emacs,
+        ("Hello, world!", ""),
+        &[KeyPress::Meta('2'), KeyPress::Meta('B'), KeyPress::Enter],
+        ("", "Hello, world!"),
+    );
+    assert_cursor(
+        EditMode::Emacs,
+        ("", "Hello, world!"),
+        &[KeyPress::Meta('-'), KeyPress::Meta('B'), KeyPress::Enter],
+        ("Hello", ", world!"),
+    );
+}
+
+#[test]
+fn meta_f() {
+    assert_cursor(
+        EditMode::Emacs,
+        ("", "Hello, world!"),
+        &[KeyPress::Meta('F'), KeyPress::Enter],
+        ("Hello", ", world!"),
+    );
+    assert_cursor(
+        EditMode::Emacs,
+        ("", "Hello, world!"),
+        &[KeyPress::Meta('2'), KeyPress::Meta('F'), KeyPress::Enter],
+        ("Hello, world", "!"),
+    );
+    assert_cursor(
+        EditMode::Emacs,
+        ("Hello, world!", ""),
+        &[KeyPress::Meta('-'), KeyPress::Meta('F'), KeyPress::Enter],
+        ("Hello, ", "world!"),
+    );
+}
+
+#[test]
+fn meta_c() {
+    assert_cursor(
+        EditMode::Emacs,
+        ("hi", ""),
+        &[KeyPress::Meta('C'), KeyPress::Enter],
+        ("hi", ""),
+    );
+    assert_cursor(
+        EditMode::Emacs,
+        ("", "hi"),
+        &[KeyPress::Meta('C'), KeyPress::Enter],
+        ("Hi", ""),
+    );
+    /* FIXME
+    assert_cursor(
+        ("", "hi test"),
+        &[KeyPress::Meta('2'), KeyPress::Meta('C'), KeyPress::Enter],
+        ("Hi Test", ""),
+    );*/
+}
+
+#[test]
+fn meta_l() {
+    assert_cursor(
+        EditMode::Emacs,
+        ("Hi", ""),
+        &[KeyPress::Meta('L'), KeyPress::Enter],
+        ("Hi", ""),
+    );
+    assert_cursor(
+        EditMode::Emacs,
+        ("", "HI"),
+        &[KeyPress::Meta('L'), KeyPress::Enter],
+        ("hi", ""),
+    );
+    /* FIXME
+    assert_cursor(
+        ("", "HI TEST"),
+        &[KeyPress::Meta('2'), KeyPress::Meta('L'), KeyPress::Enter],
+        ("hi test", ""),
+    );*/
+}
+
+#[test]
+fn meta_u() {
+    assert_cursor(
+        EditMode::Emacs,
+        ("hi", ""),
+        &[KeyPress::Meta('U'), KeyPress::Enter],
+        ("hi", ""),
+    );
+    assert_cursor(
+        EditMode::Emacs,
+        ("", "hi"),
+        &[KeyPress::Meta('U'), KeyPress::Enter],
+        ("HI", ""),
+    );
+    /* FIXME
+    assert_cursor(
+        ("", "hi test"),
+        &[KeyPress::Meta('2'), KeyPress::Meta('U'), KeyPress::Enter],
+        ("HI TEST", ""),
+    );*/
+}
+
+#[test]
+fn meta_d() {
+    assert_cursor(
+        EditMode::Emacs,
+        ("Hello", ", world!"),
+        &[KeyPress::Meta('D'), KeyPress::Enter],
+        ("Hello", "!"),
+    );
+    assert_cursor(
+        EditMode::Emacs,
+        ("Hello", ", world!"),
+        &[KeyPress::Meta('2'), KeyPress::Meta('D'), KeyPress::Enter],
+        ("Hello", ""),
+    );
+}
+
+#[test]
+fn meta_t() {
+    assert_cursor(
+        EditMode::Emacs,
+        ("Hello", ", world!"),
+        &[KeyPress::Meta('T'), KeyPress::Enter],
+        ("world, Hello", "!"),
+    );
+    /* FIXME
+    assert_cursor(
+        ("One Two", " Three Four"),
+        &[KeyPress::Meta('T'), KeyPress::Enter],
+        ("One Four Three Two", ""),
+    );*/
+}
+
+#[test]
+fn meta_y() {
+    assert_cursor(
+        EditMode::Emacs,
+        ("Hello, world", "!"),
+        &[
+            KeyPress::Ctrl('W'),
+            KeyPress::Left,
+            KeyPress::Ctrl('W'),
+            KeyPress::Ctrl('Y'),
+            KeyPress::Meta('Y'),
+            KeyPress::Enter,
+        ],
+        ("world", " !"),
+    );
+}
+
+#[test]
+fn meta_backspace() {
+    assert_cursor(
+        EditMode::Emacs,
+        ("Hello, wor", "ld!"),
+        &[KeyPress::Meta('\x08'), KeyPress::Enter],
+        ("Hello, ", "ld!"),
+    );
+}
+
+#[test]
+fn meta_digit() {
+    assert_cursor(
+        EditMode::Emacs,
+        ("", ""),
+        &[KeyPress::Meta('3'), KeyPress::Char('h'), KeyPress::Enter],
+        ("hhh", ""),
+    );
+}
diff --git a/src/test/history.rs b/src/test/history.rs
new file mode 100644
index 0000000..faa7a3f
--- /dev/null
+++ b/src/test/history.rs
@@ -0,0 +1,206 @@
+//! History related commands tests
+use super::assert_history;
+use config::EditMode;
+use consts::KeyPress;
+
+#[test]
+fn down_key() {
+    for mode in &[EditMode::Emacs, EditMode::Vi] {
+        assert_history(
+            *mode,
+            &["line1"],
+            &[KeyPress::Down, KeyPress::Enter],
+            ("", ""),
+        );
+        assert_history(
+            *mode,
+            &["line1", "line2"],
+            &[KeyPress::Up, KeyPress::Up, KeyPress::Down, KeyPress::Enter],
+            ("line2", ""),
+        );
+        assert_history(
+            *mode,
+            &["line1"],
+            &[
+                KeyPress::Char('a'),
+                KeyPress::Up,
+                KeyPress::Down, // restore original line
+                KeyPress::Enter,
+            ],
+            ("a", ""),
+        );
+        assert_history(
+            *mode,
+            &["line1"],
+            &[
+                KeyPress::Char('a'),
+                KeyPress::Down, // noop
+                KeyPress::Enter,
+            ],
+            ("a", ""),
+        );
+    }
+}
+
+#[test]
+fn up_key() {
+    for mode in &[EditMode::Emacs, EditMode::Vi] {
+        assert_history(*mode, &[], &[KeyPress::Up, KeyPress::Enter], ("", ""));
+        assert_history(
+            *mode,
+            &["line1"],
+            &[KeyPress::Up, KeyPress::Enter],
+            ("line1", ""),
+        );
+        assert_history(
+            *mode,
+            &["line1", "line2"],
+            &[KeyPress::Up, KeyPress::Up, KeyPress::Enter],
+            ("line1", ""),
+        );
+    }
+}
+
+#[test]
+fn ctrl_r() {
+    for mode in &[EditMode::Emacs, EditMode::Vi] {
+        assert_history(
+            *mode,
+            &[],
+            &[KeyPress::Ctrl('R'), KeyPress::Char('o'), KeyPress::Enter],
+            ("o", ""),
+        );
+        assert_history(
+            *mode,
+            &["rustc", "cargo"],
+            &[
+                KeyPress::Ctrl('R'),
+                KeyPress::Char('o'),
+                KeyPress::Right, // just to assert cursor pos
+                KeyPress::Enter,
+            ],
+            ("cargo", ""),
+        );
+        assert_history(
+            *mode,
+            &["rustc", "cargo"],
+            &[
+                KeyPress::Ctrl('R'),
+                KeyPress::Char('u'),
+                KeyPress::Right, // just to assert cursor pos
+                KeyPress::Enter,
+            ],
+            ("ru", "stc"),
+        );
+        assert_history(
+            *mode,
+            &["rustc", "cargo"],
+            &[
+                KeyPress::Ctrl('R'),
+                KeyPress::Char('r'),
+                KeyPress::Char('u'),
+                KeyPress::Right, // just to assert cursor pos
+                KeyPress::Enter,
+            ],
+            ("r", "ustc"),
+        );
+        assert_history(
+            *mode,
+            &["rustc", "cargo"],
+            &[
+                KeyPress::Ctrl('R'),
+                KeyPress::Char('r'),
+                KeyPress::Ctrl('R'),
+                KeyPress::Right, // just to assert cursor pos
+                KeyPress::Enter,
+            ],
+            ("r", "ustc"),
+        );
+        assert_history(
+            *mode,
+            &["rustc", "cargo"],
+            &[
+                KeyPress::Ctrl('R'),
+                KeyPress::Char('r'),
+                KeyPress::Char('z'), // no match
+                KeyPress::Right,     // just to assert cursor pos
+                KeyPress::Enter,
+            ],
+            ("car", "go"),
+        );
+        assert_history(
+            EditMode::Emacs,
+            &["rustc", "cargo"],
+            &[
+                KeyPress::Char('a'),
+                KeyPress::Ctrl('R'),
+                KeyPress::Char('r'),
+                KeyPress::Ctrl('G'), // abort (FIXME: doesn't work with vi mode)
+                KeyPress::Enter,
+            ],
+            ("a", ""),
+        );
+    }
+}
+
+#[test]
+fn ctrl_s() {
+    for mode in &[EditMode::Emacs, EditMode::Vi] {
+        assert_history(
+            *mode,
+            &["rustc", "cargo"],
+            &[
+                KeyPress::Ctrl('R'),
+                KeyPress::Char('r'),
+                KeyPress::Ctrl('R'),
+                KeyPress::Ctrl('S'),
+                KeyPress::Right, // just to assert cursor pos
+                KeyPress::Enter,
+            ],
+            ("car", "go"),
+        );
+    }
+}
+
+#[test]
+fn meta_lt() {
+    assert_history(
+        EditMode::Emacs,
+        &[""],
+        &[KeyPress::Meta('<'), KeyPress::Enter],
+        ("", ""),
+    );
+    assert_history(
+        EditMode::Emacs,
+        &["rustc", "cargo"],
+        &[KeyPress::Meta('<'), KeyPress::Enter],
+        ("rustc", ""),
+    );
+}
+
+#[test]
+fn meta_gt() {
+    assert_history(
+        EditMode::Emacs,
+        &[""],
+        &[KeyPress::Meta('>'), KeyPress::Enter],
+        ("", ""),
+    );
+    assert_history(
+        EditMode::Emacs,
+        &["rustc", "cargo"],
+        &[KeyPress::Meta('<'), KeyPress::Meta('>'), KeyPress::Enter],
+        ("", ""),
+    );
+    assert_history(
+        EditMode::Emacs,
+        &["rustc", "cargo"],
+        &[
+            KeyPress::Char('a'),
+            KeyPress::Meta('<'),
+            KeyPress::Meta('>'), // restore original line
+            KeyPress::Enter,
+        ],
+        ("a", ""),
+    );
+}
diff --git a/src/test/mod.rs b/src/test/mod.rs
new file mode 100644
index 0000000..d3216de
--- /dev/null
+++ b/src/test/mod.rs
@@ -0,0 +1,103 @@
+use std::collections::HashMap;
+use std::sync::{Arc, RwLock};
+
+use super::{Editor, Result};
+use completion::Completer;
+use config::{Config, EditMode};
+use consts::KeyPress;
+use edit::init_state;
+use keymap::{Cmd, InputState};
+use tty::Sink;
+
+mod common;
+mod emacs;
+mod history;
+mod vi_cmd;
+mod vi_insert;
+
+fn init_editor(mode: EditMode, keys: &[KeyPress]) -> Editor<()> {
+    let config = Config::builder().edit_mode(mode).build();
+    let mut editor = Editor::<()>::with_config(config);
+    editor.term.keys.extend(keys.iter().cloned());
+    editor
+}
+
+struct SimpleCompleter;
+impl Completer for SimpleCompleter {
+    fn complete(&self, line: &str, _pos: usize) -> Result<(usize, Vec<String>)> {
+        Ok((0, vec![line.to_owned() + "t"]))
+    }
+}
+
+#[test]
+fn complete_line() {
+    let mut out = Sink::new();
+    let mut s = init_state(&mut out, "rus", 3);
+    let config = Config::default();
+    let mut input_state = InputState::new(&config, Arc::new(RwLock::new(HashMap::new())));
+    let keys = &[KeyPress::Enter];
+    let mut rdr = keys.iter();
+    let completer = SimpleCompleter;
+    let cmd = super::complete_line(
+        &mut rdr,
+        &mut s,
+        &mut input_state,
+        &completer,
+        &Config::default(),
+    ).unwrap();
+    assert_eq!(Some(Cmd::AcceptLine), cmd);
+    assert_eq!("rust", s.line.as_str());
+    assert_eq!(4, s.line.pos());
+}
+
+// `keys`: keys to press
+// `expected_line`: line after enter key
+fn assert_line(mode: EditMode, keys: &[KeyPress], expected_line: &str) {
+    let mut editor = init_editor(mode, keys);
+    let actual_line = editor.readline(">>").unwrap();
+    assert_eq!(expected_line, actual_line);
+}
+
+// `initial`: line status before `keys` pressed: strings before and after cursor
+// `keys`: keys to press
+// `expected_line`: line after enter key
+fn assert_line_with_initial(
+    mode: EditMode,
+    initial: (&str, &str),
+    keys: &[KeyPress],
+    expected_line: &str,
+) {
+    let mut editor = init_editor(mode, keys);
+    let actual_line = editor.readline_with_initial(">>", initial).unwrap();
+    assert_eq!(expected_line, actual_line);
+}
+
+// `initial`: line status before `keys` pressed: strings before and after cursor
+// `keys`: keys to press
+// `expected`: line status before enter key: strings before and after cursor
+fn assert_cursor(mode: EditMode, initial: (&str, &str), keys: &[KeyPress], expected: (&str, &str)) {
+    let mut editor = init_editor(mode, keys);
+    let actual_line = editor.readline_with_initial("", initial).unwrap();
+    assert_eq!(expected.0.to_owned() + expected.1, actual_line);
+    assert_eq!(expected.0.len(), editor.term.cursor);
+}
+
+// `entries`: history entries before `keys` pressed
+// `keys`: keys to press
+// `expected`: line status before enter key: strings before and after cursor
+fn assert_history(mode: EditMode, entries: &[&str], keys: &[KeyPress], expected: (&str, &str)) {
+    let mut editor = init_editor(mode, keys);
+    for entry in entries {
+        editor.history.add(*entry);
+    }
+    let actual_line = editor.readline("").unwrap();
+    assert_eq!(expected.0.to_owned() + expected.1, actual_line);
+    assert_eq!(expected.0.len(), editor.term.cursor);
+}
+
+#[test]
+fn unknown_esc_key() {
+    for mode in &[EditMode::Emacs, EditMode::Vi] {
+        assert_line(*mode, &[KeyPress::UnknownEscSeq, KeyPress::Enter], "");
+    }
+}
diff --git a/src/test/vi_cmd.rs b/src/test/vi_cmd.rs
new file mode 100644
index 0000000..1c1e4e9
--- /dev/null
+++ b/src/test/vi_cmd.rs
@@ -0,0 +1,611 @@
+//! Vi command mode specific key bindings
+use super::{assert_cursor, assert_history};
+use config::EditMode;
+use consts::KeyPress;
+
+#[test]
+fn dollar() {
+    assert_cursor(
+        EditMode::Vi,
+        ("", "Hi"),
+        &[KeyPress::Esc, KeyPress::Char('$'), KeyPress::Enter],
+        ("Hi", ""), // FIXME
+    );
+}
+
+/*#[test]
+fn dot() {
+    // TODO
+}*/
+
+#[test]
+fn semi_colon() {
+    assert_cursor(
+        EditMode::Vi,
+        ("", "Hello, world!"),
+        &[
+            KeyPress::Esc,
+            KeyPress::Char('f'),
+            KeyPress::Char('o'),
+            KeyPress::Char(';'),
+            KeyPress::Enter,
+        ],
+        ("Hello, w", "orld!"),
+    );
+}
+
+#[test]
+fn comma() {
+    assert_cursor(
+        EditMode::Vi,
+        ("Hello, w", "orld!"),
+        &[
+            KeyPress::Esc,
+            KeyPress::Char('f'),
+            KeyPress::Char('l'),
+            KeyPress::Char(','),
+            KeyPress::Enter,
+        ],
+        ("Hel", "lo, world!"),
+    );
+}
+
+#[test]
+fn zero() {
+    assert_cursor(
+        EditMode::Vi,
+        ("Hi", ""),
+        &[KeyPress::Esc, KeyPress::Char('0'), KeyPress::Enter],
+        ("", "Hi"),
+    );
+}
+
+#[test]
+fn caret() {
+    assert_cursor(
+        EditMode::Vi,
+        (" Hi", ""),
+        &[KeyPress::Esc, KeyPress::Char('^'), KeyPress::Enter],
+        (" ", "Hi"),
+    );
+}
+
+#[test]
+fn a() {
+    assert_cursor(
+        EditMode::Vi,
+        ("B", "e"),
+        &[
+            KeyPress::Esc,
+            KeyPress::Char('a'),
+            KeyPress::Char('y'),
+            KeyPress::Enter,
+        ],
+        ("By", "e"),
+    );
+}
+
+#[test]
+fn uppercase_a() {
+    assert_cursor(
+        EditMode::Vi,
+        ("", "By"),
+        &[
+            KeyPress::Esc,
+            KeyPress::Char('A'),
+            KeyPress::Char('e'),
+            KeyPress::Enter,
+        ],
+        ("Bye", ""),
+    );
+}
+
+#[test]
+fn b() {
+    assert_cursor(
+        EditMode::Vi,
+        ("Hello, world!", ""),
+        &[KeyPress::Esc, KeyPress::Char('b'), KeyPress::Enter],
+        ("Hello, ", "world!"),
+    );
+    assert_cursor(
+        EditMode::Vi,
+        ("Hello, world!", ""),
+        &[
+            KeyPress::Esc,
+            KeyPress::Char('2'),
+            KeyPress::Char('b'),
+            KeyPress::Enter,
+        ],
+        ("Hello", ", world!"),
+    );
+}
+
+#[test]
+fn uppercase_b() {
+    assert_cursor(
+        EditMode::Vi,
+        ("Hello, world!", ""),
+        &[KeyPress::Esc, KeyPress::Char('B'), KeyPress::Enter],
+        ("Hello, ", "world!"),
+    );
+    assert_cursor(
+        EditMode::Vi,
+        ("Hello, world!", ""),
+        &[
+            KeyPress::Esc,
+            KeyPress::Char('2'),
+            KeyPress::Char('B'),
+            KeyPress::Enter,
+        ],
+        ("", "Hello, world!"),
+    );
+}
+
+#[test]
+fn uppercase_c() {
+    assert_cursor(
+        EditMode::Vi,
+        ("Hello, w", "orld!"),
+        &[
+            KeyPress::Esc,
+            KeyPress::Char('C'),
+            KeyPress::Char('i'),
+            KeyPress::Enter,
+        ],
+        ("Hello, i", ""),
+    );
+}
+
+#[test]
+fn ctrl_k() {
+    for key in &[KeyPress::Char('D'), KeyPress::Ctrl('K')] {
+        assert_cursor(
+            EditMode::Vi,
+            ("Hi", ""),
+            &[KeyPress::Esc, *key, KeyPress::Enter],
+            ("H", ""),
+        );
+        assert_cursor(
+            EditMode::Vi,
+            ("", "Hi"),
+            &[KeyPress::Esc, *key, KeyPress::Enter],
+            ("", ""),
+        );
+        assert_cursor(
+            EditMode::Vi,
+            ("By", "e"),
+            &[KeyPress::Esc, *key, KeyPress::Enter],
+            ("B", ""),
+        );
+    }
+}
+
+#[test]
+fn e() {
+    assert_cursor(
+        EditMode::Vi,
+        ("", "Hello, world!"),
+        &[KeyPress::Esc, KeyPress::Char('e'), KeyPress::Enter],
+        ("Hell", "o, world!"),
+    );
+    assert_cursor(
+        EditMode::Vi,
+        ("", "Hello, world!"),
+        &[
+            KeyPress::Esc,
+            KeyPress::Char('2'),
+            KeyPress::Char('e'),
+            KeyPress::Enter,
+        ],
+        ("Hello, worl", "d!"),
+    );
+}
+
+#[test]
+fn uppercase_e() {
+    assert_cursor(
+        EditMode::Vi,
+        ("", "Hello, world!"),
+        &[KeyPress::Esc, KeyPress::Char('E'), KeyPress::Enter],
+        ("Hello", ", world!"),
+    );
+    assert_cursor(
+        EditMode::Vi,
+        ("", "Hello, world!"),
+        &[
+            KeyPress::Esc,
+            KeyPress::Char('2'),
+            KeyPress::Char('E'),
+            KeyPress::Enter,
+        ],
+        ("Hello, world", "!"),
+    );
+}
+
+#[test]
+fn f() {
+    assert_cursor(
+        EditMode::Vi,
+        ("", "Hello, world!"),
+        &[
+            KeyPress::Esc,
+            KeyPress::Char('f'),
+            KeyPress::Char('r'),
+            KeyPress::Enter,
+        ],
+        ("Hello, wo", "rld!"),
+    );
+    assert_cursor(
+        EditMode::Vi,
+        ("", "Hello, world!"),
+        &[
+            KeyPress::Esc,
+            KeyPress::Char('3'),
+            KeyPress::Char('f'),
+            KeyPress::Char('l'),
+            KeyPress::Enter,
+        ],
+        ("Hello, wor", "ld!"),
+    );
+}
+
+#[test]
+fn uppercase_f() {
+    assert_cursor(
+        EditMode::Vi,
+        ("Hello, world!", ""),
+        &[
+            KeyPress::Esc,
+            KeyPress::Char('F'),
+            KeyPress::Char('r'),
+            KeyPress::Enter,
+        ],
+        ("Hello, wo", "rld!"),
+    );
+    assert_cursor(
+        EditMode::Vi,
+        ("Hello, world!", ""),
+        &[
+            KeyPress::Esc,
+            KeyPress::Char('3'),
+            KeyPress::Char('F'),
+            KeyPress::Char('l'),
+            KeyPress::Enter,
+        ],
+        ("He", "llo, world!"),
+    );
+}
+
+#[test]
+fn i() {
+    assert_cursor(
+        EditMode::Vi,
+        ("Be", ""),
+        &[
+            KeyPress::Esc,
+            KeyPress::Char('i'),
+            KeyPress::Char('y'),
+            KeyPress::Enter,
+        ],
+        ("By", "e"),
+    );
+}
+
+#[test]
+fn uppercase_i() {
+    assert_cursor(
+        EditMode::Vi,
+        ("Be", ""),
+        &[
+            KeyPress::Esc,
+            KeyPress::Char('I'),
+            KeyPress::Char('y'),
+            KeyPress::Enter,
+        ],
+        ("y", "Be"),
+    );
+}
+
+#[test]
+fn u() {
+    assert_cursor(
+        EditMode::Vi,
+        ("Hello, ", "world"),
+        &[
+            KeyPress::Esc,
+            KeyPress::Ctrl('W'),
+            KeyPress::Char('u'),
+            KeyPress::Enter,
+        ],
+        ("Hello,", " world"),
+    );
+}
+
+#[test]
+fn w() {
+    assert_cursor(
+        EditMode::Vi,
+        ("", "Hello, world!"),
+        &[KeyPress::Esc, KeyPress::Char('w'), KeyPress::Enter],
+        ("Hello", ", world!"),
+    );
+    assert_cursor(
+        EditMode::Vi,
+        ("", "Hello, world!"),
+        &[
+            KeyPress::Esc,
+            KeyPress::Char('2'),
+            KeyPress::Char('w'),
+            KeyPress::Enter,
+        ],
+        ("Hello, ", "world!"),
+    );
+}
+
+#[test]
+fn uppercase_w() {
+    assert_cursor(
+        EditMode::Vi,
+        ("", "Hello, world!"),
+        &[KeyPress::Esc, KeyPress::Char('W'), KeyPress::Enter],
+        ("Hello, ", "world!"),
+    );
+    assert_cursor(
+        EditMode::Vi,
+        ("", "Hello, world!"),
+        &[
+            KeyPress::Esc,
+            KeyPress::Char('2'),
+            KeyPress::Char('W'),
+            KeyPress::Enter,
+        ],
+        ("Hello, world", "!"),
+    );
+}
+
+#[test]
+fn x() {
+    assert_cursor(
+        EditMode::Vi,
+        ("", "a"),
+        &[KeyPress::Esc, KeyPress::Char('x'), KeyPress::Enter],
+        ("", ""),
+    );
+}
+
+#[test]
+fn uppercase_x() {
+    assert_cursor(
+        EditMode::Vi,
+        ("Hi", ""),
+        &[KeyPress::Esc, KeyPress::Char('X'), KeyPress::Enter],
+        ("", "i"),
+    );
+}
+
+#[test]
+fn h() {
+    for key in &[
+        KeyPress::Char('h'),
+        KeyPress::Ctrl('H'),
+        KeyPress::Backspace,
+    ] {
+        assert_cursor(
+            EditMode::Vi,
+            ("Bye", ""),
+            &[KeyPress::Esc, *key, KeyPress::Enter],
+            ("B", "ye"),
+        );
+        assert_cursor(
+            EditMode::Vi,
+            ("Bye", ""),
+            &[KeyPress::Esc, KeyPress::Char('2'), *key, KeyPress::Enter],
+            ("", "Bye"),
+        );
+    }
+}
+
+#[test]
+fn l() {
+    for key in &[KeyPress::Char('l'), KeyPress::Char(' ')] {
+        assert_cursor(
+            EditMode::Vi,
+            ("", "Hi"),
+            &[KeyPress::Esc, *key, KeyPress::Enter],
+            ("H", "i"),
+        );
+        assert_cursor(
+            EditMode::Vi,
+            ("", "Hi"),
+            &[KeyPress::Esc, KeyPress::Char('2'), *key, KeyPress::Enter],
+            ("Hi", ""),
+        );
+    }
+}
+
+#[test]
+fn j() {
+    for key in &[
+        KeyPress::Char('j'),
+        KeyPress::Char('+'),
+        KeyPress::Ctrl('N'),
+    ] {
+        assert_history(
+            EditMode::Vi,
+            &["line1", "line2"],
+            &[
+                KeyPress::Esc,
+                KeyPress::Ctrl('P'),
+                KeyPress::Ctrl('P'),
+                *key,
+                KeyPress::Enter,
+            ],
+            ("line2", ""),
+        );
+    }
+}
+
+#[test]
+fn k() {
+    for key in &[
+        KeyPress::Char('k'),
+        KeyPress::Char('-'),
+        KeyPress::Ctrl('P'),
+    ] {
+        assert_history(
+            EditMode::Vi,
+            &["line1"],
+            &[KeyPress::Esc, *key, KeyPress::Enter],
+            ("line1", ""),
+        );
+    }
+}
+
+#[test]
+fn p() {
+    assert_cursor(
+        EditMode::Vi,
+        ("Hello, ", "world"),
+        &[
+            KeyPress::Esc,
+            KeyPress::Ctrl('W'),
+            KeyPress::Char('p'),
+            KeyPress::Enter,
+        ],
+        (" Hello", ",world"),
+    );
+}
+
+#[test]
+fn uppercase_p() {
+    assert_cursor(
+        EditMode::Vi,
+        ("Hello, ", "world"),
+        &[
+            KeyPress::Esc,
+            KeyPress::Ctrl('W'),
+            KeyPress::Char('P'),
+            KeyPress::Enter,
+        ],
+        ("Hello", ", world"),
+    );
+}
+
+#[test]
+fn r() {
+    assert_cursor(
+        EditMode::Vi,
+        ("Hi", ", world!"),
+        &[
+            KeyPress::Esc,
+            KeyPress::Char('r'),
+            KeyPress::Char('o'),
+            KeyPress::Enter,
+        ],
+        ("H", "o, world!"),
+    );
+    assert_cursor(
+        EditMode::Vi,
+        ("He", "llo, world!"),
+        &[
+            KeyPress::Esc,
+            KeyPress::Char('4'),
+            KeyPress::Char('r'),
+            KeyPress::Char('i'),
+            KeyPress::Enter,
+        ],
+        ("Hiii", "i, world!"),
+    );
+}
+
+#[test]
+fn s() {
+    assert_cursor(
+        EditMode::Vi,
+        ("Hi", ", world!"),
+        &[
+            KeyPress::Esc,
+            KeyPress::Char('s'),
+            KeyPress::Char('o'),
+            KeyPress::Enter,
+        ],
+        ("Ho", ", world!"),
+    );
+    assert_cursor(
+        EditMode::Vi,
+        ("He", "llo, world!"),
+        &[
+            KeyPress::Esc,
+            KeyPress::Char('4'),
+            KeyPress::Char('s'),
+            KeyPress::Char('i'),
+            KeyPress::Enter,
+        ],
+        ("Hi", ", world!"),
+    );
+}
+
+#[test]
+fn uppercase_s() {
+    assert_cursor(
+        EditMode::Vi,
+        ("Hello, ", "world"),
+        &[KeyPress::Esc, KeyPress::Char('S'), KeyPress::Enter],
+        ("", ""),
+    );
+}
+
+#[test]
+fn t() {
+    assert_cursor(
+        EditMode::Vi,
+        ("", "Hello, world!"),
+        &[
+            KeyPress::Esc,
+            KeyPress::Char('t'),
+            KeyPress::Char('r'),
+            KeyPress::Enter,
+        ],
+        ("Hello, w", "orld!"),
+    );
+    assert_cursor(
+        EditMode::Vi,
+        ("", "Hello, world!"),
+        &[
+            KeyPress::Esc,
+            KeyPress::Char('3'),
+            KeyPress::Char('t'),
+            KeyPress::Char('l'),
+            KeyPress::Enter,
+        ],
+        ("Hello, wo", "rld!"),
+    );
+}
+
+#[test]
+fn uppercase_t() {
+    assert_cursor(
+        EditMode::Vi,
+        ("Hello, world!", ""),
+        &[
+            KeyPress::Esc,
+            KeyPress::Char('T'),
+            KeyPress::Char('r'),
+            KeyPress::Enter,
+        ],
+        ("Hello, wor", "ld!"),
+    );
+    assert_cursor(
+        EditMode::Vi,
+        ("Hello, world!", ""),
+        &[
+            KeyPress::Esc,
+            KeyPress::Char('3'),
+            KeyPress::Char('T'),
+            KeyPress::Char('l'),
+            KeyPress::Enter,
+        ],
+        ("Hel", "lo, world!"),
+    );
+}
diff --git a/src/test/vi_insert.rs b/src/test/vi_insert.rs
new file mode 100644
index 0000000..cf86680
--- /dev/null
+++ b/src/test/vi_insert.rs
@@ -0,0 +1,56 @@
+//! Vi insert mode specific key bindings
+use super::assert_cursor;
+use config::EditMode;
+use consts::KeyPress;
+
+#[test]
+fn insert_mode_by_default() {
+    assert_cursor(
+        EditMode::Vi,
+        ("", ""),
+        &[KeyPress::Char('a'), KeyPress::Enter],
+        ("a", ""),
+    );
+}
+
+#[test]
+fn ctrl_h() {
+    assert_cursor(
+        EditMode::Vi,
+        ("Hi", ""),
+        &[KeyPress::Ctrl('H'), KeyPress::Enter],
+        ("H", ""),
+    );
+}
+
+#[test]
+fn backspace() {
+    assert_cursor(
+        EditMode::Vi,
+        ("", ""),
+        &[KeyPress::Backspace, KeyPress::Enter],
+        ("", ""),
+    );
+    assert_cursor(
+        EditMode::Vi,
+        ("Hi", ""),
+        &[KeyPress::Backspace, KeyPress::Enter],
+        ("H", ""),
+    );
+    assert_cursor(
+        EditMode::Vi,
+        ("", "Hi"),
+        &[KeyPress::Backspace, KeyPress::Enter],
+        ("", "Hi"),
+    );
+}
+
+#[test]
+fn esc() {
+    assert_cursor(
+        EditMode::Vi,
+        ("", ""),
+        &[KeyPress::Char('a'), KeyPress::Esc, KeyPress::Enter],
+        ("", "a"),
+    );
+}
diff --git a/src/tty/mod.rs b/src/tty/mod.rs
index fe3ac99..e6c42dc 100644
--- a/src/tty/mod.rs
+++ b/src/tty/mod.rs
@@ -1,49 +1,181 @@
 //! This module implements and describes common TTY methods & traits
-use std::io::Write;
-use Result;
+use std::io::{self, Write};
+use unicode_segmentation::UnicodeSegmentation;
+use unicode_width::UnicodeWidthStr;
+
 use config::Config;
 use consts::KeyPress;
+use line_buffer::LineBuffer;
+use Result;
 
 /// Terminal state
-pub trait RawMode: Copy + Sized {
+pub trait RawMode: Sized {
     /// Disable RAW mode for the terminal.
     fn disable_raw_mode(&self) -> Result<()>;
 }
 
 /// Translate bytes read from stdin to keys.
-pub trait RawReader: Sized {
+pub trait RawReader {
     /// Blocking read of key pressed.
-    fn next_key(&mut self) -> Result<KeyPress>;
+    fn next_key(&mut self, single_esc_abort: bool) -> Result<KeyPress>;
     /// For CTRL-V support
     #[cfg(unix)]
     fn next_char(&mut self) -> Result<char>;
 }
 
-/// Terminal contract
-pub trait Term: Clone {
-    type Reader: RawReader;
-    type Writer: Write;
-    type Mode: RawMode;
+#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
+pub struct Position {
+    pub col: usize,
+    pub row: usize,
+}
 
-    fn new() -> Self;
-    /// Check if current terminal can provide a rich line-editing user interface.
-    fn is_unsupported(&self) -> bool;
-    /// check if stdin is connected to a terminal.
-    fn is_stdin_tty(&self) -> bool;
+/// Display prompt, line and cursor in terminal output
+pub trait Renderer {
+    fn move_cursor(&mut self, old: Position, new: Position) -> Result<()>;
+
+    /// Display prompt, line and cursor in terminal output
+    fn refresh_line(
+        &mut self,
+        prompt: &str,
+        prompt_size: Position,
+        line: &LineBuffer,
+        hint: Option<String>,
+        current_row: usize,
+        old_rows: usize,
+    ) -> Result<(Position, Position)>;
+
+    /// Calculate the number of columns and rows used to display `s` on a
+    /// `cols` width terminal
+    /// starting at `orig`.
+    fn calculate_position(&self, s: &str, orig: Position) -> Position;
+
+    fn write_and_flush(&mut self, buf: &[u8]) -> Result<()>;
+
+    /// Beep, used for completion when there is nothing to complete or when all
+    /// the choices were already shown.
+    fn beep(&mut self) -> Result<()> {
+        // TODO bell-style
+        try!(io::stderr().write_all(b"\x07"));
+        try!(io::stderr().flush());
+        Ok(())
+    }
+
+    /// Clear the screen. Used to handle ctrl+l
+    fn clear_screen(&mut self) -> Result<()>;
+
+    /// Check if a SIGWINCH signal has been received
+    fn sigwinch(&self) -> bool;
+    /// Update the number of columns/rows in the current terminal.
+    fn update_size(&mut self);
     /// Get the number of columns in the current terminal.
     fn get_columns(&self) -> usize;
     /// Get the number of rows in the current terminal.
     fn get_rows(&self) -> usize;
-    /// Check if a SIGWINCH signal has been received
-    fn sigwinch(&self) -> bool;
+}
+
+impl<'a, R: Renderer + ?Sized> Renderer for &'a mut R {
+    fn move_cursor(&mut self, old: Position, new: Position) -> Result<()> {
+        (**self).move_cursor(old, new)
+    }
+    fn refresh_line(
+        &mut self,
+        prompt: &str,
+        prompt_size: Position,
+        line: &LineBuffer,
+        hint: Option<String>,
+        current_row: usize,
+        old_rows: usize,
+    ) -> Result<(Position, Position)> {
+        (**self).refresh_line(prompt, prompt_size, line, hint, current_row, old_rows)
+    }
+    fn calculate_position(&self, s: &str, orig: Position) -> Position {
+        (**self).calculate_position(s, orig)
+    }
+    fn write_and_flush(&mut self, buf: &[u8]) -> Result<()> {
+        (**self).write_and_flush(buf)
+    }
+    fn beep(&mut self) -> Result<()> {
+        (**self).beep()
+    }
+    fn clear_screen(&mut self) -> Result<()> {
+        (**self).clear_screen()
+    }
+    fn sigwinch(&self) -> bool {
+        (**self).sigwinch()
+    }
+    fn update_size(&mut self) {
+        (**self).update_size()
+    }
+    fn get_columns(&self) -> usize {
+        (**self).get_columns()
+    }
+    fn get_rows(&self) -> usize {
+        (**self).get_rows()
+    }
+}
+
+/// Terminal contract
+pub trait Term {
+    type Reader: RawReader; // rl_instream
+    type Writer: Renderer; // rl_outstream
+    type Mode: RawMode;
+
+    fn new() -> Self;
+    /// Check if current terminal can provide a rich line-editing user
+    /// interface.
+    fn is_unsupported(&self) -> bool;
+    /// check if stdin is connected to a terminal.
+    fn is_stdin_tty(&self) -> bool;
     /// Enable RAW mode for the terminal.
     fn enable_raw_mode(&self) -> Result<Self::Mode>;
     /// Create a RAW reader
     fn create_reader(&self, config: &Config) -> Result<Self::Reader>;
     /// Create a writer
     fn create_writer(&self) -> Self::Writer;
-    /// Clear the screen. Used to handle ctrl+l
-    fn clear_screen(&mut self, w: &mut Write) -> Result<()>;
+}
+
+fn truncate(text: &str, col: usize, max_col: usize) -> &str {
+    let mut col = col;
+    let mut esc_seq = 0;
+    let mut end = text.len();
+    for (i, s) in text.grapheme_indices(true) {
+        col += width(s, &mut esc_seq);
+        if col > max_col {
+            end = i;
+            break;
+        }
+    }
+    &text[..end]
+}
+
+fn width(s: &str, esc_seq: &mut u8) -> usize {
+    if *esc_seq == 1 {
+        if s == "[" {
+            // CSI
+            *esc_seq = 2;
+        } else {
+            // two-character sequence
+            *esc_seq = 0;
+        }
+        0
+    } else if *esc_seq == 2 {
+        if s == ";" || (s.as_bytes()[0] >= b'0' && s.as_bytes()[0] <= b'9') {
+        /*} else if s == "m" {
+            // last
+            *esc_seq = 0;*/
+        } else {
+            // not supported
+            *esc_seq = 0;
+        }
+        0
+    } else if s == "\x1b" {
+        *esc_seq = 1;
+        0
+    } else if s == "\n" {
+        0
+    } else {
+        s.width()
+    }
 }
 
 // If on Windows platform import Windows TTY module
diff --git a/src/tty/test.rs b/src/tty/test.rs
index 8813fa5..29a1e79 100644
--- a/src/tty/test.rs
+++ b/src/tty/test.rs
@@ -1,17 +1,14 @@
 //! Tests specific definitions
-use std::io::{self, Sink, Write};
 use std::iter::IntoIterator;
 use std::slice::Iter;
 use std::vec::IntoIter;
 
-#[cfg(windows)]
-use winapi;
-
+use super::{truncate, Position, RawMode, RawReader, Renderer, Term};
 use config::Config;
 use consts::KeyPress;
 use error::ReadlineError;
+use line_buffer::LineBuffer;
 use Result;
-use super::{RawMode, RawReader, Term};
 
 pub type Mode = ();
 
@@ -22,7 +19,7 @@
 }
 
 impl<'a> RawReader for Iter<'a, KeyPress> {
-    fn next_key(&mut self) -> Result<KeyPress> {
+    fn next_key(&mut self, _: bool) -> Result<KeyPress> {
         match self.next() {
             Some(key) => Ok(*key),
             None => Err(ReadlineError::Eof),
@@ -35,7 +32,7 @@
 }
 
 impl RawReader for IntoIter<KeyPress> {
-    fn next_key(&mut self) -> Result<KeyPress> {
+    fn next_key(&mut self, _: bool) -> Result<KeyPress> {
         match self.next() {
             Some(key) => Ok(key),
             None => Err(ReadlineError::Eof),
@@ -43,50 +40,80 @@
     }
     #[cfg(unix)]
     fn next_char(&mut self) -> Result<char> {
-        unimplemented!();
+        match self.next() {
+            Some(KeyPress::Char(c)) => Ok(c),
+            None => Err(ReadlineError::Eof),
+            _ => unimplemented!(),
+        }
+    }
+}
+
+pub struct Sink {}
+
+impl Sink {
+    pub fn new() -> Sink {
+        Sink {}
+    }
+}
+
+impl Renderer for Sink {
+    fn move_cursor(&mut self, _: Position, _: Position) -> Result<()> {
+        Ok(())
+    }
+
+    fn refresh_line(
+        &mut self,
+        _: &str,
+        prompt_size: Position,
+        line: &LineBuffer,
+        hint: Option<String>,
+        _: usize,
+        _: usize,
+    ) -> Result<(Position, Position)> {
+        let cursor = self.calculate_position(&line[..line.pos()], prompt_size);
+        if let Some(hint) = hint {
+            truncate(&hint, 0, 80);
+        }
+        let end = self.calculate_position(&line, prompt_size);
+        Ok((cursor, end))
+    }
+
+    fn calculate_position(&self, s: &str, orig: Position) -> Position {
+        let mut pos = orig;
+        pos.col += s.len();
+        pos
+    }
+
+    fn write_and_flush(&mut self, _: &[u8]) -> Result<()> {
+        Ok(())
+    }
+
+    fn beep(&mut self) -> Result<()> {
+        Ok(())
+    }
+
+    fn clear_screen(&mut self) -> Result<()> {
+        Ok(())
+    }
+
+    fn sigwinch(&self) -> bool {
+        false
+    }
+    fn update_size(&mut self) {}
+    fn get_columns(&self) -> usize {
+        80
+    }
+    fn get_rows(&self) -> usize {
+        24
     }
 }
 
 pub type Terminal = DummyTerminal;
 
-#[derive(Clone,Debug)]
+#[derive(Clone, Debug)]
 pub struct DummyTerminal {
     pub keys: Vec<KeyPress>,
-}
-
-impl DummyTerminal {
-    #[cfg(windows)]
-    pub fn get_console_screen_buffer_info(&self) -> Result<winapi::CONSOLE_SCREEN_BUFFER_INFO> {
-        let dw_size = winapi::COORD { X: 80, Y: 24 };
-        let dw_cursor_osition = winapi::COORD { X: 0, Y: 0 };
-        let sr_window = winapi::SMALL_RECT {
-            Left: 0,
-            Top: 0,
-            Right: 0,
-            Bottom: 0,
-        };
-        let info = winapi::CONSOLE_SCREEN_BUFFER_INFO {
-            dwSize: dw_size,
-            dwCursorPosition: dw_cursor_osition,
-            wAttributes: 0,
-            srWindow: sr_window,
-            dwMaximumWindowSize: dw_size,
-        };
-        Ok(info)
-    }
-
-    #[cfg(windows)]
-    pub fn set_console_cursor_position(&mut self, _: winapi::COORD) -> Result<()> {
-        Ok(())
-    }
-
-    #[cfg(windows)]
-    pub fn fill_console_output_character(&mut self,
-                                         _: winapi::DWORD,
-                                         _: winapi::COORD)
-                                         -> Result<()> {
-        Ok(())
-    }
+    pub cursor: usize, // cursor position before last command
 }
 
 impl Term for DummyTerminal {
@@ -95,54 +122,34 @@
     type Mode = Mode;
 
     fn new() -> DummyTerminal {
-        DummyTerminal { keys: Vec::new() }
+        DummyTerminal {
+            keys: Vec::new(),
+            cursor: 0,
+        }
     }
 
     // Init checks:
 
-    /// Check if current terminal can provide a rich line-editing user interface.
     fn is_unsupported(&self) -> bool {
         false
     }
 
-    /// check if stdin is connected to a terminal.
     fn is_stdin_tty(&self) -> bool {
         true
     }
 
     // Interactive loop:
 
-    /// Get the number of columns in the current terminal.
-    fn get_columns(&self) -> usize {
-        80
-    }
-
-    /// Get the number of rows in the current terminal.
-    fn get_rows(&self) -> usize {
-        24
-    }
-
-    /// Check if a SIGWINCH signal has been received
-    fn sigwinch(&self) -> bool {
-        false
-    }
-
     fn enable_raw_mode(&self) -> Result<Mode> {
         Ok(())
     }
 
-    /// Create a RAW reader
     fn create_reader(&self, _: &Config) -> Result<IntoIter<KeyPress>> {
         Ok(self.keys.clone().into_iter())
     }
 
     fn create_writer(&self) -> Sink {
-        io::sink()
-    }
-
-    /// Clear the screen. Used to handle ctrl+l
-    fn clear_screen(&mut self, _: &mut Write) -> Result<()> {
-        Ok(())
+        Sink {}
     }
 }
 
diff --git a/src/tty/unix.rs b/src/tty/unix.rs
index 17a439e..b1d5b78 100644
--- a/src/tty/unix.rs
+++ b/src/tty/unix.rs
@@ -3,18 +3,21 @@
 use std::io::{self, Read, Stdout, Write};
 use std::sync;
 use std::sync::atomic;
+
 use libc;
 use nix;
-use nix::poll;
+use nix::poll::{self, EventFlags};
 use nix::sys::signal;
 use nix::sys::termios;
+use nix::sys::termios::SetArg;
+use unicode_segmentation::UnicodeSegmentation;
 
-use char_iter;
+use super::{truncate, width, Position, RawMode, RawReader, Renderer, Term};
 use config::Config;
 use consts::{self, KeyPress};
-use Result;
 use error;
-use super::{RawMode, RawReader, Term};
+use line_buffer::LineBuffer;
+use Result;
 
 const STDIN_FILENO: libc::c_int = libc::STDIN_FILENO;
 const STDOUT_FILENO: libc::c_int = libc::STDOUT_FILENO;
@@ -27,7 +30,8 @@
 
     unsafe {
         let mut size: libc::winsize = zeroed();
-        match libc::ioctl(STDOUT_FILENO, libc::TIOCGWINSZ, &mut size) {
+        match libc::ioctl(STDOUT_FILENO, libc::TIOCGWINSZ.into(), &mut size) {
+            // .into() for FreeBSD
             0 => (size.ws_col as usize, size.ws_row as usize), // TODO getCursorPosition
             _ => (80, 24),
         }
@@ -37,7 +41,6 @@
 /// Check TERM environment variable to see if current term is in our
 /// unsupported list
 fn is_unsupported_term() -> bool {
-    use std::ascii::AsciiExt;
     match std::env::var("TERM") {
         Ok(term) => {
             for iter in &UNSUPPORTED_TERM {
@@ -51,7 +54,6 @@
     }
 }
 
-
 /// Return whether or not STDIN, STDOUT or STDERR is a TTY
 fn is_a_tty(fd: libc::c_int) -> bool {
     unsafe { libc::isatty(fd) != 0 }
@@ -62,7 +64,7 @@
 impl RawMode for Mode {
     /// Disable RAW mode for the terminal.
     fn disable_raw_mode(&self) -> Result<()> {
-        try!(termios::tcsetattr(STDIN_FILENO, termios::TCSADRAIN, self));
+        try!(termios::tcsetattr(STDIN_FILENO, SetArg::TCSADRAIN, self));
         Ok(())
     }
 }
@@ -75,13 +77,17 @@
     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
         loop {
             let res = unsafe {
-                libc::read(STDIN_FILENO,
-                           buf.as_mut_ptr() as *mut libc::c_void,
-                           buf.len() as libc::size_t)
+                libc::read(
+                    STDIN_FILENO,
+                    buf.as_mut_ptr() as *mut libc::c_void,
+                    buf.len() as libc::size_t,
+                )
             };
             if res == -1 {
                 let error = io::Error::last_os_error();
-                if error.kind() != io::ErrorKind::Interrupted {
+                if error.kind() != io::ErrorKind::Interrupted
+                    || SIGWINCH.load(atomic::Ordering::Relaxed)
+                {
                     return Err(error);
                 }
             } else {
@@ -93,110 +99,203 @@
 
 /// Console input reader
 pub struct PosixRawReader {
-    chars: char_iter::Chars<StdinRaw>,
+    stdin: StdinRaw,
     timeout_ms: i32,
+    buf: [u8; 4],
 }
 
 impl PosixRawReader {
-    pub fn new(config: &Config) -> Result<PosixRawReader> {
-        let stdin = StdinRaw {};
+    fn new(config: &Config) -> Result<PosixRawReader> {
         Ok(PosixRawReader {
-               chars: char_iter::chars(stdin),
-               timeout_ms: config.keyseq_timeout(),
-           })
+            stdin: StdinRaw {},
+            timeout_ms: config.keyseq_timeout(),
+            buf: [0; 4],
+        })
     }
 
     fn escape_sequence(&mut self) -> Result<KeyPress> {
         // Read the next two bytes representing the escape sequence.
         let seq1 = try!(self.next_char());
         if seq1 == '[' {
-            // ESC [ sequences.
+            // ESC [ sequences. (CSI)
             let seq2 = try!(self.next_char());
             if seq2.is_digit(10) {
                 // Extended escape, read additional byte.
                 let seq3 = try!(self.next_char());
                 if seq3 == '~' {
                     Ok(match seq2 {
-                           '1' | '7' => KeyPress::Home, // '1': xterm
-                           '3' => KeyPress::Delete,
-                           '4' | '8' => KeyPress::End, // '4': xterm
-                           '5' => KeyPress::PageUp,
-                           '6' => KeyPress::PageDown,
-                           _ => {
-                        debug!(target: "rustyline", "unsupported esc sequence: ESC{:?}{:?}{:?}", seq1, seq2, seq3);
-                        KeyPress::UnknownEscSeq
+                        '1' | '7' => KeyPress::Home, // tmux, xrvt
+                        '2' => KeyPress::Insert,
+                        '3' => KeyPress::Delete,    // kdch1
+                        '4' | '8' => KeyPress::End, // tmux, xrvt
+                        '5' => KeyPress::PageUp,    // kpp
+                        '6' => KeyPress::PageDown,  // knp
+                        _ => {
+                            debug!(target: "rustyline",
+                            "unsupported esc sequence: ESC [ {} ~", seq2);
+                            KeyPress::UnknownEscSeq
+                        }
+                    })
+                } else if seq3.is_digit(10) {
+                    let seq4 = try!(self.next_char());
+                    if seq4 == '~' {
+                        Ok(match (seq2, seq3) {
+                            ('1', '1') => KeyPress::F(1),  // rxvt-unicode
+                            ('1', '2') => KeyPress::F(2),  // rxvt-unicode
+                            ('1', '3') => KeyPress::F(3),  // rxvt-unicode
+                            ('1', '4') => KeyPress::F(4),  // rxvt-unicode
+                            ('1', '5') => KeyPress::F(5),  // kf5
+                            ('1', '7') => KeyPress::F(6),  // kf6
+                            ('1', '8') => KeyPress::F(7),  // kf7
+                            ('1', '9') => KeyPress::F(8),  // kf8
+                            ('2', '0') => KeyPress::F(9),  // kf9
+                            ('2', '1') => KeyPress::F(10), // kf10
+                            ('2', '3') => KeyPress::F(11), // kf11
+                            ('2', '4') => KeyPress::F(12), // kf12
+                            _ => {
+                                debug!(target: "rustyline",
+                                "unsupported esc sequence: ESC [ {}{} ~", seq1, seq2);
+                                KeyPress::UnknownEscSeq
+                            }
+                        })
+                    } else if seq4 == ';' {
+                        let seq5 = try!(self.next_char());
+                        if seq5.is_digit(10) {
+                            let seq6 = try!(self.next_char()); // '~' expected
+                            debug!(target: "rustyline",
+                            "unsupported esc sequence: ESC [ {}{} ; {} {}", seq2, seq3, seq5, seq6);
+                        } else {
+                            debug!(target: "rustyline",
+                            "unsupported esc sequence: ESC [ {}{} ; {:?}", seq2, seq3, seq5);
+                        }
+                        Ok(KeyPress::UnknownEscSeq)
+                    } else {
+                        debug!(target: "rustyline",
+                        "unsupported esc sequence: ESC [ {}{} {:?}", seq2, seq3, seq4);
+                        Ok(KeyPress::UnknownEscSeq)
                     }
-                       })
+                } else if seq3 == ';' {
+                    let seq4 = try!(self.next_char());
+                    if seq4.is_digit(10) {
+                        let seq5 = try!(self.next_char());
+                        if seq2 == '1' {
+                            Ok(match (seq4, seq5) {
+                                ('5', 'A') => KeyPress::ControlUp,
+                                ('5', 'B') => KeyPress::ControlDown,
+                                ('5', 'C') => KeyPress::ControlRight,
+                                ('5', 'D') => KeyPress::ControlLeft,
+                                ('2', 'A') => KeyPress::ShiftUp,
+                                ('2', 'B') => KeyPress::ShiftDown,
+                                ('2', 'C') => KeyPress::ShiftRight,
+                                ('2', 'D') => KeyPress::ShiftLeft,
+                                _ => {
+                                    debug!(target: "rustyline",
+                                    "unsupported esc sequence: ESC [ {} ; {} {}", seq2, seq4, seq5);
+                                    KeyPress::UnknownEscSeq
+                                }
+                            })
+                        } else {
+                            debug!(target: "rustyline",
+                            "unsupported esc sequence: ESC [ {} ; {} {}", seq2, seq4, seq5);
+                            Ok(KeyPress::UnknownEscSeq)
+                        }
+                    } else {
+                        debug!(target: "rustyline",
+                        "unsupported esc sequence: ESC [ {} ; {:?}", seq2, seq4);
+                        Ok(KeyPress::UnknownEscSeq)
+                    }
                 } else {
-                    debug!(target: "rustyline", "unsupported esc sequence: ESC{:?}{:?}{:?}", seq1, seq2, seq3);
-                    Ok(KeyPress::UnknownEscSeq)
+                    Ok(match (seq2, seq3) {
+                        ('5', 'A') => KeyPress::ControlUp,
+                        ('5', 'B') => KeyPress::ControlDown,
+                        ('5', 'C') => KeyPress::ControlRight,
+                        ('5', 'D') => KeyPress::ControlLeft,
+                        _ => {
+                            debug!(target: "rustyline",
+                            "unsupported esc sequence: ESC [ {} {:?}", seq2, seq3);
+                            KeyPress::UnknownEscSeq
+                        }
+                    })
                 }
             } else {
+                // ANSI
                 Ok(match seq2 {
-                       'A' => KeyPress::Up, // ANSI
-                       'B' => KeyPress::Down,
-                       'C' => KeyPress::Right,
-                       'D' => KeyPress::Left,
-                       'F' => KeyPress::End,
-                       'H' => KeyPress::Home,
-                       _ => {
-                    debug!(target: "rustyline", "unsupported esc sequence: ESC{:?}{:?}", seq1, seq2);
-                    KeyPress::UnknownEscSeq
-                }
-                   })
+                    'A' => KeyPress::Up,    // kcuu1
+                    'B' => KeyPress::Down,  // kcud1
+                    'C' => KeyPress::Right, // kcuf1
+                    'D' => KeyPress::Left,  // kcub1
+                    'F' => KeyPress::End,
+                    'H' => KeyPress::Home, // khome
+                    _ => {
+                        debug!(target: "rustyline", "unsupported esc sequence: ESC [ {:?}", seq2);
+                        KeyPress::UnknownEscSeq
+                    }
+                })
             }
         } else if seq1 == 'O' {
-            // ESC O sequences.
+            // xterm
+            // ESC O sequences. (SS3)
             let seq2 = try!(self.next_char());
             Ok(match seq2 {
-                   'A' => KeyPress::Up,
-                   'B' => KeyPress::Down,
-                   'C' => KeyPress::Right,
-                   'D' => KeyPress::Left,
-                   'F' => KeyPress::End,
-                   'H' => KeyPress::Home,
-                   _ => {
-                debug!(target: "rustyline", "unsupported esc sequence: ESC{:?}{:?}", seq1, seq2);
-                KeyPress::UnknownEscSeq
-            }
-               })
+                'A' => KeyPress::Up,    // kcuu1
+                'B' => KeyPress::Down,  // kcud1
+                'C' => KeyPress::Right, // kcuf1
+                'D' => KeyPress::Left,  // kcub1
+                'F' => KeyPress::End,   // kend
+                'H' => KeyPress::Home,  // khome
+                'P' => KeyPress::F(1),  // kf1
+                'Q' => KeyPress::F(2),  // kf2
+                'R' => KeyPress::F(3),  // kf3
+                'S' => KeyPress::F(4),  // kf4
+                _ => {
+                    debug!(target: "rustyline", "unsupported esc sequence: ESC O {:?}", seq2);
+                    KeyPress::UnknownEscSeq
+                }
+            })
+        } else if seq1 == '\x1b' {
+            // ESC ESC
+            Ok(KeyPress::Esc)
         } else {
             // TODO ESC-R (r): Undo all changes made to this line.
-            Ok(match seq1 {
-                   '\x08' => KeyPress::Meta('\x08'), // Backspace
-                   '-' => KeyPress::Meta('-'),
-                   '0'...'9' => KeyPress::Meta(seq1),
-                   '<' => KeyPress::Meta('<'),
-                   '>' => KeyPress::Meta('>'),
-                   'b' | 'B' => KeyPress::Meta('B'),
-                   'c' | 'C' => KeyPress::Meta('C'),
-                   'd' | 'D' => KeyPress::Meta('D'),
-                   'f' | 'F' => KeyPress::Meta('F'),
-                   'l' | 'L' => KeyPress::Meta('L'),
-                   'n' | 'N' => KeyPress::Meta('N'),
-                   'p' | 'P' => KeyPress::Meta('P'),
-                   't' | 'T' => KeyPress::Meta('T'),
-                   'u' | 'U' => KeyPress::Meta('U'),
-                   'y' | 'Y' => KeyPress::Meta('Y'),
-                   '\x7f' => KeyPress::Meta('\x7f'), // Delete
-                   _ => {
-                debug!(target: "rustyline", "unsupported esc sequence: M-{:?}", seq1);
-                KeyPress::UnknownEscSeq
-            }
-               })
+            Ok(KeyPress::Meta(seq1))
         }
     }
 }
 
+// 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
+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, // 0x3F
+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, // 0x5F
+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, // 0x7F
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 0x9F
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 0xBF
+0,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // 0xDF
+3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, // 0xEF
+4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0, // 0xFF
+];
+
 impl RawReader for PosixRawReader {
-    fn next_key(&mut self) -> Result<KeyPress> {
+    fn next_key(&mut self, single_esc_abort: bool) -> Result<KeyPress> {
         let c = try!(self.next_char());
 
         let mut key = consts::char_to_key_press(c);
         if key == KeyPress::Esc {
-            let mut fds =
-                [poll::PollFd::new(STDIN_FILENO, poll::POLLIN, poll::EventFlags::empty())];
-            match poll::poll(&mut fds, self.timeout_ms) {
+            let timeout_ms = if single_esc_abort && self.timeout_ms == -1 {
+                0
+            } else {
+                self.timeout_ms
+            };
+            let mut fds = [poll::PollFd::new(STDIN_FILENO, EventFlags::POLLIN)];
+            match poll::poll(&mut fds, timeout_ms) {
                 Ok(n) if n == 0 => {
                     // single escape
                 }
@@ -213,22 +312,212 @@
     }
 
     fn next_char(&mut self) -> Result<char> {
-        match self.chars.next() {
-            Some(c) => Ok(try!(c)),
-            None => Err(error::ReadlineError::Eof),
+        let n = try!(self.stdin.read(&mut self.buf[..1]));
+        if n == 0 {
+            return Err(error::ReadlineError::Eof);
+        }
+        let first = self.buf[0];
+        if first >= 128 {
+            let width = UTF8_CHAR_WIDTH[first as usize] as usize;
+            if width == 0 {
+                try!(std::str::from_utf8(&self.buf[..1]));
+                unreachable!()
+            }
+            try!(self.stdin.read_exact(&mut self.buf[1..width]));
+            let s = try!(std::str::from_utf8(&self.buf[..width]));
+            Ok(s.chars().next().unwrap())
+        } else {
+            Ok(first as char)
         }
     }
 }
 
+/// Console output writer
+pub struct PosixRenderer {
+    out: Stdout,
+    cols: usize, // Number of columns in terminal
+}
+
+impl PosixRenderer {
+    fn new() -> PosixRenderer {
+        let (cols, _) = get_win_size();
+        PosixRenderer {
+            out: io::stdout(),
+            cols,
+        }
+    }
+}
+
+impl Renderer for PosixRenderer {
+    fn move_cursor(&mut self, old: Position, new: Position) -> Result<()> {
+        use std::fmt::Write;
+        let mut ab = String::new();
+        if new.row > old.row {
+            // move down
+            let row_shift = new.row - old.row;
+            if row_shift == 1 {
+                ab.push_str("\x1b[B");
+            } else {
+                write!(ab, "\x1b[{}B", row_shift).unwrap();
+            }
+        } else if new.row < old.row {
+            // move up
+            let row_shift = old.row - new.row;
+            if row_shift == 1 {
+                ab.push_str("\x1b[A");
+            } else {
+                write!(ab, "\x1b[{}A", row_shift).unwrap();
+            }
+        }
+        if new.col > old.col {
+            // move right
+            let col_shift = new.col - old.col;
+            if col_shift == 1 {
+                ab.push_str("\x1b[C");
+            } else {
+                write!(ab, "\x1b[{}C", col_shift).unwrap();
+            }
+        } else if new.col < old.col {
+            // move left
+            let col_shift = old.col - new.col;
+            if col_shift == 1 {
+                ab.push_str("\x1b[D");
+            } else {
+                write!(ab, "\x1b[{}D", col_shift).unwrap();
+            }
+        }
+        self.write_and_flush(ab.as_bytes())
+    }
+
+    fn refresh_line(
+        &mut self,
+        prompt: &str,
+        prompt_size: Position,
+        line: &LineBuffer,
+        hint: Option<String>,
+        current_row: usize,
+        old_rows: usize,
+    ) -> Result<(Position, Position)> {
+        use std::fmt::Write;
+        let mut ab = String::new();
+
+        // calculate the position of the end of the input line
+        let end_pos = self.calculate_position(line, prompt_size);
+        // calculate the desired position of the cursor
+        let cursor = self.calculate_position(&line[..line.pos()], prompt_size);
+
+        // self.old_rows < self.cursor.row if the prompt spans multiple lines and if
+        // this is the default State.
+        let cursor_row_movement = old_rows.checked_sub(current_row).unwrap_or(0);
+        // move the cursor down as required
+        if cursor_row_movement > 0 {
+            write!(ab, "\x1b[{}B", cursor_row_movement).unwrap();
+        }
+        // clear old rows
+        for _ in 0..old_rows {
+            ab.push_str("\r\x1b[0K\x1b[A");
+        }
+        // clear the line
+        ab.push_str("\r\x1b[0K");
+
+        // display the prompt
+        ab.push_str(prompt);
+        // display the input line
+        ab.push_str(line);
+        // display hint
+        if let Some(hint) = hint {
+            ab.push_str(truncate(&hint, end_pos.col, self.cols));
+        }
+        // we have to generate our own newline on line wrap
+        if end_pos.col == 0 && end_pos.row > 0 {
+            ab.push_str("\n");
+        }
+        // position the cursor
+        let cursor_row_movement = end_pos.row - cursor.row;
+        // move the cursor up as required
+        if cursor_row_movement > 0 {
+            write!(ab, "\x1b[{}A", cursor_row_movement).unwrap();
+        }
+        // position the cursor within the line
+        if cursor.col > 0 {
+            write!(ab, "\r\x1b[{}C", cursor.col).unwrap();
+        } else {
+            ab.push('\r');
+        }
+
+        try!(self.write_and_flush(ab.as_bytes()));
+        Ok((cursor, end_pos))
+    }
+
+    fn write_and_flush(&mut self, buf: &[u8]) -> Result<()> {
+        try!(self.out.write_all(buf));
+        try!(self.out.flush());
+        Ok(())
+    }
+
+    /// Control characters are treated as having zero width.
+    /// Characters with 2 column width are correctly handled (not splitted).
+    #[allow(if_same_then_else)]
+    fn calculate_position(&self, s: &str, orig: Position) -> Position {
+        let mut pos = orig;
+        let mut esc_seq = 0;
+        for c in s.graphemes(true) {
+            if c == "\n" {
+                pos.row += 1;
+                pos.col = 0;
+                continue;
+            }
+            let cw = width(c, &mut esc_seq);
+            pos.col += cw;
+            if pos.col > self.cols {
+                pos.row += 1;
+                pos.col = cw;
+            }
+        }
+        if pos.col == self.cols {
+            pos.col = 0;
+            pos.row += 1;
+        }
+        pos
+    }
+
+    /// Clear the screen. Used to handle ctrl+l
+    fn clear_screen(&mut self) -> Result<()> {
+        self.write_and_flush(b"\x1b[H\x1b[2J")
+    }
+
+    /// Check if a SIGWINCH signal has been received
+    fn sigwinch(&self) -> bool {
+        SIGWINCH.compare_and_swap(true, false, atomic::Ordering::SeqCst)
+    }
+
+    /// Try to update the number of columns in the current terminal,
+    fn update_size(&mut self) {
+        let (cols, _) = get_win_size();
+        self.cols = cols;
+    }
+
+    fn get_columns(&self) -> usize {
+        self.cols
+    }
+    /// Try to get the number of rows in the current terminal,
+    /// or assume 24 if it fails.
+    fn get_rows(&self) -> usize {
+        let (_, rows) = get_win_size();
+        rows
+    }
+}
 
 static SIGWINCH_ONCE: sync::Once = sync::ONCE_INIT;
 static SIGWINCH: atomic::AtomicBool = atomic::ATOMIC_BOOL_INIT;
 
 fn install_sigwinch_handler() {
     SIGWINCH_ONCE.call_once(|| unsafe {
-        let sigwinch = signal::SigAction::new(signal::SigHandler::Handler(sigwinch_handler),
-                                              signal::SaFlags::empty(),
-                                              signal::SigSet::empty());
+        let sigwinch = signal::SigAction::new(
+            signal::SigHandler::Handler(sigwinch_handler),
+            signal::SaFlags::empty(),
+            signal::SigSet::empty(),
+        );
         let _ = signal::sigaction(signal::SIGWINCH, &sigwinch);
     });
 }
@@ -240,7 +529,7 @@
 
 pub type Terminal = PosixTerminal;
 
-#[derive(Clone,Debug)]
+#[derive(Clone, Debug)]
 pub struct PosixTerminal {
     unsupported: bool,
     stdin_isatty: bool,
@@ -248,7 +537,7 @@
 
 impl Term for PosixTerminal {
     type Reader = PosixRawReader;
-    type Writer = Stdout;
+    type Writer = PosixRenderer;
     type Mode = Mode;
 
     fn new() -> PosixTerminal {
@@ -264,7 +553,8 @@
 
     // Init checks:
 
-    /// Check if current terminal can provide a rich line-editing user interface.
+    /// Check if current terminal can provide a rich line-editing user
+    /// interface.
     fn is_unsupported(&self) -> bool {
         self.unsupported
     }
@@ -276,40 +566,32 @@
 
     // Interactive loop:
 
-    /// Try to get the number of columns in the current terminal,
-    /// or assume 80 if it fails.
-    fn get_columns(&self) -> usize {
-        let (cols, _) = get_win_size();
-        cols
-    }
-
-    /// Try to get the number of rows in the current terminal,
-    /// or assume 24 if it fails.
-    fn get_rows(&self) -> usize {
-        let (_, rows) = get_win_size();
-        rows
-    }
-
     fn enable_raw_mode(&self) -> Result<Mode> {
         use nix::errno::Errno::ENOTTY;
-        use nix::sys::termios::{BRKINT, CS8, ECHO, ICANON, ICRNL, IEXTEN, INPCK, ISIG, ISTRIP,
-                                IXON, /* OPOST, */ VMIN, VTIME};
+        use nix::sys::termios::{ControlFlags, InputFlags, LocalFlags, SpecialCharacterIndices};
         if !self.stdin_isatty {
             try!(Err(nix::Error::from_errno(ENOTTY)));
         }
         let original_mode = try!(termios::tcgetattr(STDIN_FILENO));
-        let mut raw = original_mode;
+        let mut raw = original_mode.clone();
         // disable BREAK interrupt, CR to NL conversion on input,
         // input parity check, strip high bit (bit 8), output flow control
-        raw.c_iflag &= !(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
+        raw.input_flags &= !(InputFlags::BRKINT
+            | InputFlags::ICRNL
+            | InputFlags::INPCK
+            | InputFlags::ISTRIP
+            | InputFlags::IXON);
         // we don't want raw output, it turns newlines into straight linefeeds
-        // raw.c_oflag = raw.c_oflag & !(OPOST); // disable all output processing
-        raw.c_cflag |= CS8; // character-size mark (8 bits)
+        // raw.c_oflag = raw.c_oflag & !(OutputFlags::OPOST); // disable all output
+        // processing
+        // character-size mark (8 bits)
+        raw.control_flags |= ControlFlags::CS8;
         // disable echoing, canonical mode, extended input processing and signals
-        raw.c_lflag &= !(ECHO | ICANON | IEXTEN | ISIG);
-        raw.c_cc[VMIN] = 1; // One character-at-a-time input
-        raw.c_cc[VTIME] = 0; // with blocking read
-        try!(termios::tcsetattr(STDIN_FILENO, termios::TCSADRAIN, &raw));
+        raw.local_flags &=
+            !(LocalFlags::ECHO | LocalFlags::ICANON | LocalFlags::IEXTEN | LocalFlags::ISIG);
+        raw.control_chars[SpecialCharacterIndices::VMIN as usize] = 1; // One character-at-a-time input
+        raw.control_chars[SpecialCharacterIndices::VTIME as usize] = 0; // with blocking read
+        try!(termios::tcsetattr(STDIN_FILENO, SetArg::TCSADRAIN, &raw));
         Ok(original_mode)
     }
 
@@ -318,33 +600,32 @@
         PosixRawReader::new(config)
     }
 
-    fn create_writer(&self) -> Stdout {
-        io::stdout()
-    }
-
-    /// Check if a SIGWINCH signal has been received
-    fn sigwinch(&self) -> bool {
-        SIGWINCH.compare_and_swap(true, false, atomic::Ordering::SeqCst)
-    }
-
-    /// Clear the screen. Used to handle ctrl+l
-    fn clear_screen(&mut self, w: &mut Write) -> Result<()> {
-        try!(w.write_all(b"\x1b[H\x1b[2J"));
-        try!(w.flush());
-        Ok(())
+    fn create_writer(&self) -> PosixRenderer {
+        PosixRenderer::new()
     }
 }
 
 #[cfg(unix)]
 pub fn suspend() -> Result<()> {
-    // For macos:
-    try!(signal::kill(nix::unistd::getppid(), signal::SIGTSTP));
-    try!(signal::kill(nix::unistd::getpid(), signal::SIGTSTP));
+    use nix::unistd::Pid;
+    // suspend the whole process group
+    try!(signal::kill(Pid::from_raw(0), signal::SIGTSTP));
     Ok(())
 }
 
-#[cfg(all(unix,test))]
+#[cfg(all(unix, test))]
 mod test {
+    use super::{Position, Renderer};
+    use std::io::{self, Stdout};
+
+    #[test]
+    fn prompt_with_ansi_escape_codes() {
+        let out = io::stdout();
+        let pos = out.calculate_position("\x1b[1;32m>>\x1b[0m ", Position::default(), 80);
+        assert_eq!(3, pos.col);
+        assert_eq!(0, pos.row);
+    }
+
     #[test]
     fn test_unsupported_term() {
         ::std::env::set_var("TERM", "xterm");
diff --git a/src/tty/windows.rs b/src/tty/windows.rs
index 7b73e2f..6d9fa36 100644
--- a/src/tty/windows.rs
+++ b/src/tty/windows.rs
@@ -3,167 +3,219 @@
 use std::mem;
 use std::sync::atomic;
 
-use kernel32;
-use winapi;
+use unicode_width::UnicodeWidthChar;
+use winapi::shared::minwindef::{DWORD, WORD};
+use winapi::um::winnt::{CHAR, HANDLE};
+use winapi::um::{consoleapi, handleapi, processenv, winbase, wincon, winuser};
 
+use super::{truncate, Position, RawMode, RawReader, Renderer, Term};
 use config::Config;
 use consts::{self, KeyPress};
 use error;
+use line_buffer::LineBuffer;
 use Result;
-use super::{RawMode, RawReader, Term};
 
-const STDIN_FILENO: winapi::DWORD = winapi::STD_INPUT_HANDLE;
-const STDOUT_FILENO: winapi::DWORD = winapi::STD_OUTPUT_HANDLE;
+const STDIN_FILENO: DWORD = winbase::STD_INPUT_HANDLE;
+const STDOUT_FILENO: DWORD = winbase::STD_OUTPUT_HANDLE;
 
-fn get_std_handle(fd: winapi::DWORD) -> Result<winapi::HANDLE> {
-    let handle = unsafe { kernel32::GetStdHandle(fd) };
-    if handle == winapi::INVALID_HANDLE_VALUE {
+fn get_std_handle(fd: DWORD) -> Result<HANDLE> {
+    let handle = unsafe { processenv::GetStdHandle(fd) };
+    if handle == handleapi::INVALID_HANDLE_VALUE {
         try!(Err(io::Error::last_os_error()));
     } else if handle.is_null() {
-        try!(Err(io::Error::new(io::ErrorKind::Other,
-                                "no stdio handle available for this process")));
+        try!(Err(io::Error::new(
+            io::ErrorKind::Other,
+            "no stdio handle available for this process",
+        ),));
     }
     Ok(handle)
 }
 
 #[macro_export]
 macro_rules! check {
-    ($funcall:expr) => {
-        {
+    ($funcall:expr) => {{
         let rc = unsafe { $funcall };
         if rc == 0 {
             try!(Err(io::Error::last_os_error()));
         }
         rc
-        }
-    };
+    }};
 }
 
-fn get_win_size(handle: winapi::HANDLE) -> (usize, usize) {
+fn get_win_size(handle: HANDLE) -> (usize, usize) {
     let mut info = unsafe { mem::zeroed() };
-    match unsafe { kernel32::GetConsoleScreenBufferInfo(handle, &mut info) } {
+    match unsafe { wincon::GetConsoleScreenBufferInfo(handle, &mut info) } {
         0 => (80, 24),
-        _ => (info.dwSize.X as usize, (1 + info.srWindow.Bottom - info.srWindow.Top) as usize),
+        _ => (
+            info.dwSize.X as usize,
+            (1 + info.srWindow.Bottom - info.srWindow.Top) as usize,
+        ), // (info.srWindow.Right - info.srWindow.Left + 1)
     }
 }
 
-fn get_console_mode(handle: winapi::HANDLE) -> Result<winapi::DWORD> {
+fn get_console_mode(handle: HANDLE) -> Result<DWORD> {
     let mut original_mode = 0;
-    check!(kernel32::GetConsoleMode(handle, &mut original_mode));
+    check!(consoleapi::GetConsoleMode(handle, &mut original_mode));
     Ok(original_mode)
 }
 
 pub type Mode = ConsoleMode;
 
-#[derive(Clone,Copy,Debug)]
+#[derive(Clone, Copy, Debug)]
 pub struct ConsoleMode {
-    original_mode: winapi::DWORD,
-    stdin_handle: winapi::HANDLE,
+    original_stdin_mode: DWORD,
+    stdin_handle: HANDLE,
+    original_stdout_mode: DWORD,
+    stdout_handle: HANDLE,
 }
 
 impl RawMode for Mode {
     /// Disable RAW mode for the terminal.
     fn disable_raw_mode(&self) -> Result<()> {
-        check!(kernel32::SetConsoleMode(self.stdin_handle, self.original_mode));
+        check!(consoleapi::SetConsoleMode(
+            self.stdin_handle,
+            self.original_stdin_mode,
+        ));
+        check!(consoleapi::SetConsoleMode(
+            self.stdout_handle,
+            self.original_stdout_mode,
+        ));
         Ok(())
     }
 }
 
 /// Console input reader
 pub struct ConsoleRawReader {
-    handle: winapi::HANDLE,
-    buf: Option<u16>,
+    handle: HANDLE,
+    buf: [u16; 2],
 }
 
 impl ConsoleRawReader {
     pub fn new() -> Result<ConsoleRawReader> {
         let handle = try!(get_std_handle(STDIN_FILENO));
         Ok(ConsoleRawReader {
-               handle: handle,
-               buf: None,
-           })
+            handle,
+            buf: [0; 2],
+        })
     }
 }
 
 impl RawReader for ConsoleRawReader {
-    fn next_key(&mut self) -> Result<KeyPress> {
+    fn next_key(&mut self, _: bool) -> Result<KeyPress> {
         use std::char::decode_utf16;
-        // use winapi::{LEFT_ALT_PRESSED, LEFT_CTRL_PRESSED, RIGHT_ALT_PRESSED, RIGHT_CTRL_PRESSED};
-        use winapi::{LEFT_ALT_PRESSED, RIGHT_ALT_PRESSED};
+        use winapi::um::wincon::{
+            LEFT_ALT_PRESSED, LEFT_CTRL_PRESSED, RIGHT_ALT_PRESSED, RIGHT_CTRL_PRESSED,
+        };
 
-        let mut rec: winapi::INPUT_RECORD = unsafe { mem::zeroed() };
+        let mut rec: wincon::INPUT_RECORD = unsafe { mem::zeroed() };
         let mut count = 0;
+        let mut surrogate = false;
         loop {
             // TODO GetNumberOfConsoleInputEvents
-            check!(kernel32::ReadConsoleInputW(self.handle,
-                                               &mut rec,
-                                               1 as winapi::DWORD,
-                                               &mut count));
+            check!(consoleapi::ReadConsoleInputW(
+                self.handle,
+                &mut rec,
+                1 as DWORD,
+                &mut count,
+            ));
 
-            if rec.EventType == winapi::WINDOW_BUFFER_SIZE_EVENT {
+            if rec.EventType == wincon::WINDOW_BUFFER_SIZE_EVENT {
                 SIGWINCH.store(true, atomic::Ordering::SeqCst);
                 debug!(target: "rustyline", "SIGWINCH");
                 return Err(error::ReadlineError::WindowResize);
-            } else if rec.EventType != winapi::KEY_EVENT {
+            } else if rec.EventType != wincon::KEY_EVENT {
                 continue;
             }
-            let key_event = unsafe { rec.KeyEvent() };
+            let key_event = unsafe { rec.Event.KeyEvent() };
             // writeln!(io::stderr(), "key_event: {:?}", key_event).unwrap();
-            if key_event.bKeyDown == 0 &&
-               key_event.wVirtualKeyCode != winapi::VK_MENU as winapi::WORD {
+            if key_event.bKeyDown == 0 && key_event.wVirtualKeyCode != winuser::VK_MENU as WORD {
                 continue;
             }
+            // key_event.wRepeatCount seems to be always set to 1 (maybe because we only
+            // read one character at a time)
 
-            // let alt_gr = key_event.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED) != 0;
+            // let alt_gr = key_event.dwControlKeyState & (LEFT_CTRL_PRESSED |
+            // RIGHT_ALT_PRESSED) != 0;
             let alt = key_event.dwControlKeyState & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED) != 0;
-            // let ctrl = key_event.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED) != 0;
+            let ctrl = key_event.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED) != 0;
             let meta = alt;
 
-            let utf16 = key_event.UnicodeChar;
+            let utf16 = unsafe { *key_event.uChar.UnicodeChar() };
             if utf16 == 0 {
                 match key_event.wVirtualKeyCode as i32 {
-                    winapi::VK_LEFT => return Ok(KeyPress::Left),
-                    winapi::VK_RIGHT => return Ok(KeyPress::Right),
-                    winapi::VK_UP => return Ok(KeyPress::Up),
-                    winapi::VK_DOWN => return Ok(KeyPress::Down),
-                    winapi::VK_DELETE => return Ok(KeyPress::Delete),
-                    winapi::VK_HOME => return Ok(KeyPress::Home),
-                    winapi::VK_END => return Ok(KeyPress::End),
-                    winapi::VK_PRIOR => return Ok(KeyPress::PageUp),
-                    winapi::VK_NEXT => return Ok(KeyPress::PageDown),
+                    winuser::VK_LEFT => {
+                        return Ok(if ctrl {
+                            KeyPress::ControlLeft
+                        } else {
+                            KeyPress::Left
+                        })
+                    }
+                    winuser::VK_RIGHT => {
+                        return Ok(if ctrl {
+                            KeyPress::ControlRight
+                        } else {
+                            KeyPress::Right
+                        })
+                    }
+                    winuser::VK_UP => {
+                        return Ok(if ctrl {
+                            KeyPress::ControlUp
+                        } else {
+                            KeyPress::Up
+                        })
+                    }
+                    winuser::VK_DOWN => {
+                        return Ok(if ctrl {
+                            KeyPress::ControlDown
+                        } else {
+                            KeyPress::Down
+                        })
+                    }
+                    winuser::VK_DELETE => return Ok(KeyPress::Delete),
+                    winuser::VK_HOME => return Ok(KeyPress::Home),
+                    winuser::VK_END => return Ok(KeyPress::End),
+                    winuser::VK_PRIOR => return Ok(KeyPress::PageUp),
+                    winuser::VK_NEXT => return Ok(KeyPress::PageDown),
+                    winuser::VK_INSERT => return Ok(KeyPress::Insert),
+                    winuser::VK_F1 => return Ok(KeyPress::F(1)),
+                    winuser::VK_F2 => return Ok(KeyPress::F(2)),
+                    winuser::VK_F3 => return Ok(KeyPress::F(3)),
+                    winuser::VK_F4 => return Ok(KeyPress::F(4)),
+                    winuser::VK_F5 => return Ok(KeyPress::F(5)),
+                    winuser::VK_F6 => return Ok(KeyPress::F(6)),
+                    winuser::VK_F7 => return Ok(KeyPress::F(7)),
+                    winuser::VK_F8 => return Ok(KeyPress::F(8)),
+                    winuser::VK_F9 => return Ok(KeyPress::F(9)),
+                    winuser::VK_F10 => return Ok(KeyPress::F(10)),
+                    winuser::VK_F11 => return Ok(KeyPress::F(11)),
+                    winuser::VK_F12 => return Ok(KeyPress::F(12)),
+                    // winuser::VK_BACK is correctly handled because the key_event.UnicodeChar is
+                    // also
+                    // set.
                     _ => continue,
                 };
             } else if utf16 == 27 {
                 return Ok(KeyPress::Esc);
             } else {
-                // TODO How to support surrogate pair ?
-                self.buf = Some(utf16);
-                let orc = decode_utf16(self).next();
+                if utf16 >= 0xD800 && utf16 < 0xDC00 {
+                    surrogate = true;
+                    self.buf[0] = utf16;
+                    continue;
+                }
+                let buf = if surrogate {
+                    self.buf[1] = utf16;
+                    &self.buf[..]
+                } else {
+                    self.buf[0] = utf16;
+                    &self.buf[..1]
+                };
+                let orc = decode_utf16(buf.iter().cloned()).next();
                 if orc.is_none() {
                     return Err(error::ReadlineError::Eof);
                 }
                 let c = try!(orc.unwrap());
                 if meta {
-                    return Ok(match c {
-                                  '-' => KeyPress::Meta('-'),
-                                  '0'...'9' => KeyPress::Meta(c),
-                                  '<' => KeyPress::Meta('<'),
-                                  '>' => KeyPress::Meta('>'),
-                                  'b' | 'B' => KeyPress::Meta('B'),
-                                  'c' | 'C' => KeyPress::Meta('C'),
-                                  'd' | 'D' => KeyPress::Meta('D'),
-                                  'f' | 'F' => KeyPress::Meta('F'),
-                                  'l' | 'L' => KeyPress::Meta('L'),
-                                  'n' | 'N' => KeyPress::Meta('N'),
-                                  'p' | 'P' => KeyPress::Meta('P'),
-                                  't' | 'T' => KeyPress::Meta('T'),
-                                  'u' | 'U' => KeyPress::Meta('U'),
-                                  'y' | 'Y' => KeyPress::Meta('Y'),
-                                  _ => {
-                        debug!(target: "rustyline", "unsupported esc sequence: M-{:?}", c);
-                        KeyPress::UnknownEscSeq
-                    }
-                              });
+                    return Ok(KeyPress::Meta(c));
                 } else {
                     return Ok(consts::char_to_key_press(c));
                 }
@@ -172,13 +224,167 @@
     }
 }
 
-impl Iterator for ConsoleRawReader {
-    type Item = u16;
+pub struct ConsoleRenderer {
+    out: Stdout,
+    handle: HANDLE,
+    cols: usize, // Number of columns in terminal
+}
 
-    fn next(&mut self) -> Option<u16> {
-        let buf = self.buf;
-        self.buf = None;
-        buf
+impl ConsoleRenderer {
+    fn new(handle: HANDLE) -> ConsoleRenderer {
+        // Multi line editing is enabled by ENABLE_WRAP_AT_EOL_OUTPUT mode
+        let (cols, _) = get_win_size(handle);
+        ConsoleRenderer {
+            out: io::stdout(),
+            handle,
+            cols,
+        }
+    }
+
+    fn get_console_screen_buffer_info(&self) -> Result<wincon::CONSOLE_SCREEN_BUFFER_INFO> {
+        let mut info = unsafe { mem::zeroed() };
+        check!(wincon::GetConsoleScreenBufferInfo(self.handle, &mut info));
+        Ok(info)
+    }
+
+    fn set_console_cursor_position(&mut self, pos: wincon::COORD) -> Result<()> {
+        check!(wincon::SetConsoleCursorPosition(self.handle, pos));
+        Ok(())
+    }
+
+    fn clear(&mut self, length: DWORD, pos: wincon::COORD) -> Result<()> {
+        let mut _count = 0;
+        check!(wincon::FillConsoleOutputCharacterA(
+            self.handle,
+            ' ' as CHAR,
+            length,
+            pos,
+            &mut _count,
+        ));
+        Ok(())
+    }
+}
+
+impl Renderer for ConsoleRenderer {
+    fn move_cursor(&mut self, old: Position, new: Position) -> Result<()> {
+        let mut info = try!(self.get_console_screen_buffer_info());
+        if new.row > old.row {
+            info.dwCursorPosition.Y += (new.row - old.row) as i16;
+        } else {
+            info.dwCursorPosition.Y -= (old.row - new.row) as i16;
+        }
+        if new.col > old.col {
+            info.dwCursorPosition.X += (new.col - old.col) as i16;
+        } else {
+            info.dwCursorPosition.X -= (old.col - new.col) as i16;
+        }
+        self.set_console_cursor_position(info.dwCursorPosition)
+    }
+
+    fn refresh_line(
+        &mut self,
+        prompt: &str,
+        prompt_size: Position,
+        line: &LineBuffer,
+        hint: Option<String>,
+        current_row: usize,
+        old_rows: usize,
+    ) -> Result<(Position, Position)> {
+        // calculate the position of the end of the input line
+        let end_pos = self.calculate_position(line, prompt_size);
+        // calculate the desired position of the cursor
+        let cursor = self.calculate_position(&line[..line.pos()], prompt_size);
+
+        // position at the start of the prompt, clear to end of previous input
+        let mut info = try!(self.get_console_screen_buffer_info());
+        info.dwCursorPosition.X = 0;
+        info.dwCursorPosition.Y -= current_row as i16;
+        try!(self.set_console_cursor_position(info.dwCursorPosition));
+        try!(self.clear(
+            (info.dwSize.X * (old_rows as i16 + 1)) as DWORD,
+            info.dwCursorPosition,
+        ));
+        let mut ab = String::new();
+        // display the prompt
+        // TODO handle ansi escape code (SetConsoleTextAttribute)
+        ab.push_str(prompt);
+        // display the input line
+        ab.push_str(&line);
+        // display hint
+        if let Some(hint) = hint {
+            ab.push_str(truncate(&hint, end_pos.col, self.cols));
+        }
+        try!(self.write_and_flush(ab.as_bytes()));
+
+        // position the cursor
+        let mut info = try!(self.get_console_screen_buffer_info());
+        info.dwCursorPosition.X = cursor.col as i16;
+        info.dwCursorPosition.Y -= (end_pos.row - cursor.row) as i16;
+        try!(self.set_console_cursor_position(info.dwCursorPosition));
+        Ok((cursor, end_pos))
+    }
+
+    fn write_and_flush(&mut self, buf: &[u8]) -> Result<()> {
+        try!(self.out.write_all(buf));
+        try!(self.out.flush());
+        Ok(())
+    }
+
+    /// Characters with 2 column width are correctly handled (not splitted).
+    fn calculate_position(&self, s: &str, orig: Position) -> Position {
+        let mut pos = orig;
+        for c in s.chars() {
+            let cw = if c == '\n' {
+                pos.col = 0;
+                pos.row += 1;
+                None
+            } else {
+                c.width()
+            };
+            if let Some(cw) = cw {
+                pos.col += cw;
+                if pos.col > self.cols {
+                    pos.row += 1;
+                    pos.col = cw;
+                }
+            }
+        }
+        if pos.col == self.cols {
+            pos.col = 0;
+            pos.row += 1;
+        }
+        pos
+    }
+
+    /// Clear the screen. Used to handle ctrl+l
+    fn clear_screen(&mut self) -> Result<()> {
+        let info = try!(self.get_console_screen_buffer_info());
+        let coord = wincon::COORD { X: 0, Y: 0 };
+        check!(wincon::SetConsoleCursorPosition(self.handle, coord));
+        let n = info.dwSize.X as DWORD * info.dwSize.Y as DWORD;
+        self.clear(n, coord)
+    }
+
+    fn sigwinch(&self) -> bool {
+        SIGWINCH.compare_and_swap(true, false, atomic::Ordering::SeqCst)
+    }
+
+    /// Try to get the number of columns in the current terminal,
+    /// or assume 80 if it fails.
+    fn update_size(&mut self) {
+        let (cols, _) = get_win_size(self.handle);
+        self.cols = cols;
+    }
+
+    fn get_columns(&self) -> usize {
+        self.cols
+    }
+
+    /// Try to get the number of rows in the current terminal,
+    /// or assume 24 if it fails.
+    fn get_rows(&self) -> usize {
+        let (_, rows) = get_win_size(self.handle);
+        rows
     }
 }
 
@@ -186,42 +392,18 @@
 
 pub type Terminal = Console;
 
-#[derive(Clone,Debug)]
+#[derive(Clone, Debug)]
 pub struct Console {
     stdin_isatty: bool,
-    stdin_handle: winapi::HANDLE,
-    stdout_handle: winapi::HANDLE,
+    stdin_handle: HANDLE,
+    stdout_handle: HANDLE,
 }
 
-impl Console {
-    pub fn get_console_screen_buffer_info(&self) -> Result<winapi::CONSOLE_SCREEN_BUFFER_INFO> {
-        let mut info = unsafe { mem::zeroed() };
-        check!(kernel32::GetConsoleScreenBufferInfo(self.stdout_handle, &mut info));
-        Ok(info)
-    }
-
-    pub fn set_console_cursor_position(&mut self, pos: winapi::COORD) -> Result<()> {
-        check!(kernel32::SetConsoleCursorPosition(self.stdout_handle, pos));
-        Ok(())
-    }
-
-    pub fn fill_console_output_character(&mut self,
-                                         length: winapi::DWORD,
-                                         pos: winapi::COORD)
-                                         -> Result<()> {
-        let mut _count = 0;
-        check!(kernel32::FillConsoleOutputCharacterA(self.stdout_handle,
-                                                     ' ' as winapi::CHAR,
-                                                     length,
-                                                     pos,
-                                                     &mut _count));
-        Ok(())
-    }
-}
+impl Console {}
 
 impl Term for Console {
     type Reader = ConsoleRawReader;
-    type Writer = Stdout;
+    type Writer = ConsoleRenderer;
     type Mode = Mode;
 
     fn new() -> Console {
@@ -237,9 +419,9 @@
 
         let stdout_handle = get_std_handle(STDOUT_FILENO).unwrap_or(ptr::null_mut());
         Console {
-            stdin_isatty: stdin_isatty,
+            stdin_isatty,
             stdin_handle: stdin_handle.unwrap_or(ptr::null_mut()),
-            stdout_handle: stdout_handle,
+            stdout_handle,
         }
     }
 
@@ -256,67 +438,48 @@
     // See ReadConsoleInputW && WINDOW_BUFFER_SIZE_EVENT
     // }
 
-    /// Try to get the number of columns in the current terminal,
-    /// or assume 80 if it fails.
-    fn get_columns(&self) -> usize {
-        let (cols, _) = get_win_size(self.stdout_handle);
-        cols
-    }
-
-    /// Try to get the number of rows in the current terminal,
-    /// or assume 24 if it fails.
-    fn get_rows(&self) -> usize {
-        let (_, rows) = get_win_size(self.stdout_handle);
-        rows
-    }
-
     /// Enable RAW mode for the terminal.
     fn enable_raw_mode(&self) -> Result<Mode> {
         if !self.stdin_isatty {
-            try!(Err(io::Error::new(io::ErrorKind::Other,
-                                    "no stdio handle available for this process")));
+            try!(Err(io::Error::new(
+                io::ErrorKind::Other,
+                "no stdio handle available for this process",
+            ),));
         }
-        let original_mode = try!(get_console_mode(self.stdin_handle));
+        let original_stdin_mode = try!(get_console_mode(self.stdin_handle));
         // Disable these modes
-        let raw = original_mode &
-                  !(winapi::wincon::ENABLE_LINE_INPUT | winapi::wincon::ENABLE_ECHO_INPUT |
-                    winapi::wincon::ENABLE_PROCESSED_INPUT);
+        let raw = original_stdin_mode
+            & !(wincon::ENABLE_LINE_INPUT
+                | wincon::ENABLE_ECHO_INPUT
+                | wincon::ENABLE_PROCESSED_INPUT);
         // Enable these modes
-        let raw = raw | winapi::wincon::ENABLE_EXTENDED_FLAGS;
-        let raw = raw | winapi::wincon::ENABLE_INSERT_MODE;
-        let raw = raw | winapi::wincon::ENABLE_QUICK_EDIT_MODE;
-        let raw = raw | winapi::wincon::ENABLE_WINDOW_INPUT;
-        check!(kernel32::SetConsoleMode(self.stdin_handle, raw));
+        let raw = raw | wincon::ENABLE_EXTENDED_FLAGS;
+        let raw = raw | wincon::ENABLE_INSERT_MODE;
+        let raw = raw | wincon::ENABLE_QUICK_EDIT_MODE;
+        let raw = raw | wincon::ENABLE_WINDOW_INPUT;
+        check!(consoleapi::SetConsoleMode(self.stdin_handle, raw));
+
+        let original_stdout_mode = try!(get_console_mode(self.stdout_handle));
+        // To enable ANSI colors (Windows 10 only):
+        // https://docs.microsoft.com/en-us/windows/console/setconsolemode
+        if original_stdout_mode & wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0 {
+            let raw = original_stdout_mode | wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING;
+            check!(consoleapi::SetConsoleMode(self.stdout_handle, raw));
+        }
+
         Ok(Mode {
-               original_mode: original_mode,
-               stdin_handle: self.stdin_handle,
-           })
+            original_stdin_mode,
+            stdin_handle: self.stdin_handle,
+            original_stdout_mode,
+            stdout_handle: self.stdout_handle,
+        })
     }
 
     fn create_reader(&self, _: &Config) -> Result<ConsoleRawReader> {
         ConsoleRawReader::new()
     }
 
-    fn create_writer(&self) -> Stdout {
-        io::stdout()
-    }
-
-    fn sigwinch(&self) -> bool {
-        SIGWINCH.compare_and_swap(true, false, atomic::Ordering::SeqCst)
-    }
-
-    /// Clear the screen. Used to handle ctrl+l
-    fn clear_screen(&mut self, _: &mut Write) -> Result<()> {
-        let info = try!(self.get_console_screen_buffer_info());
-        let coord = winapi::COORD { X: 0, Y: 0 };
-        check!(kernel32::SetConsoleCursorPosition(self.stdout_handle, coord));
-        let mut _count = 0;
-        let n = info.dwSize.X as winapi::DWORD * info.dwSize.Y as winapi::DWORD;
-        check!(kernel32::FillConsoleOutputCharacterA(self.stdout_handle,
-                                                     ' ' as winapi::CHAR,
-                                                     n,
-                                                     coord,
-                                                     &mut _count));
-        Ok(())
+    fn create_writer(&self) -> ConsoleRenderer {
+        ConsoleRenderer::new(self.stdout_handle)
     }
 }
diff --git a/src/undo.rs b/src/undo.rs
new file mode 100644
index 0000000..84c8f77
--- /dev/null
+++ b/src/undo.rs
@@ -0,0 +1,468 @@
+//! Undo API
+use std::fmt::Debug;
+
+use keymap::RepeatCount;
+use line_buffer::{ChangeListener, DeleteListener, Direction, LineBuffer};
+use unicode_segmentation::UnicodeSegmentation;
+
+enum Change {
+    Begin,
+    End,
+    Insert {
+        idx: usize,
+        text: String,
+    }, // QuotedInsert, SelfInsert, Yank
+    Delete {
+        idx: usize,
+        text: String,
+    }, /* BackwardDeleteChar, BackwardKillWord, DeleteChar,
+        * KillLine, KillWholeLine, KillWord,
+        * UnixLikeDiscard, ViDeleteTo */
+    Replace {
+        idx: usize,
+        old: String,
+        new: String,
+    }, /* CapitalizeWord, Complete, DowncaseWord, Replace, TransposeChars, TransposeWords,
+        * UpcaseWord, YankPop */
+}
+
+impl Change {
+    fn undo(&self, line: &mut LineBuffer) {
+        match *self {
+            Change::Begin | Change::End => {
+                unreachable!();
+            }
+            Change::Insert { idx, ref text } => {
+                line.delete_range(idx..idx + text.len());
+            }
+            Change::Delete { idx, ref text } => {
+                line.insert_str(idx, text);
+                line.set_pos(idx + text.len());
+            }
+            Change::Replace {
+                idx,
+                ref old,
+                ref new,
+            } => {
+                line.replace(idx..idx + new.len(), old);
+            }
+        }
+    }
+
+    #[cfg(test)]
+    fn redo(&self, line: &mut LineBuffer) {
+        match *self {
+            Change::Begin | Change::End => {
+                unreachable!();
+            }
+            Change::Insert { idx, ref text } => {
+                line.insert_str(idx, text);
+            }
+            Change::Delete { idx, ref text } => {
+                line.delete_range(idx..idx + text.len());
+            }
+            Change::Replace {
+                idx,
+                ref old,
+                ref new,
+            } => {
+                line.replace(idx..idx + old.len(), new);
+            }
+        }
+    }
+
+    fn insert_seq(&self, indx: usize) -> bool {
+        if let Change::Insert { idx, ref text } = *self {
+            idx + text.len() == indx
+        } else {
+            false
+        }
+    }
+
+    fn delete_seq(&self, indx: usize, len: usize) -> bool {
+        if let Change::Delete { idx, .. } = *self {
+            // delete or backspace
+            idx == indx || idx == indx + len
+        } else {
+            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 {
+    undo_group_level: u32,
+    undos: Vec<Change>, // undoable changes
+    redos: Vec<Change>, // undone changes, redoable
+}
+
+impl Changeset {
+    pub fn new() -> Changeset {
+        Changeset {
+            undo_group_level: 0,
+            undos: Vec::new(),
+            redos: Vec::new(),
+        }
+    }
+
+    pub fn begin(&mut self) -> usize {
+        debug!(target: "rustyline", "Changeset::begin");
+        self.redos.clear();
+        let mark = self.undos.len();
+        self.undos.push(Change::Begin);
+        self.undo_group_level += 1;
+        mark
+    }
+
+    pub fn end(&mut self) {
+        debug!(target: "rustyline", "Changeset::end");
+        self.redos.clear();
+        while self.undo_group_level > 0 {
+            self.undo_group_level -= 1;
+            if let Some(&Change::Begin) = self.undos.last() {
+                // emtpy Begin..End
+                self.undos.pop();
+            } else {
+                self.undos.push(Change::End);
+            }
+        }
+    }
+
+    fn insert_char(idx: usize, c: char) -> Change {
+        let mut text = String::new();
+        text.push(c);
+        Change::Insert { idx, text }
+    }
+
+    pub fn insert(&mut self, idx: usize, c: char) {
+        debug!(target: "rustyline", "Changeset::insert({}, {:?})", idx, c);
+        self.redos.clear();
+        if !c.is_alphanumeric() || !self.undos.last().map_or(false, |lc| lc.insert_seq(idx)) {
+            self.undos.push(Self::insert_char(idx, c));
+            return;
+        }
+        // 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: AsRef<str> + Into<String> + Debug>(&mut self, idx: usize, string: S) {
+        debug!(target: "rustyline", "Changeset::insert_str({}, {:?})", idx, string);
+        self.redos.clear();
+        if string.as_ref().is_empty() {
+            return;
+        }
+        self.undos.push(Change::Insert {
+            idx,
+            text: string.into(),
+        });
+    }
+
+    pub fn delete<S: AsRef<str> + Into<String> + Debug>(&mut self, indx: usize, string: S) {
+        debug!(target: "rustyline", "Changeset::delete({}, {:?})", indx, string);
+        self.redos.clear();
+        if string.as_ref().is_empty() {
+            return;
+        }
+
+        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,
+                text: string.into(),
+            });
+            return;
+        }
+        // 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 {
+        let mut graphemes = s.graphemes(true);
+        graphemes.next().map_or(false, |grapheme| {
+            grapheme.chars().all(|c| c.is_alphanumeric())
+        }) && graphemes.next().is_none()
+    }
+
+    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();
+
+        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, n: RepeatCount) -> bool {
+        debug!(target: "rustyline", "Changeset::undo");
+        let mut count = 0;
+        let mut waiting_for_begin = 0;
+        let mut undone = false;
+        loop {
+            if let Some(change) = self.undos.pop() {
+                match change {
+                    Change::Begin => {
+                        waiting_for_begin -= 1;
+                    }
+                    Change::End => {
+                        waiting_for_begin += 1;
+                    }
+                    _ => {
+                        change.undo(line);
+                        undone = true;
+                    }
+                };
+                self.redos.push(change);
+            } else {
+                break;
+            }
+            if waiting_for_begin <= 0 {
+                count += 1;
+                if count >= n {
+                    break;
+                }
+            }
+        }
+        undone
+    }
+
+    pub fn truncate(&mut self, len: usize) {
+        debug!(target: "rustyline", "Changeset::truncate({})", len);
+        self.undos.truncate(len);
+    }
+
+    #[cfg(test)]
+    pub fn redo(&mut self, line: &mut LineBuffer) -> bool {
+        let mut waiting_for_end = 0;
+        let mut redone = false;
+        loop {
+            if let Some(change) = self.redos.pop() {
+                match change {
+                    Change::Begin => {
+                        waiting_for_end += 1;
+                    }
+                    Change::End => {
+                        waiting_for_end -= 1;
+                    }
+                    _ => {
+                        change.redo(line);
+                        redone = true;
+                    }
+                };
+                self.undos.push(change);
+            } else {
+                break;
+            }
+            if waiting_for_end <= 0 {
+                break;
+            }
+        }
+        redone
+    }
+
+    pub fn last_insert(&self) -> Option<String> {
+        for change in self.undos.iter().rev() {
+            match change {
+                Change::Insert { ref text, .. } => return Some(text.to_owned()),
+                Change::Replace { ref new, .. } => return Some(new.to_owned()),
+                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) {
+        self.insert(idx, c);
+    }
+    fn insert_str(&mut self, idx: usize, string: &str) {
+        self.insert_str(idx, string);
+    }
+    fn replace(&mut self, idx: usize, old: &str, new: &str) {
+        self.replace(idx, old, new);
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::Changeset;
+    use line_buffer::LineBuffer;
+
+    #[test]
+    fn test_insert_chars() {
+        let mut cs = Changeset::new();
+        cs.insert(0, 'H');
+        cs.insert(1, 'i');
+        assert_eq!(1, cs.undos.len());
+        assert_eq!(0, cs.redos.len());
+        cs.insert(0, ' ');
+        assert_eq!(2, cs.undos.len());
+    }
+
+    #[test]
+    fn test_insert_strings() {
+        let mut cs = Changeset::new();
+        cs.insert_str(0, "Hello");
+        cs.insert_str(5, ", ");
+        assert_eq!(2, cs.undos.len());
+        assert_eq!(0, cs.redos.len());
+    }
+
+    #[test]
+    fn test_undo_insert() {
+        let mut buf = LineBuffer::init("", 0, None);
+        buf.insert_str(0, "Hello");
+        buf.insert_str(5, ", world!");
+        let mut cs = Changeset::new();
+        assert_eq!(buf.as_str(), "Hello, world!");
+
+        cs.insert_str(5, ", world!");
+
+        cs.undo(&mut buf, 1);
+        assert_eq!(0, cs.undos.len());
+        assert_eq!(1, cs.redos.len());
+        assert_eq!(buf.as_str(), "Hello");
+
+        cs.redo(&mut buf);
+        assert_eq!(1, cs.undos.len());
+        assert_eq!(0, cs.redos.len());
+        assert_eq!(buf.as_str(), "Hello, world!");
+    }
+
+    #[test]
+    fn test_undo_delete() {
+        let mut buf = LineBuffer::init("", 0, None);
+        buf.insert_str(0, "Hello");
+        let mut cs = Changeset::new();
+        assert_eq!(buf.as_str(), "Hello");
+
+        cs.delete(5, ", world!");
+
+        cs.undo(&mut buf, 1);
+        assert_eq!(buf.as_str(), "Hello, world!");
+
+        cs.redo(&mut buf);
+        assert_eq!(buf.as_str(), "Hello");
+    }
+
+    #[test]
+    fn test_delete_chars() {
+        let mut buf = LineBuffer::init("", 0, None);
+        buf.insert_str(0, "Hlo");
+
+        let mut cs = Changeset::new();
+        cs.delete(1, "e");
+        cs.delete(1, "l");
+        assert_eq!(1, cs.undos.len());
+
+        cs.undo(&mut buf, 1);
+        assert_eq!(buf.as_str(), "Hello");
+    }
+
+    #[test]
+    fn test_backspace_chars() {
+        let mut buf = LineBuffer::init("", 0, None);
+        buf.insert_str(0, "Hlo");
+
+        let mut cs = Changeset::new();
+        cs.delete(2, "l");
+        cs.delete(1, "e");
+        assert_eq!(1, cs.undos.len());
+
+        cs.undo(&mut buf, 1);
+        assert_eq!(buf.as_str(), "Hello");
+    }
+
+    #[test]
+    fn test_undo_replace() {
+        let mut buf = LineBuffer::init("", 0, None);
+        buf.insert_str(0, "Hello, world!");
+        let mut cs = Changeset::new();
+        assert_eq!(buf.as_str(), "Hello, world!");
+
+        buf.replace(1..5, "i");
+        assert_eq!(buf.as_str(), "Hi, world!");
+        cs.replace(1, "ello", "i");
+
+        cs.undo(&mut buf, 1);
+        assert_eq!(buf.as_str(), "Hello, world!");
+
+        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);
+    }
+}