Merge remote-tracking branch 'upstream/master' into HEAD

* upstream/master: (342 commits)
  Update TODO list
  Fix some rustc warnings
  Fix some Clippy warnings
  Rustfmt
  Another fix for stderr support
  Rustfmt
  Simplify surrogate pair handling on windows
  Fix on windows when using stderr.
  Allow customization of the output stream
  Ignore all IO errors during completion
  Prepare 2.1.0 release
  Fix line refresh/redraw (#149)
  [unstable] Fix error: scoped lint `...` is experimental
  Update TODO list
  Flag ReadlineError, Cmd, KeyPress as non exhaustive
  Improve key press handling
  Fix clippy warnings
  Rename consts.rs to keys.rs
  Prepare 2.0.1 release
  Windows: fix enable_raw_mode
  ...

Change-Id: I4a5ea48918ba09f93037b8cabdb259af9edab1f2
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 c35c55d..43a3648 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "rustyline"
-version = "1.0.0"
+version = "2.1.0"
 authors = ["Katsu Kawakami <kkawa1570@gmail.com>"]
 description = "Rustyline, a readline implementation based on Antirez's Linenoise"
 documentation = "http://docs.rs/rustyline"
@@ -13,25 +13,30 @@
 [badges]
 travis-ci = { repository = "kkawakam/rustyline" }
 appveyor = { repository = "kkawakam/rustyline" }
+maintenance = { status = "actively-developed" }
 
 
 [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"
+memchr = "2.0"
+
+[target.'cfg(unix)'.dependencies]
+utf8parse = "0.1"
 
 [target.'cfg(all(unix, not(any(target_os = "fuchsia"))))'.dependencies]
-nix = "0.8"
+dirs = "1.0"
+nix = "0.11"
 
 [target.'cfg(target_os = "fuchsia")'.dependencies]
 fuchsia-zircon = "0.3.2"
 fuchsia-device = "0.1.0"
 
 [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..ea66ccc 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) => {
@@ -68,7 +61,7 @@
 
 ```toml
 [dependencies]
-rustyline = "1.0.0"
+rustyline = "2.1.0"
 ```
 
 ## Features
@@ -76,10 +69,11 @@
  - Unicode (UTF-8) (linenoise supports only ASCII)
  - Word completion (linenoise supports only line completion)
  - 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
+ - History search ([Searching for Commands in the History](http://tiswww.case.edu/php/chet/readline/readline.html#SEC8))
+ - Kill ring ([Killing Commands](http://tiswww.case.edu/php/chet/readline/readline.html#IDX3))
+ - 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,26 @@
 
 ## 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 |
+--------           | ----    | --     | ----  | ------- | -------       | ---------- | -------       | --------- | ---- | ------     | ----------------- |
+[Go-prompt][]      | Go      | Ux/win | ANSI  | Yes     | Yes           | any        | Emacs/prog    | No        | No   | Yes   | Yes               |
+[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               |
+
+[Go-prompt]: https://github.com/c-bata/go-prompt
+[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/redox-os/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..7037243
--- /dev/null
+++ b/TODO.md
@@ -0,0 +1,88 @@
+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 ? https://github.com/mattn/go-colorable/blob/master/colorable_windows.go)
+- [ ] Syntax highlighting (https://github.com/trishume/syntect/)
+- [ ] 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)
+- [X] 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
+- [ ] Do an implementation based on previous history
+
+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)
+- [X] quoted insert (#65)
+- [ ] quoted TAB (`\t`) insert and width
+- [ ] Overwrite mode (em-toggle-overwrite, vi-replace-mode, rl_insert_mode)
+- [ ] Encoding
+- [ ] [Ctrl-][Alt-][Shift-]<Key> (#121)
+
+Layout
+- [ ] Redraw perf (https://crates.io/crates/cassowary)
+
+Mouse
+- [ ] Mouse support
+
+Movement
+- [ ] Move to the corresponding opening/closing bracket
+
+Redo
+- [X] redo substitute
+
+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)
+- [ ] [ncurses](https://crates.io/crates/ncurses) alternative backend ?
+- [ ] [bracketed paste mode](https://cirw.in/blog/bracketed-paste)
+- [ ] async stdin (https://github.com/Rufflewind/tokio-file-unix)
+
+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 ? https://github.com/mattn/go-colorable/blob/master/colorable_windows.go)
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..497bc34 100644
--- a/examples/example.rs
+++ b/examples/example.rs
@@ -1,32 +1,67 @@
 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 std::borrow::Cow::{self, Borrowed, Owned};
 
-use rustyline::completion::FilenameCompleter;
+use rustyline::completion::{Completer, FilenameCompleter, Pair};
+use rustyline::config::OutputStreamType;
 use rustyline::error::ReadlineError;
-use rustyline::{Cmd, Config, CompletionType, Editor, EditMode, KeyPress};
+use rustyline::highlight::Highlighter;
+use rustyline::hint::Hinter;
+use rustyline::{Cmd, CompletionType, Config, EditMode, Editor, Helper, KeyPress};
 
-// On unix platforms you can use ANSI escape sequences
-#[cfg(unix)]
-static PROMPT: &'static str = "\x1b[1;32m>>\x1b[0m ";
+static COLORED_PROMPT: &'static str = "\x1b[1;32m>>\x1b[0m ";
 
-// Windows consoles typically don't support ANSI escape sequences out
-// of the box
-#[cfg(windows)]
 static PROMPT: &'static str = ">> ";
 
+struct MyHelper(FilenameCompleter);
+
+impl Completer for MyHelper {
+    type Candidate = Pair;
+
+    fn complete(&self, line: &str, pos: usize) -> Result<(usize, Vec<Pair>), ReadlineError> {
+        self.0.complete(line, pos)
+    }
+}
+
+impl Hinter for MyHelper {
+    fn hint(&self, line: &str, _pos: usize) -> Option<String> {
+        if line == "hello" {
+            Some(" World".to_owned())
+        } else {
+            None
+        }
+    }
+}
+
+impl Highlighter for MyHelper {
+    fn highlight_prompt<'p>(&self, prompt: &'p str) -> Cow<'p, str> {
+        if prompt == PROMPT {
+            Borrowed(COLORED_PROMPT)
+        } else {
+            Borrowed(prompt)
+        }
+    }
+
+    fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
+        Owned("\x1b[1m".to_owned() + hint + "\x1b[m")
+    }
+}
+
+impl Helper for MyHelper {}
+
 fn main() {
     init_logger().is_ok();
     let config = Config::builder()
         .history_ignore_space(true)
         .completion_type(CompletionType::List)
         .edit_mode(EditMode::Emacs)
+        .output_stream(OutputStreamType::Stdout)
         .build();
-    let c = FilenameCompleter::new();
+    let h = MyHelper(FilenameCompleter::new());
     let mut rl = Editor::with_config(config);
-    rl.set_completer(Some(c));
+    rl.set_helper(Some(h));
     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 +91,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..83697e3 100644
--- a/rustfmt.toml
+++ b/rustfmt.toml
@@ -1,3 +1,4 @@
-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
+reorder_impl_items = true
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..b71e762 100644
--- a/src/completion.rs
+++ b/src/completion.rs
@@ -1,22 +1,60 @@
 //! Completion API
 use std::borrow::Cow::{self, Borrowed, Owned};
-use std::collections::BTreeSet;
 use std::fs;
 use std::path::{self, Path};
 
 use super::Result;
 use line_buffer::LineBuffer;
+use memchr::memchr;
 
 // 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 ???
+
+/// A completion candidate.
+pub trait Candidate {
+    /// Text to display when listing alternatives.
+    fn display(&self) -> &str;
+    /// Text to insert in line.
+    fn replacement(&self) -> &str;
+}
+
+impl Candidate for String {
+    fn display(&self) -> &str {
+        self.as_str()
+    }
+
+    fn replacement(&self) -> &str {
+        self.as_str()
+    }
+}
+
+pub struct Pair {
+    pub display: String,
+    pub replacement: String,
+}
+
+impl Candidate for Pair {
+    fn display(&self) -> &str {
+        self.display.as_str()
+    }
+
+    fn replacement(&self) -> &str {
+        self.replacement.as_str()
+    }
+}
 
 /// To be called for tab-completion.
 pub trait Completer {
+    type Candidate: Candidate;
+
     /// 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/"]))
-    fn complete(&self, line: &str, pos: usize) -> Result<(usize, Vec<String>)>;
+    /// 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<Self::Candidate>)>;
     /// Updates the edited `line` with the `elected` candidate.
     fn update(&self, line: &mut LineBuffer, start: usize, elected: &str) {
         let end = line.pos();
@@ -25,18 +63,24 @@
 }
 
 impl Completer for () {
+    type Candidate = String;
+
     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!()
     }
 }
 
 impl<'c, C: ?Sized + Completer> Completer for &'c C {
-    fn complete(&self, line: &str, pos: usize) -> Result<(usize, Vec<String>)> {
+    type Candidate = C::Candidate;
+
+    fn complete(&self, line: &str, pos: usize) -> Result<(usize, Vec<Self::Candidate>)> {
         (**self).complete(line, pos)
     }
+
     fn update(&self, line: &mut LineBuffer, start: usize, elected: &str) {
         (**self).update(line, start, elected)
     }
@@ -45,7 +89,9 @@
     ($($id: ident)*) => {
         $(
             impl<C: ?Sized + Completer> Completer for $id<C> {
-                fn complete(&self, line: &str, pos: usize) -> Result<(usize, Vec<String>)> {
+                type Candidate = C::Candidate;
+
+                fn complete(&self, line: &str, pos: usize) -> Result<(usize, Vec<Self::Candidate>)> {
                     (**self).complete(line, pos)
                 }
                 fn update(&self, line: &mut LineBuffer, start: usize, elected: &str) {
@@ -62,24 +108,49 @@
 
 /// A `Completer` for file and folder names.
 pub struct FilenameCompleter {
-    break_chars: BTreeSet<char>,
+    break_chars: &'static [u8],
+    double_quotes_special_chars: &'static [u8],
 }
 
+static DOUBLE_QUOTES_ESCAPE_CHAR: Option<char> = Some('\\');
+
+// 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: [u8; 18] = [
+    b' ', b'\t', b'\n', b'"', b'\\', b'\'', b'`', b'@', b'$', b'>', b'<', b'=', b';', b'|', b'&',
+    b'{', b'(', b'\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: [u8; 17] = [
+    b' ', b'\t', b'\n', b'"', b'\'', b'`', b'@', b'$', b'>', b'<', b'=', b';', b'|', b'&', b'{',
+    b'(', b'\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
+#[cfg(unix)]
+static DOUBLE_QUOTES_SPECIAL_CHARS: [u8; 4] = [b'"', b'$', b'\\', b'`'];
+#[cfg(windows)]
+static DOUBLE_QUOTES_SPECIAL_CHARS: [u8; 1] = [b'"']; // TODO Validate: only '"' ?
+
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum Quote {
+    Double,
+    Single,
+    None,
+}
+
 impl FilenameCompleter {
     pub fn new() -> FilenameCompleter {
-        FilenameCompleter { break_chars: DEFAULT_BREAK_CHARS.iter().cloned().collect() }
+        FilenameCompleter {
+            break_chars: &DEFAULT_BREAK_CHARS,
+            double_quotes_special_chars: &DOUBLE_QUOTES_SPECIAL_CHARS,
+        }
     }
 }
 
@@ -90,10 +161,35 @@
 }
 
 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));
+    type Candidate = Pair;
+
+    fn complete(&self, line: &str, pos: usize) -> Result<(usize, Vec<Pair>)> {
+        let (start, path, esc_char, break_chars, quote) =
+            if let Some((idx, quote)) = find_unclosed_quote(&line[..pos]) {
+                let start = idx + 1;
+                if quote == Quote::Double {
+                    (
+                        start,
+                        unescape(&line[start..pos], DOUBLE_QUOTES_ESCAPE_CHAR),
+                        DOUBLE_QUOTES_ESCAPE_CHAR,
+                        &self.double_quotes_special_chars,
+                        quote,
+                    )
+                } else {
+                    (
+                        start,
+                        Borrowed(&line[start..pos]),
+                        None,
+                        &self.break_chars,
+                        quote,
+                    )
+                }
+            } 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, Quote::None)
+            };
+        let matches = try!(filename_complete(&path, esc_char, break_chars, quote));
         Ok((start, matches))
     }
 }
@@ -104,15 +200,20 @@
         return Borrowed(input);
     }
     let esc_char = esc_char.unwrap();
-    let n = input.chars().filter(|&c| c == esc_char).count();
-    if n == 0 {
+    if !input.chars().any(|c| c == esc_char) {
         return Borrowed(input);
     }
-    let mut result = String::with_capacity(input.len() - n);
+    let mut result = String::with_capacity(input.len());
     let mut chars = input.chars();
     while let Some(ch) = chars.next() {
         if ch == esc_char {
             if let Some(ch) = chars.next() {
+                if cfg!(windows) && ch != '"' {
+                    // TODO Validate: only '"' ?
+                    result.push(esc_char);
+                }
+                result.push(ch);
+            } else if cfg!(windows) {
                 result.push(ch);
             }
         } else {
@@ -124,23 +225,35 @@
 
 /// 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.
-pub fn escape(input: String, esc_char: Option<char>, break_chars: &BTreeSet<char>) -> String {
+/// when space is a breaking char and '\\' the escape char.
+pub fn escape(
+    mut input: String,
+    esc_char: Option<char>,
+    break_chars: &[u8],
+    quote: Quote,
+) -> String {
+    if quote == Quote::Single {
+        return input; // no escape in single quotes
+    }
+    let n = input
+        .bytes()
+        .filter(|b| memchr(*b, break_chars).is_some())
+        .count();
+    if n == 0 {
+        return input; // no need to escape
+    }
     if esc_char.is_none() {
+        if cfg!(windows) && quote == Quote::None {
+            input.insert(0, '"'); // force double quote
+            return input;
+        }
         return input;
     }
     let esc_char = esc_char.unwrap();
-    let n = input
-        .chars()
-        .filter(|c| break_chars.contains(c))
-        .count();
-    if n == 0 {
-        return input;
-    }
     let mut result = String::with_capacity(input.len() + n);
 
     for c in input.chars() {
-        if break_chars.contains(&c) {
+        if c.is_ascii() && memchr(c as u8, break_chars).is_some() {
             result.push(esc_char);
         }
         result.push(c);
@@ -148,11 +261,15 @@
     result
 }
 
-fn filename_complete(path: &str,
-                     esc_char: Option<char>,
-                     break_chars: &BTreeSet<char>)
-                     -> Result<Vec<String>> {
-    use std::env::{current_dir, home_dir};
+fn filename_complete(
+    path: &str,
+    esc_char: Option<char>,
+    break_chars: &[u8],
+    quote: Quote,
+) -> Result<Vec<Pair>> {
+    #[cfg(all(unix, not(any(target_os = "fuchsia"))))]
+    use dirs::home_dir;
+    use std::env::current_dir;
 
     let sep = path::MAIN_SEPARATOR;
     let (dir_name, file_name) = match path.rfind(sep) {
@@ -161,37 +278,68 @@
     };
 
     let dir_path = Path::new(dir_name);
-    let dir = if dir_path.starts_with("~") {
-        // ~[/...]
-        if let Some(home) = home_dir() {
-            match dir_path.strip_prefix("~") {
-                Ok(rel_path) => home.join(rel_path),
-                _ => home,
+    #[cfg(all(unix, not(any(target_os = "fuchsia"))))]
+    let dir = {
+        if dir_path.starts_with("~") {
+            // ~[/...]
+            if let Some(home) = home_dir() {
+                match dir_path.strip_prefix("~") {
+                    Ok(rel_path) => home.join(rel_path),
+                    _ => home,
+                }
+            } else {
+                dir_path.to_path_buf()
+            }
+        } else if dir_path.is_relative() {
+            // TODO ~user[/...] (https://crates.io/crates/users)
+            if let Ok(cwd) = current_dir() {
+                cwd.join(dir_path)
+            } else {
+                dir_path.to_path_buf()
             }
         } else {
             dir_path.to_path_buf()
         }
-    } else if dir_path.is_relative() {
-        // TODO ~user[/...] (https://crates.io/crates/users)
-        if let Ok(cwd) = current_dir() {
-            cwd.join(dir_path)
+    };
+    #[cfg(target_os = "fuchsia")]
+    let dir = {
+        if dir_path.is_relative() {
+            // TODO ~user[/...] (https://crates.io/crates/users)
+            if let Ok(cwd) = current_dir() {
+                cwd.join(dir_path)
+            } else {
+                dir_path.to_path_buf()
+            }
         } else {
             dir_path.to_path_buf()
         }
-    } else {
-        dir_path.to_path_buf()
     };
 
-    let mut entries: Vec<String> = Vec::new();
-    for entry in try!(dir.read_dir()) {
-        let entry = try!(entry);
-        if let Some(s) = entry.file_name().to_str() {
-            if s.starts_with(file_name) {
-                let mut path = String::from(dir_name) + s;
-                if try!(fs::metadata(entry.path())).is_dir() {
-                    path.push(sep);
+    let mut entries: Vec<Pair> = Vec::new();
+
+    // if dir doesn't exist, then don't offer any completions
+    if !dir.exists() {
+        return Ok(entries);
+    }
+
+    // if any of the below IO operations have errors, just ignore them
+    if let Ok(read_dir) = dir.read_dir() {
+        for entry in read_dir {
+            if let Ok(entry) = entry {
+                if let Some(s) = entry.file_name().to_str() {
+                    if s.starts_with(file_name) {
+                        if let Ok(metadata) = fs::metadata(entry.path()) {
+                            let mut path = String::from(dir_name) + s;
+                            if metadata.is_dir() {
+                                path.push(sep);
+                            }
+                            entries.push(Pair {
+                                display: String::from(s),
+                                replacement: escape(path, esc_char, break_chars, quote),
+                            });
+                        } // else ignore PermissionDenied
+                    }
                 }
-                entries.push(escape(path, esc_char, break_chars));
             }
         }
     }
@@ -202,11 +350,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: &[u8],
+) -> (usize, &'l str) {
     let line = &line[..pos];
     if line.is_empty() {
         return (0, line);
@@ -222,7 +371,7 @@
                 break;
             }
         }
-        if break_chars.contains(&c) {
+        if c.is_ascii() && memchr(c as u8, break_chars).is_some() {
             start = Some(i + c.len_utf8());
             if esc_char.is_none() {
                 break;
@@ -236,46 +385,109 @@
     }
 }
 
-pub fn longest_common_prefix(candidates: &[String]) -> Option<&str> {
+pub fn longest_common_prefix<C: Candidate>(candidates: &[C]) -> Option<&str> {
     if candidates.is_empty() {
         return None;
     } else if candidates.len() == 1 {
-        return Some(&candidates[0]);
+        return Some(&candidates[0].replacement());
     }
     let mut longest_common_prefix = 0;
     'o: loop {
         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] {
+            let b1 = c1.replacement().as_bytes();
+            let b2 = candidates[i + 1].replacement().as_bytes();
+            if b1.len() <= longest_common_prefix
+                || b2.len() <= longest_common_prefix
+                || b1[longest_common_prefix] != b2[longest_common_prefix]
+            {
                 break 'o;
             }
         }
         longest_common_prefix += 1;
     }
-    while !candidates[0].is_char_boundary(longest_common_prefix) {
+    let candidate = candidates[0].replacement();
+    while !candidate.is_char_boundary(longest_common_prefix) {
         longest_common_prefix -= 1;
     }
     if longest_common_prefix == 0 {
         return None;
     }
-    Some(&candidates[0][0..longest_common_prefix])
+    Some(&candidate[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, Quote)> {
+    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 == '\\' {
+                    // both windows and unix support escape in double quote
+                    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::EscapeInDoubleQuote == mode {
+        return Some((quote_index, Quote::Double));
+    } else if ScanMode::SingleQuote == mode {
+        return Some((quote_index, Quote::Single));
+    }
+    None
 }
 
 #[cfg(test)]
 mod tests {
-    use std::collections::BTreeSet;
-
     #[test]
     pub fn extract_word() {
-        let break_chars: BTreeSet<char> = super::DEFAULT_BREAK_CHARS.iter().cloned().collect();
+        let break_chars: &[u8] = &super::DEFAULT_BREAK_CHARS;
         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]
@@ -283,20 +495,31 @@
         use std::borrow::Cow::{self, Borrowed, Owned};
         let input = "/usr/local/b";
         assert_eq!(Borrowed(input), super::unescape(input, Some('\\')));
-        let input = "/User\\ Information";
-        let result: Cow<str> = Owned(String::from("/User Information"));
-        assert_eq!(result, super::unescape(input, Some('\\')));
+        if cfg!(windows) {
+            let input = "c:\\users\\All Users\\";
+            let result: Cow<str> = Borrowed(input);
+            assert_eq!(result, super::unescape(input, Some('\\')));
+        } else {
+            let input = "/User\\ Information";
+            let result: Cow<str> = Owned(String::from("/User Information"));
+            assert_eq!(result, super::unescape(input, Some('\\')));
+        }
     }
 
     #[test]
     pub fn escape() {
-        let break_chars: BTreeSet<char> = super::DEFAULT_BREAK_CHARS.iter().cloned().collect();
+        let break_chars: &[u8] = &super::DEFAULT_BREAK_CHARS;
         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, super::Quote::None)
+        );
         let input = String::from("/User Information");
         let result = String::from("/User\\ Information");
-        assert_eq!(result, super::escape(input, Some('\\'), &break_chars));
+        assert_eq!(
+            result,
+            super::escape(input, Some('\\'), &break_chars, super::Quote::None)
+        );
     }
 
     #[test]
@@ -333,4 +556,21 @@
         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, super::Quote::Double)),
+            super::find_unclosed_quote("ls \"User Information")
+        );
+        assert_eq!(
+            None,
+            super::find_unclosed_quote("ls \"/User Information\" /etc")
+        );
+        assert_eq!(
+            Some((0, super::Quote::Double)),
+            super::find_unclosed_quote("\"c:\\users\\All Users\\")
+        )
+    }
 }
diff --git a/src/config.rs b/src/config.rs
index 3be614c..e59a308 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -1,20 +1,29 @@
 //! 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,
+    /// if colors should be enabled.
+    color_mode: ColorMode,
+    /// Whether to use stdout or stderr
+    output_stream: OutputStreamType,
 }
 
 impl Config {
@@ -27,18 +36,38 @@
         self.max_history_size
     }
 
-    /// Tell if lines which match the previous history entry are saved or not in the history list.
+    pub(crate) fn set_max_history_size(&mut self, max_size: usize) {
+        self.max_history_size = max_size;
+    }
+
+    /// 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.
+    pub(crate) fn set_history_ignore_dups(&mut self, yes: bool) {
+        self.history_duplicates = if yes {
+            HistoryDuplicates::IgnoreConsecutive
+        } else {
+            HistoryDuplicates::AlwaysAdd
+        };
+    }
+
+    /// 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
     }
 
+    pub(crate) fn set_history_ignore_space(&mut self, yes: bool) {
+        self.history_ignore_space = yes;
+    }
+
     pub fn completion_type(&self) -> CompletionType {
         self.completion_type
     }
@@ -54,6 +83,32 @@
     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
+    }
+
+    /// Tell if colors should be enabled.
+    ///
+    /// By default, they are except if stdout is not a tty.
+    pub fn color_mode(&self) -> ColorMode {
+        self.color_mode
+    }
+
+    pub(crate) fn set_color_mode(&mut self, color_mode: ColorMode) {
+        self.color_mode = color_mode;
+    }
+
+    pub fn output_stream(&self) -> OutputStreamType {
+        self.output_stream
+    }
+
+    pub(crate) fn set_output_stream(&mut self, stream: OutputStreamType) {
+        self.output_stream = stream;
+    }
 }
 
 impl Default for Config {
@@ -64,8 +119,11 @@
             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,
+            color_mode: ColorMode::Enabled,
+            output_stream: OutputStreamType::Stdout,
         }
     }
 }
@@ -73,6 +131,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 +145,29 @@
     List,
 }
 
+/// Style of editing / Standard keymaps
 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
 pub enum EditMode {
     Emacs,
     Vi,
 }
 
+/// Colorization mode
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum ColorMode {
+    Enabled,
+    Forced,
+    Disabled,
+}
+
+/// Should the editor use stdout or stderr
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum OutputStreamType {
+    Stderr,
+    Stdout,
+}
+
+/// Configuration builder
 #[derive(Debug, Default)]
 pub struct Builder {
     p: Config,
@@ -99,57 +175,85 @@
 
 impl Builder {
     pub fn new() -> Builder {
-        Builder { p: Config::default() }
+        Builder {
+            p: Config::default(),
+        }
     }
 
     /// Set the maximum length for the history.
     pub fn max_history_size(mut self, max_size: usize) -> Builder {
-        self.p.max_history_size = max_size;
+        self.set_max_history_size(max_size);
         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 {
-            HistoryDuplicates::IgnoreConsecutive
-        } else {
-            HistoryDuplicates::AlwaysAdd
-        };
+        self.set_history_ignore_dups(yes);
         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;
+        self.set_history_ignore_space(yes);
         self
     }
 
     /// Set `completion_type`.
     pub fn completion_type(mut self, completion_type: CompletionType) -> Builder {
-        self.p.completion_type = completion_type;
+        self.set_completion_type(completion_type);
         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.set_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.set_keyseq_timeout(keyseq_timeout_ms);
         self
     }
 
     /// Choose between Emacs or Vi mode.
     pub fn edit_mode(mut self, edit_mode: EditMode) -> Builder {
-        self.p.edit_mode = edit_mode;
+        self.set_edit_mode(edit_mode);
+        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.set_auto_add_history(yes);
+        self
+    }
+
+    /// Forces colorization on or off.
+    ///
+    /// By default, colorization is on except if stdout is not a tty.
+    pub fn color_mode(mut self, color_mode: ColorMode) -> Builder {
+        self.set_color_mode(color_mode);
+        self
+    }
+
+    /// Whether to use stdout or stderr.
+    ///
+    /// Be default, use stdout
+    pub fn output_stream(mut self, stream: OutputStreamType) -> Builder {
+        self.set_output_stream(stream);
         self
     }
 
@@ -157,3 +261,79 @@
         self.p
     }
 }
+
+impl Configurer for Builder {
+    fn config_mut(&mut self) -> &mut Config {
+        &mut self.p
+    }
+}
+
+pub trait Configurer {
+    fn config_mut(&mut self) -> &mut Config;
+
+    /// Set the maximum length for the history.
+    fn set_max_history_size(&mut self, max_size: usize) {
+        self.config_mut().set_max_history_size(max_size);
+    }
+
+    /// Tell if lines which match the previous history entry are saved or not
+    /// in the history list.
+    ///
+    /// By default, they are ignored.
+    fn set_history_ignore_dups(&mut self, yes: bool) {
+        self.config_mut().set_history_ignore_dups(yes);
+    }
+
+    /// Tell if lines which begin with a space character are saved or not in
+    /// the history list.
+    ///
+    /// By default, they are saved.
+    fn set_history_ignore_space(&mut self, yes: bool) {
+        self.config_mut().set_history_ignore_space(yes);
+    }
+    /// Set `completion_type`.
+    fn set_completion_type(&mut self, completion_type: CompletionType) {
+        self.config_mut().completion_type = completion_type;
+    }
+
+    /// The number of possible completions that determines when the user is
+    /// asked whether the list of possibilities should be displayed.
+    fn set_completion_prompt_limit(&mut self, completion_prompt_limit: usize) {
+        self.config_mut().completion_prompt_limit = completion_prompt_limit;
+    }
+
+    /// Timeout for ambiguous key sequences in milliseconds.
+    fn set_keyseq_timeout(&mut self, keyseq_timeout_ms: i32) {
+        self.config_mut().keyseq_timeout = keyseq_timeout_ms;
+    }
+
+    /// Choose between Emacs or Vi mode.
+    fn set_edit_mode(&mut self, edit_mode: EditMode) {
+        self.config_mut().edit_mode = edit_mode;
+        match edit_mode {
+            EditMode::Emacs => self.set_keyseq_timeout(-1), // no timeout
+            EditMode::Vi => self.set_keyseq_timeout(500),
+        }
+    }
+
+    /// Tell if lines are automatically added to the history.
+    ///
+    /// By default, they are not.
+    fn set_auto_add_history(&mut self, yes: bool) {
+        self.config_mut().auto_add_history = yes;
+    }
+
+    /// Forces colorization on or off.
+    ///
+    /// By default, colorization is on except if stdout is not a tty.
+    fn set_color_mode(&mut self, color_mode: ColorMode) {
+        self.config_mut().set_color_mode(color_mode);
+    }
+
+    /// Whether to use stdout or stderr
+    ///
+    /// By default, use stdout
+    fn set_output_stream(&mut self, stream: OutputStreamType) {
+        self.config_mut().set_output_stream(stream);
+    }
+}
diff --git a/src/edit.rs b/src/edit.rs
new file mode 100644
index 0000000..7d706f5
--- /dev/null
+++ b/src/edit.rs
@@ -0,0 +1,560 @@
+//! 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 highlight::Highlighter;
+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>,
+    pub highlighter: Option<&'out Highlighter>,
+    no_hint: bool, // `false` if an hint has been displayed
+}
+
+impl<'out, 'prompt> State<'out, 'prompt> {
+    pub fn new(
+        out: &'out mut Renderer,
+        prompt: &'prompt str,
+        history_index: usize,
+        hinter: Option<&'out Hinter>,
+        highlighter: Option<&'out Highlighter>,
+    ) -> 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,
+            highlighter,
+            no_hint: true,
+        }
+    }
+
+    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(());
+        }
+        if self.highlighter.map_or(false, |h| {
+            self.line
+                .grapheme_at_cursor()
+                .map_or(false, |s| h.highlight_char(s))
+        }) {
+            let prompt_size = self.prompt_size;
+            try!(self.refresh(self.prompt, prompt_size, None));
+        } else {
+            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.highlighter,
+        ));
+
+        self.cursor = cursor;
+        self.old_rows = end_pos.row;
+        Ok(())
+    }
+
+    fn hint(&mut self) -> Option<String> {
+        if let Some(hinter) = self.hinter {
+            self.no_hint = false;
+            hinter.hint(self.line.as_str(), self.line.pos())
+        } else {
+            self.no_hint = true;
+            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 no_previous_hint = self.no_hint;
+                let hint = self.hint();
+                if n == 1
+                    && self.cursor.col + ch.width().unwrap_or(0) < self.out.get_columns()
+                    && (hint.is_none() && no_previous_hint) // TODO refresh only current line
+                    && !self.highlighter.map_or(true, |h| h.highlight_char(ch.encode_utf8(&mut self.byte_buffer)))
+                {
+                    // 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() {
+            return Ok(());
+        }
+        let cursor = self.line.pos();
+        self.line.insert_str(cursor, text);
+        self.refresh_line()
+    }
+
+    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,
+        highlighter: None,
+        no_hint: true,
+    }
+}
+
+#[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 d778960..f096521 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -1,17 +1,16 @@
 //! 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(all(unix, not(any(target_os = "fuchsia"))))]
 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
+/// #[non_exhaustive]
 #[derive(Debug)]
 pub enum ReadlineError {
     /// I/O Error
@@ -22,7 +21,7 @@
     Interrupted,
     /// Chars Error
     #[cfg(unix)]
-    Char(char_iter::CharsError),
+    Utf8Error,
     /// Unix Error from syscall
     #[cfg(all(unix, not(any(target_os = "fuchsia"))))]
     Errno(nix::Error),
@@ -39,9 +38,9 @@
             ReadlineError::Eof => write!(f, "EOF"),
             ReadlineError::Interrupted => write!(f, "Interrupted"),
             #[cfg(unix)]
-            ReadlineError::Char(ref err) => err.fmt(f),
+            ReadlineError::Utf8Error => write!(f, "invalid utf-8: corrupt contents"),
             #[cfg(all(unix, not(any(target_os = "fuchsia"))))]
-            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)]
@@ -57,9 +56,9 @@
             ReadlineError::Eof => "EOF",
             ReadlineError::Interrupted => "Interrupted",
             #[cfg(unix)]
-            ReadlineError::Char(ref err) => err.description(),
+            ReadlineError::Utf8Error => "invalid utf-8: corrupt contents",
             #[cfg(all(unix, not(any(target_os = "fuchsia"))))]
-            ReadlineError::Errno(ref err) => err.errno().desc(),
+            ReadlineError::Errno(ref err) => err.description(),
             #[cfg(windows)]
             ReadlineError::WindowResize => "WindowResize",
             #[cfg(windows)]
@@ -81,13 +80,6 @@
     }
 }
 
-#[cfg(unix)]
-impl From<char_iter::CharsError> for ReadlineError {
-    fn from(err: char_iter::CharsError) -> ReadlineError {
-        ReadlineError::Char(err)
-    }
-}
-
 #[cfg(windows)]
 impl From<char::DecodeUtf16Error> for ReadlineError {
     fn from(err: char::DecodeUtf16Error) -> ReadlineError {
diff --git a/src/highlight.rs b/src/highlight.rs
new file mode 100644
index 0000000..e8a9742
--- /dev/null
+++ b/src/highlight.rs
@@ -0,0 +1,64 @@
+//! Syntax highlighting
+
+use config::CompletionType;
+use std::borrow::Cow::{self, Borrowed};
+
+/// Syntax highlighter with [ansi color](https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters).
+/// Rustyline will try to handle escape sequence for ansi color on windows
+/// when not supported natively (windows <10).
+///
+/// Currently, the highlighted version *must* have the same display width as
+/// the original input.
+pub trait Highlighter {
+    /// Takes the currently edited `line` with the cursor `pos`ition and
+    /// returns the highlighted version (with ANSI color).
+    ///
+    /// For example, you can implement
+    /// [blink-matching-paren](https://www.gnu.org/software/bash/manual/html_node/Readline-Init-File-Syntax.html).
+    fn highlight<'l>(&self, line: &'l str, pos: usize) -> Cow<'l, str> {
+        let _ = pos;
+        Borrowed(line)
+    }
+    /// Takes the `prompt` and
+    /// returns the highlighted version (with ANSI color).
+    fn highlight_prompt<'p>(&self, prompt: &'p str) -> Cow<'p, str> {
+        Borrowed(prompt)
+    }
+    /// Takes the dynamic `prompt` and
+    /// returns the highlighted version (with ANSI color).
+    #[deprecated(
+        since = "2.0.1",
+        note = "please use `highlight_prompt` instead"
+    )]
+    fn highlight_dynamic_prompt<'p>(&self, prompt: &'p str) -> Cow<'p, str> {
+        Borrowed(prompt)
+    }
+    /// Takes the `hint` and
+    /// returns the highlighted version (with ANSI color).
+    fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
+        Borrowed(hint)
+    }
+    /// Takes the completion `canditate` and
+    /// returns the highlighted version (with ANSI color).
+    ///
+    /// Currently, used only with `CompletionType::List`.
+    fn highlight_candidate<'c>(
+        &self,
+        candidate: &'c str,
+        completion: CompletionType,
+    ) -> Cow<'c, str> {
+        let _ = completion;
+        Borrowed(candidate)
+    }
+    /// Tells if the `ch`ar needs to be highlighted when typed or when cursor
+    /// is moved under.
+    ///
+    /// Used to optimize refresh when a character is inserted or the cursor is
+    /// moved.
+    fn highlight_char(&self, grapheme: &str) -> bool {
+        let _ = grapheme;
+        false
+    }
+}
+
+impl Highlighter for () {}
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..dac738b 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,
@@ -23,20 +24,21 @@
 pub struct History {
     entries: VecDeque<String>,
     max_len: usize,
-    ignore_space: bool,
-    ignore_dups: bool,
+    pub(crate) ignore_space: bool,
+    pub(crate) ignore_dups: bool,
 }
 
 impl History {
     pub fn new() -> History {
         Self::with_config(Config::default())
     }
+
     pub fn with_config(config: Config) -> History {
         History {
             entries: VecDeque::new(),
             max_len: config.max_history_size(),
-            ignore_space: config.history_duplicates() == HistoryDuplicates::IgnoreConsecutive,
-            ignore_dups: config.history_ignore_space(),
+            ignore_space: config.history_ignore_space(),
+            ignore_dups: config.history_duplicates() == HistoryDuplicates::IgnoreConsecutive,
         }
     }
 
@@ -55,12 +57,13 @@
         if self.max_len == 0 {
             return false;
         }
-        if line.as_ref().is_empty() ||
-           (self.ignore_space &&
-            line.as_ref()
+        if line.as_ref().is_empty()
+            || (self.ignore_space && line
+                .as_ref()
                 .chars()
                 .next()
-                .map_or(true, |c| c.is_whitespace())) {
+                .map_or(true, |c| c.is_whitespace()))
+        {
             return false;
         }
         if self.ignore_dups {
@@ -77,19 +80,23 @@
         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 +112,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 +132,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 +157,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)
@@ -195,8 +215,8 @@
 }
 
 impl<'a> IntoIterator for &'a History {
-    type Item = &'a String;
     type IntoIter = Iter<'a>;
+    type Item = &'a String;
 
     fn into_iter(self) -> Iter<'a> {
         self.iter()
@@ -254,9 +274,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 +309,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..50f50cd 100644
--- a/src/keymap.rs
+++ b/src/keymap.rs
@@ -1,74 +1,118 @@
 //! 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 keys::KeyPress;
 use tty::RawReader;
-use super::Result;
 
+/// The number of times one command should be repeated.
 pub type RepeatCount = usize;
 
+/// Commands
+/// #[non_exhaustive]
 #[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,
         }
     }
+
     fn is_repeatable(&self) -> bool {
         match *self {
             Cmd::Move(_) => true,
@@ -76,16 +120,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 +169,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 +188,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
@@ -134,8 +207,8 @@
 }
 
 impl CharSearch {
-    fn opposite(&self) -> CharSearch {
-        match *self {
+    fn opposite(self) -> CharSearch {
+        match self {
             CharSearch::Forward(c) => CharSearch::Backward(c),
             CharSearch::ForwardBefore(c) => CharSearch::BackwardAfter(c),
             CharSearch::Backward(c) => CharSearch::Forward(c),
@@ -144,21 +217,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 +253,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 +262,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 +316,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,38 +349,52 @@
             _ => 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) => {
@@ -286,10 +420,8 @@
                     Cmd::Move(Movement::BackwardChar(n))
                 }
             }
-            KeyPress::Ctrl('G') |
-            KeyPress::Esc => Cmd::Abort,
-            KeyPress::Ctrl('H') |
-            KeyPress::Backspace => {
+            KeyPress::Ctrl('G') | KeyPress::Esc | KeyPress::Meta('\x07') => Cmd::Abort,
+            KeyPress::Ctrl('H') | KeyPress::Backspace => {
                 if positive {
                     Cmd::Kill(Movement::BackwardChar(n))
                 } else {
@@ -307,8 +439,15 @@
             KeyPress::Ctrl('L') => Cmd::ClearScreen,
             KeyPress::Ctrl('N') => Cmd::NextHistory,
             KeyPress::Ctrl('P') => Cmd::PreviousHistory,
-            KeyPress::Meta('\x08') |
-            KeyPress::Meta('\x7f') => {
+            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 {
@@ -317,110 +456,125 @@
             }
             KeyPress::Meta('<') => Cmd::BeginningOfHistory,
             KeyPress::Meta('>') => Cmd::EndOfHistory,
-            KeyPress::Meta('B') => {
+            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') => Cmd::CapitalizeWord,
-            KeyPress::Meta('D') => {
+            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') | 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('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());
-            match key {
-                KeyPress::Char(digit @ '0'...'9') => {
-                    self.num_args = self.num_args
+            try!(wrt.refresh_prompt_and_line(&format!("(arg: {}) ", self.num_args)));
+            let key = try!(rdr.next_key(false));
+            if let KeyPress::Char(digit @ '0'...'9') = key {
+                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),
+            } else {
+                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 +585,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 +605,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 +619,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 +669,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 +681,139 @@
         };
         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 +852,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 +874,7 @@
                 }
             }
             KeyPress::Ctrl('Z') => Cmd::Suspend,
+            KeyPress::Ctrl('_') => Cmd::Undo(n),
             KeyPress::UnknownEscSeq => Cmd::Noop,
             _ => Cmd::Unknown,
         }
@@ -739,26 +910,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/consts.rs b/src/keys.rs
similarity index 69%
rename from src/consts.rs
rename to src/keys.rs
index aadac84..c355e5a 100644
--- a/src/consts.rs
+++ b/src/keys.rs
@@ -1,34 +1,46 @@
 //! Key constants
 
+/// #[non_exhaustive]
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
 pub enum KeyPress {
     UnknownEscSeq,
-    Backspace,
+    Backspace, // Ctrl('H')
+    BackTab,
     Char(char),
+    ControlDown,
+    ControlLeft,
+    ControlRight,
+    ControlUp,
     Ctrl(char),
     Delete,
     Down,
     End,
     Enter, // Ctrl('M')
-    Esc,
+    Esc,   // Ctrl('[')
+    F(u8),
     Home,
+    Insert,
     Left,
     Meta(char),
     Null,
     PageDown,
     PageUp,
     Right,
+    ShiftDown,
+    ShiftLeft,
+    ShiftRight,
+    ShiftUp,
     Tab, // Ctrl('I')
     Up,
 }
 
-#[allow(match_same_arms)]
+//#[allow(clippy::match_same_arms)]
 pub fn char_to_key_press(c: char) -> KeyPress {
     if !c.is_control() {
         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 +49,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 +63,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/kill_ring.rs b/src/kill_ring.rs
index 635cb4f..03a202f 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,
         }
     }
 
@@ -38,34 +41,31 @@
 
     /// Add `text` to the kill-ring.
     pub fn kill(&mut self, text: &str, dir: Mode) {
-        match self.last_action {
-            Action::Kill => {
-                if self.slots.capacity() == 0 {
-                    // disabled
-                    return;
-                }
-                match dir {
-                    Mode::Append => self.slots[self.index].push_str(text),
-                    Mode::Prepend => self.slots[self.index].insert_str(0, text),
-                };
+        if let Action::Kill = self.last_action {
+            if self.slots.capacity() == 0 {
+                // disabled
+                return;
             }
-            _ => {
-                self.last_action = Action::Kill;
-                if self.slots.capacity() == 0 {
-                    // disabled
-                    return;
-                }
-                if self.index == self.slots.capacity() - 1 {
-                    // full
-                    self.index = 0;
-                } else if !self.slots.is_empty() {
-                    self.index += 1;
-                }
-                if self.index == self.slots.len() {
-                    self.slots.push(String::from(text))
-                } else {
-                    self.slots[self.index] = String::from(text);
-                }
+            match dir {
+                Mode::Append => self.slots[self.index].push_str(text),
+                Mode::Prepend => self.slots[self.index].insert_str(0, text),
+            };
+        } else {
+            self.last_action = Action::Kill;
+            if self.slots.capacity() == 0 {
+                // disabled
+                return;
+            }
+            if self.index == self.slots.capacity() - 1 {
+                // full
+                self.index = 0;
+            } else if !self.slots.is_empty() {
+                self.index += 1;
+            }
+            if self.index == self.slots.len() {
+                self.slots.push(String::from(text))
+            } else {
+                self.slots[self.index] = String::from(text);
             }
         }
     }
@@ -102,9 +102,30 @@
     }
 }
 
+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 +208,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 +223,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 133b569..cb1d284 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,20 +11,26 @@
 //! 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)]
+// #![feature(non_exhaustive)]
+// #![feature(tool_lints)]
 
+#[cfg(all(unix, not(any(target_os = "fuchsia"))))]
+extern crate dirs;
 extern crate libc;
-extern crate encode_unicode;
 #[macro_use]
 extern crate log;
-extern crate unicode_segmentation;
-extern crate unicode_width;
+extern crate memchr;
 #[cfg(all(unix, not(any(target_os = "fuchsia"))))]
 extern crate nix;
+extern crate unicode_segmentation;
+extern crate unicode_width;
+#[cfg(unix)]
+extern crate utf8parse;
 #[cfg(windows)]
 extern crate winapi;
 #[cfg(windows)]
@@ -34,688 +41,155 @@
 extern crate fuchsia_device;
 
 pub mod completion;
-mod consts;
+pub mod config;
+mod edit;
 pub mod error;
+pub mod highlight;
+pub mod hint;
 pub mod history;
 mod keymap;
+mod keys;
 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 completion::{longest_common_prefix, Candidate, Completer};
+pub use config::{ColorMode, CompletionType, Config, EditMode, HistoryDuplicates};
+use edit::State;
+use highlight::Highlighter;
+use hint::Hinter;
 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};
-pub use config::{CompletionType, Config, EditMode, HistoryDuplicates};
-pub use consts::KeyPress;
+use keymap::{InputState, Refresher};
+pub use keys::KeyPress;
+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,
+    highlighter: Option<&Highlighter>,
+    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 {
             // Show completion or original buffer
             if i < candidates.len() {
-                completer.update(&mut s.line, start, &candidates[i]);
+                let candidate = candidates[i].replacement();
+                // TODO we can't highlight the line buffer directly
+                /*let candidate = if let Some(highlighter) = s.highlighter {
+                    highlighter.highlight_candidate(candidate, CompletionType::Circular)
+                } else {
+                    Borrowed(candidate)
+                };*/
+                completer.update(&mut s.line, start, candidate);
                 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;
                 }
             }
         }
         Ok(Some(cmd))
     } else if CompletionType::List == config.completion_type() {
-        // beep if ambiguous
-        if candidates.len() > 1 {
-            try!(beep());
-        }
         if let Some(lcp) = longest_common_prefix(&candidates) {
-            // if we can extend the item, extend it and return to main loop
+            // if we can extend the item, extend it
             if lcp.len() > s.line.pos() - start {
                 completer.update(&mut s.line, start, lcp);
                 try!(s.refresh_line());
-                return Ok(None);
             }
         }
+        // beep if ambiguous
+        if candidates.len() > 1 {
+            try!(s.out.beep());
+        } else {
+            return Ok(None);
+        }
         // 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, highlighter, &candidates)
         } else {
             try!(s.refresh_line());
             Ok(None)
@@ -725,60 +199,71 @@
     }
 }
 
-fn page_completions<R: RawReader>(rdr: &mut R,
-                                  s: &mut State,
-                                  candidates: &[String])
-                                  -> Result<Option<Cmd>> {
+fn page_completions<R: RawReader, C: Candidate>(
+    rdr: &mut R,
+    s: &mut State,
+    input_state: &mut InputState,
+    highlighter: Option<&Highlighter>,
+    candidates: &[C],
+) -> 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.display().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 {
             let i = (col * num_rows) + row;
             if i < candidates.len() {
-                let candidate = &candidates[i];
-                ab.push_str(candidate);
-                let width = candidate.as_str().width();
+                let candidate = &candidates[i].display();
+                let width = candidate.width();
+                if let Some(highlighter) = highlighter {
+                    ab.push_str(&highlighter.highlight_candidate(candidate, CompletionType::List));
+                } else {
+                    ab.push_str(candidate);
+                }
                 if ((col + 1) * num_rows) + row < candidates.len() {
                     for _ in width..max_width {
                         ab.push(' ');
@@ -786,23 +271,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;
@@ -819,7 +308,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 {
@@ -848,10 +337,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,
             }
         }
@@ -866,43 +360,69 @@
             _ => false,
         };
     }
+    s.changes.borrow_mut().end();
     Ok(Some(cmd))
 }
 
 /// Handles reading and editting the readline buffer.
 /// 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();
+    let hinter = editor.helper.as_ref().map(|h| h as &Hinter);
+    let highlighter = if editor.term.colors_enabled() {
+        editor.helper.as_ref().map(|h| h as &Highlighter)
+    } else {
+        None
+    };
 
     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,
+        highlighter,
+    );
+    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(),
+                highlighter,
+                &editor.config,
+            ));
             if next.is_some() {
                 cmd = next.unwrap();
             } else {
@@ -911,16 +431,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 {
@@ -931,164 +456,151 @@
         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::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::Replace(n, c) => {
-                try!(edit_replace_char(&mut s, c, n));
+            Cmd::Overwrite(c) => {
+                try!(s.edit_overwrite_char(c));
             }
             Cmd::EndOfFile => {
-                if !s.edit_state.is_emacs_mode() && !s.line.is_empty() {
-                    try!(edit_move_end(&mut s));
+                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!(edit_delete(&mut s, 1))
+                    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) => {
+            Cmd::ViYankTo(ref mvt) => {
                 if let Some(text) = s.line.copy(mvt) {
-                    editor.kill_ring.kill(&text, Mode::Append)
+                    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);
@@ -1097,7 +609,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;
             }
@@ -1107,13 +619,16 @@
             }
         }
     }
+    if cfg!(windows) {
+        let _ = original_mode; // silent warning
+    }
     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();
@@ -1122,12 +637,25 @@
 
 /// 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!("");
+    editor
+        .term
+        .create_writer()
+        .write_and_flush(b"\n")
+        .unwrap();
     user_input
 }
 
@@ -1140,40 +668,78 @@
     }
 }
 
-/// 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.
+/// (parse current line once)
+pub trait Helper
+where
+    Self: Completer,
+    Self: Hinter,
+    Self: Highlighter,
+{
 }
 
-impl<C: Completer> Editor<C> {
-    pub fn new() -> Editor<C> {
+impl Helper for () {}
+
+/// 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>>>,
+}
+
+//#[allow(clippy::new_without_default)]
+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> {
-        let term = Terminal::new();
+    /// Create an editor with a specific configuration.
+    pub fn with_config(config: Config) -> Editor<H> {
+        let term = Terminal::new(config.color_mode(), config.output_stream());
         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() {
@@ -1181,7 +747,7 @@
             // Not a tty: read from file / pipe.
             readline_direct()
         } else {
-            readline_raw(prompt, self)
+            readline_raw(prompt, initial, self)
         }
     }
 
@@ -1189,39 +755,58 @@
     pub fn load_history<P: AsRef<Path> + ?Sized>(&mut self, path: &P) -> Result<()> {
         self.history.load(path)
     }
+
     /// Save the history in the specified file.
     pub fn save_history<P: AsRef<Path> + ?Sized>(&self, path: &P) -> Result<()> {
         self.history.save(path)
     }
+
     /// Add a new entry in the history.
     pub fn add_history_entry<S: AsRef<str> + Into<String>>(&mut self, line: S) -> bool {
         self.history.add(line)
     }
+
     /// Clear history.
     pub fn clear_history(&mut self) {
         self.history.clear()
     }
+
     /// Return a mutable reference to the history object.
-    pub fn get_history(&mut self) -> &mut History {
+    pub fn history_mut(&mut self) -> &mut History {
         &mut self.history
     }
+
     /// Return an immutable reference to the history object.
-    pub fn get_history_const(&self) -> &History {
+    pub fn history(&self) -> &History {
         &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;
+    }
+
+    /// Return a mutable reference to the helper.
+    pub fn helper_mut(&mut self) -> Option<&mut H> {
+        self.helper.as_mut()
+    }
+
+    /// Return an immutable reference to the helper.
+    pub fn helper(&self) -> Option<&H> {
+        self.helper.as_ref()
     }
 
     /// 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)
     }
 
     /// ```
@@ -1230,23 +815,54 @@
     ///     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> config::Configurer for Editor<H> {
+    fn config_mut(&mut self) -> &mut Config {
+        &mut self.config
+    }
+
+    fn set_max_history_size(&mut self, max_size: usize) {
+        self.config_mut().set_max_history_size(max_size);
+        self.history.set_max_len(max_size);
+    }
+
+    fn set_history_ignore_dups(&mut self, yes: bool) {
+        self.config_mut().set_history_ignore_dups(yes);
+        self.history.ignore_dups = yes;
+    }
+
+    fn set_history_ignore_space(&mut self, yes: bool) {
+        self.config_mut().set_history_ignore_space(yes);
+        self.history.ignore_space = yes;
+    }
+
+    fn set_color_mode(&mut self, color_mode: ColorMode) {
+        self.config_mut().set_color_mode(color_mode);
+        self.term.color_mode = color_mode;
+    }
+}
+
+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)
@@ -1255,201 +871,85 @@
     }
 }
 
-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),
         }
     }
 }
 
-#[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()))),
+enum StdStream {
+    Stdout(io::Stdout),
+    Stderr(io::Stderr),
+}
+impl StdStream {
+    fn from_stream_type(t: config::OutputStreamType) -> StdStream {
+        match t {
+            config::OutputStreamType::Stderr => StdStream::Stderr(io::stderr()),
+            config::OutputStreamType::Stdout => StdStream::Stdout(io::stdout()),
         }
     }
-
-    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], "");
-    }
 }
+#[cfg(unix)]
+impl std::os::unix::io::AsRawFd for StdStream {
+    fn as_raw_fd(&self) -> std::os::unix::io::RawFd {
+        match self {
+            StdStream::Stdout(e) => e.as_raw_fd(),
+            StdStream::Stderr(e) => e.as_raw_fd(),
+        }
+    }
+}
+impl io::Write for StdStream {
+    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+        match self {
+            StdStream::Stdout(ref mut e) => e.write(buf),
+            StdStream::Stderr(ref mut e) => e.write(buf),
+        }
+    }
+
+    fn flush(&mut self) -> io::Result<()> {
+        match self {
+            StdStream::Stdout(ref mut e) => e.flush(),
+            StdStream::Stderr(ref mut e) => e.flush(),
+        }
+    }
+
+    fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
+        match self {
+            StdStream::Stdout(ref mut e) => e.write_all(buf),
+            StdStream::Stderr(ref mut e) => e.write_all(buf),
+        }
+    }
+
+    fn write_fmt(&mut self, fmt: std::fmt::Arguments) -> io::Result<()> {
+        match self {
+            StdStream::Stdout(ref mut e) => e.write_fmt(fmt),
+            StdStream::Stderr(ref mut e) => e.write_fmt(fmt),
+        }
+    }
+
+    fn by_ref(&mut self) -> &mut StdStream {
+        self
+    }
+}
+
+#[cfg(test)]
+#[macro_use]
+extern crate assert_matches;
+#[cfg(test)]
+mod test;
diff --git a/src/line_buffer.rs b/src/line_buffer.rs
index 80699e2..de80a55 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;
+pub(crate) 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(crate) enum Direction {
+    Forward,
+    Backward,
+}
+
+impl Default for Direction {
+    fn default() -> Direction {
+        Direction::Forward
+    }
+}
+
+/// Listener to be notified when some text is deleted.
+pub(crate) 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(crate) 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,36 @@
         LineBuffer {
             buf: String::with_capacity(capacity),
             pos: 0,
+            dl: None,
+            cl: None,
         }
     }
 
     #[cfg(test)]
-    pub fn init(line: &str, pos: usize) -> LineBuffer {
+    pub(crate) 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(crate) fn set_delete_listener(&mut self, dl: Arc<Mutex<DeleteListener>>) {
+        self.dl = Some(dl);
+    }
+
+    pub(crate) fn set_change_listener(&mut self, dl: Rc<RefCell<ChangeListener>>) {
+        self.cl = Some(dl);
+    }
+
+    pub(crate) 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 +116,8 @@
     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;
@@ -59,6 +127,7 @@
     pub fn len(&self) -> usize {
         self.buf.len()
     }
+
     /// Returns `true` if this buffer has a length of zero.
     pub fn is_empty(&self) -> bool {
         self.buf.is_empty()
@@ -67,30 +136,24 @@
     /// 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> {
+    pub(crate) fn grapheme_at_cursor(&self) -> Option<&str> {
         if self.pos == self.buf.len() {
             None
         } else {
@@ -98,7 +161,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 +173,9 @@
             .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 +198,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 +221,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 +234,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 +283,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 +302,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,27 +361,22 @@
         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 {
-                    Some((j, y)) => {
-                        let gi = gis.next();
-                        match gi {
-                            Some((_, x)) => {
-                                if is_start_of_word(word_def, x, y) {
-                                    sow = j;
-                                    break 'inner;
-                                }
-                                gj = gi;
-                            }
-                            None => {
-                                break 'outer;
-                            }
+                if let Some((j, y)) = gj {
+                    let gi = gis.next();
+                    if let Some((_, x)) = gi {
+                        if is_start_of_word(word_def, x, y) {
+                            sow = j;
+                            break 'inner;
                         }
-                    }
-                    None => {
+                        gj = gi;
+                    } else {
                         break 'outer;
                     }
+                } else {
+                    break 'outer;
                 }
             }
         }
@@ -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,41 +412,36 @@
         }
         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 {
-                    Some((i, x)) => {
-                        let gj = gis.next();
-                        match gj {
-                            Some((j, y)) => {
-                                if at == At::Start && is_start_of_word(word_def, x, y) {
-                                    wp = j;
-                                    break 'inner;
-                                } else if at != At::Start && is_end_of_word(word_def, x, y) {
-                                    if word_def == Word::Emacs || at == At::AfterEnd {
-                                        wp = j;
-                                    } else {
-                                        wp = i;
-                                    }
-                                    break 'inner;
-                                }
-                                gi = gj;
+                if let Some((i, x)) = gi {
+                    let gj = gis.next();
+                    if let Some((j, y)) = gj {
+                        if at == At::Start && is_start_of_word(word_def, x, y) {
+                            wp = j;
+                            break 'inner;
+                        } else if at != At::Start && is_end_of_word(word_def, x, y) {
+                            if word_def == Word::Emacs || at == At::AfterEnd {
+                                wp = j;
+                            } else {
+                                wp = i;
                             }
-                            None => {
-                                break 'outer;
-                            }
+                            break 'inner;
                         }
-                    }
-                    None => {
+                        gi = gj;
+                    } else {
                         break 'outer;
                     }
+                } else {
+                    break 'outer;
                 }
             }
         }
@@ -406,21 +469,17 @@
         }
     }
 
-    fn search_char_pos(&self, cs: &CharSearch, n: RepeatCount) -> Option<usize> {
+    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) => {
+        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) => {
                 if let Some(cc) = self.grapheme_at_cursor() {
                     shift = self.pos + cc.len();
                     if shift < self.buf.len() {
@@ -439,26 +498,27 @@
             }
         };
         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()
-                     }
-                 })
+            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()
+                }
+            })
         } 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) {
+        if let Some(pos) = self.search_char_pos(cs, n) {
             self.pos = pos;
             true
         } else {
@@ -468,34 +528,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),
+            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
         }
     }
 
@@ -510,6 +576,7 @@
             .next()
             .map(|i| i + self.pos)
     }
+
     /// Alter the next word.
     pub fn edit_word(&mut self, a: WordAction) -> bool {
         if let Some(start) = self.skip_whitespace() {
@@ -517,7 +584,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 +619,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,32 +637,67 @@
     /// 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())
+                    Some(self.buf[..self.pos].to_owned())
                 }
             }
             Movement::ViFirstPrint => {
@@ -607,59 +713,111 @@
                 if self.pos == self.buf.len() {
                     None
                 } else {
-                    Some(self.buf[self.pos..].to_string())
+                    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
                 }
             }
             Movement::ViCharSearch(n, cs) => {
                 let search_result = match cs {
-                    CharSearch::ForwardBefore(c) => {
-                        self.search_char_pos(&CharSearch::Forward(c), n)
-                    }
-                    _ => self.search_char_pos(&cs, n),
+                    CharSearch::ForwardBefore(c) => self.search_char_pos(CharSearch::Forward(c), n),
+                    _ => self.search_char_pos(cs, n),
                 };
                 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())
+                    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_string())
+                    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
+    }
 }
 
 impl Deref for LineBuffer {
@@ -670,73 +828,89 @@
     }
 }
 
-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 +937,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 +947,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 +956,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 +980,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 +993,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 +1050,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 +1097,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 +1113,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 +1131,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 +1144,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 +1186,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 +1214,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 +1230,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 +1239,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 +1267,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 +1283,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 +1371,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..29bde74
--- /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 error::ReadlineError;
+use keys::KeyPress;
+
+#[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..a4c738e
--- /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 keys::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..dc3f8f0
--- /dev/null
+++ b/src/test/history.rs
@@ -0,0 +1,206 @@
+//! History related commands tests
+use super::assert_history;
+use config::EditMode;
+use keys::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..53ac34c
--- /dev/null
+++ b/src/test/mod.rs
@@ -0,0 +1,112 @@
+use std::collections::HashMap;
+use std::sync::{Arc, RwLock};
+
+use super::{Editor, Result, StdStream};
+use completion::Completer;
+use config::{Config, EditMode, OutputStreamType};
+use edit::init_state;
+use keymap::{Cmd, InputState};
+use keys::KeyPress;
+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 {
+    type Candidate = String;
+
+    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,
+        None,
+        &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], "");
+    }
+}
+
+#[test]
+fn from_stream_type() {
+    StdStream::from_stream_type(OutputStreamType::Stdout);
+}
diff --git a/src/test/vi_cmd.rs b/src/test/vi_cmd.rs
new file mode 100644
index 0000000..d8ec793
--- /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 keys::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..fa3c881
--- /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 keys::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 f7ce051..48f917c 100644
--- a/src/tty/mod.rs
+++ b/src/tty/mod.rs
@@ -1,49 +1,202 @@
 //! This module implements and describes common TTY methods & traits
-use std::io::Write;
+use std::io::{self, Write};
+use unicode_segmentation::UnicodeSegmentation;
+use unicode_width::UnicodeWidthStr;
+
+use config::{ColorMode, Config, OutputStreamType};
+use highlight::Highlighter;
+use keys::KeyPress;
+use line_buffer::LineBuffer;
 use Result;
-use config::Config;
-use consts::KeyPress;
 
 /// 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,
+        highlighter: Option<&Highlighter>,
+    ) -> 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,
+        highlighter: Option<&Highlighter>,
+    ) -> Result<(Position, Position)> {
+        (**self).refresh_line(
+            prompt,
+            prompt_size,
+            line,
+            hint,
+            current_row,
+            old_rows,
+            highlighter,
+        )
+    }
+
+    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(color_mode: ColorMode, stream: OutputStreamType) -> 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;
+    /// Check if output supports colors.
+    fn colors_enabled(&self) -> bool;
     /// Enable RAW mode for the terminal.
-    fn enable_raw_mode(&self) -> Result<Self::Mode>;
+    fn enable_raw_mode(&mut 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..8ea87a4 100644
--- a/src/tty/test.rs
+++ b/src/tty/test.rs
@@ -1,17 +1,15 @@
 //! 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 config::Config;
-use consts::KeyPress;
+use super::{truncate, Position, RawMode, RawReader, Renderer, Term};
+use config::{ColorMode, Config, OutputStreamType};
 use error::ReadlineError;
+use highlight::Highlighter;
+use keys::KeyPress;
+use line_buffer::LineBuffer;
 use Result;
-use super::{RawMode, RawReader, Term};
 
 pub type Mode = ();
 
@@ -22,12 +20,13 @@
 }
 
 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),
         }
     }
+
     #[cfg(unix)]
     fn next_char(&mut self) -> Result<char> {
         unimplemented!();
@@ -35,114 +34,135 @@
 }
 
 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),
         }
     }
+
     #[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,
+        _: Option<&Highlighter>,
+    ) -> 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
+    pub color_mode: ColorMode,
 }
 
 impl Term for DummyTerminal {
+    type Mode = Mode;
     type Reader = IntoIter<KeyPress>;
     type Writer = Sink;
-    type Mode = Mode;
 
-    fn new() -> DummyTerminal {
-        DummyTerminal { keys: Vec::new() }
+    fn new(color_mode: ColorMode, _stream: OutputStreamType) -> DummyTerminal {
+        DummyTerminal {
+            keys: Vec::new(),
+            cursor: 0,
+            color_mode: color_mode,
+        }
     }
 
     // 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 {
+    fn colors_enabled(&self) -> bool {
         false
     }
 
-    fn enable_raw_mode(&self) -> Result<Mode> {
+    // Interactive loop:
+
+    fn enable_raw_mode(&mut 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..ffcbc97 100644
--- a/src/tty/unix.rs
+++ b/src/tty/unix.rs
@@ -1,33 +1,44 @@
 //! Unix specific definitions
 use std;
-use std::io::{self, Read, Stdout, Write};
+use std::io::{self, Read, Write};
+use std::os::unix::io::AsRawFd;
 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 utf8parse::{Parser, Receiver};
 
-use char_iter;
-use config::Config;
-use consts::{self, KeyPress};
-use Result;
+use super::{truncate, width, Position, RawMode, RawReader, Renderer, Term};
+use config::{ColorMode, Config, OutputStreamType};
 use error;
-use super::{RawMode, RawReader, Term};
+use highlight::Highlighter;
+use keys::{self, KeyPress};
+use line_buffer::LineBuffer;
+use Result;
+use StdStream;
 
 const STDIN_FILENO: libc::c_int = libc::STDIN_FILENO;
 const STDOUT_FILENO: libc::c_int = libc::STDOUT_FILENO;
+const STDERR_FILENO: libc::c_int = libc::STDERR_FILENO;
 
 /// Unsupported Terminals that don't support RAW mode
 static UNSUPPORTED_TERM: [&'static str; 3] = ["dumb", "cons25", "emacs"];
 
-fn get_win_size() -> (usize, usize) {
+//#[allow(clippy::identity_conversion)]
+fn get_win_size<T: AsRawFd + ?Sized>(fileno: &T) -> (usize, usize) {
     use std::mem::zeroed;
 
     unsafe {
         let mut size: libc::winsize = zeroed();
-        match libc::ioctl(STDOUT_FILENO, libc::TIOCGWINSZ, &mut size) {
+        // https://github.com/rust-lang/libc/pull/704
+        // FIXME: ".into()" used as a temporary fix for a libc bug
+        match libc::ioctl(fileno.as_raw_fd(), libc::TIOCGWINSZ.into(), &mut size) {
             0 => (size.ws_col as usize, size.ws_row as usize), // TODO getCursorPosition
             _ => (80, 24),
         }
@@ -37,7 +48,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 +61,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 +71,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 +84,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 +106,237 @@
 
 /// Console input reader
 pub struct PosixRawReader {
-    chars: char_iter::Chars<StdinRaw>,
+    stdin: StdinRaw,
     timeout_ms: i32,
+    buf: [u8; 1],
+    parser: Parser,
+    receiver: Utf8,
+}
+
+struct Utf8 {
+    c: Option<char>,
+    valid: bool,
 }
 
 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; 1],
+            parser: Parser::new(),
+            receiver: Utf8 {
+                c: None,
+                valid: true,
+            },
+        })
     }
 
+    /// Handle ESC <seq1> sequences
     fn escape_sequence(&mut self) -> Result<KeyPress> {
-        // Read the next two bytes representing the escape sequence.
+        // Read the next byte representing the escape sequence.
         let seq1 = try!(self.next_char());
         if seq1 == '[' {
-            // ESC [ sequences.
-            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);
+            // ESC [ sequences. (CSI)
+            self.escape_csi()
+        } else if seq1 == 'O' {
+            // xterm
+            // ESC O sequences. (SS3)
+            self.escape_o()
+        } else if seq1 == '\x1b' {
+            // ESC ESC
+            Ok(KeyPress::Esc)
+        } else {
+            // TODO ESC-R (r): Undo all changes made to this line.
+            Ok(KeyPress::Meta(seq1))
+        }
+    }
+
+    /// Handle ESC [ <seq2> escape sequences
+    fn escape_csi(&mut self) -> Result<KeyPress> {
+        let seq2 = try!(self.next_char());
+        if seq2.is_digit(10) {
+            match seq2 {
+                '0' | '9' => {
+                    debug!(target: "rustyline", "unsupported esc sequence: ESC [ {:?}", seq2);
+                    Ok(KeyPress::UnknownEscSeq)
+                }
+                _ => {
+                    // Extended escape, read additional byte.
+                    self.extended_escape(seq2)
+                }
+            }
+        } else if seq2 == '[' {
+            let seq3 = try!(self.next_char());
+            // Linux console
+            Ok(match seq3 {
+                'A' => KeyPress::F(1),
+                'B' => KeyPress::F(2),
+                'C' => KeyPress::F(3),
+                'D' => KeyPress::F(4),
+                'E' => KeyPress::F(5),
+                _ => {
+                    debug!(target: "rustyline", "unsupported esc sequence: ESC [ [ {:?}", seq3);
+                    KeyPress::UnknownEscSeq
+                }
+            })
+        } else {
+            // ANSI
+            Ok(match seq2 {
+                'A' => KeyPress::Up,    // kcuu1
+                'B' => KeyPress::Down,  // kcud1
+                'C' => KeyPress::Right, // kcuf1
+                'D' => KeyPress::Left,  // kcub1
+                'F' => KeyPress::End,
+                'H' => KeyPress::Home, // khome
+                'Z' => KeyPress::BackTab,
+                _ => {
+                    debug!(target: "rustyline", "unsupported esc sequence: ESC [ {:?}", seq2);
+                    KeyPress::UnknownEscSeq
+                }
+            })
+        }
+    }
+
+    /// Handle ESC [ <seq2:digit> escape sequences
+    fn extended_escape(&mut self, seq2: char) -> Result<KeyPress> {
+        let seq3 = try!(self.next_char());
+        if seq3 == '~' {
+            Ok(match seq2 {
+                '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 [ {}{} ~", seq2, seq3);
                         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{:?}{:?}{:?}", seq1, seq2, seq3);
+                    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 [ 1 ; {} {:?}", seq4, seq5);
+                            KeyPress::UnknownEscSeq
+                        }
+                    })
+                } else {
+                    debug!(target: "rustyline",
+                           "unsupported esc sequence: ESC [ {} ; {} {:?}", seq2, seq4, seq5);
                     Ok(KeyPress::UnknownEscSeq)
                 }
             } else {
-                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);
+                debug!(target: "rustyline",
+                       "unsupported esc sequence: ESC [ {} ; {:?}", seq2, seq4);
+                Ok(KeyPress::UnknownEscSeq)
+            }
+        } else {
+            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 if seq1 == 'O' {
-            // ESC O sequences.
-            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
-            }
-               })
-        } 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
-            }
-               })
+            })
         }
     }
+
+    /// Handle ESC O <seq2> escape sequences
+    fn escape_o(&mut self) -> Result<KeyPress> {
+        let seq2 = try!(self.next_char());
+        Ok(match seq2 {
+            '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
+            'a' => KeyPress::ControlUp,
+            'b' => KeyPress::ControlDown,
+            'c' => KeyPress::ControlRight, // rxvt
+            'd' => KeyPress::ControlLeft,  // rxvt
+            _ => {
+                debug!(target: "rustyline", "unsupported esc sequence: ESC O {:?}", seq2);
+                KeyPress::UnknownEscSeq
+            }
+        })
+    }
 }
 
 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);
+        let mut key = keys::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 +353,240 @@
     }
 
     fn next_char(&mut self) -> Result<char> {
-        match self.chars.next() {
-            Some(c) => Ok(try!(c)),
-            None => Err(error::ReadlineError::Eof),
+        loop {
+            let n = try!(self.stdin.read(&mut self.buf));
+            if n == 0 {
+                return Err(error::ReadlineError::Eof);
+            }
+            let b = self.buf[0];
+            self.parser.advance(&mut self.receiver, b);
+            if !self.receiver.valid {
+                return Err(error::ReadlineError::Utf8Error);
+            } else if self.receiver.c.is_some() {
+                return Ok(self.receiver.c.take().unwrap());
+            }
         }
     }
 }
 
+impl Receiver for Utf8 {
+    /// Called whenever a codepoint is parsed successfully
+    fn codepoint(&mut self, c: char) {
+        self.c = Some(c);
+        self.valid = true;
+    }
+
+    /// Called when an invalid_sequence is detected
+    fn invalid_sequence(&mut self) {
+        self.c = None;
+        self.valid = false;
+    }
+}
+
+/// Console output writer
+pub struct PosixRenderer {
+    out: StdStream,
+    cols: usize, // Number of columns in terminal
+    buffer: String,
+}
+
+impl PosixRenderer {
+    fn new(stream_type: OutputStreamType) -> PosixRenderer {
+        let out = StdStream::from_stream_type(stream_type);
+        let (cols, _) = get_win_size(&out);
+        PosixRenderer {
+            out,
+            cols,
+            buffer: String::with_capacity(1024),
+        }
+    }
+}
+
+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,
+        highlighter: Option<&Highlighter>,
+    ) -> Result<(Position, Position)> {
+        use std::fmt::Write;
+        self.buffer.clear();
+
+        // 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!(self.buffer, "\x1b[{}B", cursor_row_movement).unwrap();
+        }
+        // clear old rows
+        for _ in 0..old_rows {
+            self.buffer.push_str("\r\x1b[0K\x1b[A");
+        }
+        // clear the line
+        self.buffer.push_str("\r\x1b[0K");
+
+        if let Some(highlighter) = highlighter {
+            // display the prompt
+            self.buffer.push_str(&highlighter.highlight_prompt(prompt));
+            // display the input line
+            self.buffer
+                .push_str(&highlighter.highlight(line, line.pos()));
+        } else {
+            // display the prompt
+            self.buffer.push_str(prompt);
+            // display the input line
+            self.buffer.push_str(line);
+        }
+        // display hint
+        if let Some(hint) = hint {
+            let truncate = truncate(&hint, end_pos.col, self.cols);
+            if let Some(highlighter) = highlighter {
+                self.buffer.push_str(&highlighter.highlight_hint(truncate));
+            } else {
+                self.buffer.push_str(truncate);
+            }
+        }
+        // we have to generate our own newline on line wrap
+        if end_pos.col == 0 && end_pos.row > 0 {
+            self.buffer.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!(self.buffer, "\x1b[{}A", cursor_row_movement).unwrap();
+        }
+        // position the cursor within the line
+        if cursor.col > 0 {
+            write!(self.buffer, "\r\x1b[{}C", cursor.col).unwrap();
+        } else {
+            self.buffer.push('\r');
+        }
+
+        try!(self.out.write_all(self.buffer.as_bytes()));
+        try!(self.out.flush());
+        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).
+    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.out);
+        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.out);
+        rows
+    }
+}
 
 static SIGWINCH_ONCE: sync::Once = sync::ONCE_INIT;
-static SIGWINCH: atomic::AtomicBool = atomic::ATOMIC_BOOL_INIT;
+static SIGWINCH: atomic::AtomicBool = atomic::AtomicBool::new(false);
 
 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,23 +598,33 @@
 
 pub type Terminal = PosixTerminal;
 
-#[derive(Clone,Debug)]
+#[derive(Clone, Debug)]
 pub struct PosixTerminal {
     unsupported: bool,
     stdin_isatty: bool,
+    stdstream_isatty: bool,
+    pub(crate) color_mode: ColorMode,
+    stream_type: OutputStreamType,
 }
 
 impl Term for PosixTerminal {
-    type Reader = PosixRawReader;
-    type Writer = Stdout;
     type Mode = Mode;
+    type Reader = PosixRawReader;
+    type Writer = PosixRenderer;
 
-    fn new() -> PosixTerminal {
+    fn new(color_mode: ColorMode, stream_type: OutputStreamType) -> PosixTerminal {
         let term = PosixTerminal {
             unsupported: is_unsupported_term(),
             stdin_isatty: is_a_tty(STDIN_FILENO),
+            stdstream_isatty: is_a_tty(if stream_type == OutputStreamType::Stdout {
+                STDOUT_FILENO
+            } else {
+                STDERR_FILENO
+            }),
+            color_mode,
+            stream_type,
         };
-        if !term.unsupported && term.stdin_isatty && is_a_tty(STDOUT_FILENO) {
+        if !term.unsupported && term.stdin_isatty && term.stdstream_isatty {
             install_sigwinch_handler();
         }
         term
@@ -264,7 +632,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
     }
@@ -274,42 +643,44 @@
         self.stdin_isatty
     }
 
+    /// Check if output supports colors.
+    fn colors_enabled(&self) -> bool {
+        match self.color_mode {
+            ColorMode::Enabled => self.stdstream_isatty,
+            ColorMode::Forced => true,
+            ColorMode::Disabled => false,
+        }
+    }
+
     // 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> {
+    fn enable_raw_mode(&mut 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)
+        // disable all output processing
+        // raw.c_oflag = raw.c_oflag & !(OutputFlags::OPOST);
+
+        // 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 +689,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(self.stream_type)
     }
 }
 
 #[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..afe70ce 100644
--- a/src/tty/windows.rs
+++ b/src/tty/windows.rs
@@ -1,184 +1,420 @@
 //! Windows specific definitions
-use std::io::{self, Stdout, Write};
+use std::io::{self, Write};
 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 config::Config;
-use consts::{self, KeyPress};
+use super::{truncate, Position, RawMode, RawReader, Renderer, Term};
+use config::OutputStreamType;
+use config::{ColorMode, Config};
 use error;
+use highlight::Highlighter;
+use keys::{self, KeyPress};
+use line_buffer::LineBuffer;
 use Result;
-use super::{RawMode, RawReader, Term};
+use StdStream;
 
-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;
+const STDERR_FILENO: DWORD = winbase::STD_ERROR_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_stdstream_mode: Option<DWORD>,
+    stdstream_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,
+        ));
+        if let Some(original_stdstream_mode) = self.original_stdstream_mode {
+            check!(consoleapi::SetConsoleMode(
+                self.stdstream_handle,
+                original_stdstream_mode,
+            ));
+        }
         Ok(())
     }
 }
 
 /// Console input reader
 pub struct ConsoleRawReader {
-    handle: winapi::HANDLE,
-    buf: Option<u16>,
+    handle: HANDLE,
 }
 
 impl ConsoleRawReader {
-    pub fn new() -> Result<ConsoleRawReader> {
+    pub fn new(stream: OutputStreamType) -> Result<ConsoleRawReader> {
         let handle = try!(get_std_handle(STDIN_FILENO));
-        Ok(ConsoleRawReader {
-               handle: handle,
-               buf: None,
-           })
+        Ok(ConsoleRawReader { handle })
     }
 }
 
 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,
+            SHIFT_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 = 0;
         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 {
+                return Err(error::ReadlineError::WindowResize); // sigwinch + err => err ignored
+            } 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)
+                == (LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED);
             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 meta = alt;
+            let ctrl = key_event.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED) != 0;
+            let meta = alt && !alt_gr;
+            let shift = key_event.dwControlKeyState & SHIFT_PRESSED != 0;
 
-            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 if shift {
+                            KeyPress::ShiftLeft
+                        } else {
+                            KeyPress::Left
+                        })
+                    }
+                    winuser::VK_RIGHT => {
+                        return Ok(if ctrl {
+                            KeyPress::ControlRight
+                        } else if shift {
+                            KeyPress::ShiftRight
+                        } else {
+                            KeyPress::Right
+                        })
+                    }
+                    winuser::VK_UP => {
+                        return Ok(if ctrl {
+                            KeyPress::ControlUp
+                        } else if shift {
+                            KeyPress::ShiftUp
+                        } else {
+                            KeyPress::Up
+                        })
+                    }
+                    winuser::VK_DOWN => {
+                        return Ok(if ctrl {
+                            KeyPress::ControlDown
+                        } else if shift {
+                            KeyPress::ShiftDown
+                        } 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 = utf16;
+                    continue;
+                }
+                let orc = if surrogate == 0 {
+                    decode_utf16(Some(utf16)).next()
+                } else {
+                    decode_utf16([surrogate, utf16].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));
+                    let mut key = keys::char_to_key_press(c);
+                    if key == KeyPress::Tab && shift {
+                        key = KeyPress::BackTab;
+                    } else if key == KeyPress::Char(' ') && ctrl {
+                        key = KeyPress::Ctrl(' ');
+                    }
+                    return Ok(key);
                 }
             }
         }
     }
 }
 
-impl Iterator for ConsoleRawReader {
-    type Item = u16;
+pub struct ConsoleRenderer {
+    out: StdStream,
+    handle: HANDLE,
+    cols: usize, // Number of columns in terminal
+    buffer: String,
+}
 
-    fn next(&mut self) -> Option<u16> {
-        let buf = self.buf;
-        self.buf = None;
-        buf
+impl ConsoleRenderer {
+    fn new(handle: HANDLE, stream_type: OutputStreamType) -> ConsoleRenderer {
+        // Multi line editing is enabled by ENABLE_WRAP_AT_EOL_OUTPUT mode
+        let (cols, _) = get_win_size(handle);
+        ConsoleRenderer {
+            out: StdStream::from_stream_type(stream_type),
+            handle,
+            cols,
+            buffer: String::with_capacity(1024),
+        }
+    }
+
+    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,
+        highlighter: Option<&Highlighter>,
+    ) -> 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,
+        ));
+        self.buffer.clear();
+        if let Some(highlighter) = highlighter {
+            // TODO handle ansi escape code (SetConsoleTextAttribute)
+            // display the prompt
+            self.buffer.push_str(&highlighter.highlight_prompt(prompt));
+            // display the input line
+            self.buffer
+                .push_str(&highlighter.highlight(line, line.pos()));
+        } else {
+            // display the prompt
+            self.buffer.push_str(prompt);
+            // display the input line
+            self.buffer.push_str(line);
+        }
+        // display hint
+        if let Some(hint) = hint {
+            let truncate = truncate(&hint, end_pos.col, self.cols);
+            if let Some(highlighter) = highlighter {
+                self.buffer.push_str(&highlighter.highlight_hint(truncate));
+            } else {
+                self.buffer.push_str(truncate);
+            }
+        }
+        try!(self.out.write_all(self.buffer.as_bytes()));
+        try!(self.out.flush());
+
+        // 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,45 +422,25 @@
 
 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,
+    stdstream_isatty: bool,
+    stdstream_handle: HANDLE,
+    pub(crate) color_mode: ColorMode,
+    ansi_colors_supported: bool,
+    stream_type: OutputStreamType,
 }
 
-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 Mode = Mode;
+    type Reader = ConsoleRawReader;
+    type Writer = ConsoleRenderer;
 
-    fn new() -> Console {
+    fn new(color_mode: ColorMode, stream_type: OutputStreamType) -> Console {
         use std::ptr;
         let stdin_handle = get_std_handle(STDIN_FILENO);
         let stdin_isatty = match stdin_handle {
@@ -235,11 +451,27 @@
             Err(_) => false,
         };
 
-        let stdout_handle = get_std_handle(STDOUT_FILENO).unwrap_or(ptr::null_mut());
+        let stdstream_handle = get_std_handle(if stream_type == OutputStreamType::Stdout {
+            STDOUT_FILENO
+        } else {
+            STDERR_FILENO
+        });
+        let stdstream_isatty = match stdstream_handle {
+            Ok(handle) => {
+                // If this function doesn't fail then fd is a TTY
+                get_console_mode(handle).is_ok()
+            }
+            Err(_) => false,
+        };
+
         Console {
-            stdin_isatty: stdin_isatty,
+            stdin_isatty,
             stdin_handle: stdin_handle.unwrap_or(ptr::null_mut()),
-            stdout_handle: stdout_handle,
+            stdstream_isatty,
+            stdstream_handle: stdstream_handle.unwrap_or(ptr::null_mut()),
+            color_mode,
+            ansi_colors_supported: false,
+            stream_type,
         }
     }
 
@@ -252,71 +484,66 @@
         self.stdin_isatty
     }
 
+    fn colors_enabled(&self) -> bool {
+        // TODO ANSI Colors & Windows <10
+        match self.color_mode {
+            ColorMode::Enabled => self.stdstream_isatty && self.ansi_colors_supported,
+            ColorMode::Forced => true,
+            ColorMode::Disabled => false,
+        }
+    }
+
     // pub fn install_sigwinch_handler(&mut self) {
     // 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> {
+    fn enable_raw_mode(&mut 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 mut 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));
+        raw |= wincon::ENABLE_EXTENDED_FLAGS;
+        raw |= wincon::ENABLE_INSERT_MODE;
+        raw |= wincon::ENABLE_QUICK_EDIT_MODE;
+        raw |= wincon::ENABLE_WINDOW_INPUT;
+        check!(consoleapi::SetConsoleMode(self.stdin_handle, raw));
+
+        let original_stdstream_mode = if self.stdstream_isatty {
+            let original_stdstream_mode = try!(get_console_mode(self.stdstream_handle));
+            // To enable ANSI colors (Windows 10 only):
+            // https://docs.microsoft.com/en-us/windows/console/setconsolemode
+            if original_stdstream_mode & wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0 {
+                let raw = original_stdstream_mode | wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING;
+                self.ansi_colors_supported =
+                    unsafe { consoleapi::SetConsoleMode(self.stdstream_handle, raw) != 0 };
+            }
+            Some(original_stdstream_mode)
+        } else {
+            None
+        };
+
         Ok(Mode {
-               original_mode: original_mode,
-               stdin_handle: self.stdin_handle,
-           })
+            original_stdin_mode,
+            stdin_handle: self.stdin_handle,
+            original_stdstream_mode,
+            stdstream_handle: self.stdstream_handle,
+        })
     }
 
     fn create_reader(&self, _: &Config) -> Result<ConsoleRawReader> {
-        ConsoleRawReader::new()
+        ConsoleRawReader::new(self.stream_type)
     }
 
-    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.stdstream_handle, self.stream_type)
     }
 }
diff --git a/src/undo.rs b/src/undo.rs
new file mode 100644
index 0000000..7f7c9ab
--- /dev/null
+++ b/src/undo.rs
@@ -0,0 +1,471 @@
+//! 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);
+    }
+}