tui_markup/generator/ratatui/mod.rs
1//! Generator implementations for ratatui crate.
2
3mod span;
4mod tag;
5#[cfg(test)]
6mod test;
7
8use ratatui_core::{
9 style::Style,
10 text::{Line, Text},
11};
12pub use tag::RatatuiTagConvertor;
13
14use crate::{
15 generator::{
16 Generator,
17 helper::{CustomTagParser, GeneratorInfallible, NoopCustomTagParser, flatten},
18 },
19 parser::ItemG,
20};
21
22/// Generator for `ratatui` crate's [Text] type.
23///
24/// See [docs/ratatui-tags.ebnf] for supported tags.
25///
26/// ## Example
27///
28/// ```
29/// # use ratatui::prelude::*;
30/// use tui_markup::{compile, generator::RatatuiTextGenerator};
31///
32/// assert_eq!(
33/// compile::<RatatuiTextGenerator>("I have a <green green text>"),
34/// Ok(Text::from(vec![Line::from(vec![
35/// Span::raw("I have a "),
36/// Span::styled("green text", Style::default().fg(Color::Green)),
37/// ])])),
38/// );
39///
40/// assert_eq!(
41/// compile::<RatatuiTextGenerator>("I can set <bg:blue background>"),
42/// Ok(Text::from(vec![Line::from(vec![
43/// Span::raw("I can set "),
44/// Span::styled("background", Style::default().bg(Color::Blue)),
45/// ])])),
46/// );
47///
48/// assert_eq!(
49/// compile::<RatatuiTextGenerator>("I can add <b bold>, <d dim>, <i italic> modifiers"),
50/// Ok(Text::from(vec![Line::from(vec![
51/// Span::raw("I can add "),
52/// Span::styled("bold", Style::default().add_modifier(Modifier::BOLD)),
53/// Span::raw(", "),
54/// Span::styled("dim", Style::default().add_modifier(Modifier::DIM)),
55/// Span::raw(", "),
56/// Span::styled("italic", Style::default().add_modifier(Modifier::ITALIC)),
57/// Span::raw(" modifiers"),
58/// ])])),
59/// );
60///
61/// assert_eq!(
62/// compile::<RatatuiTextGenerator>("I can <bg:blue combine <green them <b <i all>>>>"),
63/// Ok(Text::from(vec![Line::from(vec![
64/// Span::raw("I can "),
65/// Span::styled("combine ", Style::default().bg(Color::Blue)),
66/// Span::styled("them ", Style::default().bg(Color::Blue).fg(Color::Green)),
67/// Span::styled(
68/// "all",
69/// Style::default()
70/// .bg(Color::Blue)
71/// .fg(Color::Green)
72/// .add_modifier(Modifier::BOLD | Modifier::ITALIC)
73/// ),
74/// ])])),
75/// );
76///
77/// assert_eq!(
78/// compile::<RatatuiTextGenerator>("I can use <bg:66ccff custom color>"),
79/// Ok(Text::from(vec![Line::from(vec![
80/// Span::raw("I can use "),
81/// Span::styled(
82/// "custom color",
83/// Style::default().bg(Color::Rgb(0x66, 0xcc, 0xff))
84/// ),
85/// ])])),
86/// );
87///
88/// assert_eq!(
89/// compile::<RatatuiTextGenerator>("I can set <bg:blue,green,b,i many style> in one tag"),
90/// Ok(Text::from(vec![Line::from(vec![
91/// Span::raw("I can set "),
92/// Span::styled(
93/// "many style",
94/// Style::default()
95/// .bg(Color::Blue)
96/// .fg(Color::Green)
97/// .add_modifier(Modifier::BOLD | Modifier::ITALIC)
98/// ),
99/// Span::raw(" in one tag"),
100/// ])])),
101/// );
102/// ```
103///
104/// ### With custom tags
105///
106/// ```
107/// # use ratatui::prelude::*;
108/// use tui_markup::{compile_with, generator::RatatuiTextGenerator};
109///
110/// let g = RatatuiTextGenerator::new(|tag: &str| match tag {
111/// "keyboard" => Some(
112/// Style::default()
113/// .bg(Color::White)
114/// .fg(Color::Green)
115/// .add_modifier(Modifier::BOLD),
116/// ),
117/// _ => None,
118/// });
119///
120/// assert_eq!(
121/// compile_with("Press <keyboard W> to move up", g),
122/// Ok(Text::from(vec![Line::from(vec![
123/// Span::raw("Press "),
124/// Span::styled(
125/// "W",
126/// Style::default()
127/// .bg(Color::White)
128/// .fg(Color::Green)
129/// .add_modifier(Modifier::BOLD)
130/// ),
131/// Span::raw(" to move up"),
132/// ])])),
133/// );
134/// ```
135///
136/// ### Show output
137///
138/// Use any widget of the `ratatui` crate that supports it's [Text] type, for example:
139/// `ratatui::widgets::Paragraph`.
140///
141/// Note that the Paragraph widget includes a `ratatui::widgets::Paragraph::wrap` option
142/// that defaults to trimming leading whitespace. You need to turn this option off if you require
143/// full control over the output.
144///
145/// [docs/ratatui-tags.ebnf]: https://github.com/7sDream/tui-markup/blob/master/docs/ratatui-tags.ebnf
146#[derive(Debug)]
147pub struct RatatuiTextGenerator<P = NoopCustomTagParser<Style>> {
148 convertor: RatatuiTagConvertor<P>,
149}
150
151impl<P> Default for RatatuiTextGenerator<P> {
152 fn default() -> Self {
153 Self {
154 convertor: RatatuiTagConvertor::<P>::default(),
155 }
156 }
157}
158
159impl<P> RatatuiTextGenerator<P> {
160 /// Create a new generator, with a custom tag parser.
161 pub fn new(p: P) -> Self {
162 RatatuiTextGenerator {
163 convertor: RatatuiTagConvertor::new(p),
164 }
165 }
166}
167
168impl<'a, P> Generator<'a> for RatatuiTextGenerator<P>
169where
170 P: CustomTagParser<Output = Style>,
171{
172 type Convertor = RatatuiTagConvertor<P>;
173 type Err = GeneratorInfallible;
174 type Output = Text<'a>;
175
176 fn convertor(&mut self) -> &mut Self::Convertor {
177 &mut self.convertor
178 }
179
180 fn generate(&mut self, markup: Vec<Vec<ItemG<'a, Self>>>) -> Result<Self::Output, Self::Err> {
181 Ok(Text::from(
182 markup
183 .into_iter()
184 .map(|line| Line::from(flatten(line)))
185 .collect::<Vec<_>>(),
186 ))
187 }
188}