| 1 | //! # Cursor
|
| 2 | //!
|
| 3 | //! The `cursor` module provides functionality to work with the terminal cursor.
|
| 4 | //!
|
| 5 | //! This documentation does not contain a lot of examples. The reason is that it's fairly
|
| 6 | //! obvious how to use this crate. Although, we do provide
|
| 7 | //! [examples](https://github.com/crossterm-rs/crossterm/tree/master/examples) repository
|
| 8 | //! to demonstrate the capabilities.
|
| 9 | //!
|
| 10 | //! ## Examples
|
| 11 | //!
|
| 12 | //! Cursor actions can be performed with commands.
|
| 13 | //! Please have a look at [command documentation](../index.html#command-api) for a more detailed documentation.
|
| 14 | //!
|
| 15 | //! ```no_run
|
| 16 | //! use std::io::{self, Write};
|
| 17 | //!
|
| 18 | //! use crossterm::{
|
| 19 | //! ExecutableCommand, execute,
|
| 20 | //! cursor::{DisableBlinking, EnableBlinking, MoveTo, RestorePosition, SavePosition}
|
| 21 | //! };
|
| 22 | //!
|
| 23 | //! fn main() -> io::Result<()> {
|
| 24 | //! // with macro
|
| 25 | //! execute!(
|
| 26 | //! io::stdout(),
|
| 27 | //! SavePosition,
|
| 28 | //! MoveTo(10, 10),
|
| 29 | //! EnableBlinking,
|
| 30 | //! DisableBlinking,
|
| 31 | //! RestorePosition
|
| 32 | //! );
|
| 33 | //!
|
| 34 | //! // with function
|
| 35 | //! io::stdout()
|
| 36 | //! .execute(MoveTo(11,11))?
|
| 37 | //! .execute(RestorePosition);
|
| 38 | //!
|
| 39 | //! Ok(())
|
| 40 | //! }
|
| 41 | //! ```
|
| 42 | //!
|
| 43 | //! For manual execution control check out [crossterm::queue](../macro.queue.html).
|
| 44 |
|
| 45 | use std::fmt;
|
| 46 |
|
| 47 | use crate::{csi, impl_display, Command};
|
| 48 |
|
| 49 | pub(crate) mod sys;
|
| 50 |
|
| 51 | #[cfg (feature = "events" )]
|
| 52 | pub use sys::position;
|
| 53 |
|
| 54 | /// A command that moves the terminal cursor to the given position (column, row).
|
| 55 | ///
|
| 56 | /// # Notes
|
| 57 | /// * Top left cell is represented as `0,0`.
|
| 58 | /// * Commands must be executed/queued for execution otherwise they do nothing.
|
| 59 | #[derive (Debug, Clone, Copy, PartialEq, Eq)]
|
| 60 | pub struct MoveTo(pub u16, pub u16);
|
| 61 |
|
| 62 | impl Command for MoveTo {
|
| 63 | fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
| 64 | write!(f, csi!("{};{}H" ), self.1 + 1, self.0 + 1)
|
| 65 | }
|
| 66 |
|
| 67 | #[cfg (windows)]
|
| 68 | fn execute_winapi(&self) -> std::io::Result<()> {
|
| 69 | sys::move_to(self.0, self.1)
|
| 70 | }
|
| 71 | }
|
| 72 |
|
| 73 | /// A command that moves the terminal cursor down the given number of lines,
|
| 74 | /// and moves it to the first column.
|
| 75 | ///
|
| 76 | /// # Notes
|
| 77 | /// * This command is 1 based, meaning `MoveToNextLine(1)` moves to the next line.
|
| 78 | /// * Most terminals default 0 argument to 1.
|
| 79 | /// * Commands must be executed/queued for execution otherwise they do nothing.
|
| 80 | #[derive (Debug, Clone, Copy, PartialEq, Eq)]
|
| 81 | pub struct MoveToNextLine(pub u16);
|
| 82 |
|
| 83 | impl Command for MoveToNextLine {
|
| 84 | fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
| 85 | write!(f, csi!("{}E" ), self.0)?;
|
| 86 | Ok(())
|
| 87 | }
|
| 88 |
|
| 89 | #[cfg (windows)]
|
| 90 | fn execute_winapi(&self) -> std::io::Result<()> {
|
| 91 | if self.0 != 0 {
|
| 92 | sys::move_to_next_line(self.0)?;
|
| 93 | }
|
| 94 | Ok(())
|
| 95 | }
|
| 96 | }
|
| 97 |
|
| 98 | /// A command that moves the terminal cursor up the given number of lines,
|
| 99 | /// and moves it to the first column.
|
| 100 | ///
|
| 101 | /// # Notes
|
| 102 | /// * This command is 1 based, meaning `MoveToPreviousLine(1)` moves to the previous line.
|
| 103 | /// * Most terminals default 0 argument to 1.
|
| 104 | /// * Commands must be executed/queued for execution otherwise they do nothing.
|
| 105 | #[derive (Debug, Clone, Copy, PartialEq, Eq)]
|
| 106 | pub struct MoveToPreviousLine(pub u16);
|
| 107 |
|
| 108 | impl Command for MoveToPreviousLine {
|
| 109 | fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
| 110 | write!(f, csi!("{}F" ), self.0)?;
|
| 111 | Ok(())
|
| 112 | }
|
| 113 |
|
| 114 | #[cfg (windows)]
|
| 115 | fn execute_winapi(&self) -> std::io::Result<()> {
|
| 116 | if self.0 != 0 {
|
| 117 | sys::move_to_previous_line(self.0)?;
|
| 118 | }
|
| 119 | Ok(())
|
| 120 | }
|
| 121 | }
|
| 122 |
|
| 123 | /// A command that moves the terminal cursor to the given column on the current row.
|
| 124 | ///
|
| 125 | /// # Notes
|
| 126 | /// * This command is 0 based, meaning 0 is the leftmost column.
|
| 127 | /// * Commands must be executed/queued for execution otherwise they do nothing.
|
| 128 | #[derive (Debug, Clone, Copy, PartialEq, Eq)]
|
| 129 | pub struct MoveToColumn(pub u16);
|
| 130 |
|
| 131 | impl Command for MoveToColumn {
|
| 132 | fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
| 133 | write!(f, csi!("{}G" ), self.0 + 1)?;
|
| 134 | Ok(())
|
| 135 | }
|
| 136 |
|
| 137 | #[cfg (windows)]
|
| 138 | fn execute_winapi(&self) -> std::io::Result<()> {
|
| 139 | sys::move_to_column(self.0)
|
| 140 | }
|
| 141 | }
|
| 142 |
|
| 143 | /// A command that moves the terminal cursor to the given row on the current column.
|
| 144 | ///
|
| 145 | /// # Notes
|
| 146 | /// * This command is 0 based, meaning 0 is the topmost row.
|
| 147 | /// * Commands must be executed/queued for execution otherwise they do nothing.
|
| 148 | #[derive (Debug, Clone, Copy, PartialEq, Eq)]
|
| 149 | pub struct MoveToRow(pub u16);
|
| 150 |
|
| 151 | impl Command for MoveToRow {
|
| 152 | fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
| 153 | write!(f, csi!("{}d" ), self.0 + 1)?;
|
| 154 | Ok(())
|
| 155 | }
|
| 156 |
|
| 157 | #[cfg (windows)]
|
| 158 | fn execute_winapi(&self) -> std::io::Result<()> {
|
| 159 | sys::move_to_row(self.0)
|
| 160 | }
|
| 161 | }
|
| 162 |
|
| 163 | /// A command that moves the terminal cursor a given number of rows up.
|
| 164 | ///
|
| 165 | /// # Notes
|
| 166 | /// * This command is 1 based, meaning `MoveUp(1)` moves the cursor up one cell.
|
| 167 | /// * Most terminals default 0 argument to 1.
|
| 168 | /// * Commands must be executed/queued for execution otherwise they do nothing.
|
| 169 | #[derive (Debug, Clone, Copy, PartialEq, Eq)]
|
| 170 | pub struct MoveUp(pub u16);
|
| 171 |
|
| 172 | impl Command for MoveUp {
|
| 173 | fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
| 174 | write!(f, csi!("{}A" ), self.0)?;
|
| 175 | Ok(())
|
| 176 | }
|
| 177 |
|
| 178 | #[cfg (windows)]
|
| 179 | fn execute_winapi(&self) -> std::io::Result<()> {
|
| 180 | sys::move_up(self.0)
|
| 181 | }
|
| 182 | }
|
| 183 |
|
| 184 | /// A command that moves the terminal cursor a given number of columns to the right.
|
| 185 | ///
|
| 186 | /// # Notes
|
| 187 | /// * This command is 1 based, meaning `MoveRight(1)` moves the cursor right one cell.
|
| 188 | /// * Most terminals default 0 argument to 1.
|
| 189 | /// * Commands must be executed/queued for execution otherwise they do nothing.
|
| 190 | #[derive (Debug, Clone, Copy, PartialEq, Eq)]
|
| 191 | pub struct MoveRight(pub u16);
|
| 192 |
|
| 193 | impl Command for MoveRight {
|
| 194 | fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
| 195 | write!(f, csi!("{}C" ), self.0)?;
|
| 196 | Ok(())
|
| 197 | }
|
| 198 |
|
| 199 | #[cfg (windows)]
|
| 200 | fn execute_winapi(&self) -> std::io::Result<()> {
|
| 201 | sys::move_right(self.0)
|
| 202 | }
|
| 203 | }
|
| 204 |
|
| 205 | /// A command that moves the terminal cursor a given number of rows down.
|
| 206 | ///
|
| 207 | /// # Notes
|
| 208 | /// * This command is 1 based, meaning `MoveDown(1)` moves the cursor down one cell.
|
| 209 | /// * Most terminals default 0 argument to 1.
|
| 210 | /// * Commands must be executed/queued for execution otherwise they do nothing.
|
| 211 | #[derive (Debug, Clone, Copy, PartialEq, Eq)]
|
| 212 | pub struct MoveDown(pub u16);
|
| 213 |
|
| 214 | impl Command for MoveDown {
|
| 215 | fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
| 216 | write!(f, csi!("{}B" ), self.0)?;
|
| 217 | Ok(())
|
| 218 | }
|
| 219 |
|
| 220 | #[cfg (windows)]
|
| 221 | fn execute_winapi(&self) -> std::io::Result<()> {
|
| 222 | sys::move_down(self.0)
|
| 223 | }
|
| 224 | }
|
| 225 |
|
| 226 | /// A command that moves the terminal cursor a given number of columns to the left.
|
| 227 | ///
|
| 228 | /// # Notes
|
| 229 | /// * This command is 1 based, meaning `MoveLeft(1)` moves the cursor left one cell.
|
| 230 | /// * Most terminals default 0 argument to 1.
|
| 231 | /// * Commands must be executed/queued for execution otherwise they do nothing.
|
| 232 | #[derive (Debug, Clone, Copy, PartialEq, Eq)]
|
| 233 | pub struct MoveLeft(pub u16);
|
| 234 |
|
| 235 | impl Command for MoveLeft {
|
| 236 | fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
| 237 | write!(f, csi!("{}D" ), self.0)?;
|
| 238 | Ok(())
|
| 239 | }
|
| 240 |
|
| 241 | #[cfg (windows)]
|
| 242 | fn execute_winapi(&self) -> std::io::Result<()> {
|
| 243 | sys::move_left(self.0)
|
| 244 | }
|
| 245 | }
|
| 246 |
|
| 247 | /// A command that saves the current terminal cursor position.
|
| 248 | ///
|
| 249 | /// See the [RestorePosition](./struct.RestorePosition.html) command.
|
| 250 | ///
|
| 251 | /// # Notes
|
| 252 | ///
|
| 253 | /// - The cursor position is stored globally.
|
| 254 | /// - Commands must be executed/queued for execution otherwise they do nothing.
|
| 255 | #[derive (Debug, Clone, Copy, PartialEq, Eq)]
|
| 256 | pub struct SavePosition;
|
| 257 |
|
| 258 | impl Command for SavePosition {
|
| 259 | fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
| 260 | f.write_str(" \x1B7" )
|
| 261 | }
|
| 262 |
|
| 263 | #[cfg (windows)]
|
| 264 | fn execute_winapi(&self) -> std::io::Result<()> {
|
| 265 | sys::save_position()
|
| 266 | }
|
| 267 | }
|
| 268 |
|
| 269 | /// A command that restores the saved terminal cursor position.
|
| 270 | ///
|
| 271 | /// See the [SavePosition](./struct.SavePosition.html) command.
|
| 272 | ///
|
| 273 | /// # Notes
|
| 274 | ///
|
| 275 | /// - The cursor position is stored globally.
|
| 276 | /// - Commands must be executed/queued for execution otherwise they do nothing.
|
| 277 | #[derive (Debug, Clone, Copy, PartialEq, Eq)]
|
| 278 | pub struct RestorePosition;
|
| 279 |
|
| 280 | impl Command for RestorePosition {
|
| 281 | fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
| 282 | f.write_str(" \x1B8" )
|
| 283 | }
|
| 284 |
|
| 285 | #[cfg (windows)]
|
| 286 | fn execute_winapi(&self) -> std::io::Result<()> {
|
| 287 | sys::restore_position()
|
| 288 | }
|
| 289 | }
|
| 290 |
|
| 291 | /// A command that hides the terminal cursor.
|
| 292 | ///
|
| 293 | /// # Notes
|
| 294 | ///
|
| 295 | /// - Commands must be executed/queued for execution otherwise they do nothing.
|
| 296 | #[derive (Debug, Clone, Copy, PartialEq, Eq)]
|
| 297 | pub struct Hide;
|
| 298 |
|
| 299 | impl Command for Hide {
|
| 300 | fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
| 301 | f.write_str(csi!("?25l" ))
|
| 302 | }
|
| 303 |
|
| 304 | #[cfg (windows)]
|
| 305 | fn execute_winapi(&self) -> std::io::Result<()> {
|
| 306 | sys::show_cursor(false)
|
| 307 | }
|
| 308 | }
|
| 309 |
|
| 310 | /// A command that shows the terminal cursor.
|
| 311 | ///
|
| 312 | /// # Notes
|
| 313 | ///
|
| 314 | /// - Commands must be executed/queued for execution otherwise they do nothing.
|
| 315 | #[derive (Debug, Clone, Copy, PartialEq, Eq)]
|
| 316 | pub struct Show;
|
| 317 |
|
| 318 | impl Command for Show {
|
| 319 | fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
| 320 | f.write_str(csi!("?25h" ))
|
| 321 | }
|
| 322 |
|
| 323 | #[cfg (windows)]
|
| 324 | fn execute_winapi(&self) -> std::io::Result<()> {
|
| 325 | sys::show_cursor(true)
|
| 326 | }
|
| 327 | }
|
| 328 |
|
| 329 | /// A command that enables blinking of the terminal cursor.
|
| 330 | ///
|
| 331 | /// # Notes
|
| 332 | ///
|
| 333 | /// - Some Unix terminals (ex: GNOME and Konsole) as well as Windows versions lower than Windows 10 do not support this functionality.
|
| 334 | /// Use `SetCursorStyle` for better cross-compatibility.
|
| 335 | /// - Commands must be executed/queued for execution otherwise they do nothing.
|
| 336 | #[derive (Debug, Clone, Copy, PartialEq, Eq)]
|
| 337 | pub struct EnableBlinking;
|
| 338 | impl Command for EnableBlinking {
|
| 339 | fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
| 340 | f.write_str(csi!("?12h" ))
|
| 341 | }
|
| 342 | #[cfg (windows)]
|
| 343 | fn execute_winapi(&self) -> std::io::Result<()> {
|
| 344 | Ok(())
|
| 345 | }
|
| 346 | }
|
| 347 |
|
| 348 | /// A command that disables blinking of the terminal cursor.
|
| 349 | ///
|
| 350 | /// # Notes
|
| 351 | ///
|
| 352 | /// - Some Unix terminals (ex: GNOME and Konsole) as well as Windows versions lower than Windows 10 do not support this functionality.
|
| 353 | /// Use `SetCursorStyle` for better cross-compatibility.
|
| 354 | /// - Commands must be executed/queued for execution otherwise they do nothing.
|
| 355 | #[derive (Debug, Clone, Copy, PartialEq, Eq)]
|
| 356 | pub struct DisableBlinking;
|
| 357 | impl Command for DisableBlinking {
|
| 358 | fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
| 359 | f.write_str(csi!("?12l" ))
|
| 360 | }
|
| 361 | #[cfg (windows)]
|
| 362 | fn execute_winapi(&self) -> std::io::Result<()> {
|
| 363 | Ok(())
|
| 364 | }
|
| 365 | }
|
| 366 |
|
| 367 | /// A command that sets the style of the cursor.
|
| 368 | /// It uses two types of escape codes, one to control blinking, and the other the shape.
|
| 369 | ///
|
| 370 | /// # Note
|
| 371 | ///
|
| 372 | /// - Commands must be executed/queued for execution otherwise they do nothing.
|
| 373 | #[derive (Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
| 374 | pub enum SetCursorStyle {
|
| 375 | /// Default cursor shape configured by the user.
|
| 376 | DefaultUserShape,
|
| 377 | /// A blinking block cursor shape (■).
|
| 378 | BlinkingBlock,
|
| 379 | /// A non blinking block cursor shape (inverse of `BlinkingBlock`).
|
| 380 | SteadyBlock,
|
| 381 | /// A blinking underscore cursor shape(_).
|
| 382 | BlinkingUnderScore,
|
| 383 | /// A non blinking underscore cursor shape (inverse of `BlinkingUnderScore`).
|
| 384 | SteadyUnderScore,
|
| 385 | /// A blinking cursor bar shape (|)
|
| 386 | BlinkingBar,
|
| 387 | /// A steady cursor bar shape (inverse of `BlinkingBar`).
|
| 388 | SteadyBar,
|
| 389 | }
|
| 390 |
|
| 391 | impl Command for SetCursorStyle {
|
| 392 | fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
| 393 | match self {
|
| 394 | SetCursorStyle::DefaultUserShape => f.write_str(" \x1b[0 q" ),
|
| 395 | SetCursorStyle::BlinkingBlock => f.write_str(" \x1b[1 q" ),
|
| 396 | SetCursorStyle::SteadyBlock => f.write_str(" \x1b[2 q" ),
|
| 397 | SetCursorStyle::BlinkingUnderScore => f.write_str(" \x1b[3 q" ),
|
| 398 | SetCursorStyle::SteadyUnderScore => f.write_str(" \x1b[4 q" ),
|
| 399 | SetCursorStyle::BlinkingBar => f.write_str(" \x1b[5 q" ),
|
| 400 | SetCursorStyle::SteadyBar => f.write_str(" \x1b[6 q" ),
|
| 401 | }
|
| 402 | }
|
| 403 |
|
| 404 | #[cfg (windows)]
|
| 405 | fn execute_winapi(&self) -> std::io::Result<()> {
|
| 406 | Ok(())
|
| 407 | }
|
| 408 | }
|
| 409 |
|
| 410 | impl_display!(for MoveTo);
|
| 411 | impl_display!(for MoveToColumn);
|
| 412 | impl_display!(for MoveToRow);
|
| 413 | impl_display!(for MoveToNextLine);
|
| 414 | impl_display!(for MoveToPreviousLine);
|
| 415 | impl_display!(for MoveUp);
|
| 416 | impl_display!(for MoveDown);
|
| 417 | impl_display!(for MoveLeft);
|
| 418 | impl_display!(for MoveRight);
|
| 419 | impl_display!(for SavePosition);
|
| 420 | impl_display!(for RestorePosition);
|
| 421 | impl_display!(for Hide);
|
| 422 | impl_display!(for Show);
|
| 423 | impl_display!(for EnableBlinking);
|
| 424 | impl_display!(for DisableBlinking);
|
| 425 | impl_display!(for SetCursorStyle);
|
| 426 |
|
| 427 | #[cfg (test)]
|
| 428 | #[cfg (feature = "events" )]
|
| 429 | mod tests {
|
| 430 | use std::io::{self, stdout};
|
| 431 |
|
| 432 | use crate::execute;
|
| 433 |
|
| 434 | use super::{
|
| 435 | sys::position, MoveDown, MoveLeft, MoveRight, MoveTo, MoveUp, RestorePosition, SavePosition,
|
| 436 | };
|
| 437 |
|
| 438 | // Test is disabled, because it's failing on Travis
|
| 439 | #[test ]
|
| 440 | #[ignore ]
|
| 441 | fn test_move_to() {
|
| 442 | let (saved_x, saved_y) = position().unwrap();
|
| 443 |
|
| 444 | execute!(stdout(), MoveTo(saved_x + 1, saved_y + 1)).unwrap();
|
| 445 | assert_eq!(position().unwrap(), (saved_x + 1, saved_y + 1));
|
| 446 |
|
| 447 | execute!(stdout(), MoveTo(saved_x, saved_y)).unwrap();
|
| 448 | assert_eq!(position().unwrap(), (saved_x, saved_y));
|
| 449 | }
|
| 450 |
|
| 451 | // Test is disabled, because it's failing on Travis
|
| 452 | #[test ]
|
| 453 | #[ignore ]
|
| 454 | fn test_move_right() {
|
| 455 | let (saved_x, saved_y) = position().unwrap();
|
| 456 | execute!(io::stdout(), MoveRight(1)).unwrap();
|
| 457 | assert_eq!(position().unwrap(), (saved_x + 1, saved_y));
|
| 458 | }
|
| 459 |
|
| 460 | // Test is disabled, because it's failing on Travis
|
| 461 | #[test ]
|
| 462 | #[ignore ]
|
| 463 | fn test_move_left() {
|
| 464 | execute!(stdout(), MoveTo(2, 0), MoveLeft(2)).unwrap();
|
| 465 | assert_eq!(position().unwrap(), (0, 0));
|
| 466 | }
|
| 467 |
|
| 468 | // Test is disabled, because it's failing on Travis
|
| 469 | #[test ]
|
| 470 | #[ignore ]
|
| 471 | fn test_move_up() {
|
| 472 | execute!(stdout(), MoveTo(0, 2), MoveUp(2)).unwrap();
|
| 473 | assert_eq!(position().unwrap(), (0, 0));
|
| 474 | }
|
| 475 |
|
| 476 | // Test is disabled, because it's failing on Travis
|
| 477 | #[test ]
|
| 478 | #[ignore ]
|
| 479 | fn test_move_down() {
|
| 480 | execute!(stdout(), MoveTo(0, 0), MoveDown(2)).unwrap();
|
| 481 |
|
| 482 | assert_eq!(position().unwrap(), (0, 2));
|
| 483 | }
|
| 484 |
|
| 485 | // Test is disabled, because it's failing on Travis
|
| 486 | #[test ]
|
| 487 | #[ignore ]
|
| 488 | fn test_save_restore_position() {
|
| 489 | let (saved_x, saved_y) = position().unwrap();
|
| 490 |
|
| 491 | execute!(
|
| 492 | stdout(),
|
| 493 | SavePosition,
|
| 494 | MoveTo(saved_x + 1, saved_y + 1),
|
| 495 | RestorePosition
|
| 496 | )
|
| 497 | .unwrap();
|
| 498 |
|
| 499 | let (x, y) = position().unwrap();
|
| 500 |
|
| 501 | assert_eq!(x, saved_x);
|
| 502 | assert_eq!(y, saved_y);
|
| 503 | }
|
| 504 | }
|
| 505 | |