mirror of
https://github.com/ratatui-org/ratatui
synced 2024-11-22 04:33:13 +00:00
docs(backend): improve backend module docs (#489)
This commit is contained in:
parent
1e20475061
commit
1947c58c60
5 changed files with 413 additions and 130 deletions
|
@ -1,10 +1,7 @@
|
|||
//! This module provides the `CrosstermBackend` implementation for the `Backend` trait.
|
||||
//! It uses the `crossterm` crate to interact with the terminal.
|
||||
//! This module provides the [`CrosstermBackend`] implementation for the [`Backend`] trait. It uses
|
||||
//! the [Crossterm] crate to interact with the terminal.
|
||||
//!
|
||||
//!
|
||||
//! [`Backend`]: trait.Backend.html
|
||||
//! [`CrosstermBackend`]: struct.CrosstermBackend.html
|
||||
|
||||
//! [Crossterm]: https://crates.io/crates/crossterm
|
||||
use std::io::{self, Write};
|
||||
|
||||
use crossterm::{
|
||||
|
@ -25,36 +22,80 @@ use crate::{
|
|||
style::{Color, Modifier},
|
||||
};
|
||||
|
||||
/// A backend implementation using the `crossterm` crate.
|
||||
/// A [`Backend`] implementation that uses [Crossterm] to render to the terminal.
|
||||
///
|
||||
/// The `CrosstermBackend` struct is a wrapper around a type implementing `Write`, which
|
||||
/// is used to send commands to the terminal. It provides methods for drawing content,
|
||||
/// manipulating the cursor, and clearing the terminal screen.
|
||||
/// The `CrosstermBackend` struct is a wrapper around a writer implementing [`Write`], which is
|
||||
/// used to send commands to the terminal. It provides methods for drawing content, manipulating
|
||||
/// the cursor, and clearing the terminal screen.
|
||||
///
|
||||
/// Most applications should not call the methods on `CrosstermBackend` directly, but will instead
|
||||
/// use the [`Terminal`] struct, which provides a more ergonomic interface.
|
||||
///
|
||||
/// Usually applications will enable raw mode and switch to alternate screen mode after creating
|
||||
/// a `CrosstermBackend`. This is done by calling [`crossterm::terminal::enable_raw_mode`] and
|
||||
/// [`crossterm::terminal::EnterAlternateScreen`] (and the corresponding disable/leave functions
|
||||
/// when the application exits). This is not done automatically by the backend because it is
|
||||
/// possible that the application may want to use the terminal for other purposes (like showing
|
||||
/// help text) before entering alternate screen mode.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::backend::{Backend, CrosstermBackend};
|
||||
/// ```rust,no_run
|
||||
/// use std::io::{stdout, stderr};
|
||||
/// use crossterm::{
|
||||
/// terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
/// ExecutableCommand,
|
||||
/// };
|
||||
/// use ratatui::prelude::*;
|
||||
///
|
||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let buffer = std::io::stdout();
|
||||
/// let mut backend = CrosstermBackend::new(buffer);
|
||||
/// backend.clear()?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// let mut backend = CrosstermBackend::new(stdout());
|
||||
/// // or
|
||||
/// let backend = CrosstermBackend::new(stderr());
|
||||
/// let mut terminal = Terminal::new(backend)?;
|
||||
///
|
||||
/// enable_raw_mode()?;
|
||||
/// stdout().execute(EnterAlternateScreen)?;
|
||||
///
|
||||
/// terminal.clear()?;
|
||||
/// terminal.draw(|frame| {
|
||||
/// // -- snip --
|
||||
/// })?;
|
||||
///
|
||||
/// stdout().execute(LeaveAlternateScreen)?;
|
||||
/// disable_raw_mode()?;
|
||||
///
|
||||
/// # std::io::Result::Ok(())
|
||||
/// ```
|
||||
///
|
||||
/// See the the [examples] directory for more examples. See the [`backend`] module documentation
|
||||
/// for more details on raw mode and alternate screen.
|
||||
///
|
||||
/// [`Write`]: std::io::Write
|
||||
/// [`Terminal`]: crate::terminal::Terminal
|
||||
/// [`backend`]: crate::backend
|
||||
/// [Crossterm]: https://crates.io/crates/crossterm
|
||||
/// [examples]: https://github.com/ratatui-org/ratatui/tree/main/examples#examples
|
||||
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct CrosstermBackend<W: Write> {
|
||||
buffer: W,
|
||||
/// The writer used to send commands to the terminal.
|
||||
writer: W,
|
||||
}
|
||||
|
||||
impl<W> CrosstermBackend<W>
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
/// Creates a new `CrosstermBackend` with the given buffer.
|
||||
pub fn new(buffer: W) -> CrosstermBackend<W> {
|
||||
CrosstermBackend { buffer }
|
||||
/// Creates a new `CrosstermBackend` with the given writer.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use std::io::stdout;
|
||||
/// # use ratatui::prelude::*;
|
||||
/// let backend = CrosstermBackend::new(stdout());
|
||||
/// ```
|
||||
pub fn new(writer: W) -> CrosstermBackend<W> {
|
||||
CrosstermBackend { writer }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -64,12 +105,12 @@ where
|
|||
{
|
||||
/// Writes a buffer of bytes to the underlying buffer.
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.buffer.write(buf)
|
||||
self.writer.write(buf)
|
||||
}
|
||||
|
||||
/// Flushes the underlying buffer.
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.buffer.flush()
|
||||
self.writer.flush()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,7 +130,7 @@ where
|
|||
for (x, y, cell) in content {
|
||||
// Move the cursor if the previous location was not (x - 1, y)
|
||||
if !matches!(last_pos, Some(p) if x == p.0 + 1 && y == p.1) {
|
||||
queue!(self.buffer, MoveTo(x, y))?;
|
||||
queue!(self.writer, MoveTo(x, y))?;
|
||||
}
|
||||
last_pos = Some((x, y));
|
||||
if cell.modifier != modifier {
|
||||
|
@ -97,30 +138,30 @@ where
|
|||
from: modifier,
|
||||
to: cell.modifier,
|
||||
};
|
||||
diff.queue(&mut self.buffer)?;
|
||||
diff.queue(&mut self.writer)?;
|
||||
modifier = cell.modifier;
|
||||
}
|
||||
if cell.fg != fg {
|
||||
let color = CColor::from(cell.fg);
|
||||
queue!(self.buffer, SetForegroundColor(color))?;
|
||||
queue!(self.writer, SetForegroundColor(color))?;
|
||||
fg = cell.fg;
|
||||
}
|
||||
if cell.bg != bg {
|
||||
let color = CColor::from(cell.bg);
|
||||
queue!(self.buffer, SetBackgroundColor(color))?;
|
||||
queue!(self.writer, SetBackgroundColor(color))?;
|
||||
bg = cell.bg;
|
||||
}
|
||||
if cell.underline_color != underline_color {
|
||||
let color = CColor::from(cell.underline_color);
|
||||
queue!(self.buffer, SetUnderlineColor(color))?;
|
||||
queue!(self.writer, SetUnderlineColor(color))?;
|
||||
underline_color = cell.underline_color;
|
||||
}
|
||||
|
||||
queue!(self.buffer, Print(&cell.symbol))?;
|
||||
queue!(self.writer, Print(&cell.symbol))?;
|
||||
}
|
||||
|
||||
queue!(
|
||||
self.buffer,
|
||||
self.writer,
|
||||
SetForegroundColor(CColor::Reset),
|
||||
SetBackgroundColor(CColor::Reset),
|
||||
SetUnderlineColor(CColor::Reset),
|
||||
|
@ -129,11 +170,11 @@ where
|
|||
}
|
||||
|
||||
fn hide_cursor(&mut self) -> io::Result<()> {
|
||||
execute!(self.buffer, Hide)
|
||||
execute!(self.writer, Hide)
|
||||
}
|
||||
|
||||
fn show_cursor(&mut self) -> io::Result<()> {
|
||||
execute!(self.buffer, Show)
|
||||
execute!(self.writer, Show)
|
||||
}
|
||||
|
||||
fn get_cursor(&mut self) -> io::Result<(u16, u16)> {
|
||||
|
@ -142,7 +183,7 @@ where
|
|||
}
|
||||
|
||||
fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()> {
|
||||
execute!(self.buffer, MoveTo(x, y))
|
||||
execute!(self.writer, MoveTo(x, y))
|
||||
}
|
||||
|
||||
fn clear(&mut self) -> io::Result<()> {
|
||||
|
@ -151,7 +192,7 @@ where
|
|||
|
||||
fn clear_region(&mut self, clear_type: ClearType) -> io::Result<()> {
|
||||
execute!(
|
||||
self.buffer,
|
||||
self.writer,
|
||||
Clear(match clear_type {
|
||||
ClearType::All => crossterm::terminal::ClearType::All,
|
||||
ClearType::AfterCursor => crossterm::terminal::ClearType::FromCursorDown,
|
||||
|
@ -164,9 +205,9 @@ where
|
|||
|
||||
fn append_lines(&mut self, n: u16) -> io::Result<()> {
|
||||
for _ in 0..n {
|
||||
queue!(self.buffer, Print("\n"))?;
|
||||
queue!(self.writer, Print("\n"))?;
|
||||
}
|
||||
self.buffer.flush()
|
||||
self.writer.flush()
|
||||
}
|
||||
|
||||
fn size(&self) -> io::Result<Rect> {
|
||||
|
@ -191,7 +232,7 @@ where
|
|||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.buffer.flush()
|
||||
self.writer.flush()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,30 +1,102 @@
|
|||
#![warn(missing_docs)]
|
||||
//! This module provides the backend implementations for different terminal libraries.
|
||||
//! It defines the [`Backend`] trait which is used to abstract over the specific
|
||||
//! terminal library being used.
|
||||
//!
|
||||
//! The following terminal libraries are supported:
|
||||
//! - Crossterm (with the `crossterm` feature)
|
||||
//! - Termion (with the `termion` feature)
|
||||
//! - Termwiz (with the `termwiz` feature)
|
||||
//! It defines the [`Backend`] trait which is used to abstract over the specific terminal library
|
||||
//! being used.
|
||||
//!
|
||||
//! Supported terminal backends:
|
||||
//! - [Crossterm]: enable the `crossterm` feature (enabled by default) and use [`CrosstermBackend`]
|
||||
//! - [Termion]: enable the `termion` feature and use [`TermionBackend`]
|
||||
//! - [Termwiz]: enable the `termwiz` feature and use [`TermwizBackend`]
|
||||
//!
|
||||
//! Additionally, a [`TestBackend`] is provided for testing purposes.
|
||||
//!
|
||||
//! See the [Backend Comparison] section of the [Ratatui Book] for more details on the different
|
||||
//! backends.
|
||||
//!
|
||||
//! Each backend supports a number of features, such as [raw mode](#raw-mode), [alternate
|
||||
//! screen](#alternate-screen), and [mouse capture](#mouse-capture). These features are generally
|
||||
//! not enabled by default, and must be enabled by the application before they can be used. See the
|
||||
//! documentation for each backend for more details.
|
||||
//!
|
||||
//! Note: most applications should use the [`Terminal`] struct instead of directly calling methods
|
||||
//! on the backend.
|
||||
//!
|
||||
//! # Example
|
||||
//!
|
||||
//! ```rust
|
||||
//! use ratatui::backend::{Backend, CrosstermBackend};
|
||||
//! ```rust,no_run
|
||||
//! use std::io::stdout;
|
||||
//! use ratatui::prelude::*;
|
||||
//!
|
||||
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
//! let buffer = std::io::stdout();
|
||||
//! let mut backend = CrosstermBackend::new(buffer);
|
||||
//! backend.clear()?;
|
||||
//! # Ok(())
|
||||
//! # }
|
||||
//! let backend = CrosstermBackend::new(stdout());
|
||||
//! let mut terminal = Terminal::new(backend)?;
|
||||
//! terminal.clear()?;
|
||||
//! terminal.draw(|frame| {
|
||||
//! // -- snip --
|
||||
//! })?;
|
||||
//! # std::io::Result::Ok(())
|
||||
//! ```
|
||||
//!
|
||||
//! [`Backend`]: trait.Backend.html
|
||||
//! [`TestBackend`]: struct.TestBackend.html
|
||||
//! See the the [examples] directory for more examples.
|
||||
//!
|
||||
//! # Raw Mode
|
||||
//!
|
||||
//! Raw mode is a mode where the terminal does not perform any processing or handling of the input
|
||||
//! and output. This means that features such as echoing input characters, line buffering, and
|
||||
//! special character processing (e.g., CTRL-C for SIGINT) are disabled. This is useful for
|
||||
//! applications that want to have complete control over the terminal input and output, processing
|
||||
//! each keystroke themselves.
|
||||
//!
|
||||
//! For example, in raw mode, the terminal will not perform line buffering on the input, so the
|
||||
//! application will receive each key press as it is typed, instead of waiting for the user to
|
||||
//! press enter. This makes it suitable for real-time applications like text editors,
|
||||
//! terminal-based games, and more.
|
||||
//!
|
||||
//! Each backend handles raw mode differently, so the behavior may vary depending on the backend
|
||||
//! being used. Be sure to consult the backend's specific documentation for exact details on how it
|
||||
//! implements raw mode.
|
||||
|
||||
//! # Alternate Screen
|
||||
//!
|
||||
//! The alternate screen is a separate buffer that some terminals provide, distinct from the main
|
||||
//! screen. When activated, the terminal will display the alternate screen, hiding the current
|
||||
//! content of the main screen. Applications can write to this screen as if it were the regular
|
||||
//! terminal display, but when the application exits, the terminal will switch back to the main
|
||||
//! screen, and the contents of the alternate screen will be cleared. This is useful for
|
||||
//! applications like text editors or terminal games that want to use the full terminal window
|
||||
//! without disrupting the command line or other terminal content.
|
||||
//!
|
||||
//! This creates a seamless transition between the application and the regular terminal session, as
|
||||
//! the content displayed before launching the application will reappear after the application
|
||||
//! exits.
|
||||
//!
|
||||
//! Note that not all terminal emulators support the alternate screen, and even those that do may
|
||||
//! handle it differently. As a result, the behavior may vary depending on the backend being used.
|
||||
//! Always consult the specific backend's documentation to understand how it implements the
|
||||
//! alternate screen.
|
||||
//!
|
||||
//! # Mouse Capture
|
||||
//!
|
||||
//! Mouse capture is a mode where the terminal captures mouse events such as clicks, scrolls, and
|
||||
//! movement, and sends them to the application as special sequences or events. This enables the
|
||||
//! application to handle and respond to mouse actions, providing a more interactive and graphical
|
||||
//! user experience within the terminal. It's particularly useful for applications like
|
||||
//! terminal-based games, text editors, or other programs that require more direct interaction from
|
||||
//! the user.
|
||||
//!
|
||||
//! Each backend handles mouse capture differently, with variations in the types of events that can
|
||||
//! be captured and how they are represented. As such, the behavior may vary depending on the
|
||||
//! backend being used, and developers should consult the specific backend's documentation to
|
||||
//! understand how it implements mouse capture.
|
||||
//!
|
||||
//! [`Terminal`]: crate::terminal::Terminal
|
||||
//! [Crossterm]: https://crates.io/crates/crossterm
|
||||
//! [Termion]: https://crates.io/crates/termion
|
||||
//! [Termwiz]: https://crates.io/crates/termwiz
|
||||
//! [examples]: https://github.com/ratatui-org/ratatui/tree/main/examples#readme
|
||||
//! [Backend Comparison]:
|
||||
//! https://ratatui-org.github.io/ratatui-book/concepts/backends/comparison.html
|
||||
//! [Ratatui Book]: https://ratatui-org.github.io/ratatui-book
|
||||
use std::io;
|
||||
|
||||
use strum::{Display, EnumString};
|
||||
|
@ -53,35 +125,44 @@ pub use self::test::TestBackend;
|
|||
/// on the terminal screen.
|
||||
#[derive(Debug, Display, EnumString, Clone, Copy, Eq, PartialEq, Hash)]
|
||||
pub enum ClearType {
|
||||
/// Clear the entire screen.
|
||||
All,
|
||||
/// Clear everything after the cursor.
|
||||
AfterCursor,
|
||||
/// Clear everything before the cursor.
|
||||
BeforeCursor,
|
||||
/// Clear the current line.
|
||||
CurrentLine,
|
||||
/// Clear everything from the cursor until the next newline.
|
||||
UntilNewLine,
|
||||
}
|
||||
|
||||
/// The window sizes in columns,rows and optionally pixel width,height.
|
||||
/// The window size in characters (columns / rows) as well as pixels.
|
||||
pub struct WindowSize {
|
||||
/// Size in character/cell columents,rows.
|
||||
/// Size of the window in characters (columns / rows).
|
||||
pub columns_rows: Size,
|
||||
/// Size in pixel width,height.
|
||||
/// Size of the window in pixels.
|
||||
///
|
||||
/// The `pixels` fields may not be implemented by all terminals and return `0,0`.
|
||||
/// See <https://man7.org/linux/man-pages/man4/tty_ioctl.4.html> under section
|
||||
/// "Get and set window size" / TIOCGWINSZ where the fields are commented as "unused".
|
||||
/// The `pixels` fields may not be implemented by all terminals and return `0,0`. See
|
||||
/// <https://man7.org/linux/man-pages/man4/tty_ioctl.4.html> under section "Get and set window
|
||||
/// size" / TIOCGWINSZ where the fields are commented as "unused".
|
||||
pub pixels: Size,
|
||||
}
|
||||
|
||||
/// The `Backend` trait provides an abstraction over different terminal libraries.
|
||||
/// It defines the methods required to draw content, manipulate the cursor, and
|
||||
/// clear the terminal screen.
|
||||
/// The `Backend` trait provides an abstraction over different terminal libraries. It defines the
|
||||
/// methods required to draw content, manipulate the cursor, and clear the terminal screen.
|
||||
///
|
||||
/// Most applications should not need to interact with the `Backend` trait directly as the
|
||||
/// [`Terminal`] struct provides a higher level interface for interacting with the terminal.
|
||||
///
|
||||
/// [`Terminal`]: crate::terminal::Terminal
|
||||
pub trait Backend {
|
||||
/// Draw the given content to the terminal screen.
|
||||
///
|
||||
/// The content is provided as an iterator over `(u16, u16, &Cell)` tuples,
|
||||
/// where the first two elements represent the x and y coordinates, and the
|
||||
/// third element is a reference to the [`Cell`] to be drawn.
|
||||
fn draw<'a, I>(&mut self, content: I) -> Result<(), io::Error>
|
||||
/// The content is provided as an iterator over `(u16, u16, &Cell)` tuples, where the first two
|
||||
/// elements represent the x and y coordinates, and the third element is a reference to the
|
||||
/// [`Cell`] to be drawn.
|
||||
fn draw<'a, I>(&mut self, content: I) -> io::Result<()>
|
||||
where
|
||||
I: Iterator<Item = (u16, u16, &'a Cell)>;
|
||||
|
||||
|
@ -93,24 +174,91 @@ pub trait Backend {
|
|||
}
|
||||
|
||||
/// Hide the cursor on the terminal screen.
|
||||
fn hide_cursor(&mut self) -> Result<(), io::Error>;
|
||||
///
|
||||
///
|
||||
/// See also [`show_cursor`].
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::backend::{Backend, TestBackend};
|
||||
/// # let mut backend = TestBackend::new(80, 25);
|
||||
/// backend.hide_cursor()?;
|
||||
/// // do something with hidden cursor
|
||||
/// backend.show_cursor()?;
|
||||
/// # std::io::Result::Ok(())
|
||||
/// ```
|
||||
///
|
||||
/// [`show_cursor`]: Backend::show_cursor
|
||||
fn hide_cursor(&mut self) -> io::Result<()>;
|
||||
|
||||
/// Show the cursor on the terminal screen.
|
||||
fn show_cursor(&mut self) -> Result<(), io::Error>;
|
||||
///
|
||||
/// See [`hide_cursor`] for an example.
|
||||
///
|
||||
/// [`hide_cursor`]: Backend::hide_cursor
|
||||
fn show_cursor(&mut self) -> io::Result<()>;
|
||||
|
||||
/// Get the current cursor position on the terminal screen.
|
||||
fn get_cursor(&mut self) -> Result<(u16, u16), io::Error>;
|
||||
///
|
||||
/// The returned tuple contains the x and y coordinates of the cursor. The origin
|
||||
/// (0, 0) is at the top left corner of the screen.
|
||||
///
|
||||
/// See [`set_cursor`] for an example.
|
||||
///
|
||||
/// [`set_cursor`]: Backend::set_cursor
|
||||
fn get_cursor(&mut self) -> io::Result<(u16, u16)>;
|
||||
|
||||
/// Set the cursor position on the terminal screen to the given x and y coordinates.
|
||||
fn set_cursor(&mut self, x: u16, y: u16) -> Result<(), io::Error>;
|
||||
///
|
||||
/// The origin (0, 0) is at the top left corner of the screen.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::backend::{Backend, TestBackend};
|
||||
/// # let mut backend = TestBackend::new(80, 25);
|
||||
/// backend.set_cursor(10, 20)?;
|
||||
/// assert_eq!(backend.get_cursor()?, (10, 20));
|
||||
/// # std::io::Result::Ok(())
|
||||
/// ```
|
||||
///
|
||||
/// [`get_cursor`]: Backend::get_cursor
|
||||
fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()>;
|
||||
|
||||
/// Clears the whole terminal screen
|
||||
fn clear(&mut self) -> Result<(), io::Error>;
|
||||
/// Clears the whole terminal scree
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use ratatui::backend::{Backend, TestBackend};
|
||||
/// # let mut backend = TestBackend::new(80, 25);
|
||||
/// backend.clear()?;
|
||||
/// # std::io::Result::Ok(())
|
||||
/// ```
|
||||
fn clear(&mut self) -> io::Result<()>;
|
||||
|
||||
/// Clears a specific region of the terminal specified by the [`ClearType`] parameter
|
||||
///
|
||||
/// This method is optional and may not be implemented by all backends.
|
||||
fn clear_region(&mut self, clear_type: ClearType) -> Result<(), io::Error> {
|
||||
/// This method is optional and may not be implemented by all backends. The default
|
||||
/// implementation calls [`clear`] if the `clear_type` is [`ClearType::All`] and returns an
|
||||
/// error otherwise.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use ratatui::{prelude::*, backend::{TestBackend, ClearType}};
|
||||
/// # let mut backend = TestBackend::new(80, 25);
|
||||
/// backend.clear_region(ClearType::All)?;
|
||||
/// # std::io::Result::Ok(())
|
||||
/// ```
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This method will return an error if the terminal screen could not be cleared. It will also
|
||||
/// return an error if the `clear_type` is not supported by the backend.
|
||||
///
|
||||
/// [`clear`]: Backend::clear
|
||||
fn clear_region(&mut self, clear_type: ClearType) -> io::Result<()> {
|
||||
match clear_type {
|
||||
ClearType::All => self.clear(),
|
||||
ClearType::AfterCursor
|
||||
|
@ -124,17 +272,28 @@ pub trait Backend {
|
|||
}
|
||||
|
||||
/// Get the size of the terminal screen in columns/rows as a [`Rect`].
|
||||
fn size(&self) -> Result<Rect, io::Error>;
|
||||
///
|
||||
/// The returned [`Rect`] contains the width and height of the terminal screen.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use ratatui::{prelude::*, backend::TestBackend};
|
||||
/// let backend = TestBackend::new(80, 25);
|
||||
/// assert_eq!(backend.size()?, Rect::new(0, 0, 80, 25));
|
||||
/// # std::io::Result::Ok(())
|
||||
/// ```
|
||||
fn size(&self) -> io::Result<Rect>;
|
||||
|
||||
/// Get the size of the terminal screen in columns/rows and pixels as [`WindowSize`].
|
||||
/// Get the size of the terminal screen in columns/rows and pixels as a [`WindowSize`].
|
||||
///
|
||||
/// The reason for this not returning only the pixel size, given the redundancy with the
|
||||
/// `size()` method, is that the underlying backends most likely get both values with one
|
||||
/// syscall, and the user is also most likely to need columns,rows together with pixel size.
|
||||
fn window_size(&mut self) -> Result<WindowSize, io::Error>;
|
||||
/// syscall, and the user is also most likely to need columns and rows along with pixel size.
|
||||
fn window_size(&mut self) -> io::Result<WindowSize>;
|
||||
|
||||
/// Flush any buffered content to the terminal screen.
|
||||
fn flush(&mut self) -> Result<(), io::Error>;
|
||||
fn flush(&mut self) -> io::Result<()>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
//! This module provides the `TermionBackend` implementation for the [`Backend`] trait.
|
||||
//! It uses the Termion crate to interact with the terminal.
|
||||
//! This module provides the [`TermionBackend`] implementation for the [`Backend`] trait. It uses
|
||||
//! the [Termion] crate to interact with the terminal.
|
||||
//!
|
||||
//! [`Backend`]: crate::backend::Backend
|
||||
//! [`TermionBackend`]: crate::backend::TermionBackend
|
||||
|
||||
//! [Termion]: https://docs.rs/termion
|
||||
use std::{
|
||||
fmt,
|
||||
io::{self, Write},
|
||||
|
@ -16,36 +16,71 @@ use crate::{
|
|||
style::{Color, Modifier},
|
||||
};
|
||||
|
||||
/// A backend that uses the Termion library to draw content, manipulate the cursor,
|
||||
/// and clear the terminal screen.
|
||||
/// A [`Backend`] implementation that uses [Termion] to render to the terminal.
|
||||
///
|
||||
/// The `TermionBackend` struct is a wrapper around a writer implementing [`Write`], which is used
|
||||
/// to send commands to the terminal. It provides methods for drawing content, manipulating the
|
||||
/// cursor, and clearing the terminal screen.
|
||||
///
|
||||
/// Most applications should not call the methods on `TermionBackend` directly, but will instead
|
||||
/// use the [`Terminal`] struct, which provides a more ergonomic interface.
|
||||
///
|
||||
/// Usually applications will enable raw mode and switch to alternate screen mode when starting.
|
||||
/// This is done by calling [`IntoRawMode::into_raw_mode()`] and
|
||||
/// [`IntoAlternateScreen::into_alternate_screen()`] on the writer before creating the backend.
|
||||
/// This is not done automatically by the backend because it is possible that the application may
|
||||
/// want to use the terminal for other purposes (like showing help text) before entering alternate
|
||||
/// screen mode. This backend automatically disable raw mode and switches back to the primary
|
||||
/// screen when the writer is dropped.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::backend::{Backend, TermionBackend};
|
||||
/// ```rust,no_run
|
||||
/// use std::io::{stdout, stderr};
|
||||
/// use ratatui::prelude::*;
|
||||
/// use termion::{raw::IntoRawMode, screen::IntoAlternateScreen};
|
||||
///
|
||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let stdout = std::io::stdout();
|
||||
/// let mut backend = TermionBackend::new(stdout);
|
||||
/// backend.clear()?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// let writer = stdout().into_raw_mode()?.into_alternate_screen()?;
|
||||
/// let mut backend = TermionBackend::new(writer);
|
||||
/// // or
|
||||
/// let writer = stderr().into_raw_mode()?.into_alternate_screen()?;
|
||||
/// let backend = TermionBackend::new(stderr());
|
||||
/// let mut terminal = Terminal::new(backend)?;
|
||||
///
|
||||
/// terminal.clear()?;
|
||||
/// terminal.draw(|frame| {
|
||||
/// // -- snip --
|
||||
/// })?;
|
||||
/// # std::io::Result::Ok(())
|
||||
/// ```
|
||||
///
|
||||
/// [`IntoRawMode::into_raw_mode()`]: termion::raw::IntoRawMode
|
||||
/// [`IntoAlternateScreen::into_alternate_screen()`]: termion::screen::IntoAlternateScreen
|
||||
/// [`Terminal`]: crate::terminal::Terminal
|
||||
/// [Termion]: https://docs.rs/termion
|
||||
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct TermionBackend<W>
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
stdout: W,
|
||||
writer: W,
|
||||
}
|
||||
|
||||
impl<W> TermionBackend<W>
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
/// Creates a new Termion backend with the given output.
|
||||
pub fn new(stdout: W) -> TermionBackend<W> {
|
||||
TermionBackend { stdout }
|
||||
/// Creates a new Termion backend with the given writer.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use std::io::stdout;
|
||||
/// # use ratatui::prelude::*;
|
||||
/// let backend = TermionBackend::new(stdout());
|
||||
/// ```
|
||||
pub fn new(writer: W) -> TermionBackend<W> {
|
||||
TermionBackend { writer }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -54,11 +89,11 @@ where
|
|||
W: Write,
|
||||
{
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.stdout.write(buf)
|
||||
self.writer.write(buf)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.stdout.flush()
|
||||
self.writer.flush()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -72,39 +107,39 @@ where
|
|||
|
||||
fn clear_region(&mut self, clear_type: ClearType) -> io::Result<()> {
|
||||
match clear_type {
|
||||
ClearType::All => write!(self.stdout, "{}", termion::clear::All)?,
|
||||
ClearType::AfterCursor => write!(self.stdout, "{}", termion::clear::AfterCursor)?,
|
||||
ClearType::BeforeCursor => write!(self.stdout, "{}", termion::clear::BeforeCursor)?,
|
||||
ClearType::CurrentLine => write!(self.stdout, "{}", termion::clear::CurrentLine)?,
|
||||
ClearType::UntilNewLine => write!(self.stdout, "{}", termion::clear::UntilNewline)?,
|
||||
ClearType::All => write!(self.writer, "{}", termion::clear::All)?,
|
||||
ClearType::AfterCursor => write!(self.writer, "{}", termion::clear::AfterCursor)?,
|
||||
ClearType::BeforeCursor => write!(self.writer, "{}", termion::clear::BeforeCursor)?,
|
||||
ClearType::CurrentLine => write!(self.writer, "{}", termion::clear::CurrentLine)?,
|
||||
ClearType::UntilNewLine => write!(self.writer, "{}", termion::clear::UntilNewline)?,
|
||||
};
|
||||
self.stdout.flush()
|
||||
self.writer.flush()
|
||||
}
|
||||
|
||||
fn append_lines(&mut self, n: u16) -> io::Result<()> {
|
||||
for _ in 0..n {
|
||||
writeln!(self.stdout)?;
|
||||
writeln!(self.writer)?;
|
||||
}
|
||||
self.stdout.flush()
|
||||
self.writer.flush()
|
||||
}
|
||||
|
||||
fn hide_cursor(&mut self) -> io::Result<()> {
|
||||
write!(self.stdout, "{}", termion::cursor::Hide)?;
|
||||
self.stdout.flush()
|
||||
write!(self.writer, "{}", termion::cursor::Hide)?;
|
||||
self.writer.flush()
|
||||
}
|
||||
|
||||
fn show_cursor(&mut self) -> io::Result<()> {
|
||||
write!(self.stdout, "{}", termion::cursor::Show)?;
|
||||
self.stdout.flush()
|
||||
write!(self.writer, "{}", termion::cursor::Show)?;
|
||||
self.writer.flush()
|
||||
}
|
||||
|
||||
fn get_cursor(&mut self) -> io::Result<(u16, u16)> {
|
||||
termion::cursor::DetectCursorPos::cursor_pos(&mut self.stdout).map(|(x, y)| (x - 1, y - 1))
|
||||
termion::cursor::DetectCursorPos::cursor_pos(&mut self.writer).map(|(x, y)| (x - 1, y - 1))
|
||||
}
|
||||
|
||||
fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()> {
|
||||
write!(self.stdout, "{}", termion::cursor::Goto(x + 1, y + 1))?;
|
||||
self.stdout.flush()
|
||||
write!(self.writer, "{}", termion::cursor::Goto(x + 1, y + 1))?;
|
||||
self.writer.flush()
|
||||
}
|
||||
|
||||
fn draw<'a, I>(&mut self, content: I) -> io::Result<()>
|
||||
|
@ -147,7 +182,7 @@ where
|
|||
string.push_str(&cell.symbol);
|
||||
}
|
||||
write!(
|
||||
self.stdout,
|
||||
self.writer,
|
||||
"{string}{}{}{}",
|
||||
Fg(Color::Reset),
|
||||
Bg(Color::Reset),
|
||||
|
@ -168,7 +203,7 @@ where
|
|||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.stdout.flush()
|
||||
self.writer.flush()
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash)]
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
//! This module provides the `TermwizBackend` implementation for the [`Backend`] trait.
|
||||
//! It uses the `termwiz` crate to interact with the terminal.
|
||||
//! This module provides the `TermwizBackend` implementation for the [`Backend`] trait. It uses the
|
||||
//! [Termwiz] crate to interact with the terminal.
|
||||
//!
|
||||
//! [`Backend`]: trait.Backend.html
|
||||
//! [`TermwizBackend`]: crate::backend::TermionBackend
|
||||
//! [Termwiz]: https://crates.io/crates/termwiz
|
||||
|
||||
use std::{error::Error, io};
|
||||
|
||||
|
@ -22,24 +23,67 @@ use crate::{
|
|||
style::{Color, Modifier},
|
||||
};
|
||||
|
||||
/// Termwiz backend implementation for the [`Backend`] trait.
|
||||
/// A [`Backend`] implementation that uses [Termwiz] to render to the terminal.
|
||||
///
|
||||
/// The `TermwizBackend` struct is a wrapper around a [`BufferedTerminal`], which is used to send
|
||||
/// commands to the terminal. It provides methods for drawing content, manipulating the cursor, and
|
||||
/// clearing the terminal screen.
|
||||
///
|
||||
/// Most applications should not call the methods on `TermwizBackend` directly, but will instead
|
||||
/// use the [`Terminal`] struct, which provides a more ergonomic interface.
|
||||
///
|
||||
/// This backend automatically enables raw mode and switches to the alternate screen when it is
|
||||
/// created using the [`TermwizBackend::new`] method (and disables raw mode and returns to the main
|
||||
/// screen when dropped). Use the [`TermwizBackend::with_buffered_terminal`] to create a new
|
||||
/// instance with a custom [`BufferedTerminal`] if this is not desired.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use ratatui::backend::{Backend, TermwizBackend};
|
||||
/// use ratatui::prelude::*;
|
||||
///
|
||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let mut backend = TermwizBackend::new()?;
|
||||
/// backend.clear()?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// let backend = TermwizBackend::new()?;
|
||||
/// let mut terminal = Terminal::new(backend)?;
|
||||
///
|
||||
/// terminal.clear()?;
|
||||
/// terminal.draw(|frame| {
|
||||
/// // -- snip --
|
||||
/// })?;
|
||||
/// # std::result::Result::Ok::<(), Box<dyn std::error::Error>>(())
|
||||
/// ```
|
||||
///
|
||||
/// See the the [examples] directory for more examples. See the [`backend`] module documentation
|
||||
/// for more details on raw mode and alternate screen.
|
||||
///
|
||||
/// [`backend`]: crate::backend
|
||||
/// [`Terminal`]: crate::terminal::Terminal
|
||||
/// [`BufferedTerminal`]: termwiz::terminal::buffered::BufferedTerminal
|
||||
/// [Termwiz]: https://crates.io/crates/termwiz
|
||||
/// [examples]: https://github.com/ratatui-org/ratatui/tree/main/examples#readme
|
||||
pub struct TermwizBackend {
|
||||
buffered_terminal: BufferedTerminal<SystemTerminal>,
|
||||
}
|
||||
|
||||
impl TermwizBackend {
|
||||
/// Creates a new Termwiz backend instance.
|
||||
///
|
||||
/// The backend will automatically enable raw mode and enter the alternate screen.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if unable to do any of the following:
|
||||
/// - query the terminal capabilities.
|
||||
/// - enter raw mode.
|
||||
/// - enter the alternate screen.
|
||||
/// - create the system or buffered terminal.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use ratatui::prelude::*;
|
||||
/// let backend = TermwizBackend::new()?;
|
||||
/// # Ok::<(), Box<dyn std::error::Error>>(())
|
||||
/// ```
|
||||
pub fn new() -> Result<TermwizBackend, Box<dyn Error>> {
|
||||
let mut buffered_terminal =
|
||||
BufferedTerminal::new(SystemTerminal::new(Capabilities::new_from_env()?)?)?;
|
||||
|
|
|
@ -14,19 +14,23 @@ use crate::{
|
|||
layout::{Rect, Size},
|
||||
};
|
||||
|
||||
/// A backend used for the integration tests.
|
||||
/// A [`Backend`] implementation used for integration testing that that renders to an in memory
|
||||
/// buffer.
|
||||
///
|
||||
/// Note: that although many of the integration and unit tests in ratatui are written using this
|
||||
/// backend, it is preferable to write unit tests for widgets directly against the buffer rather
|
||||
/// than using this backend. This backend is intended for integration tests that test the entire
|
||||
/// terminal UI.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::{backend::{Backend, TestBackend}, buffer::Buffer};
|
||||
/// use ratatui::{prelude::*, backend::TestBackend};
|
||||
///
|
||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let mut backend = TestBackend::new(10, 2);
|
||||
/// backend.clear()?;
|
||||
/// backend.assert_buffer(&Buffer::with_lines(vec![" "; 2]));
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # std::io::Result::Ok(())
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
|
|
Loading…
Reference in a new issue