//! # 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 //! [examples](https://github.com/crossterm-rs/crossterm/tree/master/examples) repository //! to demonstrate the capabilities. //! //! ## Examples //! //! Cursor actions can be performed with commands. //! Please have a look at [command documentation](../index.html#command-api) for a more detailed documentation. //! //! ```no_run //! use std::io::{self, Write}; //! //! use keyfork_crossterm::{ //! ExecutableCommand, execute, //! cursor::{DisableBlinking, EnableBlinking, MoveTo, RestorePosition, SavePosition} //! }; //! //! fn main() -> io::Result<()> { //! // with macro //! execute!( //! io::stdout(), //! SavePosition, //! MoveTo(10, 10), //! EnableBlinking, //! DisableBlinking, //! RestorePosition //! ); //! //! // with function //! io::stdout() //! .execute(MoveTo(11,11))? //! .execute(RestorePosition); //! //! Ok(()) //! } //! ``` //! //! For manual execution control check out [crossterm::queue](../macro.queue.html). use std::fmt; use crate::{csi, impl_display, Command}; pub(crate) mod sys; #[cfg(feature = "events")] pub use sys::position; /// A command that moves the terminal cursor to the given position (column, row). /// /// # Notes /// * Top left cell is represented as `0,0`. /// * Commands must be executed/queued for execution otherwise they do nothing. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct MoveTo(pub u16, pub u16); impl Command for MoveTo { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { write!(f, csi!("{};{}H"), self.1 + 1, self.0 + 1) } #[cfg(windows)] fn execute_winapi(&self) -> std::io::Result<()> { sys::move_to(self.0, self.1) } } /// A command that moves the terminal cursor down the given number of lines, /// and moves it to the first column. /// /// # Notes /// * This command is 1 based, meaning `MoveToNextLine(1)` moves to the next line. /// * Most terminals default 0 argument to 1. /// * Commands must be executed/queued for execution otherwise they do nothing. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct MoveToNextLine(pub u16); impl Command for MoveToNextLine { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { write!(f, csi!("{}E"), self.0)?; Ok(()) } #[cfg(windows)] fn execute_winapi(&self) -> std::io::Result<()> { if self.0 != 0 { sys::move_to_next_line(self.0)?; } Ok(()) } } /// A command that moves the terminal cursor up the given number of lines, /// and moves it to the first column. /// /// # Notes /// * This command is 1 based, meaning `MoveToPreviousLine(1)` moves to the previous line. /// * Most terminals default 0 argument to 1. /// * Commands must be executed/queued for execution otherwise they do nothing. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct MoveToPreviousLine(pub u16); impl Command for MoveToPreviousLine { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { write!(f, csi!("{}F"), self.0)?; Ok(()) } #[cfg(windows)] fn execute_winapi(&self) -> std::io::Result<()> { if self.0 != 0 { sys::move_to_previous_line(self.0)?; } Ok(()) } } /// A command that moves the terminal cursor to the given column on the current row. /// /// # Notes /// * This command is 0 based, meaning 0 is the leftmost column. /// * Commands must be executed/queued for execution otherwise they do nothing. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct MoveToColumn(pub u16); impl Command for MoveToColumn { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { write!(f, csi!("{}G"), self.0 + 1)?; Ok(()) } #[cfg(windows)] fn execute_winapi(&self) -> std::io::Result<()> { sys::move_to_column(self.0) } } /// A command that moves the terminal cursor to the given row on the current column. /// /// # Notes /// * This command is 0 based, meaning 0 is the topmost row. /// * Commands must be executed/queued for execution otherwise they do nothing. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct MoveToRow(pub u16); impl Command for MoveToRow { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { write!(f, csi!("{}d"), self.0 + 1)?; Ok(()) } #[cfg(windows)] fn execute_winapi(&self) -> std::io::Result<()> { sys::move_to_row(self.0) } } /// A command that moves the terminal cursor a given number of rows up. /// /// # Notes /// * This command is 1 based, meaning `MoveUp(1)` moves the cursor up one cell. /// * Most terminals default 0 argument to 1. /// * Commands must be executed/queued for execution otherwise they do nothing. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct MoveUp(pub u16); impl Command for MoveUp { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { write!(f, csi!("{}A"), self.0)?; Ok(()) } #[cfg(windows)] fn execute_winapi(&self) -> std::io::Result<()> { sys::move_up(self.0) } } /// A command that moves the terminal cursor a given number of columns to the right. /// /// # Notes /// * This command is 1 based, meaning `MoveRight(1)` moves the cursor right one cell. /// * Most terminals default 0 argument to 1. /// * Commands must be executed/queued for execution otherwise they do nothing. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct MoveRight(pub u16); impl Command for MoveRight { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { write!(f, csi!("{}C"), self.0)?; Ok(()) } #[cfg(windows)] fn execute_winapi(&self) -> std::io::Result<()> { sys::move_right(self.0) } } /// A command that moves the terminal cursor a given number of rows down. /// /// # Notes /// * This command is 1 based, meaning `MoveDown(1)` moves the cursor down one cell. /// * Most terminals default 0 argument to 1. /// * Commands must be executed/queued for execution otherwise they do nothing. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct MoveDown(pub u16); impl Command for MoveDown { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { write!(f, csi!("{}B"), self.0)?; Ok(()) } #[cfg(windows)] fn execute_winapi(&self) -> std::io::Result<()> { sys::move_down(self.0) } } /// A command that moves the terminal cursor a given number of columns to the left. /// /// # Notes /// * This command is 1 based, meaning `MoveLeft(1)` moves the cursor left one cell. /// * Most terminals default 0 argument to 1. /// * Commands must be executed/queued for execution otherwise they do nothing. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct MoveLeft(pub u16); impl Command for MoveLeft { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { write!(f, csi!("{}D"), self.0)?; Ok(()) } #[cfg(windows)] fn execute_winapi(&self) -> std::io::Result<()> { sys::move_left(self.0) } } /// 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. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct SavePosition; impl Command for SavePosition { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { f.write_str("\x1B7") } #[cfg(windows)] fn execute_winapi(&self) -> std::io::Result<()> { sys::save_position() } } /// A command that restores the saved terminal cursor position. /// /// See the [SavePosition](./struct.SavePosition.html) command. /// /// # Notes /// /// - The cursor position is stored globally. /// - Commands must be executed/queued for execution otherwise they do nothing. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct RestorePosition; impl Command for RestorePosition { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { f.write_str("\x1B8") } #[cfg(windows)] fn execute_winapi(&self) -> std::io::Result<()> { sys::restore_position() } } /// A command that hides the terminal cursor. /// /// # Notes /// /// - Commands must be executed/queued for execution otherwise they do nothing. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Hide; impl Command for Hide { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { f.write_str(csi!("?25l")) } #[cfg(windows)] fn execute_winapi(&self) -> std::io::Result<()> { sys::show_cursor(false) } } /// A command that shows the terminal cursor. /// /// # Notes /// /// - Commands must be executed/queued for execution otherwise they do nothing. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Show; impl Command for Show { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { f.write_str(csi!("?25h")) } #[cfg(windows)] fn execute_winapi(&self) -> std::io::Result<()> { sys::show_cursor(true) } } /// A command that enables blinking of the terminal cursor. /// /// # Notes /// /// - Some Unix terminals (ex: GNOME and Konsole) as well as Windows versions lower than Windows 10 do not support this functionality. /// Use `SetCursorStyle` for better cross-compatibility. /// - Commands must be executed/queued for execution otherwise they do nothing. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct EnableBlinking; impl Command for EnableBlinking { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { f.write_str(csi!("?12h")) } #[cfg(windows)] fn execute_winapi(&self) -> std::io::Result<()> { Ok(()) } } /// A command that disables blinking of the terminal cursor. /// /// # Notes /// /// - Some Unix terminals (ex: GNOME and Konsole) as well as Windows versions lower than Windows 10 do not support this functionality. /// Use `SetCursorStyle` for better cross-compatibility. /// - Commands must be executed/queued for execution otherwise they do nothing. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct DisableBlinking; impl Command for DisableBlinking { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { f.write_str(csi!("?12l")) } #[cfg(windows)] fn execute_winapi(&self) -> std::io::Result<()> { Ok(()) } } /// A command that sets the style of the cursor. /// It uses two types of escape codes, one to control blinking, and the other the shape. /// /// # Note /// /// - Commands must be executed/queued for execution otherwise they do nothing. #[derive(Clone, Copy)] pub enum SetCursorStyle { /// Default cursor shape configured by the user. DefaultUserShape, /// A blinking block cursor shape (■). BlinkingBlock, /// A non blinking block cursor shape (inverse of `BlinkingBlock`). SteadyBlock, /// A blinking underscore cursor shape(_). BlinkingUnderScore, /// A non blinking underscore cursor shape (inverse of `BlinkingUnderScore`). SteadyUnderScore, /// A blinking cursor bar shape (|) BlinkingBar, /// A steady cursor bar shape (inverse of `BlinkingBar`). SteadyBar, } impl Command for SetCursorStyle { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { match self { SetCursorStyle::DefaultUserShape => f.write_str("\x1b[0 q"), SetCursorStyle::BlinkingBlock => f.write_str("\x1b[1 q"), SetCursorStyle::SteadyBlock => f.write_str("\x1b[2 q"), SetCursorStyle::BlinkingUnderScore => f.write_str("\x1b[3 q"), SetCursorStyle::SteadyUnderScore => f.write_str("\x1b[4 q"), SetCursorStyle::BlinkingBar => f.write_str("\x1b[5 q"), SetCursorStyle::SteadyBar => f.write_str("\x1b[6 q"), } } #[cfg(windows)] fn execute_winapi(&self) -> std::io::Result<()> { Ok(()) } } impl_display!(for MoveTo); impl_display!(for MoveToColumn); impl_display!(for MoveToRow); impl_display!(for MoveToNextLine); impl_display!(for MoveToPreviousLine); 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 EnableBlinking); impl_display!(for DisableBlinking); impl_display!(for SetCursorStyle); #[cfg(test)] #[cfg(feature = "events")] mod tests { use std::io::{self, stdout}; use crate::execute; use super::{ sys::position, MoveDown, MoveLeft, MoveRight, MoveTo, MoveUp, RestorePosition, SavePosition, }; // 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); } }