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}