Merge remote-tracking branch 'origin/configurer'
diff --git a/Cargo.toml b/Cargo.toml
index 47ad106..af6ebb2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "rustyline"
-version = "2.0.0-alpha"
+version = "2.0.0"
 authors = ["Katsu Kawakami <kkawa1570@gmail.com>"]
 description = "Rustyline, a readline implementation based on Antirez's Linenoise"
 documentation = "http://docs.rs/rustyline"
@@ -21,6 +21,7 @@
 log = "0.4"
 unicode-width = "0.1"
 unicode-segmentation = "1.0"
+memchr = "2.0"
 
 [target.'cfg(unix)'.dependencies]
 nix = "0.11"
diff --git a/TODO.md b/TODO.md
index 6690366..d4cd263 100644
--- a/TODO.md
+++ b/TODO.md
@@ -7,7 +7,7 @@
 - [ ] bell-style
 
 Color
-- [x] ANSI Colors & Windows 10+
+- [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)
@@ -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,6 +75,7 @@
 
 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 ?)
diff --git a/examples/example.rs b/examples/example.rs
index 3e75024..ce41cf4 100644
--- a/examples/example.rs
+++ b/examples/example.rs
@@ -2,6 +2,7 @@
 extern crate rustyline;
 
 use log::{Level, LevelFilter, Metadata, Record, SetLoggerError};
+use std::borrow::Cow::{self, Borrowed, Owned};
 
 use rustyline::completion::{Completer, FilenameCompleter, Pair};
 use rustyline::error::ReadlineError;
@@ -9,13 +10,8 @@
 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);
@@ -31,18 +27,22 @@
 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 {}
+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 {}
 
diff --git a/src/completion.rs b/src/completion.rs
index aeb9a34..ab7a020 100644
--- a/src/completion.rs
+++ b/src/completion.rs
@@ -1,11 +1,11 @@
 //! 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
@@ -108,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,
         }
     }
 }
@@ -150,25 +164,32 @@
     type Candidate = Pair;
 
     fn complete(&self, line: &str, pos: usize) -> Result<(usize, Vec<Pair>)> {
-        let (start, path, esc_char, break_chars) =
-            if let Some((idx, double_quote)) = find_unclosed_quote(&line[..pos]) {
+        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))
     }
 }
@@ -179,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 {
@@ -200,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);
@@ -223,7 +264,8 @@
 fn filename_complete(
     path: &str,
     esc_char: Option<char>,
-    break_chars: &BTreeSet<char>,
+    break_chars: &[u8],
+    quote: Quote,
 ) -> Result<Vec<Pair>> {
     use dirs::home_dir;
     use std::env::current_dir;
@@ -261,14 +303,16 @@
         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(Pair {
-                    display: String::from(s),
-                    replacement: 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
             }
         }
     }
@@ -283,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() {
@@ -300,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;
@@ -356,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;
@@ -366,6 +410,7 @@
                 if char == '"' {
                     mode = ScanMode::Normal;
                 } else if char == '\\' {
+                    // both windows and unix support escape in double quote
                     mode = ScanMode::EscapeInDoubleQuote;
                 }
             }
@@ -393,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"),
@@ -423,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]
@@ -480,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/edit.rs b/src/edit.rs
index 6a82da6..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,
         }
     }
 
@@ -96,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(())
     }
@@ -109,6 +122,7 @@
             hint,
             self.cursor.row,
             self.old_rows,
+            self.highlighter,
         ));
 
         self.cursor = cursor;
@@ -175,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
@@ -490,6 +505,7 @@
         byte_buffer: [0; 4],
         changes: Rc::new(RefCell::new(Changeset::new())),
         hinter: None,
+        highlighter: None,
     }
 }
 
diff --git a/src/highlight.rs b/src/highlight.rs
index 8e9c972..c09e5e1 100644
--- a/src/highlight.rs
+++ b/src/highlight.rs
@@ -1,13 +1,22 @@
-///! Syntax highlighting
+//! 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). TODO to be used
+/// 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
+    /// Takes the currently edited `line` with the cursor `pos`ition and
     /// returns the highlighted version (with ANSI color).
-    fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> {
+    ///
+    /// 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
@@ -15,6 +24,11 @@
     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> {
@@ -22,9 +36,25 @@
     }
     /// Takes the completion `canditate` and
     /// returns the highlighted version (with ANSI color).
-    fn highlight_canidate<'c>(&self, candidate: &'c str) -> Cow<'c, str> {
+    ///
+    /// 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/lib.rs b/src/lib.rs
index e3254b0..35f5b76 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -21,6 +21,7 @@
 extern crate libc;
 #[macro_use]
 extern crate log;
+extern crate memchr;
 #[cfg(unix)]
 extern crate nix;
 extern crate unicode_segmentation;
@@ -76,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
@@ -94,7 +96,14 @@
         loop {
             // Show completion or original buffer
             if i < candidates.len() {
-                completer.update(&mut s.line, start, candidates[i].replacement());
+                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
@@ -171,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)
@@ -185,6 +194,7 @@
     rdr: &mut R,
     s: &mut State,
     input_state: &mut InputState,
+    highlighter: Option<&Highlighter>,
     candidates: &[C],
 ) -> Result<Option<Cmd>> {
     use std::cmp;
@@ -239,8 +249,12 @@
             let i = (col * num_rows) + row;
             if i < candidates.len() {
                 let candidate = &candidates[i].display();
-                ab.push_str(candidate);
                 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(' ');
@@ -344,7 +358,6 @@
 /// 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)>,
@@ -353,11 +366,22 @@
 ) -> 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.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());
@@ -387,6 +411,7 @@
                 &mut s,
                 &mut input_state,
                 completer.unwrap(),
+                highlighter,
                 &editor.config,
             ));
             if next.is_some() {
@@ -581,6 +606,9 @@
             }
         }
     }
+    if cfg!(windows) {
+        let _ = original_mode; // silent warning
+    }
     Ok(s.line.into_string())
 }
 
@@ -647,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> {
diff --git a/src/line_buffer.rs b/src/line_buffer.rs
index 1c220ca..e17fd62 100644
--- a/src/line_buffer.rs
+++ b/src/line_buffer.rs
@@ -153,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 {
diff --git a/src/test/mod.rs b/src/test/mod.rs
index f2d6ea2..1ce9881 100644
--- a/src/test/mod.rs
+++ b/src/test/mod.rs
@@ -45,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 e96f676..e8c7db4 100644
--- a/src/tty/mod.rs
+++ b/src/tty/mod.rs
@@ -5,6 +5,7 @@
 
 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
@@ -86,8 +88,17 @@
         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 {
diff --git a/src/tty/test.rs b/src/tty/test.rs
index c4aa81c..75844be 100644
--- a/src/tty/test.rs
+++ b/src/tty/test.rs
@@ -7,6 +7,7 @@
 use config::{ColorMode, Config};
 use consts::KeyPress;
 use error::ReadlineError;
+use highlight::Highlighter;
 use line_buffer::LineBuffer;
 use Result;
 
@@ -71,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 {
diff --git a/src/tty/unix.rs b/src/tty/unix.rs
index 1a07a0e..d3b6f76 100644
--- a/src/tty/unix.rs
+++ b/src/tty/unix.rs
@@ -17,6 +17,7 @@
 use config::{ColorMode, Config};
 use consts::{self, KeyPress};
 use error;
+use highlight::Highlighter;
 use line_buffer::LineBuffer;
 use Result;
 
@@ -31,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),
         }
@@ -427,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();
@@ -450,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 {
@@ -487,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;
diff --git a/src/tty/windows.rs b/src/tty/windows.rs
index 249efad..098cae4 100644
--- a/src/tty/windows.rs
+++ b/src/tty/windows.rs
@@ -12,6 +12,7 @@
 use config::{ColorMode, Config};
 use consts::{self, KeyPress};
 use error;
+use highlight::Highlighter;
 use line_buffer::LineBuffer;
 use Result;
 
@@ -192,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 {
@@ -291,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);
@@ -307,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()));
 
@@ -450,8 +463,9 @@
     }
 
     fn colors_enabled(&self) -> bool {
+        // TODO ANSI Colors & Windows <10
         match self.color_mode {
-            ColorMode::Enabled => self.stdout_isatty,
+            ColorMode::Enabled => self.stdout_isatty && self.ansi_colors_supported,
             ColorMode::Forced => true,
             ColorMode::Disabled => false,
         }