ratatui_macros/
span.rs

1/// A macro for creating a [`Span`] using formatting syntax.
2///
3/// `span!` is similar to the [`format!`] macro, but it returns a [`Span`] instead of a `String`. In
4/// addition, it also accepts an expression for the first argument, which will be converted to a
5/// string using the [`format!`] macro.
6///
7/// If semicolon follows the first argument, then the first argument is a [`Style`] and a styled
8/// [`Span`] will be created. Otherwise, the [`Span`] will be created as a raw span (i.e. with style
9/// set to `Style::default()`).
10///
11/// # Examples
12///
13/// ```rust
14/// # use ratatui_core::style::{Color, Modifier, Style, Stylize};
15/// use ratatui_macros::span;
16///
17/// let content = "content";
18///
19/// // expression
20/// let span = span!(content);
21///
22/// // format string
23/// let span = span!("test content");
24/// let span = span!("test {}", "content");
25/// let span = span!("{} {}", "test", "content");
26/// let span = span!("test {content}");
27/// let span = span!("test {content}", content = "content");
28///
29/// // with format specifiers
30/// let span = span!("test {:4}", 123);
31/// let span = span!("test {:04}", 123);
32///
33/// let style = Style::new().green();
34///
35/// // styled expression
36/// let span = span!(style; content);
37///
38/// // styled format string
39/// let span = span!(style; "test content");
40/// let span = span!(style; "test {}", "content");
41/// let span = span!(style; "{} {}", "test", "content");
42/// let span = span!(style; "test {content}");
43/// let span = span!(style; "test {content}", content = "content");
44///
45/// // accepts any type that is convertible to Style
46/// let span = span!(Style::new().green(); "test {content}");
47/// let span = span!(Color::Green; "test {content}");
48/// let span = span!(Modifier::BOLD; "test {content}");
49///
50/// // with format specifiers
51/// let span = span!(style; "test {:4}", 123);
52/// let span = span!(style; "test {:04}", 123);
53/// ```
54///
55/// # Note
56///
57/// The first parameter must be a formatting specifier followed by a comma OR anything that can be
58/// converted into a [`Style`] followed by a semicolon.
59///
60/// For example, the following will fail to compile:
61///
62/// ```compile_fail
63/// # use ratatui::prelude::*;
64/// # use ratatui_macros::span;
65/// let span = span!(Modifier::BOLD, "hello world");
66/// ```
67///
68/// But this will work:
69///
70/// ```rust
71/// # use ratatui_core::style::{Modifier};
72/// # use ratatui_macros::span;
73/// let span = span!(Modifier::BOLD; "hello world");
74/// ```
75///
76/// The following will fail to compile:
77///
78/// ```compile_fail
79/// # use ratatui::prelude::*;
80/// # use ratatui_macros::span;
81/// let span = span!("hello", "world");
82/// ```
83///
84/// But this will work:
85///
86/// ```rust
87/// # use ratatui_macros::span;
88/// let span = span!("hello {}", "world");
89/// ```
90///
91/// [`Color`]: ratatui_core::style::Color
92/// [`Span`]: ratatui_core::text::Span
93/// [`Style`]: ratatui_core::style::Style
94/// [`format!`]: alloc::format!
95#[macro_export]
96macro_rules! span {
97    ($string:literal) => {
98        $crate::ratatui_core::text::Span::raw($crate::format!($string))
99    };
100    ($string:literal, $($arg:tt)*) => {
101        $crate::ratatui_core::text::Span::raw($crate::format!($string, $($arg)*))
102    };
103    ($expr:expr) => {
104        $crate::ratatui_core::text::Span::raw($crate::format!("{}", $expr))
105    };
106    ($style:expr, $($arg:tt)*) => {
107        compile_error!("first parameter must be a formatting specifier followed by a comma OR a `Style` followed by a semicolon")
108    };
109    ($style:expr; $string:literal) => {
110        $crate::ratatui_core::text::Span::styled($crate::format!($string), $style)
111    };
112    ($style:expr; $string:literal, $($arg:tt)*) => {
113        $crate::ratatui_core::text::Span::styled($crate::format!($string, $($arg)*), $style)
114    };
115    ($style:expr; $expr:expr) => {
116        $crate::ratatui_core::text::Span::styled($crate::format!("{}", $expr), $style)
117    };
118}
119
120#[cfg(test)]
121mod tests {
122    use ratatui_core::{
123        style::{Color, Modifier, Style},
124        text::Span,
125    };
126
127    #[test]
128    fn raw() {
129        let test = "test";
130        let content = "content";
131        let number = 123;
132
133        // literal
134        let span = span!("test content");
135        assert_eq!(span, Span::raw("test content"));
136
137        // string
138        let span = span!("test {}", "content");
139        assert_eq!(span, Span::raw("test content"));
140
141        // string variable
142        let span = span!("test {}", content);
143        assert_eq!(span, Span::raw("test content"));
144
145        // string variable in the format string
146        let span = span!("test {content}");
147        assert_eq!(span, Span::raw("test content"));
148
149        // named variable
150        let span = span!("test {content}", content = "content");
151        assert_eq!(span, Span::raw("test content"));
152
153        // named variable pointing at a local variable
154        let span = span!("test {content}", content = content);
155        assert_eq!(span, Span::raw("test content"));
156
157        // two strings
158        let span = span!("{} {}", "test", "content");
159        assert_eq!(span, Span::raw("test content"));
160
161        // two string variables
162        let span = span!("{test} {content}");
163        assert_eq!(span, Span::raw("test content"));
164
165        // a number
166        let span = span!("test {number}");
167        assert_eq!(span, Span::raw("test 123"));
168
169        // a number with a format specifier
170        let span = span!("test {number:04}");
171        assert_eq!(span, Span::raw("test 0123"));
172
173        // directly pass a number expression
174        let span = span!(number);
175        assert_eq!(span, Span::raw("123"));
176
177        // directly pass a string expression
178        let span = span!(test);
179        assert_eq!(span, Span::raw("test"));
180    }
181
182    #[test]
183    fn styled() {
184        const STYLE: Style = Style::new().fg(Color::Green);
185
186        let test = "test";
187        let content = "content";
188        let number = 123;
189
190        // literal
191        let span = span!(STYLE; "test content");
192        assert_eq!(span, Span::styled("test content", STYLE));
193
194        // string
195        let span = span!(STYLE; "test {}", "content");
196        assert_eq!(span, Span::styled("test content", STYLE));
197
198        // string variable
199        let span = span!(STYLE; "test {}", content);
200        assert_eq!(span, Span::styled("test content", STYLE));
201
202        // string variable in the format string
203        let span = span!(STYLE; "test {content}");
204        assert_eq!(span, Span::styled("test content", STYLE));
205
206        // named variable
207        let span = span!(STYLE; "test {content}", content = "content");
208        assert_eq!(span, Span::styled("test content", STYLE));
209
210        // named variable pointing at a local variable
211        let span = span!(STYLE; "test {content}", content = content);
212        assert_eq!(span, Span::styled("test content", STYLE));
213
214        // two strings
215        let span = span!(STYLE; "{} {}", "test", "content");
216        assert_eq!(span, Span::styled("test content", STYLE));
217
218        // two string variables
219        let span = span!(STYLE; "{test} {content}");
220        assert_eq!(span, Span::styled("test content", STYLE));
221
222        // a number
223        let span = span!(STYLE; "test {number}");
224        assert_eq!(span, Span::styled("test 123", STYLE));
225
226        // a number with a format specifier
227        let span = span!(STYLE; "test {number:04}");
228        assert_eq!(span, Span::styled("test 0123", STYLE));
229
230        // accepts any type that is convertible to Style
231        let span = span!(Color::Green; "test {content}");
232        assert_eq!(span, Span::styled("test content", STYLE));
233
234        let span = span!(Modifier::BOLD; "test {content}");
235        assert_eq!(span, Span::styled("test content", Style::new().bold()));
236
237        // directly pass a number expression
238        let span = span!(STYLE; number);
239        assert_eq!(span, Span::styled("123", STYLE));
240
241        // directly pass a string expression
242        let span = span!(STYLE; test);
243        assert_eq!(span, Span::styled("test", STYLE));
244    }
245}