Skip to main content

ratatui_core/terminal/
cursor.rs

1use crate::backend::Backend;
2use crate::layout::Position;
3use crate::terminal::Terminal;
4
5impl<B: Backend> Terminal<B> {
6    /// Hides the cursor.
7    ///
8    /// When using [`Terminal::draw`] / [`Terminal::try_draw`], prefer controlling the cursor with
9    /// [`Frame::set_cursor_position`]. A later successful [`Terminal::draw`] /
10    /// [`Terminal::try_draw`] call may overwrite this change.
11    ///
12    /// [`Frame::set_cursor_position`]: crate::terminal::Frame::set_cursor_position
13    /// [`Terminal::draw`]: crate::terminal::Terminal::draw
14    /// [`Terminal::try_draw`]: crate::terminal::Terminal::try_draw
15    pub fn hide_cursor(&mut self) -> Result<(), B::Error> {
16        self.backend.hide_cursor()?;
17        self.hidden_cursor = true;
18        Ok(())
19    }
20
21    /// Shows the cursor.
22    ///
23    /// When using [`Terminal::draw`] / [`Terminal::try_draw`], prefer controlling the cursor with
24    /// [`Frame::set_cursor_position`]. A later successful [`Terminal::draw`] /
25    /// [`Terminal::try_draw`] call may overwrite this change.
26    ///
27    /// [`Frame::set_cursor_position`]: crate::terminal::Frame::set_cursor_position
28    /// [`Terminal::draw`]: crate::terminal::Terminal::draw
29    /// [`Terminal::try_draw`]: crate::terminal::Terminal::try_draw
30    pub fn show_cursor(&mut self) -> Result<(), B::Error> {
31        self.backend.show_cursor()?;
32        self.hidden_cursor = false;
33        Ok(())
34    }
35
36    /// Gets the current cursor position.
37    ///
38    /// This queries the backend for the current cursor position and returns it as an `(x, y)`
39    /// tuple.
40    #[deprecated = "use `get_cursor_position()` instead which returns `Result<Position>`"]
41    pub fn get_cursor(&mut self) -> Result<(u16, u16), B::Error> {
42        let Position { x, y } = self.get_cursor_position()?;
43        Ok((x, y))
44    }
45
46    /// Sets the cursor position.
47    #[deprecated = "use `set_cursor_position((x, y))` instead which takes `impl Into<Position>`"]
48    pub fn set_cursor(&mut self, x: u16, y: u16) -> Result<(), B::Error> {
49        self.set_cursor_position(Position { x, y })
50    }
51
52    /// Gets the current cursor position.
53    ///
54    /// This queries the backend for the current cursor position. It is not limited to Ratatui's
55    /// last render pass, so direct backend mutations may also affect the returned value.
56    ///
57    /// When using [`Terminal::draw`] / [`Terminal::try_draw`], prefer controlling the cursor with
58    /// [`Frame::set_cursor_position`]. For direct control, see [`Terminal::set_cursor_position`].
59    ///
60    /// [`Frame::set_cursor_position`]: crate::terminal::Frame::set_cursor_position
61    /// [`Terminal::draw`]: crate::terminal::Terminal::draw
62    /// [`Terminal::try_draw`]: crate::terminal::Terminal::try_draw
63    pub fn get_cursor_position(&mut self) -> Result<Position, B::Error> {
64        self.backend.get_cursor_position()
65    }
66
67    /// Sets the cursor position.
68    ///
69    /// This updates the backend cursor and Ratatui's internal cursor tracking. Inline viewports
70    /// use that tracking when recomputing the viewport on resize.
71    ///
72    /// When using [`Terminal::draw`] / [`Terminal::try_draw`], consider using
73    /// [`Frame::set_cursor_position`] instead so the cursor is updated as part of the normal
74    /// rendering flow. A later successful
75    /// [`Terminal::draw`] / [`Terminal::try_draw`] call may overwrite a direct cursor move.
76    ///
77    /// [`Frame::set_cursor_position`]: crate::terminal::Frame::set_cursor_position
78    /// [`Terminal::draw`]: crate::terminal::Terminal::draw
79    /// [`Terminal::try_draw`]: crate::terminal::Terminal::try_draw
80    pub fn set_cursor_position<P: Into<Position>>(&mut self, position: P) -> Result<(), B::Error> {
81        let position = position.into();
82        self.backend.set_cursor_position(position)?;
83        self.last_known_cursor_pos = position;
84        Ok(())
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use crate::backend::{Backend, TestBackend};
91    use crate::layout::Position;
92    use crate::terminal::Terminal;
93
94    #[test]
95    fn hide_cursor_updates_terminal_state() {
96        let backend = TestBackend::new(10, 5);
97        let mut terminal = Terminal::new(backend).unwrap();
98
99        terminal.hide_cursor().unwrap();
100
101        assert!(terminal.hidden_cursor);
102        assert!(!terminal.backend().cursor_visible());
103    }
104
105    #[test]
106    fn show_cursor_updates_terminal_state() {
107        let backend = TestBackend::new(10, 5);
108        let mut terminal = Terminal::new(backend).unwrap();
109
110        terminal.hide_cursor().unwrap();
111        terminal.show_cursor().unwrap();
112
113        assert!(!terminal.hidden_cursor);
114        assert!(terminal.backend().cursor_visible());
115    }
116
117    #[test]
118    fn set_cursor_position_updates_backend_and_tracking() {
119        let backend = TestBackend::new(10, 5);
120        let mut terminal = Terminal::new(backend).unwrap();
121
122        terminal.set_cursor_position((3, 4)).unwrap();
123
124        assert_eq!(terminal.last_known_cursor_pos, Position { x: 3, y: 4 });
125        terminal
126            .backend_mut()
127            .assert_cursor_position(Position { x: 3, y: 4 });
128    }
129
130    #[test]
131    fn get_cursor_position_queries_backend() {
132        let backend = TestBackend::new(10, 5);
133        let mut terminal = Terminal::new(backend).unwrap();
134
135        terminal
136            .backend_mut()
137            .set_cursor_position(Position { x: 7, y: 2 })
138            .unwrap();
139
140        assert_eq!(
141            terminal.get_cursor_position().unwrap(),
142            Position { x: 7, y: 2 }
143        );
144    }
145
146    #[test]
147    #[allow(deprecated)]
148    fn deprecated_cursor_wrappers_delegate_to_position_apis() {
149        let backend = TestBackend::new(10, 5);
150        let mut terminal = Terminal::new(backend).unwrap();
151
152        terminal.set_cursor(4, 1).unwrap();
153
154        assert_eq!(terminal.get_cursor().unwrap(), (4, 1));
155        assert_eq!(terminal.last_known_cursor_pos, Position { x: 4, y: 1 });
156        terminal
157            .backend_mut()
158            .assert_cursor_position(Position { x: 4, y: 1 });
159    }
160}