ratatui/
backend.rs

1#![warn(missing_docs)]
2//! This module provides the backend implementations for different terminal libraries.
3//!
4//! It defines the [`Backend`] trait which is used to abstract over the specific terminal library
5//! being used.
6//!
7//! Supported terminal backends:
8//! - [Crossterm]: enable the `crossterm` feature (enabled by default) and use [`CrosstermBackend`]
9//! - [Termion]: enable the `termion` feature and use [`TermionBackend`]
10//! - [Termwiz]: enable the `termwiz` feature and use [`TermwizBackend`]
11//!
12//! Additionally, a [`TestBackend`] is provided for testing purposes.
13//!
14//! See the [Backend Comparison] section of the [Ratatui Website] for more details on the different
15//! backends.
16//!
17//! Each backend supports a number of features, such as [raw mode](#raw-mode), [alternate
18//! screen](#alternate-screen), and [mouse capture](#mouse-capture). These features are generally
19//! not enabled by default, and must be enabled by the application before they can be used. See the
20//! documentation for each backend for more details.
21//!
22//! Note: most applications should use the [`Terminal`] struct instead of directly calling methods
23//! on the backend.
24//!
25//! # Example
26//!
27//! ```rust,no_run
28//! use std::io::stdout;
29//!
30//! use ratatui::{backend::CrosstermBackend, Terminal};
31//!
32//! let backend = CrosstermBackend::new(stdout());
33//! let mut terminal = Terminal::new(backend)?;
34//! terminal.clear()?;
35//! terminal.draw(|frame| {
36//!     // -- snip --
37//! })?;
38//! # std::io::Result::Ok(())
39//! ```
40//!
41//! See the the [Examples] directory for more examples.
42//!
43//! # Raw Mode
44//!
45//! Raw mode is a mode where the terminal does not perform any processing or handling of the input
46//! and output. This means that features such as echoing input characters, line buffering, and
47//! special character processing (e.g., CTRL-C for SIGINT) are disabled. This is useful for
48//! applications that want to have complete control over the terminal input and output, processing
49//! each keystroke themselves.
50//!
51//! For example, in raw mode, the terminal will not perform line buffering on the input, so the
52//! application will receive each key press as it is typed, instead of waiting for the user to
53//! press enter. This makes it suitable for real-time applications like text editors,
54//! terminal-based games, and more.
55//!
56//! Each backend handles raw mode differently, so the behavior may vary depending on the backend
57//! being used. Be sure to consult the backend's specific documentation for exact details on how it
58//! implements raw mode.
59
60//! # Alternate Screen
61//!
62//! The alternate screen is a separate buffer that some terminals provide, distinct from the main
63//! screen. When activated, the terminal will display the alternate screen, hiding the current
64//! content of the main screen. Applications can write to this screen as if it were the regular
65//! terminal display, but when the application exits, the terminal will switch back to the main
66//! screen, and the contents of the alternate screen will be cleared. This is useful for
67//! applications like text editors or terminal games that want to use the full terminal window
68//! without disrupting the command line or other terminal content.
69//!
70//! This creates a seamless transition between the application and the regular terminal session, as
71//! the content displayed before launching the application will reappear after the application
72//! exits.
73//!
74//! Note that not all terminal emulators support the alternate screen, and even those that do may
75//! handle it differently. As a result, the behavior may vary depending on the backend being used.
76//! Always consult the specific backend's documentation to understand how it implements the
77//! alternate screen.
78//!
79//! # Mouse Capture
80//!
81//! Mouse capture is a mode where the terminal captures mouse events such as clicks, scrolls, and
82//! movement, and sends them to the application as special sequences or events. This enables the
83//! application to handle and respond to mouse actions, providing a more interactive and graphical
84//! user experience within the terminal. It's particularly useful for applications like
85//! terminal-based games, text editors, or other programs that require more direct interaction from
86//! the user.
87//!
88//! Each backend handles mouse capture differently, with variations in the types of events that can
89//! be captured and how they are represented. As such, the behavior may vary depending on the
90//! backend being used, and developers should consult the specific backend's documentation to
91//! understand how it implements mouse capture.
92//!
93//! [`TermionBackend`]: termion/struct.TermionBackend.html
94//! [`Terminal`]: crate::terminal::Terminal
95//! [`TermionBackend`]: termion/struct.TermionBackend.html
96//! [Crossterm]: https://crates.io/crates/crossterm
97//! [Termion]: https://crates.io/crates/termion
98//! [Termwiz]: https://crates.io/crates/termwiz
99//! [Examples]: https://github.com/ratatui/ratatui/tree/main/examples/README.md
100//! [Backend Comparison]:
101//!     https://ratatui.rs/concepts/backends/comparison/
102//! [Ratatui Website]: https://ratatui.rs
103use std::io;
104
105use strum::{Display, EnumString};
106
107use crate::{
108    buffer::Cell,
109    layout::{Position, Size},
110};
111
112#[cfg(all(not(windows), feature = "termion"))]
113mod termion;
114#[cfg(all(not(windows), feature = "termion"))]
115pub use self::termion::TermionBackend;
116
117#[cfg(feature = "crossterm")]
118mod crossterm;
119#[cfg(feature = "crossterm")]
120pub use self::crossterm::CrosstermBackend;
121
122#[cfg(feature = "termwiz")]
123mod termwiz;
124#[cfg(feature = "termwiz")]
125pub use self::termwiz::TermwizBackend;
126
127mod test;
128pub use self::test::TestBackend;
129
130/// Enum representing the different types of clearing operations that can be performed
131/// on the terminal screen.
132#[derive(Debug, Display, EnumString, Clone, Copy, Eq, PartialEq, Hash)]
133pub enum ClearType {
134    /// Clear the entire screen.
135    All,
136    /// Clear everything after the cursor.
137    AfterCursor,
138    /// Clear everything before the cursor.
139    BeforeCursor,
140    /// Clear the current line.
141    CurrentLine,
142    /// Clear everything from the cursor until the next newline.
143    UntilNewLine,
144}
145
146/// The window size in characters (columns / rows) as well as pixels.
147#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
148pub struct WindowSize {
149    /// Size of the window in characters (columns / rows).
150    pub columns_rows: Size,
151    /// Size of the window in pixels.
152    ///
153    /// The `pixels` fields may not be implemented by all terminals and return `0,0`. See
154    /// <https://man7.org/linux/man-pages/man4/tty_ioctl.4.html> under section "Get and set window
155    /// size" / TIOCGWINSZ where the fields are commented as "unused".
156    pub pixels: Size,
157}
158
159/// The `Backend` trait provides an abstraction over different terminal libraries. It defines the
160/// methods required to draw content, manipulate the cursor, and clear the terminal screen.
161///
162/// Most applications should not need to interact with the `Backend` trait directly as the
163/// [`Terminal`] struct provides a higher level interface for interacting with the terminal.
164///
165/// [`Terminal`]: crate::terminal::Terminal
166pub trait Backend {
167    /// Draw the given content to the terminal screen.
168    ///
169    /// The content is provided as an iterator over `(u16, u16, &Cell)` tuples, where the first two
170    /// elements represent the x and y coordinates, and the third element is a reference to the
171    /// [`Cell`] to be drawn.
172    fn draw<'a, I>(&mut self, content: I) -> io::Result<()>
173    where
174        I: Iterator<Item = (u16, u16, &'a Cell)>;
175
176    /// Insert `n` line breaks to the terminal screen.
177    ///
178    /// This method is optional and may not be implemented by all backends.
179    fn append_lines(&mut self, _n: u16) -> io::Result<()> {
180        Ok(())
181    }
182
183    /// Hide the cursor on the terminal screen.
184    ///
185    ///
186    /// See also [`show_cursor`].
187    /// # Example
188    ///
189    /// ```rust
190    /// # use ratatui::backend::{TestBackend};
191    /// # let mut backend = TestBackend::new(80, 25);
192    /// use ratatui::backend::Backend;
193    ///
194    /// backend.hide_cursor()?;
195    /// // do something with hidden cursor
196    /// backend.show_cursor()?;
197    /// # std::io::Result::Ok(())
198    /// ```
199    ///
200    /// [`show_cursor`]: Self::show_cursor
201    fn hide_cursor(&mut self) -> io::Result<()>;
202
203    /// Show the cursor on the terminal screen.
204    ///
205    /// See [`hide_cursor`] for an example.
206    ///
207    /// [`hide_cursor`]: Self::hide_cursor
208    fn show_cursor(&mut self) -> io::Result<()>;
209
210    /// Get the current cursor position on the terminal screen.
211    ///
212    /// The returned tuple contains the x and y coordinates of the cursor.
213    /// The origin (0, 0) is at the top left corner of the screen.
214    ///
215    /// See [`set_cursor_position`] for an example.
216    ///
217    /// [`set_cursor_position`]: Self::set_cursor_position
218    fn get_cursor_position(&mut self) -> io::Result<Position>;
219
220    /// Set the cursor position on the terminal screen to the given x and y coordinates.
221    ///
222    /// The origin (0, 0) is at the top left corner of the screen.
223    ///
224    /// # Example
225    ///
226    /// ```rust
227    /// # use ratatui::backend::{TestBackend};
228    /// # let mut backend = TestBackend::new(80, 25);
229    /// use ratatui::{backend::Backend, layout::Position};
230    ///
231    /// backend.set_cursor_position(Position { x: 10, y: 20 })?;
232    /// assert_eq!(backend.get_cursor_position()?, Position { x: 10, y: 20 });
233    /// # std::io::Result::Ok(())
234    /// ```
235    fn set_cursor_position<P: Into<Position>>(&mut self, position: P) -> io::Result<()>;
236
237    /// Get the current cursor position on the terminal screen.
238    ///
239    /// The returned tuple contains the x and y coordinates of the cursor. The origin
240    /// (0, 0) is at the top left corner of the screen.
241    #[deprecated = "the method get_cursor_position indicates more clearly what about the cursor to get"]
242    fn get_cursor(&mut self) -> io::Result<(u16, u16)> {
243        let Position { x, y } = self.get_cursor_position()?;
244        Ok((x, y))
245    }
246
247    /// Set the cursor position on the terminal screen to the given x and y coordinates.
248    ///
249    /// The origin (0, 0) is at the top left corner of the screen.
250    #[deprecated = "the method set_cursor_position indicates more clearly what about the cursor to set"]
251    fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()> {
252        self.set_cursor_position(Position { x, y })
253    }
254
255    /// Clears the whole terminal screen
256    ///
257    /// # Example
258    ///
259    /// ```rust,no_run
260    /// # use ratatui::backend::{TestBackend};
261    /// # let mut backend = TestBackend::new(80, 25);
262    /// use ratatui::backend::Backend;
263    ///
264    /// backend.clear()?;
265    /// # std::io::Result::Ok(())
266    /// ```
267    fn clear(&mut self) -> io::Result<()>;
268
269    /// Clears a specific region of the terminal specified by the [`ClearType`] parameter
270    ///
271    /// This method is optional and may not be implemented by all backends. The default
272    /// implementation calls [`clear`] if the `clear_type` is [`ClearType::All`] and returns an
273    /// error otherwise.
274    ///
275    /// # Example
276    ///
277    /// ```rust,no_run
278    /// # use ratatui::{backend::{TestBackend}};
279    /// # let mut backend = TestBackend::new(80, 25);
280    /// use ratatui::backend::{Backend, ClearType};
281    ///
282    /// backend.clear_region(ClearType::All)?;
283    /// # std::io::Result::Ok(())
284    /// ```
285    ///
286    /// # Errors
287    ///
288    /// This method will return an error if the terminal screen could not be cleared. It will also
289    /// return an error if the `clear_type` is not supported by the backend.
290    ///
291    /// [`clear`]: Self::clear
292    fn clear_region(&mut self, clear_type: ClearType) -> io::Result<()> {
293        match clear_type {
294            ClearType::All => self.clear(),
295            ClearType::AfterCursor
296            | ClearType::BeforeCursor
297            | ClearType::CurrentLine
298            | ClearType::UntilNewLine => Err(io::Error::new(
299                io::ErrorKind::Other,
300                format!("clear_type [{clear_type:?}] not supported with this backend"),
301            )),
302        }
303    }
304
305    /// Get the size of the terminal screen in columns/rows as a [`Size`].
306    ///
307    /// The returned [`Size`] contains the width and height of the terminal screen.
308    ///
309    /// # Example
310    ///
311    /// ```rust
312    /// # use ratatui::{backend::{TestBackend}};
313    /// # let backend = TestBackend::new(80, 25);
314    /// use ratatui::{backend::Backend, layout::Size};
315    ///
316    /// assert_eq!(backend.size()?, Size::new(80, 25));
317    /// # std::io::Result::Ok(())
318    /// ```
319    fn size(&self) -> io::Result<Size>;
320
321    /// Get the size of the terminal screen in columns/rows and pixels as a [`WindowSize`].
322    ///
323    /// The reason for this not returning only the pixel size, given the redundancy with the
324    /// `size()` method, is that the underlying backends most likely get both values with one
325    /// syscall, and the user is also most likely to need columns and rows along with pixel size.
326    fn window_size(&mut self) -> io::Result<WindowSize>;
327
328    /// Flush any buffered content to the terminal screen.
329    fn flush(&mut self) -> io::Result<()>;
330
331    /// Scroll a region of the screen upwards, where a region is specified by a (half-open) range
332    /// of rows.
333    ///
334    /// Each row in the region is replaced by the row `line_count` rows below it, except the bottom
335    /// `line_count` rows, which are replaced by empty rows. If `line_count` is equal to or larger
336    /// than the number of rows in the region, then all rows are replaced with empty rows.
337    ///
338    /// If the region includes row 0, then `line_count` rows are copied into the bottom of the
339    /// scrollback buffer. These rows are first taken from the old contents of the region, starting
340    /// from the top. If there aren't sufficient rows in the region, then the remainder are empty
341    /// rows.
342    ///
343    /// The position of the cursor afterwards is undefined.
344    ///
345    /// The behavior is designed to match what ANSI terminals do when scrolling regions are
346    /// established. With ANSI terminals, a scrolling region can be established with the "^[[X;Yr"
347    /// sequence, where X and Y define the lines of the region. The scrolling region can be reset
348    /// to be the whole screen with the "^[[r" sequence.
349    ///
350    /// When a scrolling region is established in an ANSI terminal, various operations' behaviors
351    /// are changed in such a way that the scrolling region acts like a "virtual screen". In
352    /// particular, the scrolling sequence "^[[NS", which scrolls lines up by a count of N.
353    ///
354    /// On an ANSI terminal, this method will probably translate to something like:
355    /// "^[[X;Yr^[[NS^[[r". That is, set the scrolling region, scroll up, then reset the scrolling
356    /// region.
357    ///
358    /// For examples of how this function is expected to work, refer to the tests for
359    /// [`TestBackend::scroll_region_up`].
360    #[cfg(feature = "scrolling-regions")]
361    fn scroll_region_up(&mut self, region: std::ops::Range<u16>, line_count: u16)
362        -> io::Result<()>;
363
364    /// Scroll a region of the screen downwards, where a region is specified by a (half-open) range
365    /// of rows.
366    ///
367    /// Each row in the region is replaced by the row `line_count` rows above it, except the top
368    /// `line_count` rows, which are replaced by empty rows. If `line_count` is equal to or larger
369    /// than the number of rows in the region, then all rows are replaced with empty rows.
370    ///
371    /// The position of the cursor afterwards is undefined.
372    ///
373    /// See the documentation for [`Self::scroll_region_down`] for more information about how this
374    /// is expected to be implemented for ANSI terminals. All of that applies, except the ANSI
375    /// sequence to scroll down is "^[[NT".
376    ///
377    /// This function is asymmetrical with regards to the scrollback buffer. The reason is that
378    /// this how terminals seem to implement things.
379    ///
380    /// For examples of how this function is expected to work, refer to the tests for
381    /// [`TestBackend::scroll_region_down`].
382    #[cfg(feature = "scrolling-regions")]
383    fn scroll_region_down(
384        &mut self,
385        region: std::ops::Range<u16>,
386        line_count: u16,
387    ) -> io::Result<()>;
388}
389
390#[cfg(test)]
391mod tests {
392    use strum::ParseError;
393
394    use super::*;
395
396    #[test]
397    fn clear_type_tostring() {
398        assert_eq!(ClearType::All.to_string(), "All");
399        assert_eq!(ClearType::AfterCursor.to_string(), "AfterCursor");
400        assert_eq!(ClearType::BeforeCursor.to_string(), "BeforeCursor");
401        assert_eq!(ClearType::CurrentLine.to_string(), "CurrentLine");
402        assert_eq!(ClearType::UntilNewLine.to_string(), "UntilNewLine");
403    }
404
405    #[test]
406    fn clear_type_from_str() {
407        assert_eq!("All".parse::<ClearType>(), Ok(ClearType::All));
408        assert_eq!(
409            "AfterCursor".parse::<ClearType>(),
410            Ok(ClearType::AfterCursor)
411        );
412        assert_eq!(
413            "BeforeCursor".parse::<ClearType>(),
414            Ok(ClearType::BeforeCursor)
415        );
416        assert_eq!(
417            "CurrentLine".parse::<ClearType>(),
418            Ok(ClearType::CurrentLine)
419        );
420        assert_eq!(
421            "UntilNewLine".parse::<ClearType>(),
422            Ok(ClearType::UntilNewLine)
423        );
424        assert_eq!("".parse::<ClearType>(), Err(ParseError::VariantNotFound));
425    }
426}