From 4652d4313466803e40b537b21ed87fe896f16318 Mon Sep 17 00:00:00 2001 From: Mike Grunweg Date: Thu, 20 Jan 2022 10:07:29 +0100 Subject: [PATCH 1/8] doc: Use imperative mode for doc comments in term.rs. This is consistent with e.g. PEP-257. Before, the doc comments were inconsistent on this matter. --- src/term.rs | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/src/term.rs b/src/term.rs index 56287dcb..128749b8 100644 --- a/src/term.rs +++ b/src/term.rs @@ -60,13 +60,13 @@ pub enum TermFamily { pub struct TermFeatures<'a>(&'a Term); impl<'a> TermFeatures<'a> { - /// Checks if this is a real user attended terminal (`isatty`) + /// Check if this is a real user attended terminal (`isatty`) #[inline] pub fn is_attended(&self) -> bool { is_a_terminal(self.0) } - /// Checks if colors are supported by this terminal. + /// Check if colors are supported by this terminal. /// /// This does not check if colors are enabled. Currently all terminals /// are considered to support colors @@ -75,7 +75,7 @@ impl<'a> TermFeatures<'a> { is_a_color_terminal(self.0) } - /// Checks if this terminal is an msys terminal. + /// Check if this terminal is an msys terminal. /// /// This is sometimes useful to disable features that are known to not /// work on msys terminals or require special handling. @@ -91,13 +91,13 @@ impl<'a> TermFeatures<'a> { } } - /// Checks if this terminal wants emojis. + /// Check if this terminal wants emojis. #[inline] pub fn wants_emoji(&self) -> bool { self.is_attended() && wants_emoji() } - /// Returns the family of the terminal. + /// Return the family of the terminal. #[inline] pub fn family(&self) -> TermFamily { if !self.is_attended() { @@ -203,7 +203,7 @@ impl Term { }) } - /// Returns the style for the term + /// Return the style for the term #[inline] pub fn style(&self) -> Style { match self.inner.target { @@ -214,7 +214,7 @@ impl Term { } } - /// Returns the target + /// Return the target #[inline] pub fn target(&self) -> TermTarget { self.inner.target.clone() @@ -228,7 +228,7 @@ impl Term { } } - /// Writes a string to the terminal and adds a newline. + /// Write a string to the terminal and add a newline. pub fn write_line(&self, s: &str) -> io::Result<()> { match self.inner.buffer { Some(ref mutex) => { @@ -241,7 +241,7 @@ impl Term { } } - /// Read a single character from the terminal + /// Read a single character from the terminal. /// /// This does not echo the character and blocks until a single character /// is entered. @@ -350,7 +350,7 @@ impl Term { } } - /// Flushes internal buffers. + /// Flush internal buffers. /// /// This forces the contents of the internal buffer to be written to /// the terminal. This is unnecessary for unbuffered terminals which @@ -366,25 +366,25 @@ impl Term { Ok(()) } - /// Checks if the terminal is indeed a terminal. + /// Check if the terminal is indeed a terminal. #[inline] pub fn is_term(&self) -> bool { self.is_tty } - /// Checks for common terminal features. + /// Check for common terminal features. #[inline] pub fn features(&self) -> TermFeatures<'_> { TermFeatures(self) } - /// Returns the terminal size in rows and columns or gets sensible defaults. + /// Return the terminal size in rows and columns or gets sensible defaults. #[inline] pub fn size(&self) -> (u16, u16) { self.size_checked().unwrap_or((24, DEFAULT_WIDTH)) } - /// Returns the terminal size in rows and columns. + /// Return the terminal size in rows and columns. /// /// If the size cannot be reliably determined None is returned. #[inline] @@ -392,37 +392,37 @@ impl Term { terminal_size(self) } - /// Moves the cursor to `x` and `y` + /// Move the cursor to `x` and `y` #[inline] pub fn move_cursor_to(&self, x: usize, y: usize) -> io::Result<()> { move_cursor_to(self, x, y) } - /// Moves the cursor up `n` lines + /// Move the cursor up `n` lines #[inline] pub fn move_cursor_up(&self, n: usize) -> io::Result<()> { move_cursor_up(self, n) } - /// Moves the cursor down `n` lines + /// Move the cursor down `n` lines #[inline] pub fn move_cursor_down(&self, n: usize) -> io::Result<()> { move_cursor_down(self, n) } - /// Moves the cursor left `n` lines + /// Move the cursor left `n` lines #[inline] pub fn move_cursor_left(&self, n: usize) -> io::Result<()> { move_cursor_left(self, n) } - /// Moves the cursor down `n` lines + /// Move the cursor down `n` lines #[inline] pub fn move_cursor_right(&self, n: usize) -> io::Result<()> { move_cursor_right(self, n) } - /// Clears the current line. + /// Clear the current line. /// /// The positions the cursor at the beginning of the line again. #[inline] @@ -444,19 +444,19 @@ impl Term { Ok(()) } - /// Clears the entire screen. + /// Clear the entire screen. #[inline] pub fn clear_screen(&self) -> io::Result<()> { clear_screen(self) } - /// Clears the entire screen. + /// Clear the entire screen. #[inline] pub fn clear_to_end_of_screen(&self) -> io::Result<()> { clear_to_end_of_screen(self) } - /// Clears the last char in the the current line. + /// Clear the last `n` chars in the current line. #[inline] pub fn clear_chars(&self, n: usize) -> io::Result<()> { clear_chars(self, n) @@ -470,13 +470,13 @@ impl Term { set_title(title); } - /// Makes cursor visible again + /// Make the cursor visible again #[inline] pub fn show_cursor(&self) -> io::Result<()> { show_cursor(self) } - /// Hides cursor + /// Hide the cursor #[inline] pub fn hide_cursor(&self) -> io::Result<()> { hide_cursor(self) From 56677595b2cfdd08c91f8ab581a540f58293e902 Mon Sep 17 00:00:00 2001 From: Mike Grunweg Date: Thu, 20 Jan 2022 10:12:47 +0100 Subject: [PATCH 2/8] doc: Minor grammar edits to documentation comments in term.rs. --- src/term.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/term.rs b/src/term.rs index 128749b8..477bec04 100644 --- a/src/term.rs +++ b/src/term.rs @@ -332,7 +332,7 @@ impl Term { Ok(chars.iter().collect::()) } - /// Read securely a line of input. + /// Read a line of input securely. /// /// This is similar to `read_line` but will not echo the output. This /// also switches the terminal into a different mode where not all @@ -386,7 +386,7 @@ impl Term { /// Return the terminal size in rows and columns. /// - /// If the size cannot be reliably determined None is returned. + /// If the size cannot be reliably determined `None` is returned. #[inline] pub fn size_checked(&self) -> Option<(u16, u16)> { terminal_size(self) @@ -424,7 +424,7 @@ impl Term { /// Clear the current line. /// - /// The positions the cursor at the beginning of the line again. + /// This positions the cursor at the beginning of the current line. #[inline] pub fn clear_line(&self) -> io::Result<()> { clear_line(self) @@ -456,7 +456,7 @@ impl Term { clear_to_end_of_screen(self) } - /// Clear the last `n` chars in the current line. + /// Clear the last `n` chars of the current line. #[inline] pub fn clear_chars(&self, n: usize) -> io::Result<()> { clear_chars(self, n) From 30f80051618ef9eda753bfefe4f7c51601cbda1b Mon Sep 17 00:00:00 2001 From: Mike Grunweg Date: Thu, 20 Jan 2022 10:15:09 +0100 Subject: [PATCH 3/8] doc: Fix doc comments for move_cursor_{left,right}. --- src/term.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/term.rs b/src/term.rs index 477bec04..bbbd7925 100644 --- a/src/term.rs +++ b/src/term.rs @@ -410,13 +410,13 @@ impl Term { move_cursor_down(self, n) } - /// Move the cursor left `n` lines + /// Move the cursor `n` characters to the left #[inline] pub fn move_cursor_left(&self, n: usize) -> io::Result<()> { move_cursor_left(self, n) } - /// Move the cursor down `n` lines + /// Move the cursor `n` characters to the right #[inline] pub fn move_cursor_right(&self, n: usize) -> io::Result<()> { move_cursor_right(self, n) From 7250fd18b4abff0da8aac0f9de9f3b779af5fd0d Mon Sep 17 00:00:00 2001 From: Mike Grunweg Date: Thu, 20 Jan 2022 10:15:27 +0100 Subject: [PATCH 4/8] doc: Clarify doc comments for move_cursor_to and clear_last_lines. --- src/term.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/term.rs b/src/term.rs index bbbd7925..ec6bb3d3 100644 --- a/src/term.rs +++ b/src/term.rs @@ -392,7 +392,7 @@ impl Term { terminal_size(self) } - /// Move the cursor to `x` and `y` + /// Move the cursor to row `x` and column `y`. #[inline] pub fn move_cursor_to(&self, x: usize, y: usize) -> io::Result<()> { move_cursor_to(self, x, y) @@ -430,7 +430,7 @@ impl Term { clear_line(self) } - /// Clear the last `n` lines. + /// Clear the last `n` lines before the current line. /// /// This positions the cursor at the beginning of the first line /// that was cleared. From 0fb376ff6e46bb1171d79813cc55f5f40943e8b2 Mon Sep 17 00:00:00 2001 From: Mike Grunweg Date: Thu, 20 Jan 2022 10:21:19 +0100 Subject: [PATCH 5/8] doc: Two minor wording tweaks. --- src/term.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/term.rs b/src/term.rs index ec6bb3d3..e3fb184c 100644 --- a/src/term.rs +++ b/src/term.rs @@ -203,7 +203,7 @@ impl Term { }) } - /// Return the style for the term + /// Return the style for this terminal #[inline] pub fn style(&self) -> Style { match self.inner.target { @@ -214,7 +214,7 @@ impl Term { } } - /// Return the target + /// Return the target of this terminal #[inline] pub fn target(&self) -> TermTarget { self.inner.target.clone() From c526c5cc88b538c133b8905f61cb603879a40f9f Mon Sep 17 00:00:00 2001 From: Mike Grunweg Date: Thu, 17 Feb 2022 14:57:21 +0100 Subject: [PATCH 6/8] Document the current behaviour of clear_last_lines when called with a too large argument. --- src/term.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/term.rs b/src/term.rs index 08bda30a..9503a1b4 100644 --- a/src/term.rs +++ b/src/term.rs @@ -436,8 +436,11 @@ impl Term { /// Clear the last `n` lines before the current line. /// - /// This positions the cursor at the beginning of the first line - /// that was cleared. + /// Position the cursor at the beginning of the first line that was cleared. + /// + /// **Caution.** When `n` is larger than the number of lines above the + /// current cursor, the top `n` lines are cleared --- including some lines + /// below the cursor. pub fn clear_last_lines(&self, n: usize) -> io::Result<()> { self.move_cursor_up(n)?; for _ in 0..n { From eb8b377b04d3d1d3f8a247d6ece2b97f48310fc7 Mon Sep 17 00:00:00 2001 From: Mike Grunweg Date: Fri, 29 Apr 2022 22:01:20 +0200 Subject: [PATCH 7/8] wip: Add function to compute the current cursor position. The implementation is a proof-of-concept, and now fully polished yet. --- src/common_term.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/common_term.rs b/src/common_term.rs index 020660a5..31d5eefd 100644 --- a/src/common_term.rs +++ b/src/common_term.rs @@ -38,6 +38,35 @@ pub fn move_cursor_to(out: &Term, x: usize, y: usize) -> io::Result<()> { out.write_str(&format!("\x1B[{};{}H", y + 1, x + 1)) } +/// Return the current cursor's position as a tuple `(n, m)`, +/// where `n` is the row and `m` the column of the cursor (both 1-based). +// FIXME: allow a larger range of characters than u8. +// FIXME: clear the terminal after this operation. +pub fn get_cursor_position(mut out: &Term) -> io::Result<(u8, u8)> { + // Send the code ESC6n to the terminal: asking for the current cursor position. + out.write_str("\x1b[6n")?; + // We expect a response ESC[n;mR, where n and m are the row and column of the cursor. + let mut buf = [0u8; 6]; + let num_read = io::Read::read(&mut out, &mut buf)?; + let (n, m) = match buf.as_slice() { + //[a, b, nbyte, c, mbyte, d] if [a, b, c, d] = sdf => (nbyte, mbyte), + // If we didn't read enough bytes, we certainly didn't get the response we wanted. + _ if num_read < buf.len() => return Err(std::io::Error::new( + io::ErrorKind::Other, format!("invalid terminal response: expected six bytes, only read {}", num_read) + )), + [a, b, n, c, m, d] => { + // The bytes a, b, c and d should be byte string \x1 [ ; R. + if &[*a, *b, *c, *d] != b"\x1b[;R" { + return Err(std::io::Error::new(io::ErrorKind::Other, "invalid terminal response: should be of the form ESC[n;mR")); + } else { + (n, m) + } + } + _ => unreachable!(), + }; + Ok((*n, *m)) +} + pub fn clear_chars(out: &Term, n: usize) -> io::Result<()> { if n > 0 { out.write_str(&format!("\x1b[{}D\x1b[0K", n)) From 0a2c4ce4195b1f5e70375571711ff4f5addd5ce2 Mon Sep 17 00:00:00 2001 From: Mike Grunweg Date: Fri, 29 Apr 2022 22:02:01 +0200 Subject: [PATCH 8/8] Make sure clear_last_lines never clears below the current cursor. Instead, check the number of rows above the cursor position, and error if passed a too large number. --- src/term.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/term.rs b/src/term.rs index 9503a1b4..78b3d506 100644 --- a/src/term.rs +++ b/src/term.rs @@ -434,14 +434,16 @@ impl Term { clear_line(self) } - /// Clear the last `n` lines before the current line. + /// Clear the last `n` lines before the current line, if possible. /// /// Position the cursor at the beginning of the first line that was cleared. - /// - /// **Caution.** When `n` is larger than the number of lines above the - /// current cursor, the top `n` lines are cleared --- including some lines - /// below the cursor. + /// Error when `n` is larger than the number of lines before the current cursor. pub fn clear_last_lines(&self, n: usize) -> io::Result<()> { + let (current_row, _) = get_cursor_position(self)?; + if usize::from(current_row) < n { + // We cannot move up n lines, only current_row ones. + return Err(io::Error::new(io::ErrorKind::Other, format!("can only move up {} lines, not {}", current_row, n))); + } self.move_cursor_up(n)?; for _ in 0..n { self.clear_line()?;