Merge branch 'master' into get_helper
diff --git a/Cargo.toml b/Cargo.toml
index 9b940d8..7819abd 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,15 +13,19 @@
 [badges]
 travis-ci = { repository = "kkawakam/rustyline" }
 appveyor = { repository = "kkawakam/rustyline" }
+maintenance = { status = "actively-developed" }
 
 [dependencies]
+dirs = "1.0"
 libc = "0.2"
 log = "0.4"
 unicode-width = "0.1"
 unicode-segmentation = "1.0"
+memchr = "2.0"
 
 [target.'cfg(unix)'.dependencies]
 nix = "0.11"
+utf8parse = "0.1"
 
 [target.'cfg(windows)'.dependencies]
 winapi = { version = "0.3", features = ["consoleapi", "handleapi", "minwindef", "processenv", "winbase", "wincon", "winuser"] }
diff --git a/README.md b/README.md
index 99f345b..5f31310 100644
--- a/README.md
+++ b/README.md
@@ -69,8 +69,8 @@
  - 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))
+ - 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
@@ -207,6 +207,7 @@
 
 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   | ?          | ?                 |
@@ -217,11 +218,12 @@
 [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/MovingtoMars/liner
+[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
index 948d42b..d4cd263 100644
--- a/TODO.md
+++ b/TODO.md
@@ -7,8 +7,8 @@
 - [ ] 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 ?)
+- [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
 - [ ] clicolors spec (https://docs.rs/console/0.6.1/console/fn.colors_enabled.html)
 
@@ -17,7 +17,7 @@
 - [ ] Windows escape/unescape space in path
 - [ ] file completion & escape/unescape (#106)
 - [ ] file completion & tilde (#62)
-- [ ] display versus replacement
+- [X] display versus replacement
 - [ ] composite/alternate completer (if the current completer returns nothing, try the next one)
 
 Config
@@ -31,7 +31,8 @@
 - [ ] grapheme & input auto-wrap are buggy
 
 Hints Callback
-- [x] Not implemented on windows
+- [X] Not implemented on windows
+- [ ] Do an implementation based on previous history
 
 History
 - [ ] Move to the history line n
@@ -42,9 +43,11 @@
 
 Input
 - [ ] Password input (#58)
-- [ ] quoted insert (#65)
+- [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)
 
 Mouse
 - [ ] Mouse support
@@ -53,10 +56,10 @@
 - [ ] Move to the corresponding opening/closing bracket
 
 Redo
-- [X] redo substitue
+- [X] redo substitute
 
 Repeat
-- [x] dynamic prompt (arg: ?)
+- [X] dynamic prompt (arg: ?)
 - [ ] transpose chars
 
 Syntax
@@ -72,8 +75,9 @@
 
 Unix
 - [ ] Terminfo (https://github.com/Stebalien/term)
+- [ ] [ncurses](https://crates.io/crates/ncurses) alternative backend ?
 
 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 ?)
+- [ ] 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/examples/example.rs b/examples/example.rs
index 4e9c407..ce41cf4 100644
--- a/examples/example.rs
+++ b/examples/example.rs
@@ -2,37 +2,50 @@
 extern crate rustyline;
 
 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::error::ReadlineError;
+use rustyline::highlight::Highlighter;
 use rustyline::hint::Hinter;
-use rustyline::{Cmd, CompletionType, Config, EditMode, Editor, KeyPress};
+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 Hints {}
+struct MyHelper(FilenameCompleter);
 
-impl Hinter for Hints {
+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" {
-            if cfg!(target_os = "windows") {
-                Some(" World".to_owned())
-            } else {
-                Some(" \x1b[1mWorld\x1b[m".to_owned())
-            }
+            Some(" World".to_owned())
         } else {
             None
         }
     }
 }
 
+impl Highlighter for MyHelper {
+    fn highlight_prompt<'p>(&self, _: &str) -> Cow<'static, str> {
+        Borrowed(COLORED_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()
@@ -40,9 +53,9 @@
         .completion_type(CompletionType::List)
         .edit_mode(EditMode::Emacs)
         .build();
-    let c = FilenameCompleter::new();
+    let h = MyHelper(FilenameCompleter::new());
     let mut rl = Editor::with_config(config);
-    rl.set_helper(Some((c, Hints {})));
+    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() {
diff --git a/rustfmt.toml b/rustfmt.toml
index 34977e4..83697e3 100644
--- a/rustfmt.toml
+++ b/rustfmt.toml
@@ -1,3 +1,4 @@
 wrap_comments = true
 format_strings = true
 error_on_unformatted = false
+reorder_impl_items = true
diff --git a/src/completion.rs b/src/completion.rs
index c6e65ed..ab7a020 100644
--- a/src/completion.rs
+++ b/src/completion.rs
@@ -1,25 +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")
 // 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", 11) => Ok((3, vec!["/usr/local/"]))
-    fn complete(&self, line: &str, pos: usize) -> Result<(usize, Vec<String>)>;
+    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();
@@ -28,18 +63,24 @@
 }
 
 impl Completer for () {
+    type Candidate = String;
+
     fn complete(&self, _line: &str, _pos: usize) -> Result<(usize, Vec<String>)> {
         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)
     }
@@ -48,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) {
@@ -65,34 +108,48 @@
 
 /// A `Completer` for file and folder names.
 pub struct FilenameCompleter {
-    break_chars: BTreeSet<char>,
-    double_quotes_special_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
-static DOUBLE_QUOTES_SPECIAL_CHARS: [char; 4] = ['"', '$', '\\', '`'];
+#[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(),
-            double_quotes_special_chars: DOUBLE_QUOTES_SPECIAL_CHARS.iter().cloned().collect(),
+            break_chars: &DEFAULT_BREAK_CHARS,
+            double_quotes_special_chars: &DOUBLE_QUOTES_SPECIAL_CHARS,
         }
     }
 }
@@ -104,26 +161,35 @@
 }
 
 impl Completer for FilenameCompleter {
-    fn complete(&self, line: &str, pos: usize) -> Result<(usize, Vec<String>)> {
-        let (start, path, esc_char, break_chars) =
-            if let Some((idx, double_quote)) = find_unclosed_quote(&line[..pos]) {
+    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 double_quote {
+                if quote == Quote::Double {
                     (
                         start,
-                        unescape(&line[start..pos], ESCAPE_CHAR),
-                        ESCAPE_CHAR,
+                        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)
+                    (
+                        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)
+                (start, path, ESCAPE_CHAR, &self.break_chars, Quote::None)
             };
-        let matches = try!(filename_complete(&path, esc_char, break_chars));
+        let matches = try!(filename_complete(&path, esc_char, break_chars, quote));
         Ok((start, matches))
     }
 }
@@ -134,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 {
@@ -155,19 +226,34 @@
 /// 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 {
+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);
@@ -178,9 +264,11 @@
 fn filename_complete(
     path: &str,
     esc_char: Option<char>,
-    break_chars: &BTreeSet<char>,
-) -> Result<Vec<String>> {
-    use std::env::{current_dir, home_dir};
+    break_chars: &[u8],
+    quote: Quote,
+) -> Result<Vec<Pair>> {
+    use dirs::home_dir;
+    use std::env::current_dir;
 
     let sep = path::MAIN_SEPARATOR;
     let (dir_name, file_name) = match path.rfind(sep) {
@@ -210,16 +298,21 @@
         dir_path.to_path_buf()
     };
 
-    let mut entries: Vec<String> = Vec::new();
+    let mut entries: Vec<Pair> = 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);
-                }
-                entries.push(escape(path, esc_char, break_chars));
+                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
             }
         }
     }
@@ -234,7 +327,7 @@
     line: &'l str,
     pos: usize,
     esc_char: Option<char>,
-    break_chars: &BTreeSet<char>,
+    break_chars: &[u8],
 ) -> (usize, &'l str) {
     let line = &line[..pos];
     if line.is_empty() {
@@ -251,7 +344,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;
@@ -265,17 +358,17 @@
     }
 }
 
-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();
+            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]
@@ -285,13 +378,14 @@
         }
         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)]
@@ -306,7 +400,7 @@
 /// try to find an unclosed single/double quote in `s`.
 /// Return `None` if no unclosed quote is found.
 /// Return the unclosed quote position and if it is a double quote.
-fn find_unclosed_quote(s: &str) -> Option<(usize, bool)> {
+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;
@@ -316,6 +410,7 @@
                 if char == '"' {
                     mode = ScanMode::Normal;
                 } else if char == '\\' {
+                    // both windows and unix support escape in double quote
                     mode = ScanMode::EscapeInDoubleQuote;
                 }
             }
@@ -343,19 +438,19 @@
             }
         };
     }
-    if ScanMode::DoubleQuote == mode || ScanMode::SingleQuote == mode {
-        return Some((quote_index, ScanMode::DoubleQuote == mode));
+    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"),
@@ -373,22 +468,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)
+            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]
@@ -430,12 +534,16 @@
     pub fn find_unclosed_quote() {
         assert_eq!(None, super::find_unclosed_quote("ls /etc"));
         assert_eq!(
-            Some((3, true)),
+            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 d1dbc42..86c04dc 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -20,6 +20,8 @@
     /// 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,
 }
 
 impl Config {
@@ -70,6 +72,13 @@
     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
+    }
 }
 
 impl Default for Config {
@@ -83,6 +92,7 @@
             keyseq_timeout: -1,
             edit_mode: EditMode::Emacs,
             auto_add_history: false,
+            color_mode: ColorMode::Enabled,
         }
     }
 }
@@ -111,6 +121,14 @@
     Vi,
 }
 
+/// Colorization mode
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum ColorMode {
+    Enabled,
+    Forced,
+    Disabled,
+}
+
 /// Configuration builder
 #[derive(Debug, Default)]
 pub struct Builder {
@@ -193,6 +211,14 @@
         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.p.color_mode = color_mode;
+        self
+    }
+
     pub fn build(self) -> Config {
         self.p
     }
diff --git a/src/edit.rs b/src/edit.rs
index 7dae766..f9fcca8 100644
--- a/src/edit.rs
+++ b/src/edit.rs
@@ -7,6 +7,7 @@
 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};
@@ -30,6 +31,7 @@
     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>,
 }
 
 impl<'out, 'prompt> State<'out, 'prompt> {
@@ -38,6 +40,7 @@
         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());
@@ -53,6 +56,7 @@
             byte_buffer: [0; 4],
             changes: Rc::new(RefCell::new(Changeset::new())),
             hinter,
+            highlighter,
         }
     }
 
@@ -80,6 +84,7 @@
         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(),
@@ -95,7 +100,16 @@
         if self.cursor == cursor {
             return Ok(());
         }
-        try!(self.out.move_cursor(self.cursor, cursor));
+        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(())
     }
@@ -108,6 +122,7 @@
             hint,
             self.cursor.row,
             self.old_rows,
+            self.highlighter,
         ));
 
         self.cursor = cursor;
@@ -136,12 +151,15 @@
         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()
     }
@@ -171,7 +189,8 @@
                 let hint = self.hint();
                 if n == 1
                     && self.cursor.col + ch.width().unwrap_or(0) < self.out.get_columns()
-                    && hint.is_none()
+                    && hint.is_none() // 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
@@ -486,6 +505,7 @@
         byte_buffer: [0; 4],
         changes: Rc::new(RefCell::new(Changeset::new())),
         hinter: None,
+        highlighter: None,
     }
 }
 
diff --git a/src/error.rs b/src/error.rs
index d9cfdb8..f7c1abd 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -20,7 +20,7 @@
     Interrupted,
     /// Chars Error
     #[cfg(unix)]
-    Char(str::Utf8Error),
+    Utf8Error,
     /// Unix Error from syscall
     #[cfg(unix)]
     Errno(nix::Error),
@@ -37,7 +37,7 @@
             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(unix)]
             ReadlineError::Errno(ref err) => err.fmt(f),
             #[cfg(windows)]
@@ -55,7 +55,7 @@
             ReadlineError::Eof => "EOF",
             ReadlineError::Interrupted => "Interrupted",
             #[cfg(unix)]
-            ReadlineError::Char(ref err) => err.description(),
+            ReadlineError::Utf8Error => "invalid utf-8: corrupt contents",
             #[cfg(unix)]
             ReadlineError::Errno(ref err) => err.description(),
             #[cfg(windows)]
@@ -79,13 +79,6 @@
     }
 }
 
-#[cfg(unix)]
-impl From<str::Utf8Error> for ReadlineError {
-    fn from(err: str::Utf8Error) -> 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..04bc042
--- /dev/null
+++ b/src/highlight.rs
@@ -0,0 +1,59 @@
+use config::CompletionType;
+///! Syntax highlighting
+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).
+    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/history.rs b/src/history.rs
index d4e0c94..76a5bcf 100644
--- a/src/history.rs
+++ b/src/history.rs
@@ -32,12 +32,13 @@
     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,
         }
     }
 
@@ -57,12 +58,11 @@
             return false;
         }
         if line.as_ref().is_empty()
-            || (self.ignore_space
-                && line
-                    .as_ref()
-                    .chars()
-                    .next()
-                    .map_or(true, |c| c.is_whitespace()))
+            || (self.ignore_space && line
+                .as_ref()
+                .chars()
+                .next()
+                .map_or(true, |c| c.is_whitespace()))
         {
             return false;
         }
@@ -84,6 +84,7 @@
     pub fn len(&self) -> usize {
         self.entries.len()
     }
+
     /// Return true if the history has no entry.
     pub fn is_empty(&self) -> bool {
         self.entries.is_empty()
@@ -214,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()
diff --git a/src/keymap.rs b/src/keymap.rs
index 3652493..877a12d 100644
--- a/src/keymap.rs
+++ b/src/keymap.rs
@@ -111,6 +111,7 @@
             _ => false,
         }
     }
+
     fn is_repeatable(&self) -> bool {
         match *self {
             Cmd::Move(_) => true,
@@ -205,8 +206,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),
diff --git a/src/kill_ring.rs b/src/kill_ring.rs
index 15b6649..b5d8778 100644
--- a/src/kill_ring.rs
+++ b/src/kill_ring.rs
@@ -109,6 +109,7 @@
     fn start_killing(&mut self) {
         self.killing = true;
     }
+
     fn delete(&mut self, _: usize, string: &str, dir: Direction) {
         if !self.killing {
             return;
@@ -119,6 +120,7 @@
         };
         self.kill(string, mode);
     }
+
     fn stop_killing(&mut self) {
         self.killing = false;
     }
diff --git a/src/lib.rs b/src/lib.rs
index 1221361..6c61a05 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -17,13 +17,17 @@
 //! ```
 #![allow(unknown_lints)]
 
+extern crate dirs;
 extern crate libc;
 #[macro_use]
 extern crate log;
+extern crate memchr;
 #[cfg(unix)]
 extern crate nix;
 extern crate unicode_segmentation;
 extern crate unicode_width;
+#[cfg(unix)]
+extern crate utf8parse;
 #[cfg(windows)]
 extern crate winapi;
 
@@ -32,6 +36,7 @@
 mod consts;
 mod edit;
 pub mod error;
+pub mod highlight;
 pub mod hint;
 pub mod history;
 mod keymap;
@@ -51,10 +56,11 @@
 
 use tty::{RawMode, RawReader, Renderer, Term, Terminal};
 
-use completion::{longest_common_prefix, Completer};
+use completion::{longest_common_prefix, Candidate, Completer};
 pub use config::{CompletionType, Config, EditMode, HistoryDuplicates};
 pub use consts::KeyPress;
 use edit::State;
+use highlight::Highlighter;
 use hint::Hinter;
 use history::{Direction, History};
 pub use keymap::{Anchor, At, CharSearch, Cmd, Movement, RepeatCount, Word};
@@ -71,6 +77,7 @@
     s: &mut State,
     input_state: &mut InputState,
     completer: &C,
+    highlighter: Option<&Highlighter>,
     config: &Config,
 ) -> Result<Option<Cmd>> {
     // get a list of completions
@@ -89,7 +96,14 @@
         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
@@ -122,18 +136,19 @@
         }
         Ok(Some(cmd))
     } else if CompletionType::List == config.completion_type() {
-        // beep if ambiguous
-        if candidates.len() > 1 {
-            try!(s.out.beep());
-        }
         if let Some(lcp) = longest_common_prefix(&candidates) {
-            // if we can extend the item, extend it and return to main loop
+            // 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(input_state, rdr, true));
         // if any character other than tab, pass it to the main loop
@@ -165,7 +180,7 @@
             true
         };
         if show_completions {
-            page_completions(rdr, s, input_state, &candidates)
+            page_completions(rdr, s, input_state, highlighter, &candidates)
         } else {
             try!(s.refresh_line());
             Ok(None)
@@ -175,11 +190,12 @@
     }
 }
 
-fn page_completions<R: RawReader>(
+fn page_completions<R: RawReader, C: Candidate>(
     rdr: &mut R,
     s: &mut State,
     input_state: &mut InputState,
-    candidates: &[String],
+    highlighter: Option<&Highlighter>,
+    candidates: &[C],
 ) -> Result<Option<Cmd>> {
     use std::cmp;
 
@@ -189,9 +205,10 @@
         cols,
         candidates
             .into_iter()
-            .map(|s| s.as_str().width())
+            .map(|s| s.display().width())
             .max()
-            .unwrap() + min_col_pad,
+            .unwrap()
+            + min_col_pad,
     );
     let num_cols = cols / max_width;
 
@@ -231,9 +248,13 @@
         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(' ');
@@ -337,20 +358,30 @@
 /// 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<H: Helper>(
     prompt: &str,
     initial: Option<(&str, &str)>,
     editor: &mut Editor<H>,
     original_mode: &tty::Mode,
 ) -> Result<String> {
-    let completer = editor.helper.as_ref().map(|h| h.completer());
-    let hinter = editor.helper.as_ref().map(|h| h.hinter() as &Hinter);
+    let 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.reset_kill_ring(); // TODO recreate a new kill ring vs Arc<Mutex<KillRing>>
-    let mut s = State::new(&mut stdout, prompt, editor.history.len(), hinter);
+    let mut 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());
@@ -380,6 +411,7 @@
                 &mut s,
                 &mut input_state,
                 completer.unwrap(),
+                highlighter,
                 &editor.config,
             ));
             if next.is_some() {
@@ -574,6 +606,9 @@
             }
         }
     }
+    if cfg!(windows) {
+        let _ = original_mode; // silent warning
+    }
     Ok(s.line.into_string())
 }
 
@@ -618,37 +653,17 @@
 
 /// Syntax specific helper.
 ///
-/// TODO Tokenizer/parser used for both completion, suggestion, highlighting
-pub trait Helper {
-    type Completer: Completer;
-    type Hinter: Hinter;
-
-    fn completer(&self) -> &Self::Completer;
-    fn hinter(&self) -> &Self::Hinter;
+/// 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, H: Hinter> Helper for (C, H) {
-    type Completer = C;
-    type Hinter = H;
-
-    fn completer(&self) -> &C {
-        &self.0
-    }
-    fn hinter(&self) -> &H {
-        &self.1
-    }
-}
-impl<C: Completer> Helper for C {
-    type Completer = C;
-    type Hinter = ();
-
-    fn completer(&self) -> &C {
-        self
-    }
-    fn hinter(&self) -> &() {
-        &()
-    }
-}
+impl Helper for () {}
 
 /// Line editor
 pub struct Editor<H: Helper> {
@@ -660,6 +675,7 @@
     custom_bindings: Arc<RwLock<HashMap<KeyPress, Cmd>>>,
 }
 
+#[allow(new_without_default)]
 impl<H: Helper> Editor<H> {
     /// Create an editor with the default configuration
     pub fn new() -> Editor<H> {
@@ -668,7 +684,7 @@
 
     /// Create an editor with a specific configuration.
     pub fn with_config(config: Config) -> Editor<H> {
-        let term = Terminal::new();
+        let term = Terminal::new(config.color_mode());
         Editor {
             term,
             history: History::with_config(config),
@@ -688,6 +704,7 @@
     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.
     ///
@@ -721,24 +738,29 @@
     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
     }
 
@@ -749,25 +771,21 @@
     }
 
     /// Return a mutable reference to the helper.
-    pub fn get_helper(&mut self) -> Option<&mut H> {
+    pub fn helper_mut(&mut self) -> Option<&mut H> {
         self.helper.as_mut()
     }
 
     /// Return an immutable reference to the helper.
-    pub fn get_helper_const(&self) -> Option<&H> {
+    pub fn helper(&self) -> Option<&H> {
         self.helper.as_ref()
     }
 
-    #[deprecated(since = "2.0.0", note = "Use set_helper instead")]
-    pub fn set_completer(&mut self, completer: Option<H>) {
-        self.helper = completer;
-    }
-
     /// Bind a sequence to a command.
     pub fn bind_sequence(&mut self, key_seq: KeyPress, cmd: Cmd) -> Option<Cmd> {
         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> {
         let mut bindings = self.custom_bindings.write().unwrap();
diff --git a/src/line_buffer.rs b/src/line_buffer.rs
index 7c16bb1..e17fd62 100644
--- a/src/line_buffer.rs
+++ b/src/line_buffer.rs
@@ -10,7 +10,7 @@
 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)]
@@ -22,7 +22,7 @@
 
 /// Delete (kill) direction
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum Direction {
+pub(crate) enum Direction {
     Forward,
     Backward,
 }
@@ -34,14 +34,14 @@
 }
 
 /// Listener to be notified when some text is deleted.
-pub trait DeleteListener {
+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 trait ChangeListener: DeleteListener {
+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);
@@ -78,7 +78,11 @@
     }
 
     #[cfg(test)]
-    pub fn init(line: &str, pos: usize, cl: Option<Rc<RefCell<ChangeListener>>>) -> 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);
@@ -86,16 +90,15 @@
         lb
     }
 
-    pub fn set_delete_listener(&mut self, dl: Arc<Mutex<DeleteListener>>) {
+    pub(crate) fn set_delete_listener(&mut self, dl: Arc<Mutex<DeleteListener>>) {
         self.dl = Some(dl);
     }
-    pub fn remove_delete_listener(&mut self) {
-        self.dl = None;
-    }
-    pub fn set_change_listener(&mut self, dl: Rc<RefCell<ChangeListener>>) {
+
+    pub(crate) fn set_change_listener(&mut self, dl: Rc<RefCell<ChangeListener>>) {
         self.cl = Some(dl);
     }
-    pub fn remove_change_listener(&mut self) {
+
+    pub(crate) fn remove_change_listener(&mut self) {
         self.cl = None;
     }
 
@@ -113,6 +116,7 @@
     pub fn pos(&self) -> usize {
         self.pos
     }
+
     /// Set cursor position (byte position)
     pub fn set_pos(&mut self, pos: usize) {
         assert!(pos <= self.buf.len());
@@ -123,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()
@@ -148,7 +153,7 @@
     }
 
     /// 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 {
@@ -168,6 +173,7 @@
             .last()
             .map(|(i, s)| i + self.pos + s.len())
     }
+
     /// Returns the position of the character just before the current cursor
     /// position.
     fn prev_pos(&self, n: RepeatCount) -> Option<usize> {
@@ -475,9 +481,9 @@
         }
     }
 
-    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 {
+        let search_result = match cs {
             CharSearch::Backward(c) | CharSearch::BackwardAfter(c) => self.buf[..self.pos]
                 .char_indices()
                 .rev()
@@ -504,17 +510,16 @@
             }
         };
         if let Some(pos) = search_result {
-            Some(match *cs {
+            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()
+                    shift + pos - self.buf[..shift + pos]
+                        .chars()
+                        .next_back()
+                        .unwrap()
+                        .len_utf8()
                 }
             })
         } else {
@@ -525,7 +530,7 @@
     /// 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 {
@@ -547,8 +552,8 @@
 
     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 {
             match cs {
@@ -583,6 +588,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() {
@@ -732,10 +738,8 @@
             }
             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 {
@@ -874,14 +878,18 @@
 
     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) {}
     }
 
diff --git a/src/test/mod.rs b/src/test/mod.rs
index d3216de..1ce9881 100644
--- a/src/test/mod.rs
+++ b/src/test/mod.rs
@@ -24,6 +24,8 @@
 
 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"]))
     }
@@ -43,6 +45,7 @@
         &mut s,
         &mut input_state,
         &completer,
+        None,
         &Config::default(),
     ).unwrap();
     assert_eq!(Some(Cmd::AcceptLine), cmd);
diff --git a/src/tty/mod.rs b/src/tty/mod.rs
index e6c42dc..e8c7db4 100644
--- a/src/tty/mod.rs
+++ b/src/tty/mod.rs
@@ -3,8 +3,9 @@
 use unicode_segmentation::UnicodeSegmentation;
 use unicode_width::UnicodeWidthStr;
 
-use config::Config;
+use config::{ColorMode, Config};
 use consts::KeyPress;
+use highlight::Highlighter;
 use line_buffer::LineBuffer;
 use Result;
 
@@ -42,6 +43,7 @@
         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
@@ -77,6 +79,7 @@
     fn move_cursor(&mut self, old: Position, new: Position) -> Result<()> {
         (**self).move_cursor(old, new)
     }
+
     fn refresh_line(
         &mut self,
         prompt: &str,
@@ -85,30 +88,47 @@
         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)
+        (**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()
     }
@@ -120,14 +140,16 @@
     type Writer: Renderer; // rl_outstream
     type Mode: RawMode;
 
-    fn new() -> Self;
+    fn new(color_mode: ColorMode) -> 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
diff --git a/src/tty/test.rs b/src/tty/test.rs
index 29a1e79..752bae3 100644
--- a/src/tty/test.rs
+++ b/src/tty/test.rs
@@ -4,9 +4,10 @@
 use std::vec::IntoIter;
 
 use super::{truncate, Position, RawMode, RawReader, Renderer, Term};
-use config::Config;
+use config::{ColorMode, Config};
 use consts::KeyPress;
 use error::ReadlineError;
+use highlight::Highlighter;
 use line_buffer::LineBuffer;
 use Result;
 
@@ -25,6 +26,7 @@
             None => Err(ReadlineError::Eof),
         }
     }
+
     #[cfg(unix)]
     fn next_char(&mut self) -> Result<char> {
         unimplemented!();
@@ -38,6 +40,7 @@
             None => Err(ReadlineError::Eof),
         }
     }
+
     #[cfg(unix)]
     fn next_char(&mut self) -> Result<char> {
         match self.next() {
@@ -69,6 +72,7 @@
         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 {
@@ -99,10 +103,13 @@
     fn sigwinch(&self) -> bool {
         false
     }
+
     fn update_size(&mut self) {}
+
     fn get_columns(&self) -> usize {
         80
     }
+
     fn get_rows(&self) -> usize {
         24
     }
@@ -117,11 +124,11 @@
 }
 
 impl Term for DummyTerminal {
+    type Mode = Mode;
     type Reader = IntoIter<KeyPress>;
     type Writer = Sink;
-    type Mode = Mode;
 
-    fn new() -> DummyTerminal {
+    fn new(_color_mode: ColorMode) -> DummyTerminal {
         DummyTerminal {
             keys: Vec::new(),
             cursor: 0,
@@ -138,9 +145,13 @@
         true
     }
 
+    fn colors_enabled(&self) -> bool {
+        false
+    }
+
     // Interactive loop:
 
-    fn enable_raw_mode(&self) -> Result<Mode> {
+    fn enable_raw_mode(&mut self) -> Result<Mode> {
         Ok(())
     }
 
diff --git a/src/tty/unix.rs b/src/tty/unix.rs
index b1d5b78..5cc97e3 100644
--- a/src/tty/unix.rs
+++ b/src/tty/unix.rs
@@ -11,11 +11,13 @@
 use nix::sys::termios;
 use nix::sys::termios::SetArg;
 use unicode_segmentation::UnicodeSegmentation;
+use utf8parse::{Parser, Receiver};
 
 use super::{truncate, width, Position, RawMode, RawReader, Renderer, Term};
-use config::Config;
+use config::{ColorMode, Config};
 use consts::{self, KeyPress};
 use error;
+use highlight::Highlighter;
 use line_buffer::LineBuffer;
 use Result;
 
@@ -30,8 +32,8 @@
 
     unsafe {
         let mut size: libc::winsize = zeroed();
-        match libc::ioctl(STDOUT_FILENO, libc::TIOCGWINSZ.into(), &mut size) {
-            // .into() for FreeBSD
+        // https://github.com/rust-lang/libc/pull/704
+        match libc::ioctl(STDOUT_FILENO, libc::TIOCGWINSZ as libc::c_ulong, &mut size) {
             0 => (size.ws_col as usize, size.ws_row as usize), // TODO getCursorPosition
             _ => (80, 24),
         }
@@ -101,7 +103,14 @@
 pub struct PosixRawReader {
     stdin: StdinRaw,
     timeout_ms: i32,
-    buf: [u8; 4],
+    buf: [u8; 1],
+    parser: Parser,
+    receiver: Utf8,
+}
+
+struct Utf8 {
+    c: Option<char>,
+    valid: bool,
 }
 
 impl PosixRawReader {
@@ -109,149 +118,26 @@
         Ok(PosixRawReader {
             stdin: StdinRaw {},
             timeout_ms: config.keyseq_timeout(),
-            buf: [0; 4],
+            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. (CSI)
-            let seq2 = try!(self.next_char());
-            if seq2.is_digit(10) {
-                // Extended escape, read additional byte.
-                let seq3 = try!(self.next_char());
-                if seq3 == '~' {
-                    Ok(match seq2 {
-                        '1' | '7' => KeyPress::Home, // tmux, xrvt
-                        '2' => KeyPress::Insert,
-                        '3' => KeyPress::Delete,    // kdch1
-                        '4' | '8' => KeyPress::End, // tmux, xrvt
-                        '5' => KeyPress::PageUp,    // kpp
-                        '6' => KeyPress::PageDown,  // knp
-                        _ => {
-                            debug!(target: "rustyline",
-                            "unsupported esc sequence: ESC [ {} ~", seq2);
-                            KeyPress::UnknownEscSeq
-                        }
-                    })
-                } else if seq3.is_digit(10) {
-                    let seq4 = try!(self.next_char());
-                    if seq4 == '~' {
-                        Ok(match (seq2, seq3) {
-                            ('1', '1') => KeyPress::F(1),  // rxvt-unicode
-                            ('1', '2') => KeyPress::F(2),  // rxvt-unicode
-                            ('1', '3') => KeyPress::F(3),  // rxvt-unicode
-                            ('1', '4') => KeyPress::F(4),  // rxvt-unicode
-                            ('1', '5') => KeyPress::F(5),  // kf5
-                            ('1', '7') => KeyPress::F(6),  // kf6
-                            ('1', '8') => KeyPress::F(7),  // kf7
-                            ('1', '9') => KeyPress::F(8),  // kf8
-                            ('2', '0') => KeyPress::F(9),  // kf9
-                            ('2', '1') => KeyPress::F(10), // kf10
-                            ('2', '3') => KeyPress::F(11), // kf11
-                            ('2', '4') => KeyPress::F(12), // kf12
-                            _ => {
-                                debug!(target: "rustyline",
-                                "unsupported esc sequence: ESC [ {}{} ~", seq1, seq2);
-                                KeyPress::UnknownEscSeq
-                            }
-                        })
-                    } else if seq4 == ';' {
-                        let seq5 = try!(self.next_char());
-                        if seq5.is_digit(10) {
-                            let seq6 = try!(self.next_char()); // '~' expected
-                            debug!(target: "rustyline",
-                            "unsupported esc sequence: ESC [ {}{} ; {} {}", seq2, seq3, seq5, seq6);
-                        } else {
-                            debug!(target: "rustyline",
-                            "unsupported esc sequence: ESC [ {}{} ; {:?}", seq2, seq3, seq5);
-                        }
-                        Ok(KeyPress::UnknownEscSeq)
-                    } else {
-                        debug!(target: "rustyline",
-                        "unsupported esc sequence: ESC [ {}{} {:?}", seq2, seq3, seq4);
-                        Ok(KeyPress::UnknownEscSeq)
-                    }
-                } else if seq3 == ';' {
-                    let seq4 = try!(self.next_char());
-                    if seq4.is_digit(10) {
-                        let seq5 = try!(self.next_char());
-                        if seq2 == '1' {
-                            Ok(match (seq4, seq5) {
-                                ('5', 'A') => KeyPress::ControlUp,
-                                ('5', 'B') => KeyPress::ControlDown,
-                                ('5', 'C') => KeyPress::ControlRight,
-                                ('5', 'D') => KeyPress::ControlLeft,
-                                ('2', 'A') => KeyPress::ShiftUp,
-                                ('2', 'B') => KeyPress::ShiftDown,
-                                ('2', 'C') => KeyPress::ShiftRight,
-                                ('2', 'D') => KeyPress::ShiftLeft,
-                                _ => {
-                                    debug!(target: "rustyline",
-                                    "unsupported esc sequence: ESC [ {} ; {} {}", seq2, seq4, seq5);
-                                    KeyPress::UnknownEscSeq
-                                }
-                            })
-                        } else {
-                            debug!(target: "rustyline",
-                            "unsupported esc sequence: ESC [ {} ; {} {}", seq2, seq4, seq5);
-                            Ok(KeyPress::UnknownEscSeq)
-                        }
-                    } else {
-                        debug!(target: "rustyline",
-                        "unsupported esc sequence: ESC [ {} ; {:?}", seq2, seq4);
-                        Ok(KeyPress::UnknownEscSeq)
-                    }
-                } else {
-                    Ok(match (seq2, seq3) {
-                        ('5', 'A') => KeyPress::ControlUp,
-                        ('5', 'B') => KeyPress::ControlDown,
-                        ('5', 'C') => KeyPress::ControlRight,
-                        ('5', 'D') => KeyPress::ControlLeft,
-                        _ => {
-                            debug!(target: "rustyline",
-                            "unsupported esc sequence: ESC [ {} {:?}", seq2, seq3);
-                            KeyPress::UnknownEscSeq
-                        }
-                    })
-                }
-            } else {
-                // ANSI
-                Ok(match seq2 {
-                    'A' => KeyPress::Up,    // kcuu1
-                    'B' => KeyPress::Down,  // kcud1
-                    'C' => KeyPress::Right, // kcuf1
-                    'D' => KeyPress::Left,  // kcub1
-                    'F' => KeyPress::End,
-                    'H' => KeyPress::Home, // khome
-                    _ => {
-                        debug!(target: "rustyline", "unsupported esc sequence: ESC [ {:?}", seq2);
-                        KeyPress::UnknownEscSeq
-                    }
-                })
-            }
+            self.escape_csi()
         } else if seq1 == 'O' {
             // xterm
             // ESC O sequences. (SS3)
-            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
-                _ => {
-                    debug!(target: "rustyline", "unsupported esc sequence: ESC O {:?}", seq2);
-                    KeyPress::UnknownEscSeq
-                }
-            })
+            self.escape_o()
         } else if seq1 == '\x1b' {
             // ESC ESC
             Ok(KeyPress::Esc)
@@ -260,28 +146,163 @@
             Ok(KeyPress::Meta(seq1))
         }
     }
-}
 
-// https://tools.ietf.org/html/rfc3629
-#[cfg_attr(rustfmt, rustfmt_skip)]
-static UTF8_CHAR_WIDTH: [u8; 256] = [
-1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
-1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x1F
-1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
-1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x3F
-1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
-1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x5F
-1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
-1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x7F
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 0x9F
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 0xBF
-0,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
-2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // 0xDF
-3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, // 0xEF
-4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0, // 0xFF
-];
+    /// 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 {
+            // 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
+                _ => {
+                    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 [ {}{} ; {:?}", seq2, seq3, seq5);
+                }
+                Ok(KeyPress::UnknownEscSeq)
+            } else {
+                debug!(target: "rustyline",
+                       "unsupported esc sequence: ESC [ {}{} {:?}", seq2, seq3, seq4);
+                Ok(KeyPress::UnknownEscSeq)
+            }
+        } else if seq3 == ';' {
+            let seq4 = try!(self.next_char());
+            if seq4.is_digit(10) {
+                let seq5 = try!(self.next_char());
+                if seq2 == '1' {
+                    Ok(match (seq4, seq5) {
+                        ('5', 'A') => KeyPress::ControlUp,
+                        ('5', 'B') => KeyPress::ControlDown,
+                        ('5', 'C') => KeyPress::ControlRight,
+                        ('5', 'D') => KeyPress::ControlLeft,
+                        ('2', 'A') => KeyPress::ShiftUp,
+                        ('2', 'B') => KeyPress::ShiftDown,
+                        ('2', 'C') => KeyPress::ShiftRight,
+                        ('2', 'D') => KeyPress::ShiftLeft,
+                        _ => {
+                            debug!(target: "rustyline",
+                                   "unsupported esc sequence: ESC [ {} ; {} {}", seq2, seq4, seq5);
+                            KeyPress::UnknownEscSeq
+                        }
+                    })
+                } else {
+                    debug!(target: "rustyline",
+                           "unsupported esc sequence: ESC [ {} ; {} {}", seq2, seq4, seq5);
+                    Ok(KeyPress::UnknownEscSeq)
+                }
+            } else {
+                debug!(target: "rustyline",
+                       "unsupported esc sequence: ESC [ {} ; {:?}", seq2, seq4);
+                Ok(KeyPress::UnknownEscSeq)
+            }
+        } else {
+            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
+                }
+            })
+        }
+    }
+
+    /// 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,
+            'd' => KeyPress::ControlLeft,
+            _ => {
+                debug!(target: "rustyline", "unsupported esc sequence: ESC O {:?}", seq2);
+                KeyPress::UnknownEscSeq
+            }
+        })
+    }
+}
 
 impl RawReader for PosixRawReader {
     fn next_key(&mut self, single_esc_abort: bool) -> Result<KeyPress> {
@@ -312,26 +333,36 @@
     }
 
     fn next_char(&mut self) -> Result<char> {
-        let n = try!(self.stdin.read(&mut self.buf[..1]));
-        if n == 0 {
-            return Err(error::ReadlineError::Eof);
-        }
-        let first = self.buf[0];
-        if first >= 128 {
-            let width = UTF8_CHAR_WIDTH[first as usize] as usize;
-            if width == 0 {
-                try!(std::str::from_utf8(&self.buf[..1]));
-                unreachable!()
+        loop {
+            let n = try!(self.stdin.read(&mut self.buf));
+            if n == 0 {
+                return Err(error::ReadlineError::Eof);
             }
-            try!(self.stdin.read_exact(&mut self.buf[1..width]));
-            let s = try!(std::str::from_utf8(&self.buf[..width]));
-            Ok(s.chars().next().unwrap())
-        } else {
-            Ok(first as char)
+            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: Stdout,
@@ -397,6 +428,7 @@
         hint: Option<String>,
         current_row: usize,
         old_rows: usize,
+        highlighter: Option<&Highlighter>,
     ) -> Result<(Position, Position)> {
         use std::fmt::Write;
         let mut ab = String::new();
@@ -420,13 +452,25 @@
         // clear the line
         ab.push_str("\r\x1b[0K");
 
-        // display the prompt
-        ab.push_str(prompt);
-        // display the input line
-        ab.push_str(line);
+        if let Some(highlighter) = highlighter {
+            // display the prompt
+            ab.push_str(&highlighter.highlight_prompt(prompt));
+            // display the input line
+            ab.push_str(&highlighter.highlight(line, line.pos()));
+        } else {
+            // display the prompt
+            ab.push_str(prompt);
+            // display the input line
+            ab.push_str(line);
+        }
         // display hint
         if let Some(hint) = hint {
-            ab.push_str(truncate(&hint, end_pos.col, self.cols));
+            let truncate = truncate(&hint, end_pos.col, self.cols);
+            if let Some(highlighter) = highlighter {
+                ab.push_str(&highlighter.highlight_hint(truncate));
+            } else {
+                ab.push_str(truncate);
+            }
         }
         // we have to generate our own newline on line wrap
         if end_pos.col == 0 && end_pos.row > 0 {
@@ -457,7 +501,6 @@
 
     /// Control characters are treated as having zero width.
     /// Characters with 2 column width are correctly handled (not splitted).
-    #[allow(if_same_then_else)]
     fn calculate_position(&self, s: &str, orig: Position) -> Position {
         let mut pos = orig;
         let mut esc_seq = 0;
@@ -500,6 +543,7 @@
     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 {
@@ -533,19 +577,23 @@
 pub struct PosixTerminal {
     unsupported: bool,
     stdin_isatty: bool,
+    stdout_isatty: bool,
+    color_mode: ColorMode,
 }
 
 impl Term for PosixTerminal {
+    type Mode = Mode;
     type Reader = PosixRawReader;
     type Writer = PosixRenderer;
-    type Mode = Mode;
 
-    fn new() -> PosixTerminal {
+    fn new(color_mode: ColorMode) -> PosixTerminal {
         let term = PosixTerminal {
             unsupported: is_unsupported_term(),
             stdin_isatty: is_a_tty(STDIN_FILENO),
+            stdout_isatty: is_a_tty(STDOUT_FILENO),
+            color_mode,
         };
-        if !term.unsupported && term.stdin_isatty && is_a_tty(STDOUT_FILENO) {
+        if !term.unsupported && term.stdin_isatty && term.stdout_isatty {
             install_sigwinch_handler();
         }
         term
@@ -564,9 +612,18 @@
         self.stdin_isatty
     }
 
+    /// Check if output supports colors.
+    fn colors_enabled(&self) -> bool {
+        match self.color_mode {
+            ColorMode::Enabled => self.stdout_isatty,
+            ColorMode::Forced => true,
+            ColorMode::Disabled => false,
+        }
+    }
+
     // Interactive loop:
 
-    fn enable_raw_mode(&self) -> Result<Mode> {
+    fn enable_raw_mode(&mut self) -> Result<Mode> {
         use nix::errno::Errno::ENOTTY;
         use nix::sys::termios::{ControlFlags, InputFlags, LocalFlags, SpecialCharacterIndices};
         if !self.stdin_isatty {
diff --git a/src/tty/windows.rs b/src/tty/windows.rs
index 6d9fa36..e3a3aa5 100644
--- a/src/tty/windows.rs
+++ b/src/tty/windows.rs
@@ -9,9 +9,10 @@
 use winapi::um::{consoleapi, handleapi, processenv, winbase, wincon, winuser};
 
 use super::{truncate, Position, RawMode, RawReader, Renderer, Term};
-use config::Config;
+use config::{ColorMode, Config};
 use consts::{self, KeyPress};
 use error;
+use highlight::Highlighter;
 use line_buffer::LineBuffer;
 use Result;
 
@@ -65,7 +66,7 @@
 pub struct ConsoleMode {
     original_stdin_mode: DWORD,
     stdin_handle: HANDLE,
-    original_stdout_mode: DWORD,
+    original_stdout_mode: Option<DWORD>,
     stdout_handle: HANDLE,
 }
 
@@ -76,10 +77,12 @@
             self.stdin_handle,
             self.original_stdin_mode,
         ));
-        check!(consoleapi::SetConsoleMode(
-            self.stdout_handle,
-            self.original_stdout_mode,
-        ));
+        if let Some(original_stdout_mode) = self.original_stdout_mode {
+            check!(consoleapi::SetConsoleMode(
+                self.stdout_handle,
+                original_stdout_mode,
+            ));
+        }
         Ok(())
     }
 }
@@ -134,11 +137,11 @@
             // 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 meta = alt && !alt_gr;
 
             let utf16 = unsafe { *key_event.uChar.UnicodeChar() };
             if utf16 == 0 {
@@ -190,8 +193,7 @@
                     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.
+                    // also set.
                     _ => continue,
                 };
             } else if utf16 == 27 {
@@ -289,6 +291,7 @@
         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);
@@ -305,14 +308,26 @@
             info.dwCursorPosition,
         ));
         let mut ab = String::new();
-        // display the prompt
-        // TODO handle ansi escape code (SetConsoleTextAttribute)
-        ab.push_str(prompt);
-        // display the input line
-        ab.push_str(&line);
+        if let Some(highlighter) = highlighter {
+            // TODO handle ansi escape code (SetConsoleTextAttribute)
+            // display the prompt
+            ab.push_str(&highlighter.highlight_prompt(prompt));
+            // display the input line
+            ab.push_str(&highlighter.highlight(line, line.pos()));
+        } else {
+            // display the prompt
+            ab.push_str(prompt);
+            // display the input line
+            ab.push_str(line);
+        }
         // display hint
         if let Some(hint) = hint {
-            ab.push_str(truncate(&hint, end_pos.col, self.cols));
+            let truncate = truncate(&hint, end_pos.col, self.cols);
+            if let Some(highlighter) = highlighter {
+                ab.push_str(&highlighter.highlight_hint(truncate));
+            } else {
+                ab.push_str(truncate);
+            }
         }
         try!(self.write_and_flush(ab.as_bytes()));
 
@@ -396,17 +411,20 @@
 pub struct Console {
     stdin_isatty: bool,
     stdin_handle: HANDLE,
+    stdout_isatty: bool,
     stdout_handle: HANDLE,
+    color_mode: ColorMode,
+    ansi_colors_supported: bool,
 }
 
 impl Console {}
 
 impl Term for Console {
+    type Mode = Mode;
     type Reader = ConsoleRawReader;
     type Writer = ConsoleRenderer;
-    type Mode = Mode;
 
-    fn new() -> Console {
+    fn new(color_mode: ColorMode) -> Console {
         use std::ptr;
         let stdin_handle = get_std_handle(STDIN_FILENO);
         let stdin_isatty = match stdin_handle {
@@ -416,12 +434,22 @@
             }
             Err(_) => false,
         };
+        let stdout_handle = get_std_handle(STDOUT_FILENO);
+        let stdout_isatty = match stdout_handle {
+            Ok(handle) => {
+                // If this function doesn't fail then fd is a TTY
+                get_console_mode(handle).is_ok()
+            }
+            Err(_) => false,
+        };
 
-        let stdout_handle = get_std_handle(STDOUT_FILENO).unwrap_or(ptr::null_mut());
         Console {
             stdin_isatty,
             stdin_handle: stdin_handle.unwrap_or(ptr::null_mut()),
-            stdout_handle,
+            stdout_isatty,
+            stdout_handle: stdout_handle.unwrap_or(ptr::null_mut()),
+            color_mode,
+            ansi_colors_supported: false,
         }
     }
 
@@ -434,12 +462,21 @@
         self.stdin_isatty
     }
 
+    fn colors_enabled(&self) -> bool {
+        // TODO ANSI Colors & Windows <10
+        match self.color_mode {
+            ColorMode::Enabled => self.stdout_isatty && self.ansi_colors_supported,
+            ColorMode::Forced => true,
+            ColorMode::Disabled => false,
+        }
+    }
+
     // pub fn install_sigwinch_handler(&mut self) {
     // See ReadConsoleInputW && WINDOW_BUFFER_SIZE_EVENT
     // }
 
     /// 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,
@@ -448,10 +485,9 @@
         }
         let original_stdin_mode = try!(get_console_mode(self.stdin_handle));
         // Disable these modes
-        let raw = original_stdin_mode
-            & !(wincon::ENABLE_LINE_INPUT
-                | wincon::ENABLE_ECHO_INPUT
-                | wincon::ENABLE_PROCESSED_INPUT);
+        let raw = original_stdin_mode & !(wincon::ENABLE_LINE_INPUT
+            | wincon::ENABLE_ECHO_INPUT
+            | wincon::ENABLE_PROCESSED_INPUT);
         // Enable these modes
         let raw = raw | wincon::ENABLE_EXTENDED_FLAGS;
         let raw = raw | wincon::ENABLE_INSERT_MODE;
@@ -459,13 +495,19 @@
         let raw = raw | wincon::ENABLE_WINDOW_INPUT;
         check!(consoleapi::SetConsoleMode(self.stdin_handle, raw));
 
-        let original_stdout_mode = try!(get_console_mode(self.stdout_handle));
-        // To enable ANSI colors (Windows 10 only):
-        // https://docs.microsoft.com/en-us/windows/console/setconsolemode
-        if original_stdout_mode & wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0 {
-            let raw = original_stdout_mode | wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING;
-            check!(consoleapi::SetConsoleMode(self.stdout_handle, raw));
-        }
+        let original_stdout_mode = if self.stdout_isatty {
+            let original_stdout_mode = try!(get_console_mode(self.stdout_handle));
+            // To enable ANSI colors (Windows 10 only):
+            // https://docs.microsoft.com/en-us/windows/console/setconsolemode
+            if original_stdout_mode & wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0 {
+                let raw = original_stdout_mode | wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING;
+                self.ansi_colors_supported =
+                    unsafe { consoleapi::SetConsoleMode(self.stdout_handle, raw) != 0 };
+            }
+            Some(original_stdout_mode)
+        } else {
+            None
+        };
 
         Ok(Mode {
             original_stdin_mode,
diff --git a/src/undo.rs b/src/undo.rs
index 84c8f77..7f7c9ab 100644
--- a/src/undo.rs
+++ b/src/undo.rs
@@ -177,11 +177,10 @@
             return;
         }
 
-        if !Self::single_char(string.as_ref())
-            || !self
-                .undos
-                .last()
-                .map_or(false, |lc| lc.delete_seq(indx, string.as_ref().len()))
+        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,
@@ -330,18 +329,22 @@
 
 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);
     }