Table of contents
Ratatui is a crate for building terminal user interfaces in Rust.
In this post, we'll explore the text primitives of ratatui.
Code
:dep ratatui = "0.26.2":dep ratatui-macros = "0.4.0"
fn span_to_html(s: ratatui::text::Span) -> String{ let mut html = String::new(); html.push_str("<span style=\"");
// Set foreground color if let Some(color) = &s.style.fg { html.push_str(&format!("color: {};", color)); }
// Set background color if let Some(color) = &s.style.bg { html.push_str(&format!("background-color: {};", color)); }
// Add modifiers match s.style.add_modifier { ratatui::style::Modifier::BOLD => html.push_str("font-weight: bold;"), ratatui::style::Modifier::ITALIC => html.push_str("font-style: italic;"), ratatui::style::Modifier::UNDERLINED => html.push_str("text-decoration: underline;"), _ => {} } html.push_str("\">"); html.push_str(&s.content); html.push_str("</span>"); html}
fn buffer_to_html(buf: &ratatui::buffer::Buffer) -> String { fn escape_special_html_characters(text: &str) -> String { text.replace("&", "&") .replace("<", "<") .replace(">", ">") .replace("\"", """) .replace("'", "'") }
let mut html = String::from("<pre><code>");
let w = buf.area.width; let h = buf.area.height;
for y in 0..h { for x in 0..w { let s = buf.get(x, y).symbol();
let escaped = escape_special_html_characters(s);
let style = buf.get(x, y).style();
let span = ratatui::text::Span::styled(s, style);
html.push_str(&span_to_html(span)); } html.push('\n'); }
html.push_str("</code></pre>");
html}
fn show_html<D>(content: D) where D: std::fmt::Display { println!(r#"EVCXR_BEGIN_CONTENT text/html<div style="display: flex; justify-content:start; gap: 1em; margin: 1em">{}</div>EVCXR_END_CONTENT"#, content);}Text primitives
In Ratatui, there are 3 fundamental text primitives that you should be aware of.
Span
The first is a Span.
use ratatui::text::Span;
let span = Span::raw("hello world");spanSpan { content: "hello world", style: Style { fg: None, bg: None, underline_color: None, add_modifier: NONE, sub_modifier: NONE } }A Span contains two fields.
span.content"hello world"span.styleStyle { fg: None, bg: None, underline_color: None, add_modifier: NONE, sub_modifier: NONE }A Style object contains foreground color, background color, and
modifiers for whether the style being applied is bold, italics,
etc
There are a number of constructors for Span that you may use, but
ratatui exposes a Stylize trait that makes it easy to style content
which I find very useful.
use ratatui::style::Stylize; // required trait to use style methods
"hello world".bold()Span { content: "hello world", style: Style { fg: None, bg: None, underline_color: None, add_modifier: BOLD, sub_modifier: NONE } }You can even chain these trait methods to add more styles:
"hello world".bold().yellow().on_black()Span { content: "hello world", style: Style { fg: Some(Yellow), bg: Some(Black), underline_color: None, add_modifier: BOLD, sub_modifier: NONE } }show_html(span_to_html("hello world".bold()))show_html(span_to_html("hello world".yellow().bold().on_black()))With ratatui-macros, you can even use a format! style macro to
create a Span
use ratatui_macros::span;
let world = "world";span!("hello {}", world)Span { content: "hello world", style: Style { fg: None, bg: None, underline_color: None, add_modifier: NONE, sub_modifier: NONE } }Line
The second primitive to be aware of is a Line.
A line consists of one or more spans.
use ratatui::text::Line;
let line = Line::raw("hello world");lineLine { spans: [Span { content: "hello world", style: Style { fg: None, bg: None, underline_color: None, add_modifier: NONE, sub_modifier: NONE } }], style: Style { fg: None, bg: None, underline_color: None, add_modifier: NONE, sub_modifier: NONE }, alignment: None }line.spans[Span { content: "hello world", style: Style { fg: None, bg: None, underline_color: None, add_modifier: NONE, sub_modifier: NONE } }]line.spans.len()1A unique feature of lines is that new lines are removed but the content is split into multiple spans.
let line = Line::raw("hello world\ngoodbye world");line.spans[0]Span { content: "hello world", style: Style { fg: None, bg: None, underline_color: None, add_modifier: NONE, sub_modifier: NONE } }line.spans[1]Span { content: "goodbye world", style: Style { fg: None, bg: None, underline_color: None, add_modifier: NONE, sub_modifier: NONE } }A line can also be styled with methods from the Stylize trait:
Line::raw("hello world").bold()Line { spans: [Span { content: "hello world", style: Style { fg: None, bg: None, underline_color: None, add_modifier: NONE, sub_modifier: NONE } }], style: Style { fg: None, bg: None, underline_color: None, add_modifier: BOLD, sub_modifier: NONE }, alignment: None }In this case, the individual span's styles are left untouched but the
Line's style is updated.
Another unique feature about Line is that they can be aligned.
let centered_line = line.centered();centered_lineLine { spans: [Span { content: "hello world", style: Style { fg: None, bg: None, underline_color: None, add_modifier: NONE, sub_modifier: NONE } }, Span { content: "goodbye world", style: Style { fg: None, bg: None, underline_color: None, add_modifier: NONE, sub_modifier: NONE } }], style: Style { fg: None, bg: None, underline_color: None, add_modifier: NONE, sub_modifier: NONE }, alignment: Some(Center) }With ratatui-macros, you can create a Line using the line! macro
using a vec!-like syntax.
use ratatui_macros::line;
line!["hello", " ", "world"].yellow().bold().centered();Every element in the line! macro is converted to a Span.
Text
Finally there is Text, which is a collection of Lines.
use ratatui::text::Text;
Text::from(vec![Line::raw("hello world"), Line::raw("goodbye world")]);With ratatui-macros, you can create a Text using text! macro using a
vec!-like syntax.
use ratatui_macros::text;
text!["hello world", "goodbye world"];Here, every element in the text! macro is converted to a Line.
Like Line, Text can also be aligned. In this case, the alignment
occurs on every Line inside the Text.
let t = text!["hello world", "goodbye world"].right_aligned();t.alignmentSome(Right)use ratatui::widgets::Widget;
let (x, y, width, height) = (0, 0, 50, 5);let area = ratatui::layout::Rect::new(x, y, width, height);let mut buf = ratatui::buffer::Buffer::empty(area);
text![ "left aligned bold text".bold(), "center aligned italic text".italic().into_centered_line(), "right aligned with yellow on black".yellow().on_black().into_right_aligned_line(),].render(area, &mut buf);
show_html(buffer_to_html(&buf))left aligned bold text
center aligned italic text
right aligned with yellow on black
Conclusion
In the next post, we'll examine the a few commonly used widgets.