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}