Repository: kkawakam/rustyline
Pull request #354 (2020-03-28 16:11:46)

Print an error message while trying to use the dummy implementation.
It was quite confusing that rustyline did not work under WASI with no error message.

marmistrz: bump
gwenn: Sorry for the late answer.
I am not sure but there is a dedicated method ([is_unsupported](https://github.com/kkawakam/rustyline/blob/master/src/tty/mod.rs#L214)) to tell that `stdin` is bound to an unsupported terminal. When `is_unsupported` returns false, you fallback to [`io::stdin().read_line`](https://github.com/kkawakam/rustyline/blob/master/src/lib.rs#L713).
So I suggest we redefine `is_unsupported` for [DummyTerminal](https://github.com/kkawakam/rustyline/blob/master/src/tty/test.rs#L162) on wasm32 .

Patch:
diff --git a/BUGS.md b/BUGS.md
index 550f8c1..8691077 100644
--- a/BUGS.md
+++ b/BUGS.md
@@ -23,14 +23,3 @@ We would like to support user defined actions that interact nicely with undo man
 To do so, we need to refactor current key event dispatch.
 
 See `replxx` design (`ACTION_RESULT`, `action_trait_t`).
-
-## Line wrapping (should be fixed with verions >= 6.1.2)
-
-On Unix platform, we assume that `auto_right_margin` (`am`) is enabled.
-And on Windows, we activate `ENABLE_WRAP_AT_EOL_OUTPUT`.
-But on Windows 10, `ENABLE_WRAP_AT_EOL_OUTPUT` and `ENABLE_VIRTUAL_TERMINAL_PROCESSING` seems to be incompatible.
-
-## Colors
-
-We assume that ANSI colors are supported.
-Which is not the case on Windows (except on Windows 10)!
diff --git a/Cargo.toml b/Cargo.toml
index 93c9333..dc3f73b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "rustyline"
-version = "6.1.2"
+version = "6.0.0"
 authors = ["Katsu Kawakami <kkawa1570@gmail.com>"]
 edition = "2018"
 description = "Rustyline, a readline implementation based on Antirez's Linenoise"
@@ -20,7 +20,7 @@ members = ["rustyline-derive"]
 
 [dependencies]
 cfg-if = "0.1.6"
-dirs-next = { version = "1.0", optional = true }
+dirs = { version = "2.0", optional = true }
 libc = "0.2"
 log = "0.4"
 unicode-width = "0.1"
@@ -34,18 +34,17 @@ skim = { version = "0.7", optional = true }
 
 [target.'cfg(windows)'.dependencies]
 winapi = { version = "0.3", features = ["consoleapi", "handleapi", "minwindef", "processenv", "winbase", "wincon", "winuser"] }
-scopeguard = "1.1"
 
 [dev-dependencies]
 doc-comment = "0.3"
 env_logger = "0.7"
 tempdir = "0.3"
 assert_matches = "1.2"
-rustyline-derive = { version = "0.3.1", path = "rustyline-derive" }
+rustyline-derive = { version = "0.3.0", path = "rustyline-derive" }
 
 [features]
 default = ["with-dirs"]
-with-dirs = ["dirs-next"]
+with-dirs = ["dirs"]
 with-fuzzy = ["skim"]
 
 [package.metadata.docs.rs]
diff --git a/Incremental.md b/Incremental.md
deleted file mode 100644
index 51e3130..0000000
--- a/Incremental.md
+++ /dev/null
@@ -1,52 +0,0 @@
-## Incremental computation
-
-We would like to avoid redrawing all row(s) when an event occurs.
-Currently, we redraw all row(s) except when:
-  * a character is inserted at the end of input (and there is no hint and no `highlight_char`),
-  * only the cursor is moved (input is not touched and no `highlight_char`).
-
-Ideally, we would like to redraw only impacted row(s) / cell(s).
-
-### Observable values
-
-Currently, we assume that highlighting does not impact layout / rendered text size.
-So only the following observables impact layout:
-  * prompt (interactive search, [input mode indicator](https://github.com/kkawakam/rustyline/pull/369)),
-  * [input mode](https://github.com/kkawakam/rustyline/pull/369),
-  * line(s) buffer,
-  * cursor position,
-  * hint / input validation message,
-  * screen size (line wrapping),
-  * [prompt continuation](https://github.com/kkawakam/rustyline/pull/372)s,
-  * row/wrap count.
-
-Some other values may impact layout but they are/should be constant:
-  * tab stop,
-
-### Line wrapping and highlighting
-
-Currently, there is no word wrapping (only grapheme wrapping).
-But we highlight the whole input at once.
-So there is no issue: for example, even if a keyword is wrapped, style is preserved.
-
-With [prompt continuation](https://github.com/kkawakam/rustyline/pull/372)s,
-we (will) interleave user input with continuations.
-So we need to preserve style.
-
-TODO How prompt_toolkit handle this problem ?
-Maybe using ANSI sequence directly was a bad idea. If `Highlighter` returns style ranges,
-applying style on input slice is easy (and also supporting styles on Windows < 10). 
-
-### Impacts
-
-Current granularity:
- * PUSH_CHAR at end of input
- * BEEP
- * MOVE_CURSOR
- * REFRESH whole input / rows
- * CLEAR_SCREEN (+REFRESH)
-
-Wanted additional granurality:
-  * PUSH_STRING at end of input
-  * REFRESH_DIRTY only rows / cells
-
diff --git a/README.md b/README.md
index b5bd259..f386d17 100644
--- a/README.md
+++ b/README.md
@@ -59,7 +59,7 @@ to your `Cargo.toml`:
 
 ```toml
 [dependencies]
-rustyline = "6.1.2"
+rustyline = "6.0.0"
 ```
 
 ## Features
@@ -199,8 +199,6 @@ $ # current settings of all terminal attributes:
 $ stty -a
 $ # key bindings:
 $ bind -p
-$ # print out a terminfo description:
-$ infocmp
 ```
 
 ## Similar projects
diff --git a/TODO.md b/TODO.md
index e0310d2..7189a31 100644
--- a/TODO.md
+++ b/TODO.md
@@ -16,7 +16,7 @@ Color
 
 Completion
 - [X] Quoted path
-- [X] Windows escape/unescape space in path
+- [ ] Windows escape/unescape space in path
 - [ ] file completion & escape/unescape (#106)
 - [ ] file completion & tilde (#62)
 - [X] display versus replacement
diff --git a/examples/example.rs b/examples/example.rs
index 2b3363f..9614fc0 100644
--- a/examples/example.rs
+++ b/examples/example.rs
@@ -3,7 +3,7 @@ use std::borrow::Cow::{self, Borrowed, Owned};
 use rustyline::completion::{Completer, FilenameCompleter, Pair};
 use rustyline::config::OutputStreamType;
 use rustyline::error::ReadlineError;
-use rustyline::highlight::{Highlighter, MatchingBracketHighlighter};
+use rustyline::highlight::{Highlighter, MatchingBracketHighlighter, PromptInfo};
 use rustyline::hint::{Hinter, HistoryHinter};
 use rustyline::validate::{self, MatchingBracketValidator, Validator};
 use rustyline::{Cmd, CompletionType, Config, Context, EditMode, Editor, KeyPress};
@@ -16,6 +16,7 @@ struct MyHelper {
     validator: MatchingBracketValidator,
     hinter: HistoryHinter,
     colored_prompt: String,
+    continuation_prompt: String,
 }
 
 impl Completer for MyHelper {
@@ -41,15 +42,23 @@ impl Highlighter for MyHelper {
     fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
         &'s self,
         prompt: &'p str,
-        default: bool,
+        info: PromptInfo<'_>,
     ) -> Cow<'b, str> {
-        if default {
-            Borrowed(&self.colored_prompt)
+        if info.default() {
+            if info.line_no() > 0 {
+                Borrowed(&self.continuation_prompt)
+            } else {
+                Borrowed(&self.colored_prompt)
+            }
         } else {
             Borrowed(prompt)
         }
     }
 
+    fn has_continuation_prompt(&self) -> bool {
+        true
+    }
+
     fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
         Owned("\x1b[1m".to_owned() + hint + "\x1b[m")
     }
@@ -90,7 +99,8 @@ fn main() -> rustyline::Result<()> {
         completer: FilenameCompleter::new(),
         highlighter: MatchingBracketHighlighter::new(),
         hinter: HistoryHinter {},
-        colored_prompt: "".to_owned(),
+        colored_prompt: "  0> ".to_owned(),
+        continuation_prompt: "\x1b[1;32m...> \x1b[0m".to_owned(),
         validator: MatchingBracketValidator::new(),
     };
     let mut rl = Editor::with_config(config);
@@ -102,7 +112,7 @@ fn main() -> rustyline::Result<()> {
     }
     let mut count = 1;
     loop {
-        let p = format!("{}> ", count);
+        let p = format!("{:>3}> ", count);
         rl.helper_mut().expect("No helper").colored_prompt = format!("\x1b[1;32m{}\x1b[0m", p);
         let readline = rl.readline(&p);
         match readline {
diff --git a/examples/input_validation.rs b/examples/input_validation.rs
index 1fbc015..389c5f5 100644
--- a/examples/input_validation.rs
+++ b/examples/input_validation.rs
@@ -8,16 +8,15 @@ struct InputValidator {}
 
 impl Validator for InputValidator {
     fn validate(&self, ctx: &mut ValidationContext) -> Result<ValidationResult, ReadlineError> {
-        use ValidationResult::{Incomplete, Invalid, Valid};
         let input = ctx.input();
-        let result = if !input.starts_with("SELECT") {
-            Invalid(Some(" --< Expect: SELECT stmt".to_owned()))
+        if !input.starts_with("SELECT") {
+            return Ok(ValidationResult::Invalid(Some(
+                " --< Expect: SELECT stmt".to_owned(),
+            )));
         } else if !input.ends_with(';') {
-            Incomplete
-        } else {
-            Valid(None)
-        };
-        Ok(result)
+            return Ok(ValidationResult::Incomplete);
+        }
+        Ok(ValidationResult::Valid(None))
     }
 }
 
diff --git a/rustfmt.toml b/rustfmt.toml
index 4046229..83697e3 100644
--- a/rustfmt.toml
+++ b/rustfmt.toml
@@ -2,12 +2,3 @@ wrap_comments = true
 format_strings = true
 error_on_unformatted = false
 reorder_impl_items = true
-
-condense_wildcard_suffixes = true
-format_code_in_doc_comments = true
-format_macro_matchers = true
-format_macro_bodies = true
-#merge_imports = true
-normalize_doc_attributes = true
-use_field_init_shorthand = true
-use_try_shorthand = true
\ No newline at end of file
diff --git a/rustyline-derive/Cargo.toml b/rustyline-derive/Cargo.toml
index 2efaabe..e9e94a0 100644
--- a/rustyline-derive/Cargo.toml
+++ b/rustyline-derive/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "rustyline-derive"
-version = "0.3.1"
+version = "0.3.0"
 authors = ["gwenn"]
 edition = "2018"
 description = "Rustyline macros implementation of #[derive(Completer, Helper, Hinter, Highlighter)]"
diff --git a/src/completion.rs b/src/completion.rs
index 79ac346..e2eb53f 100644
--- a/src/completion.rs
+++ b/src/completion.rs
@@ -30,11 +30,8 @@ impl Candidate for String {
     }
 }
 
-/// Completion candidate pair
 pub struct Pair {
-    /// Text to display when listing alternatives.
     pub display: String,
-    /// Text to insert in line.
     pub replacement: String,
 }
 
@@ -50,7 +47,6 @@ impl Candidate for Pair {
 
 /// To be called for tab-completion.
 pub trait Completer {
-    /// Specific completion candidate.
     type Candidate: Candidate;
 
     /// Takes the currently edited `line` with the cursor `pos`ition and
@@ -153,19 +149,14 @@ cfg_if::cfg_if! {
     }
 }
 
-/// Kind of quote.
 #[derive(Clone, Copy, Debug, PartialEq)]
 pub enum Quote {
-    /// Double quote: `"`
     Double,
-    /// Single quote: `'`
     Single,
-    /// No quote
     None,
 }
 
 impl FilenameCompleter {
-    /// Constructor
     pub fn new() -> Self {
         Self {
             break_chars: &DEFAULT_BREAK_CHARS,
@@ -173,9 +164,6 @@ impl FilenameCompleter {
         }
     }
 
-    /// Takes the currently edited `line` with the cursor `pos`ition and
-    /// returns the start position and the completion candidates for the
-    /// partial path to be completed.
     pub fn complete_path(&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]) {
@@ -202,8 +190,7 @@ impl FilenameCompleter {
                 let path = unescape(path, ESCAPE_CHAR);
                 (start, path, ESCAPE_CHAR, &self.break_chars, Quote::None)
             };
-        let mut matches = filename_complete(&path, esc_char, break_chars, quote)?;
-        matches.sort_by(|a, b| a.display().cmp(b.display()));
+        let matches = filename_complete(&path, esc_char, break_chars, quote)?;
         Ok((start, matches))
     }
 }
@@ -298,7 +285,7 @@ fn filename_complete(
     quote: Quote,
 ) -> Result<Vec<Pair>> {
     #[cfg(feature = "with-dirs")]
-    use dirs_next::home_dir;
+    use dirs::home_dir;
     use std::env::current_dir;
 
     let sep = path::MAIN_SEPARATOR;
@@ -345,12 +332,10 @@ fn filename_complete(
 
     // if any of the below IO operations have errors, just ignore them
     if let Ok(read_dir) = dir.read_dir() {
-        let file_name = normalize(file_name);
         for entry in read_dir {
             if let Ok(entry) = entry {
                 if let Some(s) = entry.file_name().to_str() {
-                    let ns = normalize(s);
-                    if ns.starts_with(file_name.as_ref()) {
+                    if s.starts_with(file_name) {
                         if let Ok(metadata) = fs::metadata(entry.path()) {
                             let mut path = String::from(dir_name) + s;
                             if metadata.is_dir() {
@@ -369,17 +354,6 @@ fn filename_complete(
     Ok(entries)
 }
 
-#[cfg(any(windows, target_os = "macos"))]
-fn normalize(s: &str) -> Cow<str> {
-    // case insensitive
-    Cow::Owned(s.to_lowercase())
-}
-
-#[cfg(not(any(windows, target_os = "macos")))]
-fn normalize(s: &str) -> Cow<str> {
-    Cow::Borrowed(s)
-}
-
 /// Given a `line` and a cursor `pos`ition,
 /// try to find backward the start of a word.
 /// Return (0, `line[..pos]`) if no break char has been found.
@@ -419,7 +393,6 @@ pub fn extract_word<'l>(
     }
 }
 
-/// Returns the longest common prefix among all `Candidate::replacement()`s.
 pub fn longest_common_prefix<C: Candidate>(candidates: &[C]) -> Option<&str> {
     if candidates.is_empty() {
         return None;
@@ -608,10 +581,4 @@ mod tests {
             super::find_unclosed_quote("\"c:\\users\\All Users\\")
         )
     }
-
-    #[cfg(windows)]
-    #[test]
-    pub fn normalize() {
-        assert_eq!(super::normalize("Windows"), "windows")
-    }
 }
diff --git a/src/config.rs b/src/config.rs
index 08aaa98..fd4e693 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -31,7 +31,6 @@ pub struct Config {
 }
 
 impl Config {
-    /// Returns a `Config` builder.
     pub fn builder() -> Builder {
         Builder::new()
     }
@@ -73,30 +72,18 @@ impl Config {
         self.history_ignore_space = yes;
     }
 
-    /// Completion behaviour.
-    ///
-    /// By default, `CompletionType::Circular`.
     pub fn completion_type(&self) -> CompletionType {
         self.completion_type
     }
 
-    /// When listing completion alternatives, only display
-    /// one screen of possibilities at a time (used for `CompletionType::List`
-    /// mode).
     pub fn completion_prompt_limit(&self) -> usize {
         self.completion_prompt_limit
     }
 
-    /// Duration (milliseconds) Rustyline will wait for a character when
-    /// reading an ambiguous key sequence (used for `EditMode::Vi` mode on unix
-    /// platform).
-    ///
-    /// By default, no timeout (-1) or 500ms if `EditMode::Vi` is activated.
     pub fn keyseq_timeout(&self) -> i32 {
         self.keyseq_timeout
     }
 
-    /// Emacs or Vi mode
     pub fn edit_mode(&self) -> EditMode {
         self.edit_mode
     }
@@ -124,9 +111,6 @@ impl Config {
         self.color_mode = color_mode;
     }
 
-    /// Tell which output stream should be used: stdout or stderr.
-    ///
-    /// By default, stdout is used.
     pub fn output_stream(&self) -> OutputStreamType {
         self.output_stream
     }
@@ -136,8 +120,6 @@ impl Config {
     }
 
     /// Horizontal space taken by a tab.
-    ///
-    /// By default, 8.
     pub fn tab_stop(&self) -> usize {
         self.tab_stop
     }
@@ -191,18 +173,14 @@ impl Default for BellStyle {
     }
 }
 
-/// History filter
 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
 pub enum HistoryDuplicates {
-    /// No filter
     AlwaysAdd,
     /// a line will not be added to the history if it matches the previous entry
     IgnoreConsecutive,
 }
 
-/// Tab completion style
 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
-#[non_exhaustive]
 pub enum CompletionType {
     /// Complete the next full match (like in Vim by default)
     Circular,
@@ -221,34 +199,24 @@ pub enum CompletionType {
 
 /// Style of editing / Standard keymaps
 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
-#[non_exhaustive]
 pub enum EditMode {
-    /// Emacs keymap
     Emacs,
-    /// Vi keymap
     Vi,
 }
 
 /// Colorization mode
 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
-#[non_exhaustive]
 pub enum ColorMode {
-    /// Activate highlighting if platform/terminal is supported.
     Enabled,
-    /// Activate highlighting even if platform is not supported (windows < 10).
     Forced,
-    /// Deactivate highlighting even if platform/terminal is supported.
     Disabled,
 }
 
 /// Should the editor use stdout or stderr
 // TODO console term::TermTarget
 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
-#[non_exhaustive]
 pub enum OutputStreamType {
-    /// Use stderr
     Stderr,
-    /// Use stdout
     Stdout,
 }
 
@@ -259,7 +227,6 @@ pub struct Builder {
 }
 
 impl Builder {
-    /// Returns a `Config` builder.
     pub fn new() -> Self {
         Self {
             p: Config::default(),
@@ -357,7 +324,6 @@ impl Builder {
         self
     }
 
-    /// Builds a `Config` with the settings specified so far.
     pub fn build(self) -> Config {
         self.p
     }
@@ -369,9 +335,7 @@ impl Configurer for Builder {
     }
 }
 
-/// Trait for component that holds a `Config`.
 pub trait Configurer {
-    /// `Config` accessor.
     fn config_mut(&mut self) -> &mut Config;
 
     /// Set the maximum length for the history.
diff --git a/src/edit.rs b/src/edit.rs
index 0285c8e..0c93725 100644
--- a/src/edit.rs
+++ b/src/edit.rs
@@ -89,7 +89,7 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
                 self.refresh_line()?;
                 continue;
             }
-            if let Ok(Cmd::Replace(..)) = rc {
+            if let Ok(Cmd::Replace(_, _)) = rc {
                 self.changes.borrow_mut().begin();
             }
             return rc;
@@ -112,7 +112,7 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
         // calculate the desired position of the cursor
         let cursor = self
             .out
-            .calculate_position(&self.line[..self.line.pos()], self.prompt_size);
+            .calculate_position(&self.line[..self.line.pos()], Position::default());
         if self.layout.cursor == cursor {
             return Ok(());
         }
@@ -123,7 +123,6 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
             self.out.move_cursor(self.layout.cursor, cursor)?;
             self.layout.prompt_size = self.prompt_size;
             self.layout.cursor = cursor;
-            debug_assert!(self.layout.prompt_size <= self.layout.cursor);
             debug_assert!(self.layout.cursor <= self.layout.end);
         }
         Ok(())
@@ -151,9 +150,28 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
             None
         };
 
-        let new_layout = self
+        // calculate the desired position of the cursor
+        let pos = self.line.pos();
+        let cursor = self
             .out
-            .compute_layout(prompt_size, default_prompt, &self.line, info);
+            .calculate_position(&self.line[..pos], Position::default());
+        // calculate the position of the end of the input line
+        let mut end = if pos == self.line.len() {
+            cursor
+        } else {
+            self.out.calculate_position(&self.line[pos..], cursor)
+        };
+        if let Some(info) = info {
+            end = self.out.calculate_position(&info, end);
+        }
+
+        let new_layout = Layout {
+            prompt_size,
+            default_prompt,
+            cursor,
+            end,
+        };
+        debug_assert!(new_layout.cursor <= new_layout.end);
 
         debug!(target: "rustyline", "old layout: {:?}", self.layout);
         debug!(target: "rustyline", "new layout: {:?}", new_layout);
@@ -318,7 +336,6 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
                     // Avoid a full update of the line in the trivial case.
                     self.layout.cursor.col += width;
                     self.layout.end.col += width;
-                    debug_assert!(self.layout.prompt_size <= self.layout.cursor);
                     debug_assert!(self.layout.cursor <= self.layout.end);
                     let bits = ch.encode_utf8(&mut self.byte_buffer);
                     let bits = bits.as_bytes();
diff --git a/src/error.rs b/src/error.rs
index c4345e9..f19600b 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -10,7 +10,6 @@ use std::io;
 // #[non_exhaustive]
 #[allow(clippy::module_name_repetitions)]
 #[derive(Debug)]
-#[non_exhaustive]
 pub enum ReadlineError {
     /// I/O Error
     Io(io::Error),
@@ -24,11 +23,8 @@ pub enum ReadlineError {
     /// Unix Error from syscall
     #[cfg(unix)]
     Errno(nix::Error),
-    /// Error generated on WINDOW_BUFFER_SIZE_EVENT to mimic unix SIGWINCH
-    /// signal
     #[cfg(windows)]
     WindowResize,
-    /// Like Utf8Error on unix
     #[cfg(windows)]
     Decode(char::DecodeUtf16Error),
 }
diff --git a/src/highlight.rs b/src/highlight.rs
index 4d62a63..07475f0 100644
--- a/src/highlight.rs
+++ b/src/highlight.rs
@@ -5,6 +5,15 @@ use memchr::memchr;
 use std::borrow::Cow::{self, Borrowed, Owned};
 use std::cell::Cell;
 
+pub struct PromptInfo<'a> {
+    pub(crate) default: bool,
+    pub(crate) offset: usize,
+    pub(crate) cursor: Option<usize>,
+    pub(crate) input: &'a str,
+    pub(crate) line: &'a str,
+    pub(crate) line_no: usize,
+}
+
 /// 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).
@@ -26,11 +35,18 @@ pub trait Highlighter {
     fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
         &'s self,
         prompt: &'p str,
-        default: bool,
+        info: PromptInfo<'_>,
     ) -> Cow<'b, str> {
-        let _ = default;
+        let _ = info;
         Borrowed(prompt)
     }
+
+    /// Returns `true` if prompt is rectangular rather than being present only
+    /// on the first line of input
+    fn has_continuation_prompt(&self) -> bool {
+        false
+    }
+
     /// Takes the `hint` and
     /// returns the highlighted version (with ANSI color).
     fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
@@ -69,9 +85,9 @@ impl<'r, H: ?Sized + Highlighter> Highlighter for &'r H {
     fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
         &'s self,
         prompt: &'p str,
-        default: bool,
+        info: PromptInfo<'_>,
     ) -> Cow<'b, str> {
-        (**self).highlight_prompt(prompt, default)
+        (**self).highlight_prompt(prompt, info)
     }
 
     fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
@@ -94,14 +110,12 @@ impl<'r, H: ?Sized + Highlighter> Highlighter for &'r H {
 const OPENS: &[u8; 3] = b"{[(";
 const CLOSES: &[u8; 3] = b"}])";
 
-/// Highlight matching bracket when typed or cursor moved on.
 #[derive(Default)]
 pub struct MatchingBracketHighlighter {
     bracket: Cell<Option<(u8, usize)>>, // memorize the character to search...
 }
 
 impl MatchingBracketHighlighter {
-    /// Constructor
     pub fn new() -> Self {
         Self {
             bracket: Cell::new(None),
@@ -232,6 +246,86 @@ fn is_close_bracket(bracket: u8) -> bool {
     memchr(bracket, CLOSES).is_some()
 }
 
+pub(crate) fn split_highlight(src: &str, offset: usize) -> (Cow<'_, str>, Cow<'_, str>) {
+    let mut style_buffer = String::with_capacity(32);
+    let mut iter = src.char_indices();
+    let mut non_escape_idx = 0;
+    while let Some((idx, c)) = iter.next() {
+        if c == '\x1b' {
+            match iter.next() {
+                Some((_, '[')) => {}
+                _ => continue, // unknown escape, skip
+            }
+            while let Some((end_idx, c)) = iter.next() {
+                match c {
+                    'm' => {
+                        let slice = &src[idx..end_idx + 1];
+                        if slice == "\x1b[0m" {
+                            style_buffer.clear();
+                        } else {
+                            style_buffer.push_str(slice);
+                        }
+                        break;
+                    }
+                    ';' | '0'..='9' => continue,
+                    _ => break, // unknown escape, skip
+                }
+            }
+            continue;
+        }
+        if non_escape_idx >= offset {
+            if style_buffer.is_empty() {
+                return (src[..idx].into(), src[idx..].into());
+            } else {
+                let mut left = String::with_capacity(idx + 4);
+                left.push_str(&src[..idx]);
+                left.push_str("\x1b[0m");
+                let mut right = String::with_capacity(src.len() - idx + style_buffer.len());
+                right.push_str(&style_buffer);
+                right.push_str(&src[idx..]);
+                return (left.into(), right.into());
+            }
+        }
+        non_escape_idx += c.len_utf8();
+    }
+    (src.into(), "".into())
+}
+
+impl PromptInfo<'_> {
+    /// Returns true if this is the default prompt
+    pub fn default(&self) -> bool {
+        self.default
+    }
+
+    /// Returns the byte offset where prompt is shown in the initial text
+    ///
+    /// This is a position right after the newline of the previous line
+    pub fn line_offset(&self) -> usize {
+        self.offset
+    }
+
+    /// Returns the byte position of the cursor relative to `line_offset` if
+    /// the cursor is in the current line
+    pub fn cursor(&self) -> Option<usize> {
+        self.cursor
+    }
+
+    /// Returns the zero-based line number of the current prompt line
+    pub fn line_no(&self) -> usize {
+        self.line_no
+    }
+
+    /// Returns the line contents shown after the prompt
+    pub fn line(&self) -> &str {
+        self.line
+    }
+
+    /// Returns the whole input (equal to `line` if input is the single line)
+    pub fn input(&self) -> &str {
+        self.input
+    }
+}
+
 #[cfg(test)]
 mod tests {
     #[test]
diff --git a/src/hint.rs b/src/hint.rs
index 4068444..8a89f9b 100644
--- a/src/hint.rs
+++ b/src/hint.rs
@@ -23,8 +23,6 @@ impl<'r, H: ?Sized + Hinter> Hinter for &'r H {
     }
 }
 
-/// Add suggestion based on previous history entries matching current user
-/// input.
 pub struct HistoryHinter {}
 
 impl Hinter for HistoryHinter {
diff --git a/src/history.rs b/src/history.rs
index ddf7047..fcb464e 100644
--- a/src/history.rs
+++ b/src/history.rs
@@ -13,9 +13,7 @@ use crate::config::{Config, HistoryDuplicates};
 /// Search direction
 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
 pub enum Direction {
-    /// Search history forward
     Forward,
-    /// Search history backward
     Reverse,
 }
 
@@ -33,15 +31,10 @@ impl History {
     // and backslashes escaped in them.
     const FILE_VERSION_V2: &'static str = "#V2";
 
-    /// Default constructor
     pub fn new() -> Self {
         Self::with_config(Config::default())
     }
 
-    /// Customized constructor with:
-    /// - `Config::max_history_size()`,
-    /// - `Config::history_ignore_space()`,
-    /// - `Config::history_duplicates()`.
     pub fn with_config(config: Config) -> Self {
         Self {
             entries: VecDeque::new(),
@@ -344,7 +337,6 @@ mod tests {
     }
 
     #[test]
-    #[cfg_attr(miri, ignore)] // unsupported operation: `getcwd` not available when isolation is enabled
     fn save() {
         let mut history = init();
         assert!(history.add("line\nfour \\ abc"));
@@ -362,7 +354,6 @@ mod tests {
     }
 
     #[test]
-    #[cfg_attr(miri, ignore)] // unsupported operation: `getcwd` not available when isolation is enabled
     fn load_legacy() {
         use std::io::Write;
         let td = tempdir::TempDir::new_in(&Path::new("."), "histo").unwrap();
diff --git a/src/keymap.rs b/src/keymap.rs
index 171f4f3..abc1022 100644
--- a/src/keymap.rs
+++ b/src/keymap.rs
@@ -14,8 +14,8 @@ use crate::tty::{RawReader, Term, Terminal};
 pub type RepeatCount = usize;
 
 /// Commands
+// #[non_exhaustive]
 #[derive(Debug, Clone, PartialEq)]
-#[non_exhaustive]
 pub enum Cmd {
     /// abort
     Abort, // Miscellaneous Command
@@ -45,9 +45,7 @@ pub enum Cmd {
     HistorySearchBackward,
     /// history-search-forward
     HistorySearchForward,
-    /// Insert text
     Insert(RepeatCount, String),
-    /// Interrupt signal (Ctrl-C)
     Interrupt,
     /// backward-delete-char, backward-kill-line, backward-kill-word
     /// delete-char, kill-line, kill-word, unix-line-discard, unix-word-rubout,
@@ -59,7 +57,6 @@ pub enum Cmd {
     Move(Movement),
     /// next-history
     NextHistory,
-    /// No action
     Noop,
     /// vi-replace
     Overwrite(char),
@@ -75,7 +72,6 @@ pub enum Cmd {
     ReverseSearchHistory,
     /// self-insert
     SelfInsert(RepeatCount, char),
-    /// Suspend signal (Ctrl-Z on unix platform)
     Suspend,
     /// transpose-chars
     TransposeChars,
@@ -83,7 +79,6 @@ pub enum Cmd {
     TransposeWords(RepeatCount),
     /// undo
     Undo(RepeatCount),
-    /// Unsupported / unexpected
     Unknown,
     /// upcase-word
     UpcaseWord,
@@ -95,27 +90,26 @@ pub enum Cmd {
     YankPop,
     /// moves cursor to the line above or switches to prev history entry if
     /// the cursor is already on the first line
-    LineUpOrPreviousHistory(RepeatCount),
+    LineUpOrPreviousHistory,
     /// moves cursor to the line below or switches to next history entry if
     /// the cursor is already on the last line
-    LineDownOrNextHistory(RepeatCount),
+    LineDownOrNextHistory,
     /// accepts the line when cursor is at the end of the text (non including
     /// trailing whitespace), inserts newline character otherwise
     AcceptOrInsertLine,
 }
 
 impl Cmd {
-    /// Tells if current command should reset kill ring.
     pub fn should_reset_kill_ring(&self) -> bool {
         #[allow(clippy::match_same_arms)]
         match *self {
             Cmd::Kill(Movement::BackwardChar(_)) | Cmd::Kill(Movement::ForwardChar(_)) => true,
             Cmd::ClearScreen
             | Cmd::Kill(_)
-            | Cmd::Replace(..)
+            | Cmd::Replace(_, _)
             | Cmd::Noop
             | Cmd::Suspend
-            | Cmd::Yank(..)
+            | Cmd::Yank(_, _)
             | Cmd::YankPop => false,
             _ => true,
         }
@@ -123,13 +117,13 @@ impl Cmd {
 
     fn is_repeatable_change(&self) -> bool {
         match *self {
-            Cmd::Insert(..)
+            Cmd::Insert(_, _)
             | Cmd::Kill(_)
-            | Cmd::ReplaceChar(..)
-            | Cmd::Replace(..)
-            | Cmd::SelfInsert(..)
+            | Cmd::ReplaceChar(_, _)
+            | Cmd::Replace(_, _)
+            | Cmd::SelfInsert(_, _)
             | Cmd::ViYankTo(_)
-            | Cmd::Yank(..) => true,
+            | Cmd::Yank(_, _) => true,
             // Cmd::TransposeChars | TODO Validate
             _ => false,
         }
@@ -203,33 +197,26 @@ pub enum Word {
 /// Where to move with respect to word boundary
 #[derive(Debug, Clone, PartialEq, Copy)]
 pub enum At {
-    /// Start of word.
     Start,
-    /// Before end of word.
     BeforeEnd,
-    /// After end of word.
     AfterEnd,
 }
 
 /// Where to paste (relative to cursor position)
 #[derive(Debug, Clone, PartialEq, Copy)]
 pub enum Anchor {
-    /// After cursor
     After,
-    /// Before cursor
     Before,
 }
 
 /// Vi character search
 #[derive(Debug, Clone, PartialEq, Copy)]
 pub enum CharSearch {
-    /// Forward search
     Forward(char),
-    /// Forward search until
+    // until
     ForwardBefore(char),
-    /// Backward search
     Backward(char),
-    /// Backward search until
+    // until
     BackwardAfter(char),
 }
 
@@ -246,10 +233,8 @@ impl CharSearch {
 
 /// Where to move
 #[derive(Debug, Clone, PartialEq)]
-#[non_exhaustive]
 pub enum Movement {
-    /// Whole current line (not really a movement but a range)
-    WholeLine,
+    WholeLine, // not really a movement
     /// beginning-of-line
     BeginningOfLine,
     /// end-of-line
@@ -270,8 +255,7 @@ pub enum Movement {
     LineUp(RepeatCount),
     /// move to the same column on the next line
     LineDown(RepeatCount),
-    /// Whole user input (not really a movement but a range)
-    WholeBuffer,
+    WholeBuffer, // not really a movement
     /// beginning-of-buffer
     BeginningOfBuffer,
     /// end-of-buffer
@@ -720,10 +704,10 @@ impl InputState {
             KeyPress::Ctrl('G') => Cmd::Abort,
             KeyPress::Char('l') | KeyPress::Char(' ') => Cmd::Move(Movement::ForwardChar(n)),
             KeyPress::Ctrl('L') => Cmd::ClearScreen,
-            KeyPress::Char('+') | KeyPress::Char('j') => Cmd::LineDownOrNextHistory(n),
+            KeyPress::Char('+') | KeyPress::Char('j') => Cmd::Move(Movement::LineDown(n)),
             // TODO: move to the start of the line.
             KeyPress::Ctrl('N') => Cmd::NextHistory,
-            KeyPress::Char('-') | KeyPress::Char('k') => Cmd::LineUpOrPreviousHistory(n),
+            KeyPress::Char('-') | KeyPress::Char('k') => Cmd::Move(Movement::LineUp(n)),
             // TODO: move to the start of the line.
             KeyPress::Ctrl('P') => Cmd::PreviousHistory,
             KeyPress::Ctrl('R') => {
@@ -780,9 +764,9 @@ impl InputState {
         };
         debug!(target: "rustyline", "Vi insert: {:?}", cmd);
         if cmd.is_repeatable_change() {
-            if let (Cmd::Replace(..), Cmd::SelfInsert(..)) = (&self.last_cmd, &cmd) {
+            if let (Cmd::Replace(_, _), Cmd::SelfInsert(_, _)) = (&self.last_cmd, &cmd) {
                 // replacing...
-            } else if let (Cmd::SelfInsert(..), Cmd::SelfInsert(..)) = (&self.last_cmd, &cmd) {
+            } else if let (Cmd::SelfInsert(_, _), Cmd::SelfInsert(_, _)) = (&self.last_cmd, &cmd) {
                 // inserting...
             } else {
                 self.last_cmd = cmd.clone();
@@ -914,8 +898,8 @@ impl InputState {
             }
             KeyPress::Ctrl('J') |
             KeyPress::Enter => Cmd::AcceptLine,
-            KeyPress::Down => Cmd::LineDownOrNextHistory(1),
-            KeyPress::Up => Cmd::LineUpOrPreviousHistory(1),
+            KeyPress::Down => Cmd::LineDownOrNextHistory,
+            KeyPress::Up => Cmd::LineUpOrPreviousHistory,
             KeyPress::Ctrl('R') => Cmd::ReverseSearchHistory,
             KeyPress::Ctrl('S') => Cmd::ForwardSearchHistory, // most terminals override Ctrl+S to suspend execution
             KeyPress::Ctrl('T') => Cmd::TransposeChars,
diff --git a/src/keys.rs b/src/keys.rs
index bf06636..72876d8 100644
--- a/src/keys.rs
+++ b/src/keys.rs
@@ -1,70 +1,38 @@
 //! Key constants
 
-/// Input key pressed
+// #[non_exhaustive]
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
-#[non_exhaustive]
 pub enum KeyPress {
-    /// Unsupported escape sequence (on unix platform)
     UnknownEscSeq,
-    /// ⌫ or `KeyPress::Ctrl('H')`
-    Backspace,
-    /// ⇤ (usually Shift-Tab)
+    Backspace, // Ctrl('H')
     BackTab,
-    /// Paste (on unix platform)
     BracketedPasteStart,
-    /// Paste (on unix platform)
     BracketedPasteEnd,
-    /// Single char
     Char(char),
-    /// Ctrl-↓
     ControlDown,
-    /// Ctrl-←
     ControlLeft,
-    /// Ctrl-→
     ControlRight,
-    /// Ctrl-↑
     ControlUp,
-    /// Ctrl-char
     Ctrl(char),
-    /// ⌦
     Delete,
-    /// ↓ arrow key
     Down,
-    /// ⇲
     End,
-    /// ↵ or `KeyPress::Ctrl('M')`
-    Enter,
-    /// Escape or `KeyPress::Ctrl('[')`
-    Esc,
-    /// Function key
+    Enter, // Ctrl('M')
+    Esc,   // Ctrl('[')
     F(u8),
-    /// ⇱
     Home,
-    /// Insert key
     Insert,
-    /// ← arrow key
     Left,
-    /// Escape-char or Alt-char
     Meta(char),
-    /// `KeyPress::Char('\0')`
     Null,
-    /// ⇟
     PageDown,
-    /// ⇞
     PageUp,
-    /// → arrow key
     Right,
-    /// Shift-↓
     ShiftDown,
-    /// Shift-←
     ShiftLeft,
-    /// Shift-→
     ShiftRight,
-    /// Shift-↑
     ShiftUp,
-    /// ⇥ or `KeyPress::Ctrl('I')`
-    Tab,
-    /// ↑ arrow key
+    Tab, // Ctrl('I')
     Up,
 }
 
diff --git a/src/layout.rs b/src/layout.rs
index 5679ec1..3488588 100644
--- a/src/layout.rs
+++ b/src/layout.rs
@@ -2,8 +2,8 @@ use std::cmp::{Ord, Ordering, PartialOrd};
 
 #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
 pub struct Position {
-    pub col: usize, // The leftmost column is number 0.
-    pub row: usize, // The highest row is number 0.
+    pub col: usize,
+    pub row: usize,
 }
 
 impl PartialOrd for Position {
@@ -26,8 +26,8 @@ pub struct Layout {
     /// Prompt Unicode/visible width and height
     pub prompt_size: Position,
     pub default_prompt: bool,
-    /// Cursor position (relative to the start of the prompt)
+    /// Cursor position (relative to the end of the prompt)
     pub cursor: Position,
-    /// Number of rows used so far (from start of prompt to end of input)
+    /// Number of rows used so far (from end of prompt to end of input)
     pub end: Position,
 }
diff --git a/src/lib.rs b/src/lib.rs
index f75253e..8501285 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -15,7 +15,7 @@
 //!     Err(_) => println!("No input"),
 //! }
 //! ```
-#![warn(missing_docs)]
+// #![feature(non_exhaustive)]
 
 pub mod completion;
 pub mod config;
@@ -319,8 +319,6 @@ fn page_completions<C: Candidate, H: Helper>(
         s.out.write_and_flush(ab.as_bytes())?;
     }
     s.out.write_and_flush(b"\n")?;
-    s.layout.end.row = 0; // dirty way to make clear_old_rows do nothing
-    s.layout.cursor.row = 0;
     s.refresh_line()?;
     Ok(None)
 }
@@ -539,13 +537,13 @@ fn readline_edit<H: Helper>(
                 // Fetch the previous command from the history list.
                 s.edit_history_next(true)?
             }
-            Cmd::LineUpOrPreviousHistory(n) => {
-                if !s.edit_move_line_up(n)? {
+            Cmd::LineUpOrPreviousHistory => {
+                if !s.edit_move_line_up(1)? {
                     s.edit_history_next(true)?
                 }
             }
-            Cmd::LineDownOrNextHistory(n) => {
-                if !s.edit_move_line_down(n)? {
+            Cmd::LineDownOrNextHistory => {
+                if !s.edit_move_line_down(1)? {
                     s.edit_history_next(false)?
                 }
             }
@@ -897,7 +895,6 @@ impl<H: Helper> Editor<H> {
         }
     }
 
-    /// Returns an iterator over edited lines
     /// ```
     /// let mut rl = rustyline::Editor::<()>::new();
     /// for readline in rl.iter("> ") {
@@ -912,7 +909,7 @@ impl<H: Helper> Editor<H> {
     ///     }
     /// }
     /// ```
-    pub fn iter<'a>(&'a mut self, prompt: &'a str) -> impl Iterator<Item = Result<String>> + 'a {
+    pub fn iter<'a>(&'a mut self, prompt: &'a str) -> Iter<'_, H> {
         Iter {
             editor: self,
             prompt,
@@ -971,7 +968,8 @@ impl<H: Helper> fmt::Debug for Editor<H> {
     }
 }
 
-struct Iter<'a, H: Helper> {
+/// Edited lines iterator
+pub struct Iter<'a, H: Helper> {
     editor: &'a mut Editor<H>,
     prompt: &'a str,
 }
diff --git a/src/line_buffer.rs b/src/line_buffer.rs
index 3e8ed01..59c4cc5 100644
--- a/src/line_buffer.rs
+++ b/src/line_buffer.rs
@@ -15,11 +15,8 @@ pub(crate) const MAX_LINE: usize = 4096;
 /// Word's case change
 #[derive(Clone, Copy)]
 pub enum WordAction {
-    /// Capitalize word
     CAPITALIZE,
-    /// lowercase word
     LOWERCASE,
-    /// uppercase word
     UPPERCASE,
 }
 
@@ -734,7 +731,6 @@ impl LineBuffer {
         }
     }
 
-    /// Delete range specified by `cs` search.
     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),
@@ -1018,7 +1014,6 @@ impl LineBuffer {
         }
     }
 
-    /// Kill range specified by `mvt`.
     pub fn kill(&mut self, mvt: &Movement) -> bool {
         let notify = match *mvt {
             Movement::ForwardChar(_) | Movement::BackwardChar(_) => false,
diff --git a/src/tty/mod.rs b/src/tty/mod.rs
index cfa3932..51fe160 100644
--- a/src/tty/mod.rs
+++ b/src/tty/mod.rs
@@ -1,7 +1,4 @@
 //! This module implements and describes common TTY methods & traits
-
-use unicode_width::UnicodeWidthStr;
-
 use crate::config::{BellStyle, ColorMode, Config, OutputStreamType};
 use crate::highlight::Highlighter;
 use crate::keys::KeyPress;
@@ -44,40 +41,6 @@ pub trait Renderer {
         highlighter: Option<&dyn Highlighter>,
     ) -> Result<()>;
 
-    /// Compute layout for rendering prompt + line + some info (either hint,
-    /// validation msg, ...). on the screen. Depending on screen width, line
-    /// wrapping may be applied.
-    fn compute_layout(
-        &self,
-        prompt_size: Position,
-        default_prompt: bool,
-        line: &LineBuffer,
-        info: Option<&str>,
-    ) -> Layout {
-        // calculate the desired position of the cursor
-        let pos = line.pos();
-        let cursor = self.calculate_position(&line[..pos], prompt_size);
-        // calculate the position of the end of the input line
-        let mut end = if pos == line.len() {
-            cursor
-        } else {
-            self.calculate_position(&line[pos..], cursor)
-        };
-        if let Some(info) = info {
-            end = self.calculate_position(&info, end);
-        }
-
-        let new_layout = Layout {
-            prompt_size,
-            default_prompt,
-            cursor,
-            end,
-        };
-        debug_assert!(new_layout.prompt_size <= new_layout.cursor);
-        debug_assert!(new_layout.cursor <= new_layout.end);
-        new_layout
-    }
-
     /// Calculate the number of columns and rows used to display `s` on a
     /// `cols` width terminal starting at `orig`.
     fn calculate_position(&self, s: &str, orig: Position) -> Position;
@@ -166,37 +129,6 @@ impl<'a, R: Renderer + ?Sized> Renderer for &'a mut R {
     }
 }
 
-// ignore ANSI escape sequence
-fn width(s: &str, esc_seq: &mut u8) -> usize {
-    if *esc_seq == 1 {
-        if s == "[" {
-            // CSI
-            *esc_seq = 2;
-        } else {
-            // two-character sequence
-            *esc_seq = 0;
-        }
-        0
-    } else if *esc_seq == 2 {
-        if s == ";" || (s.as_bytes()[0] >= b'0' && s.as_bytes()[0] <= b'9') {
-            /*} else if s == "m" {
-            // last
-             *esc_seq = 0;*/
-        } else {
-            // not supported
-            *esc_seq = 0;
-        }
-        0
-    } else if s == "\x1b" {
-        *esc_seq = 1;
-        0
-    } else if s == "\n" {
-        0
-    } else {
-        s.width()
-    }
-}
-
 /// Terminal contract
 pub trait Term {
     type Reader: RawReader; // rl_instream
@@ -224,21 +156,115 @@ pub trait Term {
     fn create_writer(&self) -> Self::Writer;
 }
 
-// If on Windows platform import Windows TTY module
-// and re-export into mod.rs scope
-#[cfg(all(windows, not(target_arch = "wasm32")))]
-mod windows;
-#[cfg(all(windows, not(target_arch = "wasm32")))]
-pub use self::windows::*;
-
-// If on Unix platform import Unix TTY module
-// and re-export into mod.rs scope
-#[cfg(all(unix, not(target_arch = "wasm32")))]
-mod unix;
-#[cfg(all(unix, not(target_arch = "wasm32")))]
-pub use self::unix::*;
-
-#[cfg(any(test, target_arch = "wasm32"))]
-mod test;
-#[cfg(any(test, target_arch = "wasm32"))]
-pub use self::test::*;
+#[cfg(not(any(test, target_arch = "wasm32")))]
+fn add_prompt_and_highlight(
+    buffer: &mut String,
+    highlighter: Option<&dyn Highlighter>,
+    line: &LineBuffer,
+    prompt: &str,
+    default_prompt: bool,
+    layout: &Layout,
+    cursor: &mut Position,
+) {
+    use crate::highlight::{split_highlight, PromptInfo};
+
+    if let Some(highlighter) = highlighter {
+        if highlighter.has_continuation_prompt() {
+            if &line[..] == "" {
+                // line.lines() is an empty iterator for empty line so
+                // we need to treat it as a special case
+                let prompt = highlighter.highlight_prompt(
+                    prompt,
+                    PromptInfo {
+                        default: default_prompt,
+                        offset: 0,
+                        cursor: Some(0),
+                        input: "",
+                        line: "",
+                        line_no: 0,
+                    },
+                );
+                buffer.push_str(&prompt);
+            } else {
+                let highlighted = highlighter.highlight(line, line.pos());
+                let lines = line.split('\n');
+                let mut highlighted_left = highlighted.to_string();
+                let mut offset = 0;
+                for (line_no, orig) in lines.enumerate() {
+                    let (hl, tail) = split_highlight(&highlighted_left, orig.len() + 1);
+                    let prompt = highlighter.highlight_prompt(
+                        prompt,
+                        PromptInfo {
+                            default: default_prompt,
+                            offset,
+                            cursor: if line.pos() > offset && line.pos() < orig.len() {
+                                Some(line.pos() - offset)
+                            } else {
+                                None
+                            },
+                            input: line,
+                            line: orig,
+                            line_no,
+                        },
+                    );
+                    buffer.push_str(&prompt);
+                    buffer.push_str(&hl);
+                    highlighted_left = tail.to_string();
+                    offset += orig.len() + 1;
+                }
+            }
+            cursor.col += layout.prompt_size.col;
+        } else {
+            // display the prompt
+            buffer.push_str(&highlighter.highlight_prompt(
+                prompt,
+                PromptInfo {
+                    default: default_prompt,
+                    offset: 0,
+                    cursor: Some(line.pos()),
+                    input: line,
+                    line,
+                    line_no: 0,
+                },
+            ));
+            // display the input line
+            buffer.push_str(&highlighter.highlight(line, line.pos()));
+            // we have to generate our own newline on line wrap
+            if layout.end.col == 0 && layout.end.row > 0 && !buffer.ends_with('\n') {
+                buffer.push_str("\n");
+            }
+            if cursor.row == 0 {
+                cursor.col += layout.prompt_size.col;
+            }
+        }
+    } else {
+        // display the prompt
+        buffer.push_str(prompt);
+        // display the input line
+        buffer.push_str(line);
+        // we have to generate our own newline on line wrap
+        if layout.end.col == 0 && layout.end.row > 0 && !buffer.ends_with('\n') {
+            buffer.push_str("\n");
+        }
+        if cursor.row == 0 {
+            cursor.col += layout.prompt_size.col;
+        }
+    }
+}
+
+cfg_if::cfg_if! {
+    if #[cfg(any(test, target_arch = "wasm32"))] {
+        mod test;
+        pub use self::test::*;
+    } else if #[cfg(windows)] {
+        // If on Windows platform import Windows TTY module
+        // and re-export into mod.rs scope
+        mod windows;
+        pub use self::windows::*;
+    } else if #[cfg(unix)] {
+        // If on Unix platform import Unix TTY module
+        // and re-export into mod.rs scope
+        mod unix;
+        pub use self::unix::*;
+    }
+}
diff --git a/src/tty/unix.rs b/src/tty/unix.rs
index 7cd837a..80c4543 100644
--- a/src/tty/unix.rs
+++ b/src/tty/unix.rs
@@ -11,15 +11,17 @@ use nix::sys::signal;
 use nix::sys::termios;
 use nix::sys::termios::SetArg;
 use unicode_segmentation::UnicodeSegmentation;
+use unicode_width::UnicodeWidthStr;
 use utf8parse::{Parser, Receiver};
 
-use super::{width, RawMode, RawReader, Renderer, Term};
+use super::{RawMode, RawReader, Renderer, Term};
 use crate::config::{BellStyle, ColorMode, Config, OutputStreamType};
 use crate::error;
 use crate::highlight::Highlighter;
 use crate::keys::{self, KeyPress};
 use crate::layout::{Layout, Position};
 use crate::line_buffer::LineBuffer;
+use crate::tty::add_prompt_and_highlight;
 use crate::Result;
 
 const STDIN_FILENO: RawFd = libc::STDIN_FILENO;
@@ -45,10 +47,6 @@ nix::ioctl_read_bad!(win_size, libc::TIOCGWINSZ, libc::winsize);
 fn get_win_size<T: AsRawFd + ?Sized>(fileno: &T) -> (usize, usize) {
     use std::mem::zeroed;
 
-    if cfg!(test) {
-        return (80, 24);
-    }
-
     unsafe {
         let mut size: libc::winsize = zeroed();
         match win_size(fileno.as_raw_fd(), &mut size) {
@@ -484,25 +482,6 @@ impl PosixRenderer {
             bell_style,
         }
     }
-
-    fn clear_old_rows(&mut self, layout: &Layout) {
-        use std::fmt::Write;
-        let current_row = layout.cursor.row;
-        let old_rows = layout.end.row;
-        // old_rows < cursor_row if the prompt spans multiple lines and if
-        // this is the default State.
-        let cursor_row_movement = old_rows.saturating_sub(current_row);
-        // move the cursor down as required
-        if cursor_row_movement > 0 {
-            write!(self.buffer, "\x1b[{}B", cursor_row_movement).unwrap();
-        }
-        // clear old rows
-        for _ in 0..old_rows {
-            self.buffer.push_str("\r\x1b[0K\x1b[A");
-        }
-        // clear the line
-        self.buffer.push_str("\r\x1b[0K");
-    }
 }
 
 impl Renderer for PosixRenderer {
@@ -563,24 +542,34 @@ impl Renderer for PosixRenderer {
         self.buffer.clear();
 
         let default_prompt = new_layout.default_prompt;
-        let cursor = new_layout.cursor;
+        let mut cursor = new_layout.cursor;
         let end_pos = new_layout.end;
+        let current_row = old_layout.cursor.row;
+        let old_rows = old_layout.end.row;
 
-        self.clear_old_rows(old_layout);
-
-        if let Some(highlighter) = highlighter {
-            // display the prompt
-            self.buffer
-                .push_str(&highlighter.highlight_prompt(prompt, default_prompt));
-            // display the input line
-            self.buffer
-                .push_str(&highlighter.highlight(line, line.pos()));
-        } else {
-            // display the prompt
-            self.buffer.push_str(prompt);
-            // display the input line
-            self.buffer.push_str(line);
+        // old_rows < cursor.row if the prompt spans multiple lines and if
+        // this is the default State.
+        let cursor_row_movement = old_rows.saturating_sub(current_row);
+        // move the cursor down as required
+        if cursor_row_movement > 0 {
+            write!(self.buffer, "\x1b[{}B", cursor_row_movement).unwrap();
+        }
+        // clear old rows
+        for _ in 0..old_rows {
+            self.buffer.push_str("\r\x1b[0K\x1b[A");
         }
+        // clear the line
+        self.buffer.push_str("\r\x1b[0K");
+
+        add_prompt_and_highlight(
+            &mut self.buffer,
+            highlighter,
+            line,
+            prompt,
+            default_prompt,
+            &new_layout,
+            &mut cursor,
+        );
         // display hint
         if let Some(hint) = hint {
             if let Some(highlighter) = highlighter {
@@ -589,10 +578,6 @@ impl Renderer for PosixRenderer {
                 self.buffer.push_str(hint);
             }
         }
-        // we have to generate our own newline on line wrap
-        if end_pos.col == 0 && end_pos.row > 0 && !self.buffer.ends_with('\n') {
-            self.buffer.push_str("\n");
-        }
         // position the cursor
         let new_cursor_row_movement = end_pos.row - cursor.row;
         // move the cursor up as required
@@ -600,10 +585,10 @@ impl Renderer for PosixRenderer {
             write!(self.buffer, "\x1b[{}A", new_cursor_row_movement).unwrap();
         }
         // position the cursor within the line
-        if cursor.col > 0 {
-            write!(self.buffer, "\r\x1b[{}C", cursor.col).unwrap();
-        } else {
+        if cursor.col == 0 {
             self.buffer.push('\r');
+        } else {
+            write!(self.buffer, "\r\x1b[{}C", cursor.col).unwrap();
         }
 
         self.write_and_flush(self.buffer.as_bytes())?;
@@ -711,6 +696,36 @@ impl Renderer for PosixRenderer {
     }
 }
 
+fn width(s: &str, esc_seq: &mut u8) -> usize {
+    if *esc_seq == 1 {
+        if s == "[" {
+            // CSI
+            *esc_seq = 2;
+        } else {
+            // two-character sequence
+            *esc_seq = 0;
+        }
+        0
+    } else if *esc_seq == 2 {
+        if s == ";" || (s.as_bytes()[0] >= b'0' && s.as_bytes()[0] <= b'9') {
+            /*} else if s == "m" {
+            // last
+             *esc_seq = 0;*/
+        } else {
+            // not supported
+            *esc_seq = 0;
+        }
+        0
+    } else if s == "\x1b" {
+        *esc_seq = 1;
+        0
+    } else if s == "\n" {
+        0
+    } else {
+        s.width()
+    }
+}
+
 fn read_digits_until(rdr: &mut PosixRawReader, sep: char) -> Result<Option<u32>> {
     let mut num: u32 = 0;
     loop {
@@ -898,7 +913,6 @@ fn write_and_flush(out: OutputStreamType, buf: &[u8]) -> Result<()> {
 mod test {
     use super::{Position, PosixRenderer, PosixTerminal, Renderer};
     use crate::config::{BellStyle, OutputStreamType};
-    use crate::line_buffer::LineBuffer;
 
     #[test]
     #[ignore]
@@ -929,29 +943,4 @@ mod test {
         fn assert_sync<T: Sync>() {}
         assert_sync::<PosixTerminal>();
     }
-
-    #[test]
-    fn test_line_wrap() {
-        let mut out = PosixRenderer::new(OutputStreamType::Stdout, 4, true, BellStyle::default());
-        let prompt = "> ";
-        let default_prompt = true;
-        let prompt_size = out.calculate_position(prompt, Position::default());
-
-        let mut line = LineBuffer::init("", 0, None);
-        let old_layout = out.compute_layout(prompt_size, default_prompt, &line, None);
-        assert_eq!(Position { col: 2, row: 0 }, old_layout.cursor);
-        assert_eq!(old_layout.cursor, old_layout.end);
-
-        assert_eq!(Some(true), line.insert('a', out.cols - prompt_size.col + 1));
-        let new_layout = out.compute_layout(prompt_size, default_prompt, &line, None);
-        assert_eq!(Position { col: 1, row: 1 }, new_layout.cursor);
-        assert_eq!(new_layout.cursor, new_layout.end);
-        out.refresh_line(prompt, &line, None, &old_layout, &new_layout, None)
-            .unwrap();
-        #[rustfmt::skip]
-        assert_eq!(
-            "\r\u{1b}[0K> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\u{1b}[1C",
-            out.buffer
-        );
-    }
 }
diff --git a/src/tty/windows.rs b/src/tty/windows.rs
index ea499c7..f4f852d 100644
--- a/src/tty/windows.rs
+++ b/src/tty/windows.rs
@@ -1,24 +1,22 @@
 //! Windows specific definitions
-#![allow(clippy::try_err)] // suggested fix does not work (cannot infer...)
-
-use std::io::{self, ErrorKind, Write};
+use std::io::{self, Write};
 use std::mem;
 use std::sync::atomic;
 
-use log::{debug, warn};
-use unicode_segmentation::UnicodeSegmentation;
-use unicode_width::UnicodeWidthStr;
-use winapi::shared::minwindef::{BOOL, DWORD, FALSE, TRUE, WORD};
+use log::debug;
+use unicode_width::UnicodeWidthChar;
+use winapi::shared::minwindef::{DWORD, WORD};
 use winapi::um::winnt::{CHAR, HANDLE};
 use winapi::um::{consoleapi, handleapi, processenv, winbase, wincon, winuser};
 
-use super::{width, RawMode, RawReader, Renderer, Term};
+use super::{RawMode, RawReader, Renderer, Term};
 use crate::config::{BellStyle, ColorMode, Config, OutputStreamType};
 use crate::error;
 use crate::highlight::Highlighter;
 use crate::keys::{self, KeyPress};
 use crate::layout::{Layout, Position};
 use crate::line_buffer::LineBuffer;
+use crate::tty::add_prompt_and_highlight;
 use crate::Result;
 
 const STDIN_FILENO: DWORD = winbase::STD_INPUT_HANDLE;
@@ -38,18 +36,21 @@ fn get_std_handle(fd: DWORD) -> Result<HANDLE> {
     Ok(handle)
 }
 
-fn check(rc: BOOL) -> Result<()> {
-    if rc == FALSE {
-        Err(io::Error::last_os_error())?
-    } else {
-        Ok(())
-    }
+#[macro_export]
+macro_rules! check {
+    ($funcall:expr) => {{
+        let rc = unsafe { $funcall };
+        if rc == 0 {
+            Err(io::Error::last_os_error())?;
+        }
+        rc
+    }};
 }
 
 fn get_win_size(handle: HANDLE) -> (usize, usize) {
     let mut info = unsafe { mem::zeroed() };
     match unsafe { wincon::GetConsoleScreenBufferInfo(handle, &mut info) } {
-        FALSE => (80, 24),
+        0 => (80, 24),
         _ => (
             info.dwSize.X as usize,
             (1 + info.srWindow.Bottom - info.srWindow.Top) as usize,
@@ -59,7 +60,7 @@ fn get_win_size(handle: HANDLE) -> (usize, usize) {
 
 fn get_console_mode(handle: HANDLE) -> Result<DWORD> {
     let mut original_mode = 0;
-    check(unsafe { consoleapi::GetConsoleMode(handle, &mut original_mode) })?;
+    check!(consoleapi::GetConsoleMode(handle, &mut original_mode));
     Ok(original_mode)
 }
 
@@ -77,11 +78,15 @@ pub struct ConsoleMode {
 impl RawMode for ConsoleMode {
     /// Disable RAW mode for the terminal.
     fn disable_raw_mode(&self) -> Result<()> {
-        check(unsafe { consoleapi::SetConsoleMode(self.stdin_handle, self.original_stdin_mode) })?;
+        check!(consoleapi::SetConsoleMode(
+            self.stdin_handle,
+            self.original_stdin_mode,
+        ));
         if let Some(original_stdstream_mode) = self.original_stdstream_mode {
-            check(unsafe {
-                consoleapi::SetConsoleMode(self.stdstream_handle, original_stdstream_mode)
-            })?;
+            check!(consoleapi::SetConsoleMode(
+                self.stdstream_handle,
+                original_stdstream_mode,
+            ));
         }
         Ok(())
     }
@@ -112,9 +117,12 @@ impl RawReader for ConsoleRawReader {
         let mut surrogate = 0;
         loop {
             // TODO GetNumberOfConsoleInputEvents
-            check(unsafe {
-                consoleapi::ReadConsoleInputW(self.handle, &mut rec, 1 as DWORD, &mut count)
-            })?;
+            check!(consoleapi::ReadConsoleInputW(
+                self.handle,
+                &mut rec,
+                1 as DWORD,
+                &mut count,
+            ));
 
             if rec.EventType == wincon::WINDOW_BUFFER_SIZE_EVENT {
                 SIGWINCH.store(true, atomic::Ordering::SeqCst);
@@ -269,81 +277,26 @@ impl ConsoleRenderer {
 
     fn get_console_screen_buffer_info(&self) -> Result<wincon::CONSOLE_SCREEN_BUFFER_INFO> {
         let mut info = unsafe { mem::zeroed() };
-        check(unsafe { wincon::GetConsoleScreenBufferInfo(self.handle, &mut info) })?;
+        check!(wincon::GetConsoleScreenBufferInfo(self.handle, &mut info));
         Ok(info)
     }
 
     fn set_console_cursor_position(&mut self, pos: wincon::COORD) -> Result<()> {
-        check(unsafe { wincon::SetConsoleCursorPosition(self.handle, pos) })
+        check!(wincon::SetConsoleCursorPosition(self.handle, pos));
+        Ok(())
     }
 
-    fn clear(&mut self, length: DWORD, pos: wincon::COORD, attr: WORD) -> Result<()> {
+    fn clear(&mut self, length: DWORD, pos: wincon::COORD) -> Result<()> {
         let mut _count = 0;
-        check(unsafe {
-            wincon::FillConsoleOutputCharacterA(self.handle, ' ' as CHAR, length, pos, &mut _count)
-        })?;
-        check(unsafe {
-            wincon::FillConsoleOutputAttribute(self.handle, attr, length, pos, &mut _count)
-        })
-    }
-
-    fn set_cursor_visible(&mut self, visible: BOOL) -> Result<()> {
-        set_cursor_visible(self.handle, visible)
-    }
-
-    // You can't have both ENABLE_WRAP_AT_EOL_OUTPUT and
-    // ENABLE_VIRTUAL_TERMINAL_PROCESSING. So we need to wrap manually.
-    fn wrap_at_eol(&mut self, s: &str, mut col: usize) -> usize {
-        let mut esc_seq = 0;
-        for c in s.graphemes(true) {
-            if c == "\n" {
-                col = 0;
-                self.buffer.push_str(c);
-            } else {
-                let cw = width(c, &mut esc_seq);
-                col += cw;
-                if col > self.cols {
-                    self.buffer.push('\n');
-                    col = cw;
-                }
-                self.buffer.push_str(c);
-            }
-        }
-        if col == self.cols {
-            self.buffer.push('\n');
-            col = 0;
-        }
-        col
-    }
-
-    // position at the start of the prompt, clear to end of previous input
-    fn clear_old_rows(
-        &mut self,
-        info: &wincon::CONSOLE_SCREEN_BUFFER_INFO,
-        layout: &Layout,
-    ) -> Result<()> {
-        let current_row = layout.cursor.row;
-        let old_rows = layout.end.row;
-        let mut coord = info.dwCursorPosition;
-        coord.X = 0;
-        coord.Y -= current_row as i16;
-        self.set_console_cursor_position(coord)?;
-        self.clear(
-            (info.dwSize.X * (old_rows as i16 + 1)) as DWORD,
-            coord,
-            info.wAttributes,
-        )
-    }
-}
-
-fn set_cursor_visible(handle: HANDLE, visible: BOOL) -> Result<()> {
-    let mut info = unsafe { mem::zeroed() };
-    check(unsafe { wincon::GetConsoleCursorInfo(handle, &mut info) })?;
-    if info.bVisible == visible {
-        return Ok(());
+        check!(wincon::FillConsoleOutputCharacterA(
+            self.handle,
+            ' ' as CHAR,
+            length,
+            pos,
+            &mut _count,
+        ));
+        Ok(())
     }
-    info.bVisible = visible;
-    check(unsafe { wincon::SetConsoleCursorInfo(handle, &info) })
 }
 
 impl Renderer for ConsoleRenderer {
@@ -374,39 +327,37 @@ impl Renderer for ConsoleRenderer {
         highlighter: Option<&dyn Highlighter>,
     ) -> Result<()> {
         let default_prompt = new_layout.default_prompt;
-        let cursor = new_layout.cursor;
+        let mut cursor = new_layout.cursor;
         let end_pos = new_layout.end;
+        let current_row = old_layout.cursor.row;
+        let old_rows = old_layout.end.row;
 
         self.buffer.clear();
-        let mut col = 0;
-        if let Some(highlighter) = highlighter {
-            // TODO handle ansi escape code (SetConsoleTextAttribute)
-            // append the prompt
-            col = self.wrap_at_eol(&highlighter.highlight_prompt(prompt, default_prompt), col);
-            // append the input line
-            col = self.wrap_at_eol(&highlighter.highlight(line, line.pos()), col);
-        } else {
-            // append the prompt
-            self.buffer.push_str(prompt);
-            // append the input line
-            self.buffer.push_str(line);
-        }
+        add_prompt_and_highlight(
+            &mut self.buffer,
+            highlighter,
+            line,
+            prompt,
+            default_prompt,
+            &new_layout,
+            &mut cursor,
+        );
+
         // append hint
         if let Some(hint) = hint {
             if let Some(highlighter) = highlighter {
-                self.wrap_at_eol(&highlighter.highlight_hint(hint), col);
+                self.buffer.push_str(&highlighter.highlight_hint(hint));
             } else {
                 self.buffer.push_str(hint);
             }
         }
-        let info = self.get_console_screen_buffer_info()?;
-        self.set_cursor_visible(FALSE)?; // just to avoid flickering
-        let handle = self.handle;
-        scopeguard::defer! {
-            let _ = set_cursor_visible(handle, TRUE);
-        }
         // position at the start of the prompt, clear to end of previous input
-        self.clear_old_rows(&info, old_layout)?;
+        let info = self.get_console_screen_buffer_info()?;
+        let mut coord = info.dwCursorPosition;
+        coord.X = 0;
+        coord.Y -= current_row as i16;
+        self.set_console_cursor_position(coord)?;
+        self.clear((info.dwSize.X * (old_rows as i16 + 1)) as DWORD, coord)?;
         // display prompt, input line and hint
         self.write_and_flush(self.buffer.as_bytes())?;
 
@@ -436,12 +387,15 @@ impl Renderer for ConsoleRenderer {
     /// Characters with 2 column width are correctly handled (not split).
     fn calculate_position(&self, s: &str, orig: Position) -> Position {
         let mut pos = orig;
-        for c in s.graphemes(true) {
-            if c == "\n" {
+        for c in s.chars() {
+            let cw = if c == '\n' {
                 pos.col = 0;
                 pos.row += 1;
+                None
             } else {
-                let cw = c.width();
+                c.width()
+            };
+            if let Some(cw) = cw {
                 pos.col += cw;
                 if pos.col > self.cols {
                     pos.row += 1;
@@ -471,9 +425,9 @@ impl Renderer for ConsoleRenderer {
     fn clear_screen(&mut self) -> Result<()> {
         let info = self.get_console_screen_buffer_info()?;
         let coord = wincon::COORD { X: 0, Y: 0 };
-        check(unsafe { wincon::SetConsoleCursorPosition(self.handle, coord) })?;
+        check!(wincon::SetConsoleCursorPosition(self.handle, coord));
         let n = info.dwSize.X as DWORD * info.dwSize.Y as DWORD;
-        self.clear(n, coord, info.wAttributes)
+        self.clear(n, coord)
     }
 
     fn sigwinch(&self) -> bool {
@@ -511,15 +465,7 @@ impl Renderer for ConsoleRenderer {
         debug!(target: "rustyline", "initial cursor location: {:?}, {:?}", info.dwCursorPosition.X, info.dwCursorPosition.Y);
         info.dwCursorPosition.X = 0;
         info.dwCursorPosition.Y += 1;
-        let res = self.set_console_cursor_position(info.dwCursorPosition);
-        if let Err(error::ReadlineError::Io(ref e)) = res {
-            if e.kind() == ErrorKind::Other && e.raw_os_error() == Some(87) {
-                warn!(target: "rustyline", "invalid cursor position: ({:?}, {:?}) in ({:?}, {:?})", info.dwCursorPosition.X, info.dwCursorPosition.Y, info.dwSize.X, info.dwSize.Y);
-                println!();
-                return Ok(());
-            }
-        }
-        res
+        self.set_console_cursor_position(info.dwCursorPosition)
     }
 }
 
@@ -633,37 +579,20 @@ impl Term for Console {
         raw |= wincon::ENABLE_INSERT_MODE;
         raw |= wincon::ENABLE_QUICK_EDIT_MODE;
         raw |= wincon::ENABLE_WINDOW_INPUT;
-        check(unsafe { consoleapi::SetConsoleMode(self.stdin_handle, raw) })?;
+        check!(consoleapi::SetConsoleMode(self.stdin_handle, raw));
 
         let original_stdstream_mode = if self.stdstream_isatty {
             let original_stdstream_mode = get_console_mode(self.stdstream_handle)?;
-
-            let mut mode = original_stdstream_mode;
-            if mode & wincon::ENABLE_WRAP_AT_EOL_OUTPUT == 0 {
-                mode |= wincon::ENABLE_WRAP_AT_EOL_OUTPUT;
-                debug!(target: "rustyline", "activate ENABLE_WRAP_AT_EOL_OUTPUT");
-                unsafe {
-                    assert!(consoleapi::SetConsoleMode(self.stdstream_handle, mode) != 0);
-                }
-            }
             // To enable ANSI colors (Windows 10 only):
             // https://docs.microsoft.com/en-us/windows/console/setconsolemode
-            self.ansi_colors_supported = mode & wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0;
-            if self.ansi_colors_supported {
-                if self.color_mode == ColorMode::Disabled {
-                    mode &= !wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING;
-                    debug!(target: "rustyline", "deactivate ENABLE_VIRTUAL_TERMINAL_PROCESSING");
-                    unsafe {
-                        assert!(consoleapi::SetConsoleMode(self.stdstream_handle, mode) != 0);
-                    }
-                } else {
-                    debug!(target: "rustyline", "ANSI colors already enabled");
-                }
-            } else if self.color_mode != ColorMode::Disabled {
-                mode |= wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING;
+            if original_stdstream_mode & wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0 {
+                let raw = original_stdstream_mode | wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING;
                 self.ansi_colors_supported =
-                    unsafe { consoleapi::SetConsoleMode(self.stdstream_handle, mode) != 0 };
+                    unsafe { consoleapi::SetConsoleMode(self.stdstream_handle, raw) != 0 };
                 debug!(target: "rustyline", "ansi_colors_supported: {}", self.ansi_colors_supported);
+            } else {
+                debug!(target: "rustyline", "ANSI colors already enabled");
+                self.ansi_colors_supported = true;
             }
             Some(original_stdstream_mode)
         } else {
diff --git a/src/validate.rs b/src/validate.rs
index b37ea63..b6348a3 100644
--- a/src/validate.rs
+++ b/src/validate.rs
@@ -15,7 +15,6 @@ pub enum ValidationResult {
     Valid(Option<String>),
 }
 
-/// Give access to user input.
 pub struct ValidationContext<'i> {
     i: &'i mut dyn Invoke,
 }
@@ -25,7 +24,6 @@ impl<'i> ValidationContext<'i> {
         ValidationContext { i }
     }
 
-    /// Returns user input.
     pub fn input(&self) -> &str {
         self.i.input()
     }
@@ -89,7 +87,6 @@ pub struct MatchingBracketValidator {
 }
 
 impl MatchingBracketValidator {
-    /// Constructor
     pub fn new() -> Self {
         Self { _priv: () }
     }


Test patch:
diff --git a/src/test/highlight.rs b/src/test/highlight.rs
new file mode 100644
index 0000000..905197a
--- /dev/null
+++ b/src/test/highlight.rs
@@ -0,0 +1,22 @@
+use crate::highlight::split_highlight;
+
+#[test]
+fn split_bold() {
+    let (a, b) = split_highlight("\x1b[1mword1 word2\x1b[0m", 5);
+    assert_eq!(a, "\x1b[1mword1\x1b[0m");
+    assert_eq!(b, "\x1b[1m word2\x1b[0m");
+}
+
+#[test]
+fn split_at_the_reset() {
+    let (a, b) = split_highlight("\x1b[1mword1\x1b[0m word2", 5);
+    assert_eq!(a, "\x1b[1mword1\x1b[0m");
+    assert_eq!(b, " word2");
+}
+
+#[test]
+fn split_nowhere() {
+    let (a, b) = split_highlight("\x1b[1mword1\x1b[0m word2", 6);
+    assert_eq!(a, "\x1b[1mword1\x1b[0m ");
+    assert_eq!(b, "word2");
+}
diff --git a/src/test/mod.rs b/src/test/mod.rs
index 365fe91..3ed6e8c 100644
--- a/src/test/mod.rs
+++ b/src/test/mod.rs
@@ -15,6 +15,7 @@ use crate::{Context, Editor, Helper, Result};
 
 mod common;
 mod emacs;
+mod highlight;
 mod history;
 mod vi_cmd;
 mod vi_insert;
diff --git a/src/tty/test.rs b/src/tty/test.rs
index 34333b1..f8dbb02 100644
--- a/src/tty/test.rs
+++ b/src/tty/test.rs
@@ -159,10 +159,16 @@ impl Term for DummyTerminal {
 
     // Init checks:
 
+    #[cfg(not(target_arch = "wasm32"))]
     fn is_unsupported(&self) -> bool {
         false
     }
 
+    #[cfg(target_arch = "wasm32")]
+    fn is_unsupported(&self) -> bool {
+        true
+    }
+
     fn is_stdin_tty(&self) -> bool {
         true
     }