Repository: kkawakam/rustyline
Pull request #355 (2020-04-01 16:55:16)

Revert "Merge pull request #338 from tailhook/prompt"
This reverts commit 148e1ee6f353a2c4ba69bb3fc21fa56db0a61bc9, reversing
changes made to e7bf9012197717bbaad5f75ed91583886d906038.


Patch:
diff --git a/examples/example.rs b/examples/example.rs
index 9614fc0..2b3363f 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, PromptInfo};
+use rustyline::highlight::{Highlighter, MatchingBracketHighlighter};
 use rustyline::hint::{Hinter, HistoryHinter};
 use rustyline::validate::{self, MatchingBracketValidator, Validator};
 use rustyline::{Cmd, CompletionType, Config, Context, EditMode, Editor, KeyPress};
@@ -16,7 +16,6 @@ struct MyHelper {
     validator: MatchingBracketValidator,
     hinter: HistoryHinter,
     colored_prompt: String,
-    continuation_prompt: String,
 }
 
 impl Completer for MyHelper {
@@ -42,23 +41,15 @@ impl Highlighter for MyHelper {
     fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
         &'s self,
         prompt: &'p str,
-        info: PromptInfo<'_>,
+        default: bool,
     ) -> Cow<'b, str> {
-        if info.default() {
-            if info.line_no() > 0 {
-                Borrowed(&self.continuation_prompt)
-            } else {
-                Borrowed(&self.colored_prompt)
-            }
+        if default {
+            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")
     }
@@ -99,8 +90,7 @@ fn main() -> rustyline::Result<()> {
         completer: FilenameCompleter::new(),
         highlighter: MatchingBracketHighlighter::new(),
         hinter: HistoryHinter {},
-        colored_prompt: "  0> ".to_owned(),
-        continuation_prompt: "\x1b[1;32m...> \x1b[0m".to_owned(),
+        colored_prompt: "".to_owned(),
         validator: MatchingBracketValidator::new(),
     };
     let mut rl = Editor::with_config(config);
@@ -112,7 +102,7 @@ fn main() -> rustyline::Result<()> {
     }
     let mut count = 1;
     loop {
-        let p = format!("{:>3}> ", count);
+        let p = format!("{}> ", 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/src/edit.rs b/src/edit.rs
index 0c93725..bc8a8a1 100644
--- a/src/edit.rs
+++ b/src/edit.rs
@@ -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()], Position::default());
+            .calculate_position(&self.line[..self.line.pos()], self.prompt_size);
         if self.layout.cursor == cursor {
             return Ok(());
         }
@@ -123,6 +123,7 @@ 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(())
@@ -152,9 +153,7 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
 
         // calculate the desired position of the cursor
         let pos = self.line.pos();
-        let cursor = self
-            .out
-            .calculate_position(&self.line[..pos], Position::default());
+        let cursor = self.out.calculate_position(&self.line[..pos], prompt_size);
         // calculate the position of the end of the input line
         let mut end = if pos == self.line.len() {
             cursor
@@ -171,6 +170,7 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
             cursor,
             end,
         };
+        debug_assert!(new_layout.prompt_size <= new_layout.cursor);
         debug_assert!(new_layout.cursor <= new_layout.end);
 
         debug!(target: "rustyline", "old layout: {:?}", self.layout);
@@ -336,6 +336,7 @@ 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/highlight.rs b/src/highlight.rs
index 07475f0..5349a07 100644
--- a/src/highlight.rs
+++ b/src/highlight.rs
@@ -5,15 +5,6 @@ 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).
@@ -35,18 +26,11 @@ pub trait Highlighter {
     fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
         &'s self,
         prompt: &'p str,
-        info: PromptInfo<'_>,
+        default: bool,
     ) -> Cow<'b, str> {
-        let _ = info;
+        let _ = default;
         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> {
@@ -85,9 +69,9 @@ impl<'r, H: ?Sized + Highlighter> Highlighter for &'r H {
     fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
         &'s self,
         prompt: &'p str,
-        info: PromptInfo<'_>,
+        default: bool,
     ) -> Cow<'b, str> {
-        (**self).highlight_prompt(prompt, info)
+        (**self).highlight_prompt(prompt, default)
     }
 
     fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
@@ -246,86 +230,6 @@ 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/layout.rs b/src/layout.rs
index 3488588..6e317c3 100644
--- a/src/layout.rs
+++ b/src/layout.rs
@@ -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 end of the prompt)
+    /// Cursor position (relative to the start of the prompt)
     pub cursor: Position,
-    /// Number of rows used so far (from end of prompt to end of input)
+    /// Number of rows used so far (from start of prompt to end of input)
     pub end: Position,
 }
diff --git a/src/tty/mod.rs b/src/tty/mod.rs
index 51fe160..3f6097e 100644
--- a/src/tty/mod.rs
+++ b/src/tty/mod.rs
@@ -156,102 +156,6 @@ pub trait Term {
     fn create_writer(&self) -> Self::Writer;
 }
 
-#[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;
diff --git a/src/tty/unix.rs b/src/tty/unix.rs
index 80c4543..a39a0bd 100644
--- a/src/tty/unix.rs
+++ b/src/tty/unix.rs
@@ -21,7 +21,6 @@ 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;
@@ -542,7 +541,7 @@ impl Renderer for PosixRenderer {
         self.buffer.clear();
 
         let default_prompt = new_layout.default_prompt;
-        let mut cursor = new_layout.cursor;
+        let cursor = new_layout.cursor;
         let end_pos = new_layout.end;
         let current_row = old_layout.cursor.row;
         let old_rows = old_layout.end.row;
@@ -561,15 +560,19 @@ impl Renderer for PosixRenderer {
         // 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,
-        );
+        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);
+        }
         // display hint
         if let Some(hint) = hint {
             if let Some(highlighter) = highlighter {
@@ -578,6 +581,10 @@ 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
@@ -585,10 +592,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 {
-            self.buffer.push('\r');
-        } else {
+        if cursor.col > 0 {
             write!(self.buffer, "\r\x1b[{}C", cursor.col).unwrap();
+        } else {
+            self.buffer.push('\r');
         }
 
         self.write_and_flush(self.buffer.as_bytes())?;
diff --git a/src/tty/windows.rs b/src/tty/windows.rs
index f4f852d..302504e 100644
--- a/src/tty/windows.rs
+++ b/src/tty/windows.rs
@@ -16,7 +16,6 @@ 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;
@@ -327,22 +326,26 @@ impl Renderer for ConsoleRenderer {
         highlighter: Option<&dyn Highlighter>,
     ) -> Result<()> {
         let default_prompt = new_layout.default_prompt;
-        let mut cursor = new_layout.cursor;
+        let 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();
-        add_prompt_and_highlight(
-            &mut self.buffer,
-            highlighter,
-            line,
-            prompt,
-            default_prompt,
-            &new_layout,
-            &mut cursor,
-        );
-
+        if let Some(highlighter) = highlighter {
+            // TODO handle ansi escape code (SetConsoleTextAttribute)
+            // append the prompt
+            self.buffer
+                .push_str(&highlighter.highlight_prompt(prompt, default_prompt));
+            // append the input line
+            self.buffer
+                .push_str(&highlighter.highlight(line, line.pos()));
+        } else {
+            // append the prompt
+            self.buffer.push_str(prompt);
+            // append the input line
+            self.buffer.push_str(line);
+        }
         // append hint
         if let Some(hint) = hint {
             if let Some(highlighter) = highlighter {


Test patch:
diff --git a/src/test/highlight.rs b/src/test/highlight.rs
deleted file mode 100644
index 905197a..0000000
--- a/src/test/highlight.rs
+++ /dev/null
@@ -1,22 +0,0 @@
-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 3ed6e8c..365fe91 100644
--- a/src/test/mod.rs
+++ b/src/test/mod.rs
@@ -15,7 +15,6 @@ use crate::{Context, Editor, Helper, Result};
 
 mod common;
 mod emacs;
-mod highlight;
 mod history;
 mod vi_cmd;
 mod vi_insert;