crossterm_cursor/cursor/
ansi.rs

1//! This is an ANSI specific implementation for cursor related action.
2//! This module is used for windows 10 terminals and UNIX terminals by default.
3//! Note that the cursor position is 0 based. This means that we start counting at 0 when setting the cursor position etc.
4
5use crossterm_utils::{csi, write_cout, Result};
6
7use crate::sys::{get_cursor_position, show_cursor};
8
9use super::Cursor;
10
11pub(crate) fn goto_csi_sequence(x: u16, y: u16) -> String {
12    format!(csi!("{};{}H"), y + 1, x + 1)
13}
14
15pub(crate) fn move_up_csi_sequence(count: u16) -> String {
16    format!(csi!("{}A"), count)
17}
18
19pub(crate) fn move_right_csi_sequence(count: u16) -> String {
20    format!(csi!("{}C"), count)
21}
22
23pub(crate) fn move_down_csi_sequence(count: u16) -> String {
24    format!(csi!("{}B"), count)
25}
26
27pub(crate) fn move_left_csi_sequence(count: u16) -> String {
28    format!(csi!("{}D"), count)
29}
30
31pub(crate) static SAVE_POSITION_CSI_SEQUENCE: &'static str = csi!("s");
32pub(crate) static RESTORE_POSITION_CSI_SEQUENCE: &'static str = csi!("u");
33pub(crate) static HIDE_CSI_SEQUENCE: &'static str = csi!("?25l");
34pub(crate) static SHOW_CSI_SEQUENCE: &'static str = csi!("?25h");
35pub(crate) static BLINKING_ON_CSI_SEQUENCE: &'static str = csi!("?12h");
36pub(crate) static BLINKING_OFF_CSI_SEQUENCE: &'static str = csi!("?12l");
37
38/// This struct is an ANSI implementation for cursor related actions.
39pub(crate) struct AnsiCursor;
40
41impl AnsiCursor {
42    pub(crate) fn new() -> AnsiCursor {
43        AnsiCursor
44    }
45}
46
47impl Cursor for AnsiCursor {
48    fn goto(&self, x: u16, y: u16) -> Result<()> {
49        write_cout!(goto_csi_sequence(x, y))?;
50        Ok(())
51    }
52
53    fn pos(&self) -> Result<(u16, u16)> {
54        get_cursor_position()
55    }
56
57    fn move_up(&self, count: u16) -> Result<()> {
58        write_cout!(move_up_csi_sequence(count))?;
59        Ok(())
60    }
61
62    fn move_right(&self, count: u16) -> Result<()> {
63        write_cout!(move_right_csi_sequence(count))?;
64        Ok(())
65    }
66
67    fn move_down(&self, count: u16) -> Result<()> {
68        write_cout!(move_down_csi_sequence(count))?;
69        Ok(())
70    }
71
72    fn move_left(&self, count: u16) -> Result<()> {
73        write_cout!(move_left_csi_sequence(count))?;
74        Ok(())
75    }
76
77    fn save_position(&self) -> Result<()> {
78        write_cout!(SAVE_POSITION_CSI_SEQUENCE)?;
79        Ok(())
80    }
81
82    fn restore_position(&self) -> Result<()> {
83        write_cout!(RESTORE_POSITION_CSI_SEQUENCE)?;
84        Ok(())
85    }
86
87    fn hide(&self) -> Result<()> {
88        show_cursor(false)?;
89        Ok(())
90    }
91
92    fn show(&self) -> Result<()> {
93        show_cursor(true)?;
94        Ok(())
95    }
96
97    fn blink(&self, blink: bool) -> Result<()> {
98        if blink {
99            write_cout!(BLINKING_ON_CSI_SEQUENCE)?;
100        } else {
101            write_cout!(BLINKING_OFF_CSI_SEQUENCE)?;
102        }
103        Ok(())
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use super::{AnsiCursor, Cursor};
110
111    // TODO - Test is ingored, because it's stalled on Travis CI
112    #[test]
113    #[ignore]
114    fn test_save_restore_position() {
115        if try_enable_ansi() {
116            let cursor = AnsiCursor::new();
117
118            let (saved_x, saved_y) = cursor.pos().unwrap();
119
120            cursor.save_position().unwrap();
121            cursor.goto(saved_x + 1, saved_y + 1).unwrap();
122            cursor.restore_position().unwrap();
123
124            let (x, y) = cursor.pos().unwrap();
125
126            assert_eq!(x, saved_x);
127            assert_eq!(y, saved_y);
128        }
129    }
130
131    // TODO - Test is ingored, because it's stalled on Travis CI
132    #[test]
133    #[ignore]
134    fn test_goto() {
135        if try_enable_ansi() {
136            let cursor = AnsiCursor::new();
137
138            let (saved_x, saved_y) = cursor.pos().unwrap();
139
140            cursor.goto(saved_x + 1, saved_y + 1).unwrap();
141            assert_eq!(cursor.pos().unwrap(), (saved_x + 1, saved_y + 1));
142
143            cursor.goto(saved_x, saved_y).unwrap();
144            assert_eq!(cursor.pos().unwrap(), (saved_x, saved_y));
145        }
146    }
147
148    fn try_enable_ansi() -> bool {
149        #[cfg(windows)]
150        {
151            if cfg!(target_os = "windows") {
152                use crossterm_utils::sys::winapi::ansi::set_virtual_terminal_processing;
153
154                // if it is not listed we should try with WinApi to check if we do support ANSI-codes.
155                match set_virtual_terminal_processing(true) {
156                    Ok(_) => return true,
157                    Err(_) => return false,
158                }
159            }
160        }
161
162        true
163    }
164}