Expand description
Styled text for terminals: spans that are safe to print, widths counted in cells, and lines wrapped or cut to fit.
Use it to lay out text before anything is written to a terminal. It has no dependency on a terminal, a colour mode, or an output stream, so the same layout can be tested as plain strings.
Layout works on plain data. A Span is text in an anstyle::Style.
A Line is a list of spans, and Text is a list of lines that wraps
to a width. No escape sequence exists in any of them. Whatever writes the
lines to a terminal adds the styling after layout, so nothing here strips
or repairs one.
The main types are Text for content and Overflow for what to do with
a line that is too wide. Renderable, Context, and Measurement
let a widget measure itself and draw at a width. The functions
text_width, cluster_width, and prefix_len count cells, and
overlay combines two styles.
§Quick start
Put text in a Text, pick an Overflow policy, and render it at a
width. The default policy keeps each word whole, so a long address keeps a
line of its own and runs past the width instead of breaking:
use ctl_text::anstyle::AnsiColor;
use ctl_text::{Context, Renderable, Style, Text};
let green = Style::new().fg_color(Some(AnsiColor::Green.into()));
let text = Text::styled("see example.test/releases/tag/pkg@1.2.3/notes", green);
let lines = text.render(&Context::new(30));
assert_eq!(lines[0].to_string(), "see");
assert_eq!(
lines[1].to_string(),
"example.test/releases/tag/pkg@1.2.3/notes"
);§Safe by construction
Text from outside the program, such as a commit subject, can hold an
escape sequence that moves the cursor. It can also hold a directional
override that reorders the line. Span::new and Text remove every
character that unsafe_chars::is_unsafe refuses, so a widget built on
them cannot pass one on.
A line break, such as a carriage return, becomes a space inside a span,
since a span is one line. Inside a text it becomes a newline. Either way
the words around it stay apart. unsafe_chars::strip_unsafe applies the
same rule to a string that goes to a terminal as it is.
§Widths
text_width counts terminal cells per extended grapheme cluster, so a
family emoji is 2 cells and 日本 is 4. The cluster boundaries and the
widths come from two Unicode data sets, whose versions
UNICODE_VERSIONS states.
The width of a string is that sum, not the width one data set gives the whole string. So two letters that a font joins into one glyph still count 1 cell each.
§Writing a widget
A widget implements Renderable. It reports how narrow and how wide it
can be, then draws itself at the width it is given. A container does the
same over its children.
use ctl_text::anstyle::Effects;
use ctl_text::{Context, Line, Measurement, Renderable, Span, Style};
/// A bar of up to 12 cells, filled in proportion to `done / total`.
struct Meter {
done: u16,
total: u16,
}
impl Renderable for Meter {
fn measure(&self, _: &Context) -> Measurement {
Measurement::exact(12)
}
fn render(&self, context: &Context) -> Vec<Line> {
let cells = context.width().min(12);
let share = u32::from(cells) * u32::from(self.done.min(self.total))
/ u32::from(self.total.max(1));
let filled = u16::try_from(share).unwrap_or(cells);
let bar = "█".repeat(filled.into());
vec![Line::from(Span::new(
bar,
Style::new().effects(Effects::BOLD),
))]
}
}
let meter = Meter { done: 3, total: 4 };
assert_eq!(meter.measure(&Context::new(80)), Measurement::exact(12));
assert_eq!(meter.render(&Context::new(80))[0].to_string(), "█████████");§Cargo features
This crate has no optional features.
Re-exports§
Modules§
- unsafe_
chars - Which characters are unsafe to print to a terminal as they are, and the function that removes them.
Structs§
- Context
- What a widget is told when it measures and renders: the width to fill, the tab stop spacing, and the box characters.
- Line
- A list of spans that sit on one terminal line.
- Measurement
- The narrowest and widest a piece of content can usefully be, in cells.
- Span
- A run of text in one style, on one line.
- Text
- Styled text of any length: a paragraph, a table cell, a title.
- Unicode
Version - A Unicode version, such as 17.0.0.
- Unicode
Versions - The Unicode versions of the two data sets that decide a width.
Enums§
- BoxChars
- The characters a widget draws borders and rules with.
- Overflow
- What happens to a line wider than the width it is given.
Constants§
- ELLIPSIS
- The text an
Overflow::Ellipsiscut ends with. One cell wide. - TAB_
WIDTH - The tab stop spacing used when a caller sets none: every 8 cells.
- UNICODE_
VERSIONS - The Unicode versions behind
cluster_widthandtext_width.
Traits§
- Renderable
- Content that knows its own size and can draw itself at a width.
Functions§
- cluster_
width - The width in cells of
cluster, taken as one extended grapheme cluster. - overlay
basewithtoppainted over it.- prefix_
len - The byte offset where the longest prefix of whole clusters that fits in
widthcells ends. - text_
width - The width in cells of
text: the sum ofcluster_widthover its extended grapheme clusters. A sum overu16::MAXsaturates.