Skip to main content

tui_markup/generator/ansi/
span.rs

1use std::fmt::Display;
2
3use anstyle::{Color, Style};
4
5use crate::generator::{
6    Tag, TagConvertor,
7    helper::{FlattenableSpan, FlattenableStyle},
8};
9
10/// A single styled text segment in the ANSI output.
11///
12/// Stores a style and the text it applies to. The text borrows from the original markup input
13/// (zero-copy).
14///
15/// Display writes `{style}{text}{style:#}` — the ANSI escape sequence, then the text, then the
16/// reset.
17#[derive(Debug, Clone)]
18pub struct StyledSpan<'a> {
19    style: Style,
20    text: &'a str,
21}
22
23impl<'a> StyledSpan<'a> {
24    /// Create a new styled span.
25    pub fn new(style: Style, text: &'a str) -> Self {
26        Self { style, text }
27    }
28
29    /// Get the style applied to this span.
30    pub fn style(&self) -> &Style {
31        &self.style
32    }
33
34    /// Get the text content of this span.
35    pub fn text(&self) -> &'a str {
36        self.text
37    }
38}
39
40impl Display for StyledSpan<'_> {
41    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42        write!(f, "{}{}{:#}", self.style, self.text, self.style)
43    }
44}
45
46/// A collection of [`StyledSpan`]s representing styled text ready for ANSI terminal output.
47///
48/// This is the output type of the
49/// [`ANSIStringsGenerator`](crate::generator::ansi::ANSIStringsGenerator). It implements `Display`,
50/// writing ANSI escape sequences for each styled span.
51#[derive(Debug, Clone)]
52pub struct StyledText<'a> {
53    spans: Vec<StyledSpan<'a>>,
54}
55
56impl<'a> StyledText<'a> {
57    /// Create a new styled text from spans.
58    pub fn new(spans: Vec<StyledSpan<'a>>) -> Self {
59        Self { spans }
60    }
61
62    /// Get a reference to the underlying spans.
63    pub fn spans(&self) -> &[StyledSpan<'a>] {
64        &self.spans
65    }
66}
67
68impl Display for StyledText<'_> {
69    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70        for span in &self.spans {
71            Display::fmt(span, f)?;
72        }
73        Ok(())
74    }
75}
76
77impl<'a> From<Vec<StyledSpan<'a>>> for StyledText<'a> {
78    fn from(spans: Vec<StyledSpan<'a>>) -> Self {
79        Self::new(spans)
80    }
81}
82
83// --- From<Tag> impl for flatten ---
84
85impl<'a, C> From<Tag<'a, C>> for Style
86where
87    C: TagConvertor<'a, Color = Color, Modifier = Style, Custom = Style>,
88{
89    fn from(t: Tag<'a, C>) -> Self {
90        match t {
91            Tag::Fg(c) => Self::new().fg_color(Some(c)),
92            Tag::Bg(c) => Self::new().bg_color(Some(c)),
93            Tag::Modifier(s) | Tag::Custom(s) => s,
94        }
95    }
96}
97
98// --- FlattenableStyle impl ---
99
100impl FlattenableStyle for Style {
101    /// `other` fg/bg override `self`; effects are additive (OR'd).
102    fn patch(self, other: Self) -> Self {
103        Self::new()
104            .fg_color(other.get_fg_color().or(self.get_fg_color()))
105            .bg_color(other.get_bg_color().or(self.get_bg_color()))
106            .effects(self.get_effects() | other.get_effects())
107    }
108}
109
110// --- FlattenableSpan impl ---
111
112impl<'a> FlattenableSpan<'a, Style> for StyledSpan<'a> {
113    fn with_style(s: &'a str, style: Option<Style>) -> Self {
114        Self::new(style.unwrap_or_default(), s)
115    }
116}