crossterm/
cursor.rs

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