1//! Primitives for styled text.
3//! A terminal UI is at its root a lot of strings. In order to make it accessible and stylish,
4//! those strings may be associated to a set of styles. `tui` has three ways to represent them:
5//! - A single line string where all graphemes have the same style is represented by a [`Span`].
6//! - A single line string where each grapheme may have its own style is represented by [`Spans`].
7//! - A multiple line string where each grapheme may have its own style is represented by a
10//! These types form a hierarchy: [`Spans`] is a collection of [`Span`] and each line of [`Text`]
13//! Keep it mind that a lot of widgets will use those types to advertise what kind of string is
14//! supported for their properties. Moreover, `tui` provides convenient `From` implementations so
15//! that you can start by using simple `String` or `&str` and then promote them to the previous
16//! primitives when you need additional styling capabilities.
18//! For example, for the [`crate::widgets::Block`] widget, all the following calls are valid to set
19//! its `title` property (which is a [`Spans`] under the hood):
22//! # use helix_tui::widgets::Block;
23//! # use helix_tui::text::{Span, Spans};
24//! # use helix_view::graphics::{Color, Style};
25//! // A simple string with no styling.
26//! // Converted to Spans(vec![
27//! // Span { content: Cow::Borrowed("My title"), style: Style { .. } }
29//! let block = Block::default().title("My title");
31//! // A simple string with a unique style.
32//! // Converted to Spans(vec![
33//! // Span { content: Cow::Borrowed("My title"), style: Style { fg: Some(Color::Yellow), .. }
35//! let block = Block::default().title(
36//! Span::styled("My title", Style::default().fg(Color::Yellow))
39//! // A string with multiple styles.
40//! // Converted to Spans(vec![
41//! // Span { content: Cow::Borrowed("My"), style: Style { fg: Some(Color::Yellow), .. } },
42//! // Span { content: Cow::Borrowed(" title"), .. }
44//! let block = Block::default().title(vec![
45//! Span::styled("My", Style::default().fg(Color::Yellow)),
46//! Span::raw(" title"),
49use helix_core::line_ending::str_is_line_ending;
50use helix_core::unicode::width::UnicodeWidthStr;
51use helix_view::graphics::Style;
53use unicode_segmentation::UnicodeSegmentation;
55/// A grapheme associated to a style.
56#[derive(Debug, Clone, PartialEq)]
57pub struct StyledGrapheme<'a> {
62/// A string where all graphemes have the same style.
63#[derive(Debug, Clone, PartialEq)]
65 pub content: Cow<'a, str>,
70 /// Create a span with no style.
75 /// # use helix_tui::text::Span;
76 /// Span::raw("My text");
77 /// Span::raw(String::from("My text"));
79 pub fn raw<T>(content: T) -> Span<'a>
81 T: Into<Cow<'a, str>>,
84 content: content.into(),
85 style: Style::default(),
89 /// Create a span with a style.
94 /// # use helix_tui::text::Span;
95 /// # use helix_view::graphics::{Color, Modifier, Style};
96 /// let style = Style::default().fg(Color::Yellow).add_modifier(Modifier::ITALIC);
97 /// Span::styled("My text", style);
98 /// Span::styled(String::from("My text"), style);
100 pub fn styled<T>(content: T, style: Style) -> Span<'a>
102 T: Into<Cow<'a, str>>,
105 content: content.into(),
110 /// Returns the width of the content held by this span.
111 pub fn width(&self) -> usize {
115 /// Returns an iterator over the graphemes held by this span.
117 /// `base_style` is the [`Style`] that will be patched with each grapheme [`Style`] to get
118 /// the resulting [`Style`].
123 /// # use helix_tui::text::{Span, StyledGrapheme};
124 /// # use helix_view::graphics::{Color, Modifier, Style};
125 /// # use std::iter::Iterator;
126 /// let style = Style::default().fg(Color::Yellow);
127 /// let span = Span::styled("Text", style);
128 /// let style = Style::default().fg(Color::Green).bg(Color::Black);
129 /// let styled_graphemes = span.styled_graphemes(style);
135 /// fg: Some(Color::Yellow),
136 /// bg: Some(Color::Black),
137 /// add_modifier: Modifier::empty(),
138 /// sub_modifier: Modifier::empty(),
144 /// fg: Some(Color::Yellow),
145 /// bg: Some(Color::Black),
146 /// add_modifier: Modifier::empty(),
147 /// sub_modifier: Modifier::empty(),
153 /// fg: Some(Color::Yellow),
154 /// bg: Some(Color::Black),
155 /// add_modifier: Modifier::empty(),
156 /// sub_modifier: Modifier::empty(),
162 /// fg: Some(Color::Yellow),
163 /// bg: Some(Color::Black),
164 /// add_modifier: Modifier::empty(),
165 /// sub_modifier: Modifier::empty(),
169 /// styled_graphemes.collect::<Vec<StyledGrapheme>>()
172 pub fn styled_graphemes(
175 ) -> impl Iterator<Item = StyledGrapheme<'a>> {
176 UnicodeSegmentation::graphemes(self.content.as_ref(), true)
177 .map(move |g| StyledGrapheme {
179 style: base_style.patch(self.style),
181 .filter(|s| !str_is_line_ending(s.symbol))
185impl<'a> From<String> for Span<'a> {
186 fn from(s: String) -> Span<'a> {
191impl<'a> From<&'a str> for Span<'a> {
192 fn from(s: &'a str) -> Span<'a> {
197/// A string composed of clusters of graphemes, each with their own style.
198#[derive(Debug, Clone, PartialEq)]
199pub struct Spans<'a>(pub Vec<Span<'a>>);
201impl<'a> Default for Spans<'a> {
202 fn default() -> Spans<'a> {
208 /// Returns the width of the underlying string.
213 /// # use helix_tui::text::{Span, Spans};
214 /// # use helix_view::graphics::{Color, Style};
215 /// let spans = Spans::from(vec![
216 /// Span::styled("My", Style::default().fg(Color::Yellow)),
217 /// Span::raw(" text"),
219 /// assert_eq!(7, spans.width());
221 pub fn width(&self) -> usize {
222 self.0.iter().map(Span::width).sum()
226impl<'a> From<String> for Spans<'a> {
227 fn from(s: String) -> Spans<'a> {
228 Spans(vec![Span::from(s)])
232impl<'a> From<&'a str> for Spans<'a> {
233 fn from(s: &'a str) -> Spans<'a> {
234 Spans(vec![Span::from(s)])
238impl<'a> From<Vec<Span<'a>>> for Spans<'a> {
239 fn from(spans: Vec<Span<'a>>) -> Spans<'a> {
244impl<'a> From<Span<'a>> for Spans<'a> {
245 fn from(span: Span<'a>) -> Spans<'a> {
250impl<'a> From<Spans<'a>> for String {
251 fn from(line: Spans<'a>) -> String {
252 line.0.iter().fold(String::new(), |mut acc, s| {
253 acc.push_str(s.content.as_ref());
259/// A string split over multiple lines where each line is composed of several clusters, each with
262/// A [`Text`], like a [`Span`], can be constructed using one of the many `From` implementations
263/// or via the [`Text::raw`] and [`Text::styled`] methods. Helpfully, [`Text`] also implements
264/// [`core::iter::Extend`] which enables the concatenation of several [`Text`] blocks.
267/// # use helix_tui::text::Text;
268/// # use helix_view::graphics::{Color, Modifier, Style};
269/// let style = Style::default().fg(Color::Yellow).add_modifier(Modifier::ITALIC);
271/// // An initial two lines of `Text` built from a `&str`
272/// let mut text = Text::from("The first line\nThe second line");
273/// assert_eq!(2, text.height());
275/// // Adding two more unstyled lines
276/// text.extend(Text::raw("These are two\nmore lines!"));
277/// assert_eq!(4, text.height());
279/// // Adding a final two styled lines
280/// text.extend(Text::styled("Some more lines\nnow with more style!", style));
281/// assert_eq!(6, text.height());
283#[derive(Debug, Clone, PartialEq)]
285 pub lines: Vec<Spans<'a>>,
288impl<'a> Default for Text<'a> {
289 fn default() -> Text<'a> {
290 Text { lines: Vec::new() }
295 /// Create some text (potentially multiple lines) with no style.
300 /// # use helix_tui::text::Text;
301 /// Text::raw("The first line\nThe second line");
302 /// Text::raw(String::from("The first line\nThe second line"));
304 pub fn raw<T>(content: T) -> Text<'a>
306 T: Into<Cow<'a, str>>,
309 lines: match content.into() {
310 Cow::Borrowed(s) => s.lines().map(Spans::from).collect(),
311 Cow::Owned(s) => s.lines().map(|l| Spans::from(l.to_owned())).collect(),
316 /// Create some text (potentially multiple lines) with a style.
321 /// # use helix_tui::text::Text;
322 /// # use helix_view::graphics::{Color, Modifier, Style};
323 /// let style = Style::default().fg(Color::Yellow).add_modifier(Modifier::ITALIC);
324 /// Text::styled("The first line\nThe second line", style);
325 /// Text::styled(String::from("The first line\nThe second line"), style);
327 pub fn styled<T>(content: T, style: Style) -> Text<'a>
329 T: Into<Cow<'a, str>>,
331 let mut text = Text::raw(content);
332 text.patch_style(style);
336 /// Returns the max width of all the lines.
341 /// use helix_tui::text::Text;
342 /// let text = Text::from("The first line\nThe second line");
343 /// assert_eq!(15, text.width());
345 pub fn width(&self) -> usize {
353 /// Returns the height.
358 /// use helix_tui::text::Text;
359 /// let text = Text::from("The first line\nThe second line");
360 /// assert_eq!(2, text.height());
362 pub fn height(&self) -> usize {
366 /// Apply a new style to existing text.
371 /// # use helix_tui::text::Text;
372 /// # use helix_view::graphics::{Color, Modifier, Style};
373 /// let style = Style::default().fg(Color::Yellow).add_modifier(Modifier::ITALIC);
374 /// let mut raw_text = Text::raw("The first line\nThe second line");
375 /// let styled_text = Text::styled(String::from("The first line\nThe second line"), style);
376 /// assert_ne!(raw_text, styled_text);
378 /// raw_text.patch_style(style);
379 /// assert_eq!(raw_text, styled_text);
381 pub fn patch_style(&mut self, style: Style) {
382 for line in &mut self.lines {
383 for span in &mut line.0 {
384 span.style = span.style.patch(style);
390impl<'a> From<String> for Text<'a> {
391 fn from(s: String) -> Text<'a> {
396impl<'a> From<&'a str> for Text<'a> {
397 fn from(s: &'a str) -> Text<'a> {
402impl<'a> From<Span<'a>> for Text<'a> {
403 fn from(span: Span<'a>) -> Text<'a> {
405 lines: vec![Spans::from(span)],
410impl<'a> From<Spans<'a>> for Text<'a> {
411 fn from(spans: Spans<'a>) -> Text<'a> {
412 Text { lines: vec![spans] }
416impl<'a> From<Vec<Spans<'a>>> for Text<'a> {
417 fn from(lines: Vec<Spans<'a>>) -> Text<'a> {
422impl<'a> IntoIterator for Text<'a> {
423 type Item = Spans<'a>;
424 type IntoIter = std::vec::IntoIter<Self::Item>;
426 fn into_iter(self) -> Self::IntoIter {
427 self.lines.into_iter()
431impl<'a> Extend<Spans<'a>> for Text<'a> {
432 fn extend<T: IntoIterator<Item = Spans<'a>>>(&mut self, iter: T) {
433 self.lines.extend(iter);
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list