From 0479d68f502354ed1edc6ee39cd05e989b21a531 Mon Sep 17 00:00:00 2001 From: Timon Date: Sun, 27 Oct 2019 14:33:47 +0100 Subject: [PATCH] Removed old Cursor Api (#289) --- CHANGELOG.md | 19 +- src/crossterm.rs | 6 - src/cursor.rs | 439 +++++++++++++------------------- src/cursor/ansi.rs | 30 +++ src/cursor/cursor.rs | 45 ---- src/cursor/cursor/ansi.rs | 166 ------------ src/cursor/cursor/windows.rs | 111 -------- src/cursor/sys.rs | 13 +- src/cursor/sys/unix.rs | 31 +-- src/cursor/sys/windows.rs | 145 +++++++++-- src/lib.rs | 36 +-- src/screen/alternate.rs | 8 +- src/terminal/terminal/ansi.rs | 5 +- src/terminal/terminal/winapi.rs | 11 +- 14 files changed, 394 insertions(+), 671 deletions(-) create mode 100644 src/cursor/ansi.rs delete mode 100644 src/cursor/cursor.rs delete mode 100644 src/cursor/cursor/ansi.rs delete mode 100644 src/cursor/cursor/windows.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 253474d..8b0f08f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,20 @@ -# Next Version - -- Derived 'Copy' for 'KeyEvent' +# Master +- `input` module + - Derive 'Copy' for 'KeyEvent' +- `cursor` module + - Remove `TerminalCursor`, `cursor`, `Crossterm::cursor()` + - Introduce static function `crossterm::cursor::position` in place of `TerminalCursor::pos` + - Rename `Goto` to `MoveTo` + - Rename `Up` to `MoveLeft` + - Rename `Right` to `MoveRight` + - Rename `Down` to `MoveDown` + - Rename `BlinkOn` to `EnableBlinking` + - Rename `BlinkOff` to `DisableBlinking` + - Rename `ResetPos` to `ResetPosition` + - Rename `SavePos` to `SavePosition` + - Remove re-export cursor module types at root level, are now accessible from `crossterm::cursor` + # Version 0.12.1 - All the `crossterm_` crates code was moved to the `crossterm` crate diff --git a/src/crossterm.rs b/src/crossterm.rs index e25eee7..8a8ad9c 100644 --- a/src/crossterm.rs +++ b/src/crossterm.rs @@ -10,12 +10,6 @@ impl Crossterm { Crossterm } - /// Crates a new `TerminalCursor`. - #[cfg(feature = "cursor")] - pub fn cursor(&self) -> crate::cursor::TerminalCursor { - crate::cursor::TerminalCursor::new() - } - /// Creates a new `TerminalInput`. #[cfg(feature = "input")] pub fn input(&self) -> crate::input::TerminalInput { diff --git a/src/cursor.rs b/src/cursor.rs index 01de11c..58b3f73 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -1,6 +1,6 @@ //! # Cursor //! -//! The `cursor` module provides a functionality to work with the terminal cursor. +//! The `cursor` module provides functionality to work with the terminal cursor. //! //! This documentation does not contain a lot of examples. The reason is that it's fairly //! obvious how to use this crate. Although, we do provide @@ -9,239 +9,79 @@ //! //! ## Examples //! -//! Basic usage: -//! -//! ```no_run -//! // You can replace the following line with `use crossterm::TerminalCursor;` -//! // if you're using the `crossterm` crate with the `cursor` feature enabled. -//! use crossterm::{Result, TerminalCursor}; -//! -//! fn main() -> Result<()> { -//! // Get a cursor, save position -//! let cursor = TerminalCursor::new(); -//! cursor.save_position()?; -//! -//! // Do something with the cursor -//! cursor.goto(10, 10)?; -//! cursor.blink(true)?; -//! -//! // Be a good citizen, cleanup -//! cursor.blink(false)?; -//! cursor.restore_position() -//! } -//! ``` -//! -//! Commands: +//! Cursor actions can be performed with commands. +//! Please have a look at [command documention](../index.html#command-api) for a more detailed documentation. //! //! ```no_run //! use std::io::{stdout, Write}; //! -//! use crossterm::{BlinkOff, BlinkOn, execute, Goto, ResetPos, Result, SavePos}; -//! +//! use crossterm::{ +//! ExecutableCommand, execute, Result, +//! cursor::{DisableBlinking, EnableBlinking, MoveTo, RestorePosition, SavePosition} +//! }; //! //! fn main() -> Result<()> { +//! // with macro //! execute!( //! stdout(), -//! SavePos, -//! Goto(10, 10), -//! BlinkOn, -//! BlinkOff, -//! ResetPos -//! ) +//! SavePosition, +//! MoveTo(10, 10), +//! EnableBlinking, +//! DisableBlinking, +//! RestorePosition +//! ); +//! +//! // with function +//! stdout() +//! .execute(MoveTo(11,11))? +//! .execute(RestorePosition); +//! +//! Ok(()) //! } //! ``` -use cursor::ansi::{self, AnsiCursor}; -#[cfg(windows)] -use cursor::windows::WinApiCursor; -use cursor::Cursor; +//! +//! For manual execution control check out [crossterm::queue](../macro.queue.html). + +pub use sys::position; use crate::impl_display; #[cfg(windows)] -use crate::utils::supports_ansi; -use crate::utils::{Command, Result}; +use crate::utils::Result; -mod cursor; -mod sys; +use crate::utils::Command; -/// A terminal cursor. -/// -/// The `TerminalCursor` instance is stateless and does not hold any data. -/// You can create as many instances as you want and they will always refer to the -/// same terminal cursor. -/// -/// The cursor position is 0 based. For example `0` means first column/row, `1` -/// second column/row, etc. -/// -/// # Examples -/// -/// Basic usage: -/// -/// ```no_run -/// use crossterm::{Result, TerminalCursor}; -/// -/// fn main() -> Result<()> { -/// let cursor = TerminalCursor::new(); -/// cursor.save_position()?; -/// -/// cursor.goto(10, 10)?; -/// cursor.blink(true)?; -/// -/// cursor.blink(false)?; -/// cursor.restore_position() -/// } -/// ``` -pub struct TerminalCursor { - #[cfg(windows)] - cursor: Box<(dyn Cursor + Sync + Send)>, - #[cfg(unix)] - cursor: AnsiCursor, -} +mod ansi; +pub(crate) mod sys; -impl TerminalCursor { - /// Creates a new `TerminalCursor`. - pub fn new() -> TerminalCursor { - #[cfg(windows)] - let cursor = if supports_ansi() { - Box::new(AnsiCursor::new()) as Box<(dyn Cursor + Sync + Send)> - } else { - Box::new(WinApiCursor::new()) as Box<(dyn Cursor + Sync + Send)> - }; - - #[cfg(unix)] - let cursor = AnsiCursor::new(); - - TerminalCursor { cursor } - } - - /// Moves the cursor to the given position. - pub fn goto(&self, column: u16, row: u16) -> Result<()> { - self.cursor.goto(column, row) - } - - /// Returns the cursor position (`(column, row)` tuple). - pub fn pos(&self) -> Result<(u16, u16)> { - self.cursor.pos() - } - - /// Moves the cursor `row_count` times up. - pub fn move_up(&mut self, row_count: u16) -> Result<&mut TerminalCursor> { - self.cursor.move_up(row_count)?; - Ok(self) - } - - /// Moves the cursor `col_count` times right. - pub fn move_right(&mut self, col_count: u16) -> Result<&mut TerminalCursor> { - self.cursor.move_right(col_count)?; - Ok(self) - } - - /// Moves the cursor `row_count` times down. - pub fn move_down(&mut self, row_count: u16) -> Result<&mut TerminalCursor> { - self.cursor.move_down(row_count)?; - Ok(self) - } - - /// Moves the cursor `col_count` times left. - pub fn move_left(&mut self, col_count: u16) -> Result<&mut TerminalCursor> { - self.cursor.move_left(col_count)?; - Ok(self) - } - - /// Saves the cursor position. - /// - /// See the [restore_position](struct.TerminalCursor.html#method.restore_position) method. - /// - /// # Notes - /// - /// The cursor position is stored globally and is not related to the current/any - /// `TerminalCursor` instance. - pub fn save_position(&self) -> Result<()> { - self.cursor.save_position() - } - - /// Restores the saved cursor position. - /// - /// See the [save_position](struct.TerminalCursor.html#method.save_position) method. - pub fn restore_position(&self) -> Result<()> { - self.cursor.restore_position() - } - - /// Hides the cursor. - /// - /// See the [show](struct.TerminalCursor.html#method.show) method. - pub fn hide(&self) -> Result<()> { - self.cursor.hide() - } - - /// Shows the cursor. - /// - /// See the [hide](struct.TerminalCursor.html#method.hide) method. - pub fn show(&self) -> Result<()> { - self.cursor.show() - } - - /// Enables or disables the cursor blinking. - /// - /// # Notes - /// - /// Windows versions lower than Windows 10 do not support this functionality. - pub fn blink(&self, blink: bool) -> Result<()> { - self.cursor.blink(blink) - } -} - -/// Creates a new `TerminalCursor`. -/// -/// # Examples -/// -/// Basic usage: -/// -/// ```no_run -/// use crossterm::{cursor, Result}; -/// -/// fn main() -> Result<()> { -/// let cursor = cursor(); -/// cursor.save_position()?; -/// -/// cursor.goto(10, 10)?; -/// cursor.blink(true)?; -/// -/// cursor.blink(false)?; -/// cursor.restore_position() -/// } -/// ``` -pub fn cursor() -> TerminalCursor { - TerminalCursor::new() -} - -/// A command to move the cursor to the given position. +/// A command that moves the terminal cursor to the given position (column, row). /// /// # Notes /// -/// Commands must be executed/queued for execution otherwise they do nothing. -pub struct Goto(pub u16, pub u16); +/// * Top left cell is represented as `0,0`. +/// * Commands must be executed/queued for execution otherwise they do nothing. +pub struct MoveTo(pub u16, pub u16); -impl Command for Goto { +impl Command for MoveTo { type AnsiType = String; fn ansi_code(&self) -> Self::AnsiType { - ansi::goto_csi_sequence(self.0, self.1) + ansi::move_to_csi_sequence(self.0, self.1) } #[cfg(windows)] fn execute_winapi(&self) -> Result<()> { - WinApiCursor::new().goto(self.0, self.1) + sys::move_to(self.0, self.1) } } -/// A command to move the cursor given rows up. +/// A command that moves the terminal cursor a given number of rows up. /// /// # Notes /// /// Commands must be executed/queued for execution otherwise they do nothing. -pub struct Up(pub u16); +pub struct MoveUp(pub u16); -impl Command for Up { +impl Command for MoveUp { type AnsiType = String; fn ansi_code(&self) -> Self::AnsiType { @@ -250,18 +90,18 @@ impl Command for Up { #[cfg(windows)] fn execute_winapi(&self) -> Result<()> { - WinApiCursor::new().move_up(self.0) + sys::move_up(self.0) } } -/// A command to move the cursor given rows down. +/// A command that moves the terminal cursor a given number of rows down. /// /// # Notes /// /// Commands must be executed/queued for execution otherwise they do nothing. -pub struct Down(pub u16); +pub struct MoveDown(pub u16); -impl Command for Down { +impl Command for MoveDown { type AnsiType = String; fn ansi_code(&self) -> Self::AnsiType { @@ -270,18 +110,18 @@ impl Command for Down { #[cfg(windows)] fn execute_winapi(&self) -> Result<()> { - WinApiCursor::new().move_down(self.0) + sys::move_down(self.0) } } -/// A command to move the cursor given columns left. +/// A command that moves the terminal cursor a given number of columns to the left. /// /// # Notes /// /// Commands must be executed/queued for execution otherwise they do nothing. -pub struct Left(pub u16); +pub struct MoveLeft(pub u16); -impl Command for Left { +impl Command for MoveLeft { type AnsiType = String; fn ansi_code(&self) -> Self::AnsiType { @@ -290,18 +130,28 @@ impl Command for Left { #[cfg(windows)] fn execute_winapi(&self) -> Result<()> { - WinApiCursor::new().move_left(self.0) + sys::move_left(self.0) } } -/// A command to move the cursor given columns right. +/// A command that moves the terminal cursor a given number of columns to the right. /// /// # Notes /// /// Commands must be executed/queued for execution otherwise they do nothing. -pub struct Right(pub u16); +pub struct MoveRight(pub u16); -impl Command for Right { +/// A command that saves the current terminal cursor position. +/// +/// See the [RestorePosition](./struct.RestorePosition.html) command. +/// +/// # Notes +/// +/// - The cursor position is stored globally. +/// - Commands must be executed/queued for execution otherwise they do nothing. +pub struct SavePosition; + +impl Command for MoveRight { type AnsiType = String; fn ansi_code(&self) -> Self::AnsiType { @@ -310,21 +160,11 @@ impl Command for Right { #[cfg(windows)] fn execute_winapi(&self) -> Result<()> { - WinApiCursor::new().move_right(self.0) + sys::move_right(self.0) } } -/// A command to save the cursor position. -/// -/// # Notes -/// -/// The cursor position is stored globally and is not related to the current/any -/// `TerminalCursor` instance. -/// -/// Commands must be executed/queued for execution otherwise they do nothing. -pub struct SavePos; - -impl Command for SavePos { +impl Command for SavePosition { type AnsiType = &'static str; fn ansi_code(&self) -> Self::AnsiType { @@ -333,18 +173,21 @@ impl Command for SavePos { #[cfg(windows)] fn execute_winapi(&self) -> Result<()> { - WinApiCursor::new().save_position() + sys::save_position() } } -/// A command to restore the saved cursor position. +/// A command that restores the saved terminal cursor position. +/// +/// See the [SavePosition](./struct.SavePosition.html) command. /// /// # Notes /// -/// Commands must be executed/queued for execution otherwise they do nothing. -pub struct ResetPos; +/// - The cursor position is stored globally. +/// - Commands must be executed/queued for execution otherwise they do nothing. +pub struct RestorePosition; -impl Command for ResetPos { +impl Command for RestorePosition { type AnsiType = &'static str; fn ansi_code(&self) -> Self::AnsiType { @@ -353,18 +196,15 @@ impl Command for ResetPos { #[cfg(windows)] fn execute_winapi(&self) -> Result<()> { - WinApiCursor::new().restore_position() + sys::restore_position() } } -/// A command to hide the cursor. +/// A command that hides the terminal cursor. /// /// # Notes /// -/// The cursor position is stored globally and is not related to the current/any -/// `TerminalCursor` instance. -/// -/// Commands must be executed/queued for execution otherwise they do nothing. +/// - Commands must be executed/queued for execution otherwise they do nothing. pub struct Hide; impl Command for Hide { @@ -376,18 +216,15 @@ impl Command for Hide { #[cfg(windows)] fn execute_winapi(&self) -> Result<()> { - WinApiCursor::new().hide() + sys::show_cursor(false) } } -/// A command to show the cursor. +/// A command that shows the terminal cursor. /// /// # Notes /// -/// The cursor position is stored globally and is not related to the current/any -/// `TerminalCursor` instance. -/// -/// Commands must be executed/queued for execution otherwise they do nothing. +/// - Commands must be executed/queued for execution otherwise they do nothing. pub struct Show; impl Command for Show { @@ -399,24 +236,23 @@ impl Command for Show { #[cfg(windows)] fn execute_winapi(&self) -> Result<()> { - WinApiCursor::new().show() + sys::show_cursor(true) } } -/// A command to enable the cursor blinking. +/// A command that enables blinking of the terminal cursor. /// /// # Notes /// -/// Windows versions lower than Windows 10 do not support this functionality. -/// -/// Commands must be executed/queued for execution otherwise they do nothing. -pub struct BlinkOn; +/// - Windows versions lower than Windows 10 do not support this functionality. +/// - Commands must be executed/queued for execution otherwise they do nothing. +pub struct EnableBlinking; -impl Command for BlinkOn { +impl Command for EnableBlinking { type AnsiType = &'static str; fn ansi_code(&self) -> Self::AnsiType { - ansi::BLINKING_ON_CSI_SEQUENCE + ansi::ENABLE_BLINKING_CSI_SEQUENCE } #[cfg(windows)] @@ -425,20 +261,19 @@ impl Command for BlinkOn { } } -/// A command to disable the cursor blinking. +/// A command that disables blinking of the terminal cursor. /// /// # Notes /// -/// Windows versions lower than Windows 10 do not support this functionality. -/// -/// Commands must be executed/queued for execution otherwise they do nothing. -pub struct BlinkOff; +/// - Windows versions lower than Windows 10 do not support this functionality. +/// - Commands must be executed/queued for execution otherwise they do nothing. +pub struct DisableBlinking; -impl Command for BlinkOff { +impl Command for DisableBlinking { type AnsiType = &'static str; fn ansi_code(&self) -> Self::AnsiType { - ansi::BLINKING_OFF_CSI_SEQUENCE + ansi::DISABLE_BLINKING_CSI_SEQUENCE } #[cfg(windows)] @@ -447,14 +282,90 @@ impl Command for BlinkOff { } } -impl_display!(for Goto); -impl_display!(for Up); -impl_display!(for Down); -impl_display!(for Left); -impl_display!(for Right); -impl_display!(for SavePos); -impl_display!(for ResetPos); +impl_display!(for MoveTo); +impl_display!(for MoveUp); +impl_display!(for MoveDown); +impl_display!(for MoveLeft); +impl_display!(for MoveRight); +impl_display!(for SavePosition); +impl_display!(for RestorePosition); impl_display!(for Hide); impl_display!(for Show); -impl_display!(for BlinkOn); -impl_display!(for BlinkOff); +impl_display!(for EnableBlinking); +impl_display!(for DisableBlinking); + +#[cfg(test)] +mod tests { + use super::{ + position, MoveDown, MoveLeft, MoveRight, MoveTo, MoveUp, RestorePosition, SavePosition, + }; + use crate::execute; + use std::io::{self, stdout, Write}; + + // Test is disabled, because it's failing on Travis + #[test] + #[ignore] + fn test_move_to() { + let (saved_x, saved_y) = position().unwrap(); + + execute!(stdout(), MoveTo(saved_x + 1, saved_y + 1)).unwrap(); + assert_eq!(position().unwrap(), (saved_x + 1, saved_y + 1)); + + execute!(stdout(), MoveTo(saved_x, saved_y)).unwrap(); + assert_eq!(position().unwrap(), (saved_x, saved_y)); + } + + // Test is disabled, because it's failing on Travis + #[test] + #[ignore] + fn test_move_right() { + let (saved_x, saved_y) = position().unwrap(); + execute!(io::stdout(), MoveRight(1)).unwrap(); + assert_eq!(position().unwrap(), (saved_x + 1, saved_y)); + } + + // Test is disabled, because it's failing on Travis + #[test] + #[ignore] + fn test_move_left() { + execute!(stdout(), MoveTo(2, 0), MoveLeft(2)).unwrap(); + assert_eq!(position().unwrap(), (0, 0)); + } + + // Test is disabled, because it's failing on Travis + #[test] + #[ignore] + fn test_move_up() { + execute!(stdout(), MoveTo(0, 2), MoveUp(2)).unwrap(); + assert_eq!(position().unwrap(), (0, 0)); + } + + // Test is disabled, because it's failing on Travis + #[test] + #[ignore] + fn test_move_down() { + execute!(stdout(), MoveTo(0, 0), MoveDown(2)).unwrap(); + + assert_eq!(position().unwrap(), (0, 2)); + } + + // Test is disabled, because it's failing on Travis + #[test] + #[ignore] + fn test_save_restore_position() { + let (saved_x, saved_y) = position().unwrap(); + + execute!( + stdout(), + SavePosition, + MoveTo(saved_x + 1, saved_y + 1), + RestorePosition + ) + .unwrap(); + + let (x, y) = position().unwrap(); + + assert_eq!(x, saved_x); + assert_eq!(y, saved_y); + } +} diff --git a/src/cursor/ansi.rs b/src/cursor/ansi.rs new file mode 100644 index 0000000..5045a07 --- /dev/null +++ b/src/cursor/ansi.rs @@ -0,0 +1,30 @@ +//! This module provides cursor related ANSI escape codes. + +use crate::csi; + +pub(crate) fn move_to_csi_sequence(x: u16, y: u16) -> String { + format!(csi!("{};{}H"), y + 1, x + 1) +} + +pub(crate) fn move_up_csi_sequence(count: u16) -> String { + format!(csi!("{}A"), count) +} + +pub(crate) fn move_right_csi_sequence(count: u16) -> String { + format!(csi!("{}C"), count) +} + +pub(crate) fn move_down_csi_sequence(count: u16) -> String { + format!(csi!("{}B"), count) +} + +pub(crate) fn move_left_csi_sequence(count: u16) -> String { + format!(csi!("{}D"), count) +} + +pub(crate) static SAVE_POSITION_CSI_SEQUENCE: &'static str = "\x1B7"; +pub(crate) static RESTORE_POSITION_CSI_SEQUENCE: &'static str = "\x1B8"; +pub(crate) static HIDE_CSI_SEQUENCE: &'static str = csi!("?25l"); +pub(crate) static SHOW_CSI_SEQUENCE: &'static str = csi!("?25h"); +pub(crate) static ENABLE_BLINKING_CSI_SEQUENCE: &'static str = csi!("?12h"); +pub(crate) static DISABLE_BLINKING_CSI_SEQUENCE: &'static str = csi!("?12l"); diff --git a/src/cursor/cursor.rs b/src/cursor/cursor.rs deleted file mode 100644 index 331dd9c..0000000 --- a/src/cursor/cursor.rs +++ /dev/null @@ -1,45 +0,0 @@ -//! A module that contains all the actions related to cursor movement in the terminal. -//! Like: moving the cursor position; saving and resetting the cursor position; hiding showing and control -//! the blinking of the cursor. -//! -//! Note that positions of the cursor are 0 -based witch means that the coordinates (cells) starts counting from 0 - -use crate::utils::Result; - -pub(crate) mod ansi; -#[cfg(windows)] -pub(crate) mod windows; - -///! This trait defines the actions that can be performed with the terminal cursor. -///! This trait can be implemented so that a concrete implementation of the ITerminalCursor can fulfill -///! the wishes to work on a specific platform. -///! -///! ## For example: -///! -///! This trait is implemented for `WinApi` (Windows specific) and `ANSI` (Unix specific), -///! so that cursor related actions can be performed on both UNIX and Windows systems. -pub(crate) trait Cursor: Sync + Send { - /// Goto location (`x`, `y`) in the current terminal window. - fn goto(&self, x: u16, y: u16) -> Result<()>; - /// Get the cursor location `(x, y)` in the current terminal window. - fn pos(&self) -> Result<(u16, u16)>; - /// Move cursor `n` times up - fn move_up(&self, count: u16) -> Result<()>; - /// Move the cursor `n` times to the right. - fn move_right(&self, count: u16) -> Result<()>; - /// Move the cursor `n` times down. - fn move_down(&self, count: u16) -> Result<()>; - /// Move the cursor `n` times left. - fn move_left(&self, count: u16) -> Result<()>; - /// Save cursor position so that its saved position can be recalled later. Note that this position - /// is stored program based not per instance of the cursor struct. - fn save_position(&self) -> Result<()>; - /// Return to saved cursor position - fn restore_position(&self) -> Result<()>; - /// Hide the terminal cursor. - fn hide(&self) -> Result<()>; - /// Show the terminal cursor - fn show(&self) -> Result<()>; - /// Enable or disable the blinking of the cursor. - fn blink(&self, blink: bool) -> Result<()>; -} diff --git a/src/cursor/cursor/ansi.rs b/src/cursor/cursor/ansi.rs deleted file mode 100644 index 6cefb7d..0000000 --- a/src/cursor/cursor/ansi.rs +++ /dev/null @@ -1,166 +0,0 @@ -//! This is an ANSI specific implementation for cursor related action. -//! This module is used for windows 10 terminals and UNIX terminals by default. -//! Note that the cursor position is 0 based. This means that we start counting at 0 when setting the cursor position etc. - -use crate::utils::Result; -use crate::{csi, write_cout}; - -use super::{ - super::sys::{get_cursor_position, show_cursor}, - Cursor, -}; - -pub(crate) fn goto_csi_sequence(x: u16, y: u16) -> String { - format!(csi!("{};{}H"), y + 1, x + 1) -} - -pub(crate) fn move_up_csi_sequence(count: u16) -> String { - format!(csi!("{}A"), count) -} - -pub(crate) fn move_right_csi_sequence(count: u16) -> String { - format!(csi!("{}C"), count) -} - -pub(crate) fn move_down_csi_sequence(count: u16) -> String { - format!(csi!("{}B"), count) -} - -pub(crate) fn move_left_csi_sequence(count: u16) -> String { - format!(csi!("{}D"), count) -} - -pub(crate) static SAVE_POSITION_CSI_SEQUENCE: &'static str = csi!("s"); -pub(crate) static RESTORE_POSITION_CSI_SEQUENCE: &'static str = csi!("u"); -pub(crate) static HIDE_CSI_SEQUENCE: &'static str = csi!("?25l"); -pub(crate) static SHOW_CSI_SEQUENCE: &'static str = csi!("?25h"); -pub(crate) static BLINKING_ON_CSI_SEQUENCE: &'static str = csi!("?12h"); -pub(crate) static BLINKING_OFF_CSI_SEQUENCE: &'static str = csi!("?12l"); - -/// This struct is an ANSI implementation for cursor related actions. -pub(crate) struct AnsiCursor; - -impl AnsiCursor { - pub(crate) fn new() -> AnsiCursor { - AnsiCursor - } -} - -impl Cursor for AnsiCursor { - fn goto(&self, x: u16, y: u16) -> Result<()> { - write_cout!(goto_csi_sequence(x, y))?; - Ok(()) - } - - fn pos(&self) -> Result<(u16, u16)> { - get_cursor_position() - } - - fn move_up(&self, count: u16) -> Result<()> { - write_cout!(move_up_csi_sequence(count))?; - Ok(()) - } - - fn move_right(&self, count: u16) -> Result<()> { - write_cout!(move_right_csi_sequence(count))?; - Ok(()) - } - - fn move_down(&self, count: u16) -> Result<()> { - write_cout!(move_down_csi_sequence(count))?; - Ok(()) - } - - fn move_left(&self, count: u16) -> Result<()> { - write_cout!(move_left_csi_sequence(count))?; - Ok(()) - } - - fn save_position(&self) -> Result<()> { - write_cout!(SAVE_POSITION_CSI_SEQUENCE)?; - Ok(()) - } - - fn restore_position(&self) -> Result<()> { - write_cout!(RESTORE_POSITION_CSI_SEQUENCE)?; - Ok(()) - } - - fn hide(&self) -> Result<()> { - show_cursor(false)?; - Ok(()) - } - - fn show(&self) -> Result<()> { - show_cursor(true)?; - Ok(()) - } - - fn blink(&self, blink: bool) -> Result<()> { - if blink { - write_cout!(BLINKING_ON_CSI_SEQUENCE)?; - } else { - write_cout!(BLINKING_OFF_CSI_SEQUENCE)?; - } - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::{AnsiCursor, Cursor}; - - // TODO - Test is ingored, because it's stalled on Travis CI - #[test] - #[ignore] - fn test_save_restore_position() { - if try_enable_ansi() { - let cursor = AnsiCursor::new(); - - let (saved_x, saved_y) = cursor.pos().unwrap(); - - cursor.save_position().unwrap(); - cursor.goto(saved_x + 1, saved_y + 1).unwrap(); - cursor.restore_position().unwrap(); - - let (x, y) = cursor.pos().unwrap(); - - assert_eq!(x, saved_x); - assert_eq!(y, saved_y); - } - } - - // TODO - Test is ingored, because it's stalled on Travis CI - #[test] - #[ignore] - fn test_goto() { - if try_enable_ansi() { - let cursor = AnsiCursor::new(); - - let (saved_x, saved_y) = cursor.pos().unwrap(); - - cursor.goto(saved_x + 1, saved_y + 1).unwrap(); - assert_eq!(cursor.pos().unwrap(), (saved_x + 1, saved_y + 1)); - - cursor.goto(saved_x, saved_y).unwrap(); - assert_eq!(cursor.pos().unwrap(), (saved_x, saved_y)); - } - } - - fn try_enable_ansi() -> bool { - #[cfg(windows)] - { - if cfg!(target_os = "windows") { - use crate::utils::sys::winapi::ansi::set_virtual_terminal_processing; - - // if it is not listed we should try with WinApi to check if we do support ANSI-codes. - match set_virtual_terminal_processing(true) { - Ok(_) => return true, - Err(_) => return false, - } - } - } - - true - } -} diff --git a/src/cursor/cursor/windows.rs b/src/cursor/cursor/windows.rs deleted file mode 100644 index 69c26fc..0000000 --- a/src/cursor/cursor/windows.rs +++ /dev/null @@ -1,111 +0,0 @@ -//! This is a WINAPI specific implementation for cursor related actions. -//! This module is used for Windows terminals that do not support ANSI escape codes. -//! Note that the cursor position is 0 based. This means that we start counting at 0 when setting the cursor position. - -use crate::utils::Result; - -use super::{super::sys::windows::ScreenBufferCursor, Cursor}; - -/// This struct is a windows implementation for cursor related actions. -pub(crate) struct WinApiCursor; - -impl WinApiCursor { - pub(crate) fn new() -> WinApiCursor { - WinApiCursor - } -} - -impl Cursor for WinApiCursor { - fn goto(&self, x: u16, y: u16) -> Result<()> { - let cursor = ScreenBufferCursor::new()?; - cursor.goto(x as i16, y as i16)?; - Ok(()) - } - - fn pos(&self) -> Result<(u16, u16)> { - let cursor = ScreenBufferCursor::new()?; - Ok(cursor.position()?.into()) - } - - fn move_up(&self, count: u16) -> Result<()> { - let (xpos, ypos) = self.pos()?; - self.goto(xpos, ypos - count)?; - Ok(()) - } - - fn move_right(&self, count: u16) -> Result<()> { - let (xpos, ypos) = self.pos()?; - self.goto(xpos + count, ypos)?; - Ok(()) - } - - fn move_down(&self, count: u16) -> Result<()> { - let (xpos, ypos) = self.pos()?; - self.goto(xpos, ypos + count)?; - Ok(()) - } - - fn move_left(&self, count: u16) -> Result<()> { - let (xpos, ypos) = self.pos()?; - self.goto(xpos - count, ypos)?; - Ok(()) - } - - fn save_position(&self) -> Result<()> { - ScreenBufferCursor::save_cursor_pos()?; - Ok(()) - } - - fn restore_position(&self) -> Result<()> { - ScreenBufferCursor::restore_cursor_pos()?; - Ok(()) - } - - fn hide(&self) -> Result<()> { - ScreenBufferCursor::new()?.set_visibility(false)?; - Ok(()) - } - - fn show(&self) -> Result<()> { - ScreenBufferCursor::new()?.set_visibility(true)?; - Ok(()) - } - - fn blink(&self, _blink: bool) -> Result<()> { - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::{Cursor, WinApiCursor}; - - #[test] - fn test_goto() { - let cursor = WinApiCursor::new(); - - let (saved_x, saved_y) = cursor.pos().unwrap(); - - cursor.goto(saved_x + 1, saved_y + 1).unwrap(); - assert_eq!(cursor.pos().unwrap(), (saved_x + 1, saved_y + 1)); - - cursor.goto(saved_x, saved_y).unwrap(); - assert_eq!(cursor.pos().unwrap(), (saved_x, saved_y)); - } - - #[test] - fn test_save_restore_position() { - let cursor = WinApiCursor::new(); - - let (saved_x, saved_y) = cursor.pos().unwrap(); - - cursor.save_position().unwrap(); - cursor.goto(saved_x + 1, saved_y + 1).unwrap(); - cursor.restore_position().unwrap(); - - let (x, y) = cursor.pos().unwrap(); - - assert_eq!(x, saved_x); - assert_eq!(y, saved_y); - } -} diff --git a/src/cursor/sys.rs b/src/cursor/sys.rs index 466a250..a013e50 100644 --- a/src/cursor/sys.rs +++ b/src/cursor/sys.rs @@ -1,11 +1,14 @@ +//! This module provides platform related functions. + #[cfg(unix)] -pub(crate) use self::unix::get_cursor_position; -#[cfg(unix)] -pub(crate) use self::unix::show_cursor; +pub use self::unix::position; #[cfg(windows)] -pub(crate) use self::windows::get_cursor_position; +pub use self::windows::position; #[cfg(windows)] -pub(crate) use self::windows::show_cursor; +pub(crate) use self::windows::{ + move_down, move_left, move_right, move_to, move_up, restore_position, save_position, + show_cursor, +}; #[cfg(windows)] pub(crate) mod windows; diff --git a/src/cursor/sys/unix.rs b/src/cursor/sys/unix.rs index a74aaab..239a01f 100644 --- a/src/cursor/sys/unix.rs +++ b/src/cursor/sys/unix.rs @@ -1,3 +1,5 @@ +//! UNIX related logic to cursor manipulation. + use std::io::{self, Write}; use crate::input::{InputEvent, TerminalInput}; @@ -5,38 +7,29 @@ use crate::utils::{ sys::unix::{disable_raw_mode, enable_raw_mode, is_raw_mode_enabled}, Result, }; -use crate::{csi, write_cout}; -pub(crate) fn get_cursor_position() -> Result<(u16, u16)> { +/// Returns the cursor position (column, row). +/// +/// The top left cell is represented `0,0`. +pub fn position() -> Result<(u16, u16)> { if is_raw_mode_enabled() { - pos_raw() + read_position_raw() } else { - pos() + read_position() } } -pub(crate) fn show_cursor(show_cursor: bool) -> Result<()> { - if show_cursor { - write_cout!(csi!("?25h"))?; - } else { - write_cout!(csi!("?25l"))?; - } - Ok(()) -} - -fn pos() -> Result<(u16, u16)> { +fn read_position() -> Result<(u16, u16)> { enable_raw_mode()?; - let pos = pos_raw(); + let pos = read_position_raw(); disable_raw_mode()?; pos } -fn pos_raw() -> Result<(u16, u16)> { - // Where is the cursor? - // Use `ESC [ 6 n`. +fn read_position_raw() -> Result<(u16, u16)> { + // Use `ESC [ 6 n` to and retrieve the cursor position. let mut stdout = io::stdout(); - // Write command stdout.write_all(b"\x1B[6n")?; stdout.flush()?; diff --git a/src/cursor/sys/windows.rs b/src/cursor/sys/windows.rs index 40b7572..90726e5 100644 --- a/src/cursor/sys/windows.rs +++ b/src/cursor/sys/windows.rs @@ -1,4 +1,4 @@ -//! This module handles some logic for cursor interaction in the windows console. +//! WinApi related logic to cursor manipulation. use std::io; use std::sync::Mutex; @@ -14,7 +14,14 @@ use lazy_static::lazy_static; use crate::utils::Result; -pub(crate) fn get_cursor_position() -> Result<(u16, u16)> { +lazy_static! { + static ref SAVED_CURSOR_POS: Mutex> = Mutex::new(None); +} + +/// Returns the cursor position (column, row). +/// +/// The top left cell is represented `0,0`. +pub fn position() -> Result<(u16, u16)> { let cursor = ScreenBufferCursor::new()?; Ok(cursor.position()?.into()) } @@ -23,28 +30,63 @@ pub(crate) fn show_cursor(show_cursor: bool) -> Result<()> { ScreenBufferCursor::from(Handle::current_out_handle()?).set_visibility(show_cursor) } -lazy_static! { - static ref SAVED_CURSOR_POS: Mutex> = Mutex::new(None); +pub(crate) fn move_to(column: u16, row: u16) -> Result<()> { + let cursor = ScreenBufferCursor::new()?; + cursor.move_to(column as i16, row as i16)?; + Ok(()) } -pub(crate) struct ScreenBufferCursor { +pub(crate) fn move_up(count: u16) -> Result<()> { + let (column, row) = position()?; + move_to(column, row - count)?; + Ok(()) +} + +pub(crate) fn move_right(count: u16) -> Result<()> { + let (column, row) = position()?; + move_to(column + count, row)?; + Ok(()) +} + +pub(crate) fn move_down(count: u16) -> Result<()> { + let (column, row) = position()?; + move_to(column, row + count)?; + Ok(()) +} + +pub(crate) fn move_left(count: u16) -> Result<()> { + let (column, row) = position()?; + move_to(column - count, row)?; + Ok(()) +} + +pub(crate) fn save_position() -> Result<()> { + ScreenBufferCursor::new()?.save_position()?; + Ok(()) +} + +pub(crate) fn restore_position() -> Result<()> { + ScreenBufferCursor::new()?.restore_position()?; + Ok(()) +} + +/// WinApi wrapper over terminal cursor behaviour. +struct ScreenBufferCursor { screen_buffer: ScreenBuffer, } impl ScreenBufferCursor { - pub(crate) fn new() -> Result { + fn new() -> Result { Ok(ScreenBufferCursor { screen_buffer: ScreenBuffer::from(Handle::new(HandleType::CurrentOutputHandle)?), }) } - /// get the current cursor position. - pub(crate) fn position(&self) -> Result { + fn position(&self) -> Result { Ok(self.screen_buffer.info()?.cursor_pos()) } - /// Set the cursor position to the given x and y. Note that this is 0 based. - pub(crate) fn goto(&self, x: i16, y: i16) -> Result<()> { + fn move_to(&self, x: i16, y: i16) -> Result<()> { if x < 0 || x >= ::max_value() { Err(io::Error::new( io::ErrorKind::Other, @@ -78,8 +120,7 @@ impl ScreenBufferCursor { Ok(()) } - /// change the cursor visibility. - pub(crate) fn set_visibility(&self, visible: bool) -> Result<()> { + fn set_visibility(&self, visible: bool) -> Result<()> { let cursor_info = CONSOLE_CURSOR_INFO { dwSize: 100, bVisible: if visible { TRUE } else { FALSE }, @@ -96,21 +137,16 @@ impl ScreenBufferCursor { Ok(()) } - /// Reset to saved cursor position - pub(crate) fn restore_cursor_pos() -> Result<()> { - let cursor = ScreenBufferCursor::new()?; - + fn restore_position(&self) -> Result<()> { if let Some((x, y)) = *SAVED_CURSOR_POS.lock().unwrap() { - cursor.goto(x, y)?; + self.move_to(x, y)?; } Ok(()) } - /// Save current cursor position to recall later. - pub(crate) fn save_cursor_pos() -> Result<()> { - let cursor = ScreenBufferCursor::new()?; - let position = cursor.position()?; + fn save_position(&self) -> Result<()> { + let position = self.position()?; let mut locked_pos = SAVED_CURSOR_POS.lock().unwrap(); *locked_pos = Some((position.x, position.y)); @@ -134,3 +170,70 @@ impl From for ScreenBufferCursor { } } } + +#[cfg(test)] +mod tests { + use super::{ + move_down, move_left, move_right, move_to, move_up, position, restore_position, + save_position, + }; + + #[test] + fn test_move_to_winapi() { + let (saved_x, saved_y) = position().unwrap(); + + move_to(saved_x + 1, saved_y + 1).unwrap(); + assert_eq!(position().unwrap(), (saved_x + 1, saved_y + 1)); + + move_to(saved_x, saved_y).unwrap(); + assert_eq!(position().unwrap(), (saved_x, saved_y)); + } + + #[test] + fn test_move_right_winapi() { + let (saved_x, saved_y) = position().unwrap(); + move_right(1).unwrap(); + assert_eq!(position().unwrap(), (saved_x + 1, saved_y)); + } + + #[test] + fn test_move_left_winapi() { + move_to(2, 0).unwrap(); + + move_left(2).unwrap(); + + assert_eq!(position().unwrap(), (0, 0)); + } + + #[test] + fn test_move_up_winapi() { + move_to(0, 2).unwrap(); + + move_up(2).unwrap(); + + assert_eq!(position().unwrap(), (0, 0)); + } + + #[test] + fn test_move_down_winapi() { + move_to(0, 0).unwrap(); + + move_down(2).unwrap(); + + assert_eq!(position().unwrap(), (0, 2)); + } + + #[test] + fn test_save_restore_position_winapi() { + let (saved_x, saved_y) = position().unwrap(); + + save_position().unwrap(); + move_to(saved_x + 1, saved_y + 1).unwrap(); + restore_position().unwrap(); + + let (x, y) = position().unwrap(); + + assert_eq!(x, saved_x); + assert_eq!(y, saved_y); + } +} diff --git a/src/lib.rs b/src/lib.rs index 1139473..e96369f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -52,10 +52,10 @@ //! //! ```no_run //! use std::io::Write; -//! use crossterm::{Goto, QueueableCommand}; +//! use crossterm::{QueueableCommand, cursor}; //! //! let mut stdout = std::io::stdout(); -//! stdout.queue(Goto(5,5)); +//! stdout.queue(cursor::MoveTo(5,5)); //! //! // some other code ... //! @@ -69,10 +69,10 @@ //! //! ```no_run //! use std::io::Write; -//! use crossterm::{queue, Goto, QueueableCommand}; +//! use crossterm::{queue, QueueableCommand, cursor}; //! //! let mut stdout = std::io::stdout(); -//! queue!(stdout, Goto(5, 5)); +//! queue!(stdout, cursor::MoveTo(5, 5)); //! //! // some other code ... //! @@ -93,20 +93,20 @@ //! //! ```no_run //! use std::io::Write; -//! use crossterm::{ExecutableCommand, Goto}; +//! use crossterm::{ExecutableCommand, cursor}; //! //! let mut stdout = std::io::stdout(); -//! stdout.execute(Goto(5,5)); +//! stdout.execute(cursor::MoveTo(5,5)); //! ``` //! //! Macros: //! //! ```no_run //! use std::io::Write; -//! use crossterm::{execute, ExecutableCommand, Goto}; +//! use crossterm::{execute, ExecutableCommand, cursor}; //! //! let mut stdout = std::io::stdout(); -//! execute!(stdout, Goto(5, 5)); +//! execute!(stdout, cursor::MoveTo(5, 5)); //! ``` //! //! ## Examples @@ -117,7 +117,10 @@ //! //! ```no_run //! use std::io::{stdout, Write}; -//! use crossterm::{ExecutableCommand, QueueableCommand, Color, PrintStyledFont, Colorize, Clear, ClearType, Goto, Result}; +//! use crossterm::{ +//! ExecutableCommand, QueueableCommand, Color, PrintStyledFont, +//! Colorize, Clear, ClearType, cursor::MoveTo, Result +//! }; //! //! fn main() -> Result<()> { //! let mut stdout = stdout(); @@ -128,7 +131,7 @@ //! for x in 0..150 { //! if (y == 0 || y == 40 - 1) || (x == 0 || x == 150 - 1) { //! stdout -//! .queue(Goto(x,y))? +//! .queue(MoveTo(x,y))? //! .queue(PrintStyledFont( "█".magenta()))?; //! } //! } @@ -142,7 +145,10 @@ //! //! ```no_run //! use std::io::{stdout, Write}; -//! use crossterm::{execute, queue, Color, PrintStyledFont, Colorize, Goto, Clear, ClearType, Result}; +//! use crossterm::{ +//! execute, queue, Color, PrintStyledFont, +//! Colorize, cursor::MoveTo, Clear, ClearType, Result +//! }; //! //! fn main() -> Result<()> { //! let mut stdout = stdout(); @@ -152,7 +158,7 @@ //! for y in 0..40 { //! for x in 0..150 { //! if (y == 0 || y == 40 - 1) || (x == 0 || x == 150 - 1) { -//! queue!(stdout, Goto(x,y), PrintStyledFont( "█".magenta()))?; +//! queue!(stdout, MoveTo(x,y), PrintStyledFont( "█".magenta()))?; //! } //! } //! } @@ -161,11 +167,6 @@ //! } //!``` -#[cfg(feature = "cursor")] -pub use cursor::{ - cursor, BlinkOff, BlinkOn, Down, Goto, Hide, Left, ResetPos, Right, SavePos, Show, - TerminalCursor, Up, -}; #[cfg(feature = "input")] pub use input::{ input, AsyncReader, InputEvent, KeyEvent, MouseButton, MouseEvent, SyncReader, TerminalInput, @@ -186,6 +187,7 @@ pub use utils::{Command, ErrorKind, ExecutableCommand, Output, QueueableCommand, pub use self::crossterm::Crossterm; mod crossterm; + /// A functionality to work with the terminal cursor #[cfg(feature = "cursor")] pub mod cursor; diff --git a/src/screen/alternate.rs b/src/screen/alternate.rs index f1dc890..9dc8f26 100644 --- a/src/screen/alternate.rs +++ b/src/screen/alternate.rs @@ -1,11 +1,11 @@ -#[cfg(windows)] -use crate::utils::supports_ansi; -use crate::utils::Result; - pub(crate) use ansi::AnsiAlternateScreen; #[cfg(windows)] pub(crate) use windows::WinApiAlternateScreen; +#[cfg(windows)] +use crate::utils::supports_ansi; +use crate::utils::Result; + pub(crate) mod ansi; #[cfg(windows)] pub(crate) mod windows; diff --git a/src/terminal/terminal/ansi.rs b/src/terminal/terminal/ansi.rs index 0642fb0..87c90b8 100644 --- a/src/terminal/terminal/ansi.rs +++ b/src/terminal/terminal/ansi.rs @@ -1,9 +1,8 @@ //! This is an `ANSI escape code` specific implementation for terminal related action. //! This module is used for windows 10 terminals and unix terminals by default. -use crate::cursor::TerminalCursor; use crate::utils::Result; -use crate::{csi, write_cout}; +use crate::{csi, cursor, write_cout}; use super::{super::sys::get_terminal_size, ClearType, Terminal}; @@ -45,7 +44,7 @@ impl Terminal for AnsiTerminal { }; if clear_type == ClearType::All { - TerminalCursor::new().goto(0, 0)?; + write_cout!(cursor::MoveTo(0, 0))?; } Ok(()) diff --git a/src/terminal/terminal/winapi.rs b/src/terminal/terminal/winapi.rs index 2fe6e36..20e890a 100644 --- a/src/terminal/terminal/winapi.rs +++ b/src/terminal/terminal/winapi.rs @@ -6,7 +6,7 @@ use crossterm_winapi::{Console, Coord, Handle, ScreenBuffer, Size}; -use crate::cursor::TerminalCursor; +use crate::cursor; use crate::utils::{ErrorKind, Result}; use super::{super::sys::winapi::get_terminal_size, ClearType, Terminal}; @@ -218,8 +218,7 @@ fn clear_entire_screen(buffer_size: Size, current_attribute: u16) -> Result<()> clear(start_location, cells_to_write, current_attribute)?; // put the cursor back at cell 0,0 - let cursor = TerminalCursor::new(); - cursor.goto(0, 0)?; + cursor::sys::move_to(0, 0)?; Ok(()) } @@ -234,8 +233,7 @@ fn clear_current_line(location: Coord, buffer_size: Size, current_attribute: u16 clear(start_location, cells_to_write, current_attribute)?; // put the cursor back at cell 1 on current row - let cursor = TerminalCursor::new(); - cursor.goto(0, location.y as u16)?; + cursor::sys::move_to(0, location.y as u16)?; Ok(()) } @@ -252,8 +250,7 @@ fn clear_until_line(location: Coord, buffer_size: Size, current_attribute: u16) clear(start_location, cells_to_write, current_attribute)?; // put the cursor back at original cursor position before we did the clearing - let cursor = TerminalCursor::new(); - cursor.goto(x as u16, y as u16)?; + cursor::sys::move_to(x as u16, y as u16)?; Ok(()) }