docs(backend): improve backend module docs (#489)

This commit is contained in:
Josh McKinney 2023-09-11 17:36:44 -07:00 committed by GitHub
parent 1e20475061
commit 1947c58c60
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 413 additions and 130 deletions

View file

@ -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()
}
}

View file

@ -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)]

View file

@ -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)]

View file

@ -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()?)?)?;

View file

@ -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))]