Repository: kkawakam/rustyline (rust)

Issue:
The last line of text printed by the program disappears if it doesn't end with a newline
Sample program:

```rust
use rustyline::{Editor,error::ReadlineError};

fn main() {
    let mut e: Editor<()> = Editor::new();

    loop {
        let rl = e.readline("$ ");
        match rl {
            Ok(_) => {
                print!("text\nwithout\nnewline");
            }
            Err(ReadlineError::Interrupted) | Err(ReadlineError::Eof) => break,
            Err(err) => panic!(err),
        }
    }
}
```

Expected output:

```
$ some text here
text
without
newline$ some other
```

Actual output:

```
$ some text here
text
without
$ some other
```

Pull request description:
Make sure prompt is at the leftmost edge of the screen
Fix #216

Patch:
diff --git a/TODO.md b/TODO.md
index d0fd37f..eb7cc25 100644
--- a/TODO.md
+++ b/TODO.md
@@ -8,7 +8,9 @@ Bell
 
 Color
 - [X] ANSI Colors & Windows 10+
-- [ ] ANSI Colors & Windows <10 (https://docs.rs/console/0.6.1/console/fn.strip_ansi_codes.html ? https://github.com/mattn/go-colorable/blob/master/colorable_windows.go)
+- [ ] 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,
+  https://github.com/mattn/ansicolor-w32.c)
 - [ ] Syntax highlighting (https://github.com/trishume/syntect/)
 - [ ] clicolors spec (https://docs.rs/console/0.6.1/console/fn.colors_enabled.html)
 
diff --git a/src/config.rs b/src/config.rs
index d92a603..52cbb74 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -173,6 +173,7 @@ pub enum ColorMode {
 }
 
 /// Should the editor use stdout or stderr
+// TODO console term::TermTarget
 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
 pub enum OutputStreamType {
     Stderr,
diff --git a/src/edit.rs b/src/edit.rs
index eedaa07..5a88a38 100644
--- a/src/edit.rs
+++ b/src/edit.rs
@@ -124,6 +124,10 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
         Ok(())
     }
 
+    pub fn move_cursor_at_leftmost<R: RawReader>(&mut self, rdr: &mut R) -> Result<()> {
+        self.out.move_cursor_at_leftmost(rdr)
+    }
+
     fn refresh(&mut self, prompt: &str, prompt_size: Position, info: Info<'_>) -> Result<()> {
         let info = match info {
             Info::NoHint => None,
diff --git a/src/error.rs b/src/error.rs
index 9f6f3f1..fb2d4d6 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -74,6 +74,12 @@ impl From<io::Error> for ReadlineError {
     }
 }
 
+impl From<io::ErrorKind> for ReadlineError {
+    fn from(kind: io::ErrorKind) -> Self {
+        ReadlineError::Io(io::Error::from(kind))
+    }
+}
+
 #[cfg(unix)]
 impl From<nix::Error> for ReadlineError {
     fn from(err: nix::Error) -> Self {
diff --git a/src/lib.rs b/src/lib.rs
index f2dad47..adb1138 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -390,9 +390,9 @@ fn readline_edit<H: Helper>(
             .update((left.to_owned() + right).as_ref(), left.len());
     }
 
-    s.refresh_line()?;
-
     let mut rdr = editor.term.create_reader(&editor.config)?;
+    s.move_cursor_at_leftmost(&mut rdr)?;
+    s.refresh_line()?;
 
     loop {
         let rc = s.next_cmd(&mut input_state, &mut rdr, false);
diff --git a/src/tty/mod.rs b/src/tty/mod.rs
index 9adf4a3..37f5104 100644
--- a/src/tty/mod.rs
+++ b/src/tty/mod.rs
@@ -77,6 +77,9 @@ pub trait Renderer {
     fn get_rows(&self) -> usize;
     /// Check if output supports colors.
     fn colors_enabled(&self) -> bool;
+
+    /// Make sure prompt is at the leftmost edge of the screen
+    fn move_cursor_at_leftmost(&mut self, rdr: &mut RawReader) -> Result<()>;
 }
 
 impl<'a, R: Renderer + ?Sized> Renderer for &'a mut R {
@@ -140,6 +143,10 @@ impl<'a, R: Renderer + ?Sized> Renderer for &'a mut R {
     fn colors_enabled(&self) -> bool {
         (**self).colors_enabled()
     }
+
+    fn move_cursor_at_leftmost(&mut self, rdr: &mut RawReader) -> Result<()> {
+        (**self).move_cursor_at_leftmost(rdr)
+    }
 }
 
 /// Terminal contract
diff --git a/src/tty/unix.rs b/src/tty/unix.rs
index be194fb..798a5bb 100644
--- a/src/tty/unix.rs
+++ b/src/tty/unix.rs
@@ -641,6 +641,40 @@ impl Renderer for PosixRenderer {
     fn colors_enabled(&self) -> bool {
         self.colors_enabled
     }
+
+    fn move_cursor_at_leftmost(&mut self, rdr: &mut RawReader) -> Result<()> {
+        /* Report cursor location */
+        self.write_and_flush(b"\x1b[6n")?;
+        /* Read the response: ESC [ rows ; cols R */
+        if rdr.next_char()? != '\x1b' {
+            return Err(error::ReadlineError::from(io::ErrorKind::InvalidData));
+        }
+        if rdr.next_char()? != '[' {
+            return Err(error::ReadlineError::from(io::ErrorKind::InvalidData));
+        }
+        read_digits_until(rdr, ';')?;
+        let col = read_digits_until(rdr, 'R')?;
+        debug!(target: "rustyline", "initial cursor location: {:?}", col);
+        if col != 1 {
+            self.write_and_flush(b"\n")?;
+        }
+        Ok(())
+    }
+}
+
+fn read_digits_until(rdr: &mut RawReader, sep: char) -> Result<u32> {
+    let mut num: u32 = 0;
+    loop {
+        match rdr.next_char()? {
+            digit @ '0'..='9' => {
+                num = num.saturating_mul(10).saturating_add(digit.to_digit(10).unwrap());
+                continue
+            },
+            c if c == sep => break,
+            _ => return Err(error::ReadlineError::from(io::ErrorKind::InvalidData)),
+        }
+    }
+    Ok(num)
 }
 
 static SIGWINCH_ONCE: sync::Once = sync::ONCE_INIT;
diff --git a/src/tty/windows.rs b/src/tty/windows.rs
index 56b0fef..bca908d 100644
--- a/src/tty/windows.rs
+++ b/src/tty/windows.rs
@@ -435,6 +435,18 @@ impl Renderer for ConsoleRenderer {
     fn colors_enabled(&self) -> bool {
         self.colors_enabled
     }
+
+    fn move_cursor_at_leftmost(&mut self, _: &mut RawReader) -> Result<()> {
+        self.write_and_flush(b"")?; // we must do this otherwise the cursor position is not reported correctly
+        let mut info = self.get_console_screen_buffer_info()?;
+        if info.dwCursorPosition.X == 0 {
+            return Ok(());
+        }
+        debug!(target: "rustyline", "initial cursor location: {:?}, {:?}", info.dwCursorPosition.X, info.dwCursorPosition.Y);
+        info.dwCursorPosition.X = 0;
+        info.dwCursorPosition.Y += 1;
+        self.set_console_cursor_position(info.dwCursorPosition)
+    }
 }
 
 static SIGWINCH: atomic::AtomicBool = atomic::AtomicBool::new(false);


Test patch:
diff --git a/src/tty/test.rs b/src/tty/test.rs
index d72f336..fc80222 100644
--- a/src/tty/test.rs
+++ b/src/tty/test.rs
@@ -125,6 +125,10 @@ impl Renderer for Sink {
     fn colors_enabled(&self) -> bool {
         false
     }
+
+    fn move_cursor_at_leftmost(&mut self, _: &mut RawReader) -> Result<()> {
+        Ok(())
+    }
 }
 
 pub type Terminal = DummyTerminal;