Skip to content

Commit

Permalink
feat(text): expose graphemes on Line
Browse files Browse the repository at this point in the history
- add `Line::styled()`
- add `Line::styled_graphemes()`
- add `StyledGrapheme::new()`
  • Loading branch information
lthoerner authored and joshka committed Jul 15, 2023
1 parent c605407 commit 8dbc44c
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 51 deletions.
25 changes: 24 additions & 1 deletion src/text/grapheme.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,31 @@
use crate::style::Style;
use crate::style::{Style, Styled};

/// A grapheme associated to a style.
/// Note that, although `StyledGrapheme` is the smallest divisible unit of text,
/// it actually is not a member of the text type hierarchy (`Text` -> `Line` -> `Span`).
/// It is a separate type used mostly for rendering purposes. A `Span` consists of components that
/// can be split into `StyledGrapheme`s, but it does not contain a collection of `StyledGrapheme`s.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StyledGrapheme<'a> {
pub symbol: &'a str,
pub style: Style,
}

impl<'a> StyledGrapheme<'a> {
pub fn new(symbol: &'a str, style: Style) -> StyledGrapheme<'a> {
StyledGrapheme { symbol, style }
}
}

impl<'a> Styled for StyledGrapheme<'a> {
type Item = StyledGrapheme<'a>;

fn style(&self) -> Style {
self.style
}

fn set_style(mut self, style: Style) -> Self::Item {
self.style = style;
self
}
}
86 changes: 84 additions & 2 deletions src/text/line.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#![allow(deprecated)]
use super::{Span, Spans, Style};
use std::borrow::Cow;

use super::{Span, Spans, Style, StyledGrapheme};
use crate::layout::Alignment;

#[derive(Debug, Clone, PartialEq, Default, Eq)]
Expand All @@ -9,6 +11,24 @@ pub struct Line<'a> {
}

impl<'a> Line<'a> {
/// Create a line with a style.
///
/// # Examples
///
/// ```rust
/// # use ratatui::text::Line;
/// # use ratatui::style::{Color, Modifier, Style};
/// let style = Style::default().fg(Color::Yellow).add_modifier(Modifier::ITALIC);
/// Line::styled("My text", style);
/// Line::styled(String::from("My text"), style);
/// ```
pub fn styled<T>(content: T, style: Style) -> Line<'a>
where
T: Into<Cow<'a, str>>,
{
Line::from(Span::styled(content, style))
}

/// Returns the width of the underlying string.
///
/// ## Examples
Expand All @@ -26,6 +46,38 @@ impl<'a> Line<'a> {
self.spans.iter().map(Span::width).sum()
}

/// Returns an iterator over the graphemes held by this line.
///
/// `base_style` is the [`Style`] that will be patched with each grapheme [`Style`] to get
/// the resulting [`Style`].
///
/// ## Examples
///
/// ```rust
/// # use ratatui::text::{Line, StyledGrapheme};
/// # use ratatui::style::{Color, Modifier, Style};
/// # use std::iter::Iterator;
/// let line = Line::styled("Text", Style::default().fg(Color::Yellow));
/// let style = Style::default().fg(Color::Green).bg(Color::Black);
/// assert_eq!(
/// line.styled_graphemes(style).collect::<Vec<StyledGrapheme>>(),
/// vec![
/// StyledGrapheme::new("T", Style::default().fg(Color::Yellow).bg(Color::Black)),
/// StyledGrapheme::new("e", Style::default().fg(Color::Yellow).bg(Color::Black)),
/// StyledGrapheme::new("x", Style::default().fg(Color::Yellow).bg(Color::Black)),
/// StyledGrapheme::new("t", Style::default().fg(Color::Yellow).bg(Color::Black)),
/// ]
/// );
/// ```
pub fn styled_graphemes(
&'a self,
base_style: Style,
) -> impl Iterator<Item = StyledGrapheme<'a>> {
self.spans
.iter()
.flat_map(move |span| span.styled_graphemes(base_style))
}

/// Patches the style of each Span in an existing Line, adding modifiers from the given style.
///
/// ## Examples
Expand Down Expand Up @@ -146,7 +198,7 @@ mod tests {
use crate::{
layout::Alignment,
style::{Color, Modifier, Style},
text::{Line, Span, Spans},
text::{Line, Span, Spans, StyledGrapheme},
};

#[test]
Expand Down Expand Up @@ -248,4 +300,34 @@ mod tests {
let line = Line::from("This is default");
assert_eq!(None, line.alignment);
}

#[test]
fn styled_graphemes() {
const RED: Style = Style::new().fg(Color::Red);
const GREEN: Style = Style::new().fg(Color::Green);
const BLUE: Style = Style::new().fg(Color::Blue);
const RED_ON_WHITE: Style = Style::new().fg(Color::Red).bg(Color::White);
const GREEN_ON_WHITE: Style = Style::new().fg(Color::Green).bg(Color::White);
const BLUE_ON_WHITE: Style = Style::new().fg(Color::Blue).bg(Color::White);

let line = Line::from(vec![
Span::styled("He", RED),
Span::styled("ll", GREEN),
Span::styled("o!", BLUE),
]);
let styled_graphemes = line
.styled_graphemes(Style::new().bg(Color::White))
.collect::<Vec<StyledGrapheme>>();
assert_eq!(
styled_graphemes,
vec![
StyledGrapheme::new("H", RED_ON_WHITE),
StyledGrapheme::new("e", RED_ON_WHITE),
StyledGrapheme::new("l", GREEN_ON_WHITE),
StyledGrapheme::new("l", GREEN_ON_WHITE),
StyledGrapheme::new("o", BLUE_ON_WHITE),
StyledGrapheme::new("!", BLUE_ON_WHITE),
],
);
}
}
54 changes: 6 additions & 48 deletions src/text/span.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,58 +70,16 @@ impl<'a> Span<'a> {
/// # use ratatui::text::{Span, StyledGrapheme};
/// # use ratatui::style::{Color, Modifier, Style};
/// # use std::iter::Iterator;
/// let style = Style::default().fg(Color::Yellow);
/// let span = Span::styled("Text", style);
/// let span = Span::styled("Text", Style::default().fg(Color::Yellow));
/// let style = Style::default().fg(Color::Green).bg(Color::Black);
/// let styled_graphemes = span.styled_graphemes(style);
/// assert_eq!(
/// span.styled_graphemes(style).collect::<Vec<StyledGrapheme>>(),
/// vec![
/// StyledGrapheme {
/// symbol: "T",
/// style: Style {
/// fg: Some(Color::Yellow),
/// bg: Some(Color::Black),
/// #[cfg(feature = "crossterm")]
/// underline_color: None,
/// add_modifier: Modifier::empty(),
/// sub_modifier: Modifier::empty(),
/// },
/// },
/// StyledGrapheme {
/// symbol: "e",
/// style: Style {
/// fg: Some(Color::Yellow),
/// bg: Some(Color::Black),
/// #[cfg(feature = "crossterm")]
/// underline_color: None,
/// add_modifier: Modifier::empty(),
/// sub_modifier: Modifier::empty(),
/// },
/// },
/// StyledGrapheme {
/// symbol: "x",
/// style: Style {
/// fg: Some(Color::Yellow),
/// bg: Some(Color::Black),
/// #[cfg(feature = "crossterm")]
/// underline_color: None,
/// add_modifier: Modifier::empty(),
/// sub_modifier: Modifier::empty(),
/// },
/// },
/// StyledGrapheme {
/// symbol: "t",
/// style: Style {
/// fg: Some(Color::Yellow),
/// bg: Some(Color::Black),
/// #[cfg(feature = "crossterm")]
/// underline_color: None,
/// add_modifier: Modifier::empty(),
/// sub_modifier: Modifier::empty(),
/// },
/// },
/// StyledGrapheme::new("T", Style::default().fg(Color::Yellow).bg(Color::Black)),
/// StyledGrapheme::new("e", Style::default().fg(Color::Yellow).bg(Color::Black)),
/// StyledGrapheme::new("x", Style::default().fg(Color::Yellow).bg(Color::Black)),
/// StyledGrapheme::new("t", Style::default().fg(Color::Yellow).bg(Color::Black)),
/// ],
/// styled_graphemes.collect::<Vec<StyledGrapheme>>()
/// );
/// ```
pub fn styled_graphemes(
Expand Down

0 comments on commit 8dbc44c

Please sign in to comment.