rich_ratatui/lib.rs
1//! # rich-ratatui
2//!
3//! [ratatui](https://ratatui.rs) interop for
4//! [rs-rich](https://github.com/buchochelliq-labs/rs-rich-cli), the Rust port
5//! of Python's `rich`, both ways. Not a port of anything upstream; it builds
6//! on core `rich`'s public API and on [`ratatui_core`], the crate ratatui's
7//! own types live in, so the `Buffer`, `Rect` and `Widget` here are the ones
8//! a `ratatui` 0.30 app already uses.
9//!
10//! - **rich in ratatui.** [`RichWidget`] is a ratatui
11//! [`Widget`](ratatui_core::widgets::Widget) that draws any
12//! [`rich::Renderable`] (a `Table`, a `Panel`, `Markdown`, `Syntax`, a
13//! `Tree`, markup) into a ratatui [`Buffer`](ratatui_core::buffer::Buffer),
14//! so a ratatui app can adopt rich's renderables one widget at a time.
15//! - **ratatui in rich-interact.** `RatatuiComponent` (the off-by-default
16//! `interact` feature) is a `rich_interact::Component` whose view is drawn
17//! by ratatui widgets, so an existing widget runs unchanged inside a
18//! rich-interact event loop, and under its headless driver in tests.
19//! - **The conversions underneath**, useful on their own:
20//! [`style`] maps styles and colours both ways, and reads ratatui's colour
21//! names as console styles ([`style_from_color_name`]); [`buffer`] writes rich
22//! lines into a buffer ([`lines_to_buffer`]), reads a buffer back into
23//! rich lines ([`buffer_to_lines`]), and walks a rectangle of a buffer cell
24//! by cell without allocating ([`cells`]).
25//!
26//! ## Use rich in your ratatui app
27//!
28//! ```
29//! use ratatui_core::buffer::Buffer;
30//! use ratatui_core::layout::Rect;
31//! use ratatui_core::widgets::Widget;
32//! use rich::Table;
33//! use rich_ratatui::RichWidget;
34//!
35//! let mut table = Table::new().title("Inventory");
36//! table.add_column("item");
37//! table.add_column("qty");
38//! table.add_row(&["apples", "[green]3[/]"]);
39//! table.add_row(&["pears", "[bold red]0[/]"]);
40//!
41//! // In an app: `frame.render_widget(&RichWidget::new(&table), area)`.
42//! let area = Rect::new(0, 0, 24, 7);
43//! let mut buffer = Buffer::empty(area);
44//! RichWidget::new(&table).render(area, &mut buffer);
45//!
46//! let row: String = (0..area.width).map(|x| buffer[(x, 1)].symbol()).collect();
47//! assert_eq!(row.trim_end(), "┏━━━━━━━━┳━━━━━┓");
48//! ```
49//!
50//! ## What converts, and what is lost
51//!
52//! The mapping is lossless for the common subset: no colour, the terminal
53//! default (rich's `default` ⇄ ratatui's `Color::Reset`), the 16 named
54//! colours, the 256-colour palette, 24-bit RGB, and nine attributes (bold,
55//! dim, italic, underline, blink, rapid blink (`blink2`), reverse, conceal
56//! (hidden), strike), each tri-state: on, explicitly off, or unset. A rich
57//! `Panel` drawn into a buffer and read back shows the same text and the same
58//! SGR codes. It loses:
59//!
60//! - rich's **colour names**: `grey0` comes back as `color(16)`, `#FF0000`
61//! as `#ff0000`. The colour (kind, number, RGB) is the same; only
62//! `Color::name` differs.
63//! - rich's legacy **Windows** colours, which come back as the standard
64//! colour with the same number.
65//! - rich's **`underline2`, `frame`, `encircle`, `overline`**: ratatui has
66//! no modifier for them; they are dropped going to ratatui.
67//! - rich's **hyperlinks** (OSC 8) and **meta**: a ratatui cell holds a
68//! symbol and a style, nothing else, so links are dropped and their text
69//! kept.
70//! - ratatui's **underline colour** (its `underline-color` feature): rich
71//! has no such colour; it is dropped going to rich.
72//! - In a *buffer*, `Color::Reset` and "no colour" are the same thing (a
73//! cell always has a colour; a fresh one has `Reset`), so reading a buffer
74//! takes `Reset` as unset, not as rich's `default`.
75//! - **Widths:** rich and ratatui measure some characters (some emoji)
76//! differently. Writing, each segment is placed at the column rich
77//! measured, so a disagreement cannot shift the rest of the line; reading,
78//! a wide symbol is measured as ratatui measured it when it was set.
79//!
80//! ## Features
81//!
82//! | Feature | Default | What it adds |
83//! |---|---|---|
84//! | `interact` | no | `RatatuiComponent`, through `rs-rich-interact` (and crossterm) |
85
86// A builder method that returns `Self` is `#[must_use]`: dropping its
87// result drops the value (CONTRIBUTING.md, "API conventions").
88#![warn(clippy::return_self_not_must_use)]
89
90pub mod buffer;
91#[cfg(feature = "interact")]
92mod interact;
93pub mod style;
94mod widget;
95
96/// The ratatui core crate this one is built against, re-exported so an app
97/// can name the same `Buffer`, `Rect` and `Widget` without a version
98/// mismatch.
99pub use ratatui_core;
100
101pub use buffer::{buffer_area_to_lines, buffer_to_lines, cells, lines_to_buffer};
102#[cfg(feature = "interact")]
103pub use interact::RatatuiComponent;
104pub use style::{
105 style_from_color_name, to_ratatui_color, to_ratatui_style, to_rich_color, to_rich_style,
106};
107pub use widget::RichWidget;