Repository: kkawakam/rustyline
Pull request #283 (2019-08-27 17:57:46)

[WIP] Invalidate prompt_size on sigwinch
Partial fix for #280


Patch:
diff --git a/src/edit.rs b/src/edit.rs
index 80fe90b..89288dd 100644
--- a/src/edit.rs
+++ b/src/edit.rs
@@ -11,8 +11,9 @@ use crate::highlight::Highlighter;
 use crate::history::Direction;
 use crate::keymap::{Anchor, At, CharSearch, Cmd, Movement, RepeatCount, Word};
 use crate::keymap::{InputState, Refresher};
+use crate::layout::{Layout, Position};
 use crate::line_buffer::{LineBuffer, WordAction, MAX_LINE};
-use crate::tty::{Position, Renderer, Term, Terminal};
+use crate::tty::{Renderer, Term, Terminal};
 use crate::undo::Changeset;
 
 /// Represent the state during line editing.
@@ -22,9 +23,7 @@ pub struct State<'out, 'prompt, H: Helper> {
     prompt: &'prompt str,  // Prompt to display (rl_prompt)
     prompt_size: Position, // Prompt Unicode/visible width and height
     pub line: LineBuffer,  // Edited line buffer
-    pub cursor: Position,  /* Cursor position (relative to the start of the prompt
-                            * for `row`) */
-    pub old_rows: usize, // Number of rows used so far (from start of prompt to end of input)
+    pub layout: Layout,
     saved_line_for_history: LineBuffer, // Current edited line before history browsing
     byte_buffer: [u8; 4],
     pub changes: Rc<RefCell<Changeset>>, // changes to line, for undo/redo
@@ -53,8 +52,7 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
             prompt,
             prompt_size,
             line: LineBuffer::with_capacity(MAX_LINE).can_growth(true),
-            cursor: prompt_size,
-            old_rows: 0,
+            layout: Layout::default(),
             saved_line_for_history: LineBuffer::with_capacity(MAX_LINE).can_growth(true),
             byte_buffer: [0; 4],
             changes: Rc::new(RefCell::new(Changeset::new())),
@@ -83,6 +81,9 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
             let rc = input_state.next_cmd(rdr, self, single_esc_abort);
             if rc.is_err() && self.out.sigwinch() {
                 self.out.update_size();
+                self.prompt_size = self
+                    .out
+                    .calculate_position(self.prompt, Position::default());
                 self.refresh_line()?;
                 continue;
             }
@@ -110,16 +111,19 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
         let cursor = self
             .out
             .calculate_position(&self.line[..self.line.pos()], self.prompt_size);
-        if self.cursor == cursor {
+        if self.layout.cursor == cursor {
             return Ok(());
         }
         if self.highlight_char() {
             let prompt_size = self.prompt_size;
             self.refresh(self.prompt, prompt_size, true, Info::NoHint)?;
         } else {
-            self.out.move_cursor(self.cursor, cursor)?;
+            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);
         }
-        self.cursor = cursor;
         Ok(())
     }
 
@@ -144,19 +148,36 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
         } else {
             None
         };
-        let (cursor, end_pos) = self.out.refresh_line(
-            prompt,
+
+        // calculate the desired position of the cursor
+        let pos = self.line.pos();
+        let cursor = self.out.calculate_position(&self.line[..pos], prompt_size);
+        // calculate the position of the end of the input line
+        let end = if pos == self.line.len() {
+            cursor
+        } else {
+            self.out.calculate_position(&self.line[pos..], cursor)
+        };
+
+        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);
+
+        self.out.refresh_line(
+            prompt,
             &self.line,
             info,
-            self.cursor.row,
-            self.old_rows,
+            &self.layout,
+            &new_layout,
             highlighter,
         )?;
+        self.layout = new_layout;
 
-        self.cursor = cursor;
-        self.old_rows = end_pos.row;
         Ok(())
     }
 
@@ -242,9 +263,8 @@ impl<'out, 'prompt, H: Helper> fmt::Debug for State<'out, 'prompt, H> {
             .field("prompt", &self.prompt)
             .field("prompt_size", &self.prompt_size)
             .field("buf", &self.line)
-            .field("cursor", &self.cursor)
             .field("cols", &self.out.get_columns())
-            .field("old_rows", &self.old_rows)
+            .field("layout", &self.layout)
             .field("saved_line_for_history", &self.saved_line_for_history)
             .finish()
     }
@@ -258,16 +278,18 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
                 let prompt_size = self.prompt_size;
                 let no_previous_hint = self.hint.is_none();
                 self.hint();
+                let width = ch.width().unwrap_or(0);
                 if n == 1
-                    && self.cursor.col + ch.width().unwrap_or(0) < self.out.get_columns()
+                    && width != 0 // Ctrl-V + \t or \n ...
+                    && self.layout.cursor.col + width < self.out.get_columns()
                     && (self.hint.is_none() && no_previous_hint) // TODO refresh only current line
                     && !self.highlight_char()
                 {
                     // Avoid a full update of the line in the trivial case.
-                    let cursor = self
-                        .out
-                        .calculate_position(&self.line[..self.line.pos()], self.prompt_size);
-                    self.cursor = cursor;
+                    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();
                     self.out.write_and_flush(bits)
@@ -577,8 +599,7 @@ pub fn init_state<'out, H: Helper>(
         prompt: "",
         prompt_size: Position::default(),
         line: LineBuffer::init(line, pos, None),
-        cursor: Position::default(),
-        old_rows: 0,
+        layout: Layout::default(),
         saved_line_for_history: LineBuffer::with_capacity(100),
         byte_buffer: [0; 4],
         changes: Rc::new(RefCell::new(Changeset::new())),
diff --git a/src/layout.rs b/src/layout.rs
new file mode 100644
index 0000000..6e317c3
--- /dev/null
+++ b/src/layout.rs
@@ -0,0 +1,33 @@
+use std::cmp::{Ord, Ordering, PartialOrd};
+
+#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
+pub struct Position {
+    pub col: usize,
+    pub row: usize,
+}
+
+impl PartialOrd for Position {
+    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+impl Ord for Position {
+    fn cmp(&self, other: &Self) -> Ordering {
+        match self.row.cmp(&other.row) {
+            Ordering::Equal => self.col.cmp(&other.col),
+            o => o,
+        }
+    }
+}
+
+#[derive(Debug, Default)]
+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)
+    pub cursor: Position,
+    /// Number of rows used so far (from start of prompt to end of input)
+    pub end: Position,
+}
diff --git a/src/lib.rs b/src/lib.rs
index 718781f..e052c61 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -27,6 +27,7 @@ pub mod history;
 mod keymap;
 mod keys;
 mod kill_ring;
+mod layout;
 pub mod line_buffer;
 mod undo;
 
@@ -160,7 +161,7 @@ fn complete_line<H: Helper>(
         let show_completions = if candidates.len() > config.completion_prompt_limit() {
             let msg = format!("\nDisplay all {} possibilities? (y or n)", candidates.len());
             s.out.write_and_flush(msg.as_bytes())?;
-            s.old_rows += 1;
+            s.layout.end.row += 1;
             while cmd != Cmd::SelfInsert(1, 'y')
                 && cmd != Cmd::SelfInsert(1, 'Y')
                 && cmd != Cmd::SelfInsert(1, 'n')
@@ -522,7 +523,7 @@ fn readline_edit<H: Helper>(
             Cmd::AcceptLine => {
                 #[cfg(test)]
                 {
-                    editor.term.cursor = s.cursor.col;
+                    editor.term.cursor = s.layout.cursor.col;
                 }
                 // Accept the line regardless of where the cursor is.
                 s.edit_move_end()?;
diff --git a/src/tty/mod.rs b/src/tty/mod.rs
index d58762c..3acf138 100644
--- a/src/tty/mod.rs
+++ b/src/tty/mod.rs
@@ -5,6 +5,7 @@ use unicode_width::UnicodeWidthStr;
 use crate::config::{BellStyle, ColorMode, Config, OutputStreamType};
 use crate::highlight::Highlighter;
 use crate::keys::KeyPress;
+use crate::layout::{Layout, Position};
 use crate::line_buffer::LineBuffer;
 use crate::Result;
 
@@ -25,12 +26,6 @@ pub trait RawReader {
     fn read_pasted_text(&mut self) -> Result<String>;
 }
 
-#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
-pub struct Position {
-    pub col: usize,
-    pub row: usize,
-}
-
 /// Display prompt, line and cursor in terminal output
 pub trait Renderer {
     type Reader: RawReader;
@@ -42,14 +37,12 @@ pub trait Renderer {
     fn refresh_line(
         &mut self,
         prompt: &str,
-        prompt_size: Position,
-        default_prompt: bool,
         line: &LineBuffer,
         hint: Option<&str>,
-        current_row: usize,
-        old_rows: usize,
+        old_layout: &Layout,
+        new_layout: &Layout,
         highlighter: Option<&dyn Highlighter>,
-    ) -> Result<(Position, Position)>;
+    ) -> Result<()>;
 
     /// Calculate the number of columns and rows used to display `s` on a
     /// `cols` width terminal starting at `orig`.
@@ -89,24 +82,13 @@ impl<'a, R: Renderer + ?Sized> Renderer for &'a mut R {
     fn refresh_line(
         &mut self,
         prompt: &str,
-        prompt_size: Position,
-        default_prompt: bool,
         line: &LineBuffer,
         hint: Option<&str>,
-        current_row: usize,
-        old_rows: usize,
+        old_layout: &Layout,
+        new_layout: &Layout,
         highlighter: Option<&dyn Highlighter>,
-    ) -> Result<(Position, Position)> {
-        (**self).refresh_line(
-            prompt,
-            prompt_size,
-            default_prompt,
-            line,
-            hint,
-            current_row,
-            old_rows,
-            highlighter,
-        )
+    ) -> Result<()> {
+        (**self).refresh_line(prompt, line, hint, old_layout, new_layout, highlighter)
     }
 
     fn calculate_position(&self, s: &str, orig: Position) -> Position {
diff --git a/src/tty/unix.rs b/src/tty/unix.rs
index d86e54a..1df61ca 100644
--- a/src/tty/unix.rs
+++ b/src/tty/unix.rs
@@ -15,11 +15,12 @@ use nix::sys::termios::SetArg;
 use unicode_segmentation::UnicodeSegmentation;
 use utf8parse::{Parser, Receiver};
 
-use super::{truncate, width, Position, RawMode, RawReader, Renderer, Term};
+use super::{truncate, width, 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::Result;
 
@@ -521,23 +522,22 @@ impl Renderer for PosixRenderer {
     fn refresh_line(
         &mut self,
         prompt: &str,
-        prompt_size: Position,
-        default_prompt: bool,
         line: &LineBuffer,
         hint: Option<&str>,
-        current_row: usize,
-        old_rows: usize,
+        old_layout: &Layout,
+        new_layout: &Layout,
         highlighter: Option<&dyn Highlighter>,
-    ) -> Result<(Position, Position)> {
+    ) -> Result<()> {
         use std::fmt::Write;
         self.buffer.clear();
 
-        // calculate the position of the end of the input line
-        let end_pos = self.calculate_position(line, prompt_size);
-        // calculate the desired position of the cursor
-        let cursor = self.calculate_position(&line[..line.pos()], prompt_size);
+        let default_prompt = new_layout.default_prompt;
+        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.old_rows < self.cursor.row if the prompt spans multiple lines and if
+        // old_rows < cursor.row if the prompt spans multiple lines and if
         // this is the default State.
         let cursor_row_movement = old_rows.checked_sub(current_row).unwrap_or(0);
         // move the cursor down as required
@@ -591,7 +591,8 @@ impl Renderer for PosixRenderer {
         }
 
         self.write_and_flush(self.buffer.as_bytes())?;
-        Ok((cursor, end_pos))
+
+        Ok(())
     }
 
     fn write_and_flush(&self, buf: &[u8]) -> Result<()> {
diff --git a/src/tty/windows.rs b/src/tty/windows.rs
index a878e86..4edfde9 100644
--- a/src/tty/windows.rs
+++ b/src/tty/windows.rs
@@ -9,11 +9,12 @@ use winapi::shared::minwindef::{DWORD, WORD};
 use winapi::um::winnt::{CHAR, HANDLE};
 use winapi::um::{consoleapi, handleapi, processenv, winbase, wincon, winuser};
 
-use super::{truncate, Position, RawMode, RawReader, Renderer, Term};
+use super::{truncate, 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::Result;
 
@@ -318,18 +319,17 @@ impl Renderer for ConsoleRenderer {
     fn refresh_line(
         &mut self,
         prompt: &str,
-        prompt_size: Position,
-        default_prompt: bool,
         line: &LineBuffer,
         hint: Option<&str>,
-        current_row: usize,
-        old_rows: usize,
+        old_layout: &Layout,
+        new_layout: &Layout,
         highlighter: Option<&dyn Highlighter>,
-    ) -> Result<(Position, Position)> {
-        // calculate the position of the end of the input line
-        let end_pos = self.calculate_position(line, prompt_size);
-        // calculate the desired position of the cursor
-        let cursor = self.calculate_position(&line[..line.pos()], prompt_size);
+    ) -> Result<()> {
+        let default_prompt = new_layout.default_prompt;
+        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;
 
         // position at the start of the prompt, clear to end of previous input
         let info = self.get_console_screen_buffer_info()?;
@@ -365,10 +365,12 @@ impl Renderer for ConsoleRenderer {
         self.write_and_flush(self.buffer.as_bytes())?;
 
         // position the cursor
+        let mut coord = self.get_console_screen_buffer_info()?.dwCursorPosition;
         coord.X = cursor.col as i16;
         coord.Y -= (end_pos.row - cursor.row) as i16;
         self.set_console_cursor_position(coord)?;
-        Ok((cursor, end_pos))
+
+        Ok(())
     }
 
     fn write_and_flush(&self, buf: &[u8]) -> Result<()> {


Test patch:
diff --git a/src/tty/test.rs b/src/tty/test.rs
index ff70756..ac0518a 100644
--- a/src/tty/test.rs
+++ b/src/tty/test.rs
@@ -3,11 +3,12 @@ use std::iter::IntoIterator;
 use std::slice::Iter;
 use std::vec::IntoIter;
 
-use super::{truncate, Position, RawMode, RawReader, Renderer, Term};
+use super::{truncate, RawMode, RawReader, Renderer, Term};
 use crate::config::{BellStyle, ColorMode, Config, OutputStreamType};
 use crate::error::ReadlineError;
 use crate::highlight::Highlighter;
 use crate::keys::KeyPress;
+use crate::layout::{Layout, Position};
 use crate::line_buffer::LineBuffer;
 use crate::Result;
 
@@ -76,21 +77,17 @@ impl Renderer for Sink {
 
     fn refresh_line(
         &mut self,
-        _: &str,
-        prompt_size: Position,
-        _: bool,
-        line: &LineBuffer,
+        _prompt: &str,
+        _line: &LineBuffer,
         hint: Option<&str>,
-        _: usize,
-        _: usize,
-        _: Option<&dyn Highlighter>,
-    ) -> Result<(Position, Position)> {
-        let cursor = self.calculate_position(&line[..line.pos()], prompt_size);
+        _old_layout: &Layout,
+        _new_layout: &Layout,
+        _highlighter: Option<&dyn Highlighter>,
+    ) -> Result<()> {
         if let Some(hint) = hint {
             truncate(&hint, 0, 80);
         }
-        let end = self.calculate_position(&line, prompt_size);
-        Ok((cursor, end))
+        Ok(())
     }
 
     fn calculate_position(&self, s: &str, orig: Position) -> Position {