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