@akiojin/gwt 9.0.4 → 9.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/README.ja.md +106 -146
  2. package/README.md +103 -143
  3. package/bin/gwt.cjs +1 -1
  4. package/package.json +5 -5
  5. package/rustfmt.toml +0 -2
  6. package/scripts/check-release-flow.sh +2 -8
  7. package/scripts/postinstall.js +17 -7
  8. package/scripts/run-local-backend-tests-on-commit.sh +6 -12
  9. package/scripts/test-all.sh +1 -5
  10. package/scripts/check-e2e-coverage-threshold.mjs +0 -238
  11. package/scripts/run-local-e2e-coverage-on-commit.sh +0 -69
  12. package/scripts/run-local-e2e-on-commit.sh +0 -60
  13. package/scripts/verify-ci-node-toolchain.sh +0 -76
  14. package/scripts/voice-eval.sh +0 -48
  15. package/vendor/ratatui-core/src/backend/test.rs +0 -1077
  16. package/vendor/ratatui-core/src/backend.rs +0 -405
  17. package/vendor/ratatui-core/src/buffer/assert.rs +0 -71
  18. package/vendor/ratatui-core/src/buffer/buffer.rs +0 -1388
  19. package/vendor/ratatui-core/src/buffer/cell.rs +0 -377
  20. package/vendor/ratatui-core/src/buffer.rs +0 -9
  21. package/vendor/ratatui-core/src/layout/alignment.rs +0 -89
  22. package/vendor/ratatui-core/src/layout/constraint.rs +0 -526
  23. package/vendor/ratatui-core/src/layout/direction.rs +0 -63
  24. package/vendor/ratatui-core/src/layout/flex.rs +0 -212
  25. package/vendor/ratatui-core/src/layout/layout.rs +0 -2838
  26. package/vendor/ratatui-core/src/layout/margin.rs +0 -79
  27. package/vendor/ratatui-core/src/layout/offset.rs +0 -66
  28. package/vendor/ratatui-core/src/layout/position.rs +0 -253
  29. package/vendor/ratatui-core/src/layout/rect/iter.rs +0 -356
  30. package/vendor/ratatui-core/src/layout/rect/ops.rs +0 -136
  31. package/vendor/ratatui-core/src/layout/rect.rs +0 -1114
  32. package/vendor/ratatui-core/src/layout/size.rs +0 -147
  33. package/vendor/ratatui-core/src/layout.rs +0 -333
  34. package/vendor/ratatui-core/src/lib.rs +0 -82
  35. package/vendor/ratatui-core/src/style/anstyle.rs +0 -348
  36. package/vendor/ratatui-core/src/style/color.rs +0 -788
  37. package/vendor/ratatui-core/src/style/palette/material.rs +0 -608
  38. package/vendor/ratatui-core/src/style/palette/tailwind.rs +0 -653
  39. package/vendor/ratatui-core/src/style/palette.rs +0 -6
  40. package/vendor/ratatui-core/src/style/palette_conversion.rs +0 -82
  41. package/vendor/ratatui-core/src/style/stylize.rs +0 -668
  42. package/vendor/ratatui-core/src/style.rs +0 -1069
  43. package/vendor/ratatui-core/src/symbols/bar.rs +0 -51
  44. package/vendor/ratatui-core/src/symbols/block.rs +0 -51
  45. package/vendor/ratatui-core/src/symbols/border.rs +0 -709
  46. package/vendor/ratatui-core/src/symbols/braille.rs +0 -21
  47. package/vendor/ratatui-core/src/symbols/half_block.rs +0 -3
  48. package/vendor/ratatui-core/src/symbols/line.rs +0 -259
  49. package/vendor/ratatui-core/src/symbols/marker.rs +0 -82
  50. package/vendor/ratatui-core/src/symbols/merge.rs +0 -748
  51. package/vendor/ratatui-core/src/symbols/pixel.rs +0 -30
  52. package/vendor/ratatui-core/src/symbols/scrollbar.rs +0 -46
  53. package/vendor/ratatui-core/src/symbols/shade.rs +0 -5
  54. package/vendor/ratatui-core/src/symbols.rs +0 -15
  55. package/vendor/ratatui-core/src/terminal/frame.rs +0 -192
  56. package/vendor/ratatui-core/src/terminal/terminal.rs +0 -926
  57. package/vendor/ratatui-core/src/terminal/viewport.rs +0 -58
  58. package/vendor/ratatui-core/src/terminal.rs +0 -40
  59. package/vendor/ratatui-core/src/text/grapheme.rs +0 -84
  60. package/vendor/ratatui-core/src/text/line.rs +0 -1678
  61. package/vendor/ratatui-core/src/text/masked.rs +0 -149
  62. package/vendor/ratatui-core/src/text/span.rs +0 -904
  63. package/vendor/ratatui-core/src/text/text.rs +0 -1434
  64. package/vendor/ratatui-core/src/text.rs +0 -64
  65. package/vendor/ratatui-core/src/widgets/stateful_widget.rs +0 -193
  66. package/vendor/ratatui-core/src/widgets/widget.rs +0 -174
  67. package/vendor/ratatui-core/src/widgets.rs +0 -9
@@ -1,1678 +0,0 @@
1
- #![deny(missing_docs)]
2
- #![warn(clippy::pedantic, clippy::nursery, clippy::arithmetic_side_effects)]
3
- use alloc::borrow::Cow;
4
- use alloc::string::{String, ToString};
5
- use alloc::vec;
6
- use alloc::vec::Vec;
7
- use core::fmt;
8
-
9
- use unicode_truncate::UnicodeTruncateStr;
10
- use unicode_width::UnicodeWidthStr;
11
-
12
- use crate::buffer::Buffer;
13
- use crate::layout::{Alignment, Rect};
14
- use crate::style::{Style, Styled};
15
- use crate::text::{Span, StyledGrapheme, Text};
16
- use crate::widgets::Widget;
17
-
18
- /// A line of text, consisting of one or more [`Span`]s.
19
- ///
20
- /// [`Line`]s are used wherever text is displayed in the terminal and represent a single line of
21
- /// text. When a [`Line`] is rendered, it is rendered as a single line of text, with each [`Span`]
22
- /// being rendered in order (left to right).
23
- ///
24
- /// Any newlines in the content are removed when creating a [`Line`] using the constructor or
25
- /// conversion methods.
26
- ///
27
- /// # Constructor Methods
28
- ///
29
- /// - [`Line::default`] creates a line with empty content and the default style.
30
- /// - [`Line::raw`] creates a line with the given content and the default style.
31
- /// - [`Line::styled`] creates a line with the given content and style.
32
- ///
33
- /// # Conversion Methods
34
- ///
35
- /// - [`Line::from`] creates a `Line` from a [`String`].
36
- /// - [`Line::from`] creates a `Line` from a [`&str`].
37
- /// - [`Line::from`] creates a `Line` from a [`Vec`] of [`Span`]s.
38
- /// - [`Line::from`] creates a `Line` from single [`Span`].
39
- /// - [`String::from`] converts a line into a [`String`].
40
- /// - [`Line::from_iter`] creates a line from an iterator of items that are convertible to [`Span`].
41
- ///
42
- /// # Setter Methods
43
- ///
44
- /// These methods are fluent setters. They return a `Line` with the property set.
45
- ///
46
- /// - [`Line::spans`] sets the content of the line.
47
- /// - [`Line::style`] sets the style of the line.
48
- /// - [`Line::alignment`] sets the alignment of the line.
49
- /// - [`Line::left_aligned`] sets the alignment of the line to [`Alignment::Left`].
50
- /// - [`Line::centered`] sets the alignment of the line to [`Alignment::Center`].
51
- /// - [`Line::right_aligned`] sets the alignment of the line to [`Alignment::Right`].
52
- ///
53
- /// # Iteration Methods
54
- ///
55
- /// - [`Line::iter`] returns an iterator over the spans of this line.
56
- /// - [`Line::iter_mut`] returns a mutable iterator over the spans of this line.
57
- /// - [`Line::into_iter`] returns an iterator over the spans of this line.
58
- ///
59
- /// # Other Methods
60
- ///
61
- /// - [`Line::patch_style`] patches the style of the line, adding modifiers from the given style.
62
- /// - [`Line::reset_style`] resets the style of the line.
63
- /// - [`Line::width`] returns the unicode width of the content held by this line.
64
- /// - [`Line::styled_graphemes`] returns an iterator over the graphemes held by this line.
65
- /// - [`Line::push_span`] adds a span to the line.
66
- ///
67
- /// # Compatibility Notes
68
- ///
69
- /// Before v0.26.0, [`Line`] did not have a `style` field and instead relied on only the styles that
70
- /// were set on each [`Span`] contained in the `spans` field. The [`Line::patch_style`] method was
71
- /// the only way to set the overall style for individual lines. For this reason, this field may not
72
- /// be supported yet by all widgets (outside of the `ratatui` crate itself).
73
- ///
74
- /// # Examples
75
- ///
76
- /// ## Creating Lines
77
- /// [`Line`]s can be created from [`Span`]s, [`String`]s, and [`&str`]s. They can be styled with a
78
- /// [`Style`].
79
- ///
80
- /// ```rust
81
- /// use ratatui_core::style::{Color, Modifier, Style, Stylize};
82
- /// use ratatui_core::text::{Line, Span};
83
- ///
84
- /// let style = Style::new().yellow();
85
- /// let line = Line::raw("Hello, world!").style(style);
86
- /// let line = Line::styled("Hello, world!", style);
87
- /// let line = Line::styled("Hello, world!", (Color::Yellow, Modifier::BOLD));
88
- ///
89
- /// let line = Line::from("Hello, world!");
90
- /// let line = Line::from(String::from("Hello, world!"));
91
- /// let line = Line::from(vec![
92
- /// Span::styled("Hello", Style::new().blue()),
93
- /// Span::raw(" world!"),
94
- /// ]);
95
- /// ```
96
- ///
97
- /// ## Styling Lines
98
- ///
99
- /// The line's [`Style`] is used by the rendering widget to determine how to style the line. Each
100
- /// [`Span`] in the line will be styled with the [`Style`] of the line, and then with its own
101
- /// [`Style`]. If the line is longer than the available space, the style is applied to the entire
102
- /// line, and the line is truncated. `Line` also implements [`Styled`] which means you can use the
103
- /// methods of the [`Stylize`] trait.
104
- ///
105
- /// ```rust
106
- /// use ratatui_core::style::{Color, Modifier, Style, Stylize};
107
- /// use ratatui_core::text::Line;
108
- ///
109
- /// let line = Line::from("Hello world!").style(Style::new().yellow().italic());
110
- /// let line = Line::from("Hello world!").style(Color::Yellow);
111
- /// let line = Line::from("Hello world!").style((Color::Yellow, Color::Black));
112
- /// let line = Line::from("Hello world!").style((Color::Yellow, Modifier::ITALIC));
113
- /// let line = Line::from("Hello world!").yellow().italic();
114
- /// ```
115
- ///
116
- /// ## Aligning Lines
117
- ///
118
- /// The line's [`Alignment`] is used by the rendering widget to determine how to align the line
119
- /// within the available space. If the line is longer than the available space, the alignment is
120
- /// ignored and the line is truncated.
121
- ///
122
- /// ```rust
123
- /// use ratatui_core::layout::Alignment;
124
- /// use ratatui_core::text::Line;
125
- ///
126
- /// let line = Line::from("Hello world!").alignment(Alignment::Right);
127
- /// let line = Line::from("Hello world!").centered();
128
- /// let line = Line::from("Hello world!").left_aligned();
129
- /// let line = Line::from("Hello world!").right_aligned();
130
- /// ```
131
- ///
132
- /// ## Rendering Lines
133
- ///
134
- /// `Line` implements the [`Widget`] trait, which means it can be rendered to a [`Buffer`].
135
- ///
136
- /// ```rust
137
- /// use ratatui_core::buffer::Buffer;
138
- /// use ratatui_core::layout::Rect;
139
- /// use ratatui_core::style::{Style, Stylize};
140
- /// use ratatui_core::text::Line;
141
- /// use ratatui_core::widgets::Widget;
142
- ///
143
- /// # fn render(area: Rect, buf: &mut Buffer) {
144
- /// // in another widget's render method
145
- /// let line = Line::from("Hello world!").style(Style::new().yellow().italic());
146
- /// line.render(area, buf);
147
- /// # }
148
- /// ```
149
- ///
150
- /// Or you can use the `render_widget` method on the `Frame` in a `Terminal::draw` closure.
151
- ///
152
- /// ```rust,ignore
153
- /// # use ratatui::{Frame, layout::Rect, text::Line};
154
- /// # fn draw(frame: &mut Frame, area: Rect) {
155
- /// let line = Line::from("Hello world!");
156
- /// frame.render_widget(line, area);
157
- /// # }
158
- /// ```
159
- /// ## Rendering Lines with a Paragraph widget
160
- ///
161
- /// Usually apps will use the `Paragraph` widget instead of rendering a [`Line`] directly as it
162
- /// provides more functionality.
163
- ///
164
- /// ```rust,ignore
165
- /// use ratatui::{
166
- /// buffer::Buffer,
167
- /// layout::Rect,
168
- /// style::Stylize,
169
- /// text::Line,
170
- /// widgets::{Paragraph, Widget, Wrap},
171
- /// };
172
- ///
173
- /// # fn render(area: Rect, buf: &mut Buffer) {
174
- /// let line = Line::from("Hello world!").yellow().italic();
175
- /// Paragraph::new(line)
176
- /// .wrap(Wrap { trim: true })
177
- /// .render(area, buf);
178
- /// # }
179
- /// ```
180
- ///
181
- /// [`Stylize`]: crate::style::Stylize
182
- #[derive(Default, Clone, Eq, PartialEq, Hash)]
183
- pub struct Line<'a> {
184
- /// The style of this line of text.
185
- pub style: Style,
186
-
187
- /// The alignment of this line of text.
188
- pub alignment: Option<Alignment>,
189
-
190
- /// The spans that make up this line of text.
191
- pub spans: Vec<Span<'a>>,
192
- }
193
-
194
- impl fmt::Debug for Line<'_> {
195
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
196
- if self.spans.is_empty() {
197
- f.write_str("Line::default()")?;
198
- } else if self.spans.len() == 1 && self.spans[0].style == Style::default() {
199
- f.write_str(r#"Line::from(""#)?;
200
- f.write_str(&self.spans[0].content)?;
201
- f.write_str(r#"")"#)?;
202
- } else if self.spans.len() == 1 {
203
- f.write_str("Line::from(")?;
204
- self.spans[0].fmt(f)?;
205
- f.write_str(")")?;
206
- } else {
207
- f.write_str("Line::from_iter(")?;
208
- f.debug_list().entries(&self.spans).finish()?;
209
- f.write_str(")")?;
210
- }
211
- self.style.fmt_stylize(f)?;
212
- match self.alignment {
213
- Some(Alignment::Left) => write!(f, ".left_aligned()"),
214
- Some(Alignment::Center) => write!(f, ".centered()"),
215
- Some(Alignment::Right) => write!(f, ".right_aligned()"),
216
- None => Ok(()),
217
- }
218
- }
219
- }
220
-
221
- fn cow_to_spans<'a>(content: impl Into<Cow<'a, str>>) -> Vec<Span<'a>> {
222
- match content.into() {
223
- Cow::Borrowed(s) => s.lines().map(Span::raw).collect(),
224
- Cow::Owned(s) => s.lines().map(|v| Span::raw(v.to_string())).collect(),
225
- }
226
- }
227
-
228
- impl<'a> Line<'a> {
229
- /// Create a line with the default style.
230
- ///
231
- /// `content` can be any type that is convertible to [`Cow<str>`] (e.g. [`&str`], [`String`],
232
- /// [`Cow<str>`], or your own type that implements [`Into<Cow<str>>`]).
233
- ///
234
- /// A [`Line`] can specify a [`Style`], which will be applied before the style of each [`Span`]
235
- /// in the line.
236
- ///
237
- /// Any newlines in the content are removed.
238
- ///
239
- /// # Examples
240
- ///
241
- /// ```rust
242
- /// use std::borrow::Cow;
243
- ///
244
- /// use ratatui_core::text::Line;
245
- ///
246
- /// Line::raw("test content");
247
- /// Line::raw(String::from("test content"));
248
- /// Line::raw(Cow::from("test content"));
249
- /// ```
250
- pub fn raw<T>(content: T) -> Self
251
- where
252
- T: Into<Cow<'a, str>>,
253
- {
254
- Self {
255
- spans: cow_to_spans(content),
256
- ..Default::default()
257
- }
258
- }
259
-
260
- /// Create a line with the given style.
261
- ///
262
- /// `content` can be any type that is convertible to [`Cow<str>`] (e.g. [`&str`], [`String`],
263
- /// [`Cow<str>`], or your own type that implements [`Into<Cow<str>>`]).
264
- ///
265
- /// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
266
- /// your own type that implements [`Into<Style>`]).
267
- ///
268
- /// # Examples
269
- ///
270
- /// Any newlines in the content are removed.
271
- ///
272
- /// ```rust
273
- /// use std::borrow::Cow;
274
- ///
275
- /// use ratatui_core::style::{Style, Stylize};
276
- /// use ratatui_core::text::Line;
277
- ///
278
- /// let style = Style::new().yellow().italic();
279
- /// Line::styled("My text", style);
280
- /// Line::styled(String::from("My text"), style);
281
- /// Line::styled(Cow::from("test content"), style);
282
- /// ```
283
- ///
284
- /// [`Color`]: crate::style::Color
285
- pub fn styled<T, S>(content: T, style: S) -> Self
286
- where
287
- T: Into<Cow<'a, str>>,
288
- S: Into<Style>,
289
- {
290
- Self {
291
- spans: cow_to_spans(content),
292
- style: style.into(),
293
- ..Default::default()
294
- }
295
- }
296
-
297
- /// Sets the spans of this line of text.
298
- ///
299
- /// `spans` accepts any iterator that yields items that are convertible to [`Span`] (e.g.
300
- /// [`&str`], [`String`], [`Span`], or your own type that implements [`Into<Span>`]).
301
- ///
302
- /// # Examples
303
- ///
304
- /// ```rust
305
- /// use ratatui_core::style::Stylize;
306
- /// use ratatui_core::text::Line;
307
- ///
308
- /// let line = Line::default().spans(vec!["Hello".blue(), " world!".green()]);
309
- /// let line = Line::default().spans([1, 2, 3].iter().map(|i| format!("Item {}", i)));
310
- /// ```
311
- #[must_use = "method moves the value of self and returns the modified value"]
312
- pub fn spans<I>(mut self, spans: I) -> Self
313
- where
314
- I: IntoIterator,
315
- I::Item: Into<Span<'a>>,
316
- {
317
- self.spans = spans.into_iter().map(Into::into).collect();
318
- self
319
- }
320
-
321
- /// Sets the style of this line of text.
322
- ///
323
- /// Defaults to [`Style::default()`].
324
- ///
325
- /// Note: This field was added in v0.26.0. Prior to that, the style of a line was determined
326
- /// only by the style of each [`Span`] contained in the line. For this reason, this field may
327
- /// not be supported by all widgets (outside of the `ratatui` crate itself).
328
- ///
329
- /// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
330
- /// your own type that implements [`Into<Style>`]).
331
- ///
332
- /// # Examples
333
- /// ```rust
334
- /// use ratatui_core::style::{Style, Stylize};
335
- /// use ratatui_core::text::Line;
336
- ///
337
- /// let mut line = Line::from("foo").style(Style::new().red());
338
- /// ```
339
- ///
340
- /// [`Color`]: crate::style::Color
341
- #[must_use = "method moves the value of self and returns the modified value"]
342
- pub fn style<S: Into<Style>>(mut self, style: S) -> Self {
343
- self.style = style.into();
344
- self
345
- }
346
-
347
- /// Sets the target alignment for this line of text.
348
- ///
349
- /// Defaults to: [`None`], meaning the alignment is determined by the rendering widget.
350
- /// Setting the alignment of a Line generally overrides the alignment of its
351
- /// parent Text or Widget.
352
- ///
353
- /// # Examples
354
- ///
355
- /// ```rust
356
- /// use ratatui_core::layout::Alignment;
357
- /// use ratatui_core::text::Line;
358
- ///
359
- /// let mut line = Line::from("Hi, what's up?");
360
- /// assert_eq!(None, line.alignment);
361
- /// assert_eq!(
362
- /// Some(Alignment::Right),
363
- /// line.alignment(Alignment::Right).alignment
364
- /// )
365
- /// ```
366
- #[must_use = "method moves the value of self and returns the modified value"]
367
- pub fn alignment(self, alignment: Alignment) -> Self {
368
- Self {
369
- alignment: Some(alignment),
370
- ..self
371
- }
372
- }
373
-
374
- /// Left-aligns this line of text.
375
- ///
376
- /// Convenience shortcut for `Line::alignment(Alignment::Left)`.
377
- /// Setting the alignment of a Line generally overrides the alignment of its
378
- /// parent Text or Widget, with the default alignment being inherited from the parent.
379
- ///
380
- /// # Examples
381
- ///
382
- /// ```rust
383
- /// use ratatui_core::text::Line;
384
- ///
385
- /// let line = Line::from("Hi, what's up?").left_aligned();
386
- /// ```
387
- #[must_use = "method moves the value of self and returns the modified value"]
388
- pub fn left_aligned(self) -> Self {
389
- self.alignment(Alignment::Left)
390
- }
391
-
392
- /// Center-aligns this line of text.
393
- ///
394
- /// Convenience shortcut for `Line::alignment(Alignment::Center)`.
395
- /// Setting the alignment of a Line generally overrides the alignment of its
396
- /// parent Text or Widget, with the default alignment being inherited from the parent.
397
- ///
398
- /// # Examples
399
- ///
400
- /// ```rust
401
- /// use ratatui_core::text::Line;
402
- ///
403
- /// let line = Line::from("Hi, what's up?").centered();
404
- /// ```
405
- #[must_use = "method moves the value of self and returns the modified value"]
406
- pub fn centered(self) -> Self {
407
- self.alignment(Alignment::Center)
408
- }
409
-
410
- /// Right-aligns this line of text.
411
- ///
412
- /// Convenience shortcut for `Line::alignment(Alignment::Right)`.
413
- /// Setting the alignment of a Line generally overrides the alignment of its
414
- /// parent Text or Widget, with the default alignment being inherited from the parent.
415
- ///
416
- /// # Examples
417
- ///
418
- /// ```rust
419
- /// use ratatui_core::text::Line;
420
- ///
421
- /// let line = Line::from("Hi, what's up?").right_aligned();
422
- /// ```
423
- #[must_use = "method moves the value of self and returns the modified value"]
424
- pub fn right_aligned(self) -> Self {
425
- self.alignment(Alignment::Right)
426
- }
427
-
428
- /// Returns the width of the underlying string.
429
- ///
430
- /// # Examples
431
- ///
432
- /// ```rust
433
- /// use ratatui_core::style::Stylize;
434
- /// use ratatui_core::text::Line;
435
- ///
436
- /// let line = Line::from(vec!["Hello".blue(), " world!".green()]);
437
- /// assert_eq!(12, line.width());
438
- /// ```
439
- #[must_use]
440
- pub fn width(&self) -> usize {
441
- UnicodeWidthStr::width(self)
442
- }
443
-
444
- /// Returns an iterator over the graphemes held by this line.
445
- ///
446
- /// `base_style` is the [`Style`] that will be patched with each grapheme [`Style`] to get
447
- /// the resulting [`Style`].
448
- ///
449
- /// `base_style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`],
450
- /// or your own type that implements [`Into<Style>`]).
451
- ///
452
- /// # Examples
453
- ///
454
- /// ```rust
455
- /// use std::iter::Iterator;
456
- ///
457
- /// use ratatui_core::style::{Color, Style};
458
- /// use ratatui_core::text::{Line, StyledGrapheme};
459
- ///
460
- /// let line = Line::styled("Text", Style::default().fg(Color::Yellow));
461
- /// let style = Style::default().fg(Color::Green).bg(Color::Black);
462
- /// assert_eq!(
463
- /// line.styled_graphemes(style)
464
- /// .collect::<Vec<StyledGrapheme>>(),
465
- /// vec![
466
- /// StyledGrapheme::new("T", Style::default().fg(Color::Yellow).bg(Color::Black)),
467
- /// StyledGrapheme::new("e", Style::default().fg(Color::Yellow).bg(Color::Black)),
468
- /// StyledGrapheme::new("x", Style::default().fg(Color::Yellow).bg(Color::Black)),
469
- /// StyledGrapheme::new("t", Style::default().fg(Color::Yellow).bg(Color::Black)),
470
- /// ]
471
- /// );
472
- /// ```
473
- ///
474
- /// [`Color`]: crate::style::Color
475
- pub fn styled_graphemes<S: Into<Style>>(
476
- &'a self,
477
- base_style: S,
478
- ) -> impl Iterator<Item = StyledGrapheme<'a>> {
479
- let style = base_style.into().patch(self.style);
480
- self.spans
481
- .iter()
482
- .flat_map(move |span| span.styled_graphemes(style))
483
- }
484
-
485
- /// Patches the style of this Line, adding modifiers from the given style.
486
- ///
487
- /// This is useful for when you want to apply a style to a line that already has some styling.
488
- /// In contrast to [`Line::style`], this method will not overwrite the existing style, but
489
- /// instead will add the given style's modifiers to this Line's style.
490
- ///
491
- /// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
492
- /// your own type that implements [`Into<Style>`]).
493
- ///
494
- /// This is a fluent setter method which must be chained or used as it consumes self
495
- ///
496
- /// # Examples
497
- ///
498
- /// ```rust
499
- /// use ratatui_core::style::{Color, Modifier};
500
- /// use ratatui_core::text::Line;
501
- ///
502
- /// let line = Line::styled("My text", Modifier::ITALIC);
503
- ///
504
- /// let styled_line = Line::styled("My text", (Color::Yellow, Modifier::ITALIC));
505
- ///
506
- /// assert_eq!(styled_line, line.patch_style(Color::Yellow));
507
- /// ```
508
- ///
509
- /// [`Color`]: crate::style::Color
510
- #[must_use = "method moves the value of self and returns the modified value"]
511
- pub fn patch_style<S: Into<Style>>(mut self, style: S) -> Self {
512
- self.style = self.style.patch(style);
513
- self
514
- }
515
-
516
- /// Resets the style of this Line.
517
- ///
518
- /// Equivalent to calling `patch_style(Style::reset())`.
519
- ///
520
- /// This is a fluent setter method which must be chained or used as it consumes self
521
- ///
522
- /// # Examples
523
- ///
524
- /// ```rust
525
- /// # let style = Style::default().yellow();
526
- /// use ratatui_core::style::{Style, Stylize};
527
- /// use ratatui_core::text::Line;
528
- ///
529
- /// let line = Line::styled("My text", style);
530
- ///
531
- /// assert_eq!(Style::reset(), line.reset_style().style);
532
- /// ```
533
- #[must_use = "method moves the value of self and returns the modified value"]
534
- pub fn reset_style(self) -> Self {
535
- self.patch_style(Style::reset())
536
- }
537
-
538
- /// Returns an iterator over the spans of this line.
539
- pub fn iter(&self) -> core::slice::Iter<'_, Span<'a>> {
540
- self.spans.iter()
541
- }
542
-
543
- /// Returns a mutable iterator over the spans of this line.
544
- pub fn iter_mut(&mut self) -> core::slice::IterMut<'_, Span<'a>> {
545
- self.spans.iter_mut()
546
- }
547
-
548
- /// Adds a span to the line.
549
- ///
550
- /// `span` can be any type that is convertible into a `Span`. For example, you can pass a
551
- /// `&str`, a `String`, or a `Span`.
552
- ///
553
- /// # Examples
554
- ///
555
- /// ```rust
556
- /// use ratatui_core::text::{Line, Span};
557
- ///
558
- /// let mut line = Line::from("Hello, ");
559
- /// line.push_span(Span::raw("world!"));
560
- /// line.push_span(" How are you?");
561
- /// ```
562
- pub fn push_span<T: Into<Span<'a>>>(&mut self, span: T) {
563
- self.spans.push(span.into());
564
- }
565
- }
566
-
567
- impl UnicodeWidthStr for Line<'_> {
568
- fn width(&self) -> usize {
569
- self.spans.iter().map(UnicodeWidthStr::width).sum()
570
- }
571
-
572
- fn width_cjk(&self) -> usize {
573
- self.spans.iter().map(UnicodeWidthStr::width_cjk).sum()
574
- }
575
- }
576
-
577
- impl<'a> IntoIterator for Line<'a> {
578
- type Item = Span<'a>;
579
- type IntoIter = alloc::vec::IntoIter<Span<'a>>;
580
-
581
- fn into_iter(self) -> Self::IntoIter {
582
- self.spans.into_iter()
583
- }
584
- }
585
-
586
- impl<'a> IntoIterator for &'a Line<'a> {
587
- type Item = &'a Span<'a>;
588
- type IntoIter = core::slice::Iter<'a, Span<'a>>;
589
-
590
- fn into_iter(self) -> Self::IntoIter {
591
- self.iter()
592
- }
593
- }
594
-
595
- impl<'a> IntoIterator for &'a mut Line<'a> {
596
- type Item = &'a mut Span<'a>;
597
- type IntoIter = core::slice::IterMut<'a, Span<'a>>;
598
-
599
- fn into_iter(self) -> Self::IntoIter {
600
- self.iter_mut()
601
- }
602
- }
603
-
604
- impl From<String> for Line<'_> {
605
- fn from(s: String) -> Self {
606
- Self::raw(s)
607
- }
608
- }
609
-
610
- impl<'a> From<&'a str> for Line<'a> {
611
- fn from(s: &'a str) -> Self {
612
- Self::raw(s)
613
- }
614
- }
615
-
616
- impl<'a> From<Cow<'a, str>> for Line<'a> {
617
- fn from(s: Cow<'a, str>) -> Self {
618
- Self::raw(s)
619
- }
620
- }
621
-
622
- impl<'a> From<Vec<Span<'a>>> for Line<'a> {
623
- fn from(spans: Vec<Span<'a>>) -> Self {
624
- Self {
625
- spans,
626
- ..Default::default()
627
- }
628
- }
629
- }
630
-
631
- impl<'a> From<Span<'a>> for Line<'a> {
632
- fn from(span: Span<'a>) -> Self {
633
- Self::from(vec![span])
634
- }
635
- }
636
-
637
- impl<'a> From<Line<'a>> for String {
638
- fn from(line: Line<'a>) -> Self {
639
- line.iter().fold(Self::new(), |mut acc, s| {
640
- acc.push_str(s.content.as_ref());
641
- acc
642
- })
643
- }
644
- }
645
-
646
- impl<'a, T> FromIterator<T> for Line<'a>
647
- where
648
- T: Into<Span<'a>>,
649
- {
650
- fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
651
- Self::from(iter.into_iter().map(Into::into).collect::<Vec<_>>())
652
- }
653
- }
654
-
655
- /// Adds a `Span` to a `Line`, returning a new `Line` with the `Span` added.
656
- impl<'a> core::ops::Add<Span<'a>> for Line<'a> {
657
- type Output = Self;
658
-
659
- fn add(mut self, rhs: Span<'a>) -> Self::Output {
660
- self.spans.push(rhs);
661
- self
662
- }
663
- }
664
-
665
- /// Adds two `Line`s together, returning a new `Text` with the contents of the two `Line`s.
666
- impl<'a> core::ops::Add<Self> for Line<'a> {
667
- type Output = Text<'a>;
668
-
669
- fn add(self, rhs: Self) -> Self::Output {
670
- Text::from(vec![self, rhs])
671
- }
672
- }
673
-
674
- impl<'a> core::ops::AddAssign<Span<'a>> for Line<'a> {
675
- fn add_assign(&mut self, rhs: Span<'a>) {
676
- self.spans.push(rhs);
677
- }
678
- }
679
-
680
- impl<'a> Extend<Span<'a>> for Line<'a> {
681
- fn extend<T: IntoIterator<Item = Span<'a>>>(&mut self, iter: T) {
682
- self.spans.extend(iter);
683
- }
684
- }
685
-
686
- impl Widget for Line<'_> {
687
- fn render(self, area: Rect, buf: &mut Buffer) {
688
- Widget::render(&self, area, buf);
689
- }
690
- }
691
-
692
- impl Widget for &Line<'_> {
693
- fn render(self, area: Rect, buf: &mut Buffer) {
694
- self.render_with_alignment(area, buf, None);
695
- }
696
- }
697
-
698
- impl Line<'_> {
699
- /// An internal implementation method for `Widget::render` that allows the parent widget to
700
- /// define a default alignment, to be used if `Line::alignment` is `None`.
701
- pub(crate) fn render_with_alignment(
702
- &self,
703
- area: Rect,
704
- buf: &mut Buffer,
705
- parent_alignment: Option<Alignment>,
706
- ) {
707
- let area = area.intersection(buf.area);
708
- if area.is_empty() {
709
- return;
710
- }
711
- let area = Rect { height: 1, ..area };
712
- let line_width = self.width();
713
- if line_width == 0 {
714
- return;
715
- }
716
-
717
- buf.set_style(area, self.style);
718
-
719
- let alignment = self.alignment.or(parent_alignment);
720
-
721
- let area_width = usize::from(area.width);
722
- let can_render_complete_line = line_width <= area_width;
723
- if can_render_complete_line {
724
- let indent_width = match alignment {
725
- Some(Alignment::Center) => (area_width.saturating_sub(line_width)) / 2,
726
- Some(Alignment::Right) => area_width.saturating_sub(line_width),
727
- Some(Alignment::Left) | None => 0,
728
- };
729
- let indent_width = u16::try_from(indent_width).unwrap_or(u16::MAX);
730
- let area = area.indent_x(indent_width);
731
- render_spans(&self.spans, area, buf, 0);
732
- } else {
733
- // There is not enough space to render the whole line. As the right side is truncated by
734
- // the area width, only truncate the left.
735
- let skip_width = match alignment {
736
- Some(Alignment::Center) => (line_width.saturating_sub(area_width)) / 2,
737
- Some(Alignment::Right) => line_width.saturating_sub(area_width),
738
- Some(Alignment::Left) | None => 0,
739
- };
740
- render_spans(&self.spans, area, buf, skip_width);
741
- }
742
- }
743
- }
744
-
745
- /// Renders all the spans of the line that should be visible.
746
- fn render_spans(spans: &[Span], mut area: Rect, buf: &mut Buffer, span_skip_width: usize) {
747
- for (span, span_width, offset) in spans_after_width(spans, span_skip_width) {
748
- area = area.indent_x(offset);
749
- if area.is_empty() {
750
- break;
751
- }
752
- span.render(area, buf);
753
- let span_width = u16::try_from(span_width).unwrap_or(u16::MAX);
754
- area = area.indent_x(span_width);
755
- }
756
- }
757
-
758
- /// Returns an iterator over the spans that lie after a given skip width from the start of the
759
- /// `Line` (including a partially visible span if the `skip_width` lands within a span).
760
- fn spans_after_width<'a>(
761
- spans: &'a [Span],
762
- mut skip_width: usize,
763
- ) -> impl Iterator<Item = (Span<'a>, usize, u16)> {
764
- spans
765
- .iter()
766
- .map(|span| (span, span.width()))
767
- // Filter non visible spans out.
768
- .filter_map(move |(span, span_width)| {
769
- // Ignore spans that are completely before the offset. Decrement `span_skip_width` by
770
- // the span width until we find a span that is partially or completely visible.
771
- if skip_width >= span_width {
772
- skip_width = skip_width.saturating_sub(span_width);
773
- return None;
774
- }
775
-
776
- // Apply the skip from the start of the span, not the end as the end will be trimmed
777
- // when rendering the span to the buffer.
778
- let available_width = span_width.saturating_sub(skip_width);
779
- skip_width = 0; // ensure the next span is rendered in full
780
- Some((span, span_width, available_width))
781
- })
782
- .map(|(span, span_width, available_width)| {
783
- if span_width <= available_width {
784
- // Span is fully visible. Clone here is fast as the underlying content is `Cow`.
785
- return (span.clone(), span_width, 0u16);
786
- }
787
- // Span is only partially visible. As the end is truncated by the area width, only
788
- // truncate the start of the span.
789
- let (content, actual_width) = span.content.unicode_truncate_start(available_width);
790
-
791
- // When the first grapheme of the span was truncated, start rendering from a position
792
- // that takes that into account by indenting the start of the area
793
- let first_grapheme_offset = available_width.saturating_sub(actual_width);
794
- let first_grapheme_offset = u16::try_from(first_grapheme_offset).unwrap_or(u16::MAX);
795
- (
796
- Span::styled(content, span.style),
797
- actual_width,
798
- first_grapheme_offset,
799
- )
800
- })
801
- }
802
-
803
- /// A trait for converting a value to a [`Line`].
804
- ///
805
- /// This trait is automatically implemented for any type that implements the [`Display`] trait. As
806
- /// such, `ToLine` shouln't be implemented directly: [`Display`] should be implemented instead, and
807
- /// you get the `ToLine` implementation for free.
808
- ///
809
- /// [`Display`]: std::fmt::Display
810
- pub trait ToLine {
811
- /// Converts the value to a [`Line`].
812
- fn to_line(&self) -> Line<'_>;
813
- }
814
-
815
- /// # Panics
816
- ///
817
- /// In this implementation, the `to_line` method panics if the `Display` implementation returns an
818
- /// error. This indicates an incorrect `Display` implementation since `fmt::Write for String` never
819
- /// returns an error itself.
820
- impl<T: fmt::Display> ToLine for T {
821
- fn to_line(&self) -> Line<'_> {
822
- Line::from(self.to_string())
823
- }
824
- }
825
-
826
- impl fmt::Display for Line<'_> {
827
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
828
- for span in &self.spans {
829
- write!(f, "{span}")?;
830
- }
831
- Ok(())
832
- }
833
- }
834
-
835
- impl Styled for Line<'_> {
836
- type Item = Self;
837
-
838
- fn style(&self) -> Style {
839
- self.style
840
- }
841
-
842
- fn set_style<S: Into<Style>>(self, style: S) -> Self::Item {
843
- self.style(style)
844
- }
845
- }
846
-
847
- #[cfg(test)]
848
- mod tests {
849
- use alloc::format;
850
- use core::iter;
851
- use std::dbg;
852
-
853
- use rstest::{fixture, rstest};
854
-
855
- use super::*;
856
- use crate::style::{Color, Modifier, Stylize};
857
-
858
- #[fixture]
859
- fn small_buf() -> Buffer {
860
- Buffer::empty(Rect::new(0, 0, 10, 1))
861
- }
862
-
863
- #[test]
864
- fn raw_str() {
865
- let line = Line::raw("test content");
866
- assert_eq!(line.spans, [Span::raw("test content")]);
867
- assert_eq!(line.alignment, None);
868
-
869
- let line = Line::raw("a\nb");
870
- assert_eq!(line.spans, [Span::raw("a"), Span::raw("b")]);
871
- assert_eq!(line.alignment, None);
872
- }
873
-
874
- #[test]
875
- fn styled_str() {
876
- let style = Style::new().yellow();
877
- let content = "Hello, world!";
878
- let line = Line::styled(content, style);
879
- assert_eq!(line.spans, [Span::raw(content)]);
880
- assert_eq!(line.style, style);
881
- }
882
-
883
- #[test]
884
- fn styled_string() {
885
- let style = Style::new().yellow();
886
- let content = String::from("Hello, world!");
887
- let line = Line::styled(content.clone(), style);
888
- assert_eq!(line.spans, [Span::raw(content)]);
889
- assert_eq!(line.style, style);
890
- }
891
-
892
- #[test]
893
- fn styled_cow() {
894
- let style = Style::new().yellow();
895
- let content = Cow::from("Hello, world!");
896
- let line = Line::styled(content.clone(), style);
897
- assert_eq!(line.spans, [Span::raw(content)]);
898
- assert_eq!(line.style, style);
899
- }
900
-
901
- #[test]
902
- fn spans_vec() {
903
- let line = Line::default().spans(vec!["Hello".blue(), " world!".green()]);
904
- assert_eq!(
905
- line.spans,
906
- vec![
907
- Span::styled("Hello", Style::new().blue()),
908
- Span::styled(" world!", Style::new().green()),
909
- ]
910
- );
911
- }
912
-
913
- #[test]
914
- fn spans_iter() {
915
- let line = Line::default().spans([1, 2, 3].iter().map(|i| format!("Item {i}")));
916
- assert_eq!(
917
- line.spans,
918
- vec![
919
- Span::raw("Item 1"),
920
- Span::raw("Item 2"),
921
- Span::raw("Item 3"),
922
- ]
923
- );
924
- }
925
-
926
- #[test]
927
- fn style() {
928
- let line = Line::default().style(Style::new().red());
929
- assert_eq!(line.style, Style::new().red());
930
- }
931
-
932
- #[test]
933
- fn alignment() {
934
- let line = Line::from("This is left").alignment(Alignment::Left);
935
- assert_eq!(Some(Alignment::Left), line.alignment);
936
-
937
- let line = Line::from("This is default");
938
- assert_eq!(None, line.alignment);
939
- }
940
-
941
- #[test]
942
- fn width() {
943
- let line = Line::from(vec![
944
- Span::styled("My", Style::default().fg(Color::Yellow)),
945
- Span::raw(" text"),
946
- ]);
947
- assert_eq!(7, line.width());
948
-
949
- let empty_line = Line::default();
950
- assert_eq!(0, empty_line.width());
951
- }
952
-
953
- #[test]
954
- fn patch_style() {
955
- let raw_line = Line::styled("foobar", Color::Yellow);
956
- let styled_line = Line::styled("foobar", (Color::Yellow, Modifier::ITALIC));
957
-
958
- assert_ne!(raw_line, styled_line);
959
-
960
- let raw_line = raw_line.patch_style(Modifier::ITALIC);
961
- assert_eq!(raw_line, styled_line);
962
- }
963
-
964
- #[test]
965
- fn reset_style() {
966
- let line =
967
- Line::styled("foobar", Style::default().yellow().on_red().italic()).reset_style();
968
-
969
- assert_eq!(Style::reset(), line.style);
970
- }
971
-
972
- #[test]
973
- fn stylize() {
974
- assert_eq!(Line::default().green().style, Color::Green.into());
975
- assert_eq!(
976
- Line::default().on_green().style,
977
- Style::new().bg(Color::Green)
978
- );
979
- assert_eq!(Line::default().italic().style, Modifier::ITALIC.into());
980
- }
981
-
982
- #[test]
983
- fn from_string() {
984
- let s = String::from("Hello, world!");
985
- let line = Line::from(s);
986
- assert_eq!(line.spans, [Span::from("Hello, world!")]);
987
-
988
- let s = String::from("Hello\nworld!");
989
- let line = Line::from(s);
990
- assert_eq!(line.spans, [Span::from("Hello"), Span::from("world!")]);
991
- }
992
-
993
- #[test]
994
- fn from_str() {
995
- let s = "Hello, world!";
996
- let line = Line::from(s);
997
- assert_eq!(line.spans, [Span::from("Hello, world!")]);
998
-
999
- let s = "Hello\nworld!";
1000
- let line = Line::from(s);
1001
- assert_eq!(line.spans, [Span::from("Hello"), Span::from("world!")]);
1002
- }
1003
-
1004
- #[test]
1005
- fn to_line() {
1006
- let line = 42.to_line();
1007
- assert_eq!(line.spans, [Span::from("42")]);
1008
- }
1009
-
1010
- #[test]
1011
- fn from_vec() {
1012
- let spans = vec![
1013
- Span::styled("Hello,", Style::default().fg(Color::Red)),
1014
- Span::styled(" world!", Style::default().fg(Color::Green)),
1015
- ];
1016
- let line = Line::from(spans.clone());
1017
- assert_eq!(line.spans, spans);
1018
- }
1019
-
1020
- #[test]
1021
- fn from_iter() {
1022
- let line = Line::from_iter(vec!["Hello".blue(), " world!".green()]);
1023
- assert_eq!(
1024
- line.spans,
1025
- vec![
1026
- Span::styled("Hello", Style::new().blue()),
1027
- Span::styled(" world!", Style::new().green()),
1028
- ]
1029
- );
1030
- }
1031
-
1032
- #[test]
1033
- fn collect() {
1034
- let line: Line = iter::once("Hello".blue())
1035
- .chain(iter::once(" world!".green()))
1036
- .collect();
1037
- assert_eq!(
1038
- line.spans,
1039
- vec![
1040
- Span::styled("Hello", Style::new().blue()),
1041
- Span::styled(" world!", Style::new().green()),
1042
- ]
1043
- );
1044
- }
1045
-
1046
- #[test]
1047
- fn from_span() {
1048
- let span = Span::styled("Hello, world!", Style::default().fg(Color::Yellow));
1049
- let line = Line::from(span.clone());
1050
- assert_eq!(line.spans, [span]);
1051
- }
1052
-
1053
- #[test]
1054
- fn add_span() {
1055
- assert_eq!(
1056
- Line::raw("Red").red() + Span::raw("blue").blue(),
1057
- Line {
1058
- spans: vec![Span::raw("Red"), Span::raw("blue").blue()],
1059
- style: Style::new().red(),
1060
- alignment: None,
1061
- },
1062
- );
1063
- }
1064
-
1065
- #[test]
1066
- fn add_line() {
1067
- assert_eq!(
1068
- Line::raw("Red").red() + Line::raw("Blue").blue(),
1069
- Text {
1070
- lines: vec![Line::raw("Red").red(), Line::raw("Blue").blue()],
1071
- style: Style::default(),
1072
- alignment: None,
1073
- }
1074
- );
1075
- }
1076
-
1077
- #[test]
1078
- fn add_assign_span() {
1079
- let mut line = Line::raw("Red").red();
1080
- line += Span::raw("Blue").blue();
1081
- assert_eq!(
1082
- line,
1083
- Line {
1084
- spans: vec![Span::raw("Red"), Span::raw("Blue").blue()],
1085
- style: Style::new().red(),
1086
- alignment: None,
1087
- },
1088
- );
1089
- }
1090
-
1091
- #[test]
1092
- fn extend() {
1093
- let mut line = Line::from("Hello, ");
1094
- line.extend([Span::raw("world!")]);
1095
- assert_eq!(line.spans, [Span::raw("Hello, "), Span::raw("world!")]);
1096
-
1097
- let mut line = Line::from("Hello, ");
1098
- line.extend([Span::raw("world! "), Span::raw("How are you?")]);
1099
- assert_eq!(
1100
- line.spans,
1101
- [
1102
- Span::raw("Hello, "),
1103
- Span::raw("world! "),
1104
- Span::raw("How are you?")
1105
- ]
1106
- );
1107
- }
1108
-
1109
- #[test]
1110
- fn into_string() {
1111
- let line = Line::from(vec![
1112
- Span::styled("Hello,", Style::default().fg(Color::Red)),
1113
- Span::styled(" world!", Style::default().fg(Color::Green)),
1114
- ]);
1115
- let s: String = line.into();
1116
- assert_eq!(s, "Hello, world!");
1117
- }
1118
-
1119
- #[test]
1120
- fn styled_graphemes() {
1121
- const RED: Style = Style::new().red();
1122
- const GREEN: Style = Style::new().green();
1123
- const BLUE: Style = Style::new().blue();
1124
- const RED_ON_WHITE: Style = Style::new().red().on_white();
1125
- const GREEN_ON_WHITE: Style = Style::new().green().on_white();
1126
- const BLUE_ON_WHITE: Style = Style::new().blue().on_white();
1127
-
1128
- let line = Line::from(vec![
1129
- Span::styled("He", RED),
1130
- Span::styled("ll", GREEN),
1131
- Span::styled("o!", BLUE),
1132
- ]);
1133
- let styled_graphemes = line
1134
- .styled_graphemes(Style::new().bg(Color::White))
1135
- .collect::<Vec<StyledGrapheme>>();
1136
- assert_eq!(
1137
- styled_graphemes,
1138
- vec![
1139
- StyledGrapheme::new("H", RED_ON_WHITE),
1140
- StyledGrapheme::new("e", RED_ON_WHITE),
1141
- StyledGrapheme::new("l", GREEN_ON_WHITE),
1142
- StyledGrapheme::new("l", GREEN_ON_WHITE),
1143
- StyledGrapheme::new("o", BLUE_ON_WHITE),
1144
- StyledGrapheme::new("!", BLUE_ON_WHITE),
1145
- ],
1146
- );
1147
- }
1148
-
1149
- #[test]
1150
- fn display_line_from_vec() {
1151
- let line_from_vec = Line::from(vec![Span::raw("Hello,"), Span::raw(" world!")]);
1152
-
1153
- assert_eq!(format!("{line_from_vec}"), "Hello, world!");
1154
- }
1155
-
1156
- #[test]
1157
- fn display_styled_line() {
1158
- let styled_line = Line::styled("Hello, world!", Style::new().green().italic());
1159
-
1160
- assert_eq!(format!("{styled_line}"), "Hello, world!");
1161
- }
1162
-
1163
- #[test]
1164
- fn display_line_from_styled_span() {
1165
- let styled_span = Span::styled("Hello, world!", Style::new().green().italic());
1166
- let line_from_styled_span = Line::from(styled_span);
1167
-
1168
- assert_eq!(format!("{line_from_styled_span}"), "Hello, world!");
1169
- }
1170
-
1171
- #[test]
1172
- fn left_aligned() {
1173
- let line = Line::from("Hello, world!").left_aligned();
1174
- assert_eq!(line.alignment, Some(Alignment::Left));
1175
- }
1176
-
1177
- #[test]
1178
- fn centered() {
1179
- let line = Line::from("Hello, world!").centered();
1180
- assert_eq!(line.alignment, Some(Alignment::Center));
1181
- }
1182
-
1183
- #[test]
1184
- fn right_aligned() {
1185
- let line = Line::from("Hello, world!").right_aligned();
1186
- assert_eq!(line.alignment, Some(Alignment::Right));
1187
- }
1188
-
1189
- #[test]
1190
- pub fn push_span() {
1191
- let mut line = Line::from("A");
1192
- line.push_span(Span::raw("B"));
1193
- line.push_span("C");
1194
- assert_eq!(
1195
- line.spans,
1196
- vec![Span::raw("A"), Span::raw("B"), Span::raw("C")]
1197
- );
1198
- }
1199
-
1200
- mod widget {
1201
- use unicode_segmentation::UnicodeSegmentation;
1202
- use unicode_width::UnicodeWidthStr;
1203
-
1204
- use super::*;
1205
- use crate::buffer::Cell;
1206
-
1207
- const BLUE: Style = Style::new().blue();
1208
- const GREEN: Style = Style::new().green();
1209
- const ITALIC: Style = Style::new().italic();
1210
-
1211
- #[fixture]
1212
- fn hello_world() -> Line<'static> {
1213
- Line::from(vec![
1214
- Span::styled("Hello ", BLUE),
1215
- Span::styled("world!", GREEN),
1216
- ])
1217
- .style(ITALIC)
1218
- }
1219
-
1220
- #[test]
1221
- fn render() {
1222
- let mut buf = Buffer::empty(Rect::new(0, 0, 15, 1));
1223
- hello_world().render(Rect::new(0, 0, 15, 1), &mut buf);
1224
- let mut expected = Buffer::with_lines(["Hello world! "]);
1225
- expected.set_style(Rect::new(0, 0, 15, 1), ITALIC);
1226
- expected.set_style(Rect::new(0, 0, 6, 1), BLUE);
1227
- expected.set_style(Rect::new(6, 0, 6, 1), GREEN);
1228
- assert_eq!(buf, expected);
1229
- }
1230
-
1231
- #[rstest]
1232
- fn render_out_of_bounds(hello_world: Line<'static>, mut small_buf: Buffer) {
1233
- let out_of_bounds = Rect::new(20, 20, 10, 1);
1234
- hello_world.render(out_of_bounds, &mut small_buf);
1235
- assert_eq!(small_buf, Buffer::empty(small_buf.area));
1236
- }
1237
-
1238
- #[test]
1239
- fn render_only_styles_line_area() {
1240
- let mut buf = Buffer::empty(Rect::new(0, 0, 20, 1));
1241
- hello_world().render(Rect::new(0, 0, 15, 1), &mut buf);
1242
- let mut expected = Buffer::with_lines(["Hello world! "]);
1243
- expected.set_style(Rect::new(0, 0, 15, 1), ITALIC);
1244
- expected.set_style(Rect::new(0, 0, 6, 1), BLUE);
1245
- expected.set_style(Rect::new(6, 0, 6, 1), GREEN);
1246
- assert_eq!(buf, expected);
1247
- }
1248
-
1249
- #[test]
1250
- fn render_only_styles_first_line() {
1251
- let mut buf = Buffer::empty(Rect::new(0, 0, 20, 2));
1252
- hello_world().render(buf.area, &mut buf);
1253
- let mut expected = Buffer::with_lines(["Hello world! ", " "]);
1254
- expected.set_style(Rect::new(0, 0, 20, 1), ITALIC);
1255
- expected.set_style(Rect::new(0, 0, 6, 1), BLUE);
1256
- expected.set_style(Rect::new(6, 0, 6, 1), GREEN);
1257
- assert_eq!(buf, expected);
1258
- }
1259
-
1260
- #[test]
1261
- fn render_truncates() {
1262
- let mut buf = Buffer::empty(Rect::new(0, 0, 10, 1));
1263
- Line::from("Hello world!").render(Rect::new(0, 0, 5, 1), &mut buf);
1264
- assert_eq!(buf, Buffer::with_lines(["Hello "]));
1265
- }
1266
-
1267
- #[test]
1268
- fn render_centered() {
1269
- let line = hello_world().alignment(Alignment::Center);
1270
- let mut buf = Buffer::empty(Rect::new(0, 0, 15, 1));
1271
- line.render(Rect::new(0, 0, 15, 1), &mut buf);
1272
- let mut expected = Buffer::with_lines([" Hello world! "]);
1273
- expected.set_style(Rect::new(0, 0, 15, 1), ITALIC);
1274
- expected.set_style(Rect::new(1, 0, 6, 1), BLUE);
1275
- expected.set_style(Rect::new(7, 0, 6, 1), GREEN);
1276
- assert_eq!(buf, expected);
1277
- }
1278
-
1279
- #[test]
1280
- fn render_right_aligned() {
1281
- let line = hello_world().alignment(Alignment::Right);
1282
- let mut buf = Buffer::empty(Rect::new(0, 0, 15, 1));
1283
- line.render(Rect::new(0, 0, 15, 1), &mut buf);
1284
- let mut expected = Buffer::with_lines([" Hello world!"]);
1285
- expected.set_style(Rect::new(0, 0, 15, 1), ITALIC);
1286
- expected.set_style(Rect::new(3, 0, 6, 1), BLUE);
1287
- expected.set_style(Rect::new(9, 0, 6, 1), GREEN);
1288
- assert_eq!(buf, expected);
1289
- }
1290
-
1291
- #[test]
1292
- fn render_truncates_left() {
1293
- let mut buf = Buffer::empty(Rect::new(0, 0, 5, 1));
1294
- Line::from("Hello world")
1295
- .left_aligned()
1296
- .render(buf.area, &mut buf);
1297
- assert_eq!(buf, Buffer::with_lines(["Hello"]));
1298
- }
1299
-
1300
- #[test]
1301
- fn render_truncates_right() {
1302
- let mut buf = Buffer::empty(Rect::new(0, 0, 5, 1));
1303
- Line::from("Hello world")
1304
- .right_aligned()
1305
- .render(buf.area, &mut buf);
1306
- assert_eq!(buf, Buffer::with_lines(["world"]));
1307
- }
1308
-
1309
- #[test]
1310
- fn render_truncates_center() {
1311
- let mut buf = Buffer::empty(Rect::new(0, 0, 5, 1));
1312
- Line::from("Hello world")
1313
- .centered()
1314
- .render(buf.area, &mut buf);
1315
- assert_eq!(buf, Buffer::with_lines(["lo wo"]));
1316
- }
1317
-
1318
- /// Part of a regression test for <https://github.com/ratatui/ratatui/issues/1032> which
1319
- /// found panics with truncating lines that contained multi-byte characters.
1320
- #[test]
1321
- fn regression_1032() {
1322
- let line = Line::from(
1323
- "🦀 RFC8628 OAuth 2.0 Device Authorization GrantでCLIからGithubのaccess tokenを取得する",
1324
- );
1325
- let mut buf = Buffer::empty(Rect::new(0, 0, 83, 1));
1326
- line.render(buf.area, &mut buf);
1327
- assert_eq!(
1328
- buf,
1329
- Buffer::with_lines([
1330
- "🦀 RFC8628 OAuth 2.0 Device Authorization GrantでCLIからGithubのaccess tokenを取得 "
1331
- ])
1332
- );
1333
- }
1334
-
1335
- /// Documentary test to highlight the crab emoji width / length discrepancy
1336
- ///
1337
- /// Part of a regression test for <https://github.com/ratatui/ratatui/issues/1032> which
1338
- /// found panics with truncating lines that contained multi-byte characters.
1339
- #[test]
1340
- fn crab_emoji_width() {
1341
- let crab = "🦀";
1342
- assert_eq!(crab.len(), 4); // bytes
1343
- assert_eq!(crab.chars().count(), 1);
1344
- assert_eq!(crab.graphemes(true).count(), 1);
1345
- assert_eq!(crab.width(), 2); // display width
1346
- }
1347
-
1348
- /// Part of a regression test for <https://github.com/ratatui/ratatui/issues/1032> which
1349
- /// found panics with truncating lines that contained multi-byte characters.
1350
- #[rstest]
1351
- #[case::left_4(Alignment::Left, 4, "1234")]
1352
- #[case::left_5(Alignment::Left, 5, "1234 ")]
1353
- #[case::left_6(Alignment::Left, 6, "1234🦀")]
1354
- #[case::left_7(Alignment::Left, 7, "1234🦀7")]
1355
- #[case::right_4(Alignment::Right, 4, "7890")]
1356
- #[case::right_5(Alignment::Right, 5, " 7890")]
1357
- #[case::right_6(Alignment::Right, 6, "🦀7890")]
1358
- #[case::right_7(Alignment::Right, 7, "4🦀7890")]
1359
- fn render_truncates_emoji(
1360
- #[case] alignment: Alignment,
1361
- #[case] buf_width: u16,
1362
- #[case] expected: &str,
1363
- ) {
1364
- let line = Line::from("1234🦀7890").alignment(alignment);
1365
- let mut buf = Buffer::empty(Rect::new(0, 0, buf_width, 1));
1366
- line.render(buf.area, &mut buf);
1367
- assert_eq!(buf, Buffer::with_lines([expected]));
1368
- }
1369
-
1370
- /// Part of a regression test for <https://github.com/ratatui/ratatui/issues/1032> which
1371
- /// found panics with truncating lines that contained multi-byte characters.
1372
- ///
1373
- /// centering is tricky because there's an ambiguity about whether to take one more char
1374
- /// from the left or the right when the line width is odd. This interacts with the width of
1375
- /// the crab emoji, which is 2 characters wide by hitting the left or right side of the
1376
- /// emoji.
1377
- #[rstest]
1378
- #[case::center_6_0(6, 0, "")]
1379
- #[case::center_6_1(6, 1, " ")] // lef side of "🦀"
1380
- #[case::center_6_2(6, 2, "🦀")]
1381
- #[case::center_6_3(6, 3, "b🦀")]
1382
- #[case::center_6_4(6, 4, "b🦀c")]
1383
- #[case::center_7_0(7, 0, "")]
1384
- #[case::center_7_1(7, 1, " ")] // right side of "🦀"
1385
- #[case::center_7_2(7, 2, "🦀")]
1386
- #[case::center_7_3(7, 3, "🦀c")]
1387
- #[case::center_7_4(7, 4, "b🦀c")]
1388
- #[case::center_8_0(8, 0, "")]
1389
- #[case::center_8_1(8, 1, " ")] // right side of "🦀"
1390
- #[case::center_8_2(8, 2, " c")] // right side of "🦀c"
1391
- #[case::center_8_3(8, 3, "🦀c")]
1392
- #[case::center_8_4(8, 4, "🦀cd")]
1393
- #[case::center_8_5(8, 5, "b🦀cd")]
1394
- #[case::center_9_0(9, 0, "")]
1395
- #[case::center_9_1(9, 1, "c")]
1396
- #[case::center_9_2(9, 2, " c")] // right side of "🦀c"
1397
- #[case::center_9_3(9, 3, " cd")]
1398
- #[case::center_9_4(9, 4, "🦀cd")]
1399
- #[case::center_9_5(9, 5, "🦀cde")]
1400
- #[case::center_9_6(9, 6, "b🦀cde")]
1401
- fn render_truncates_emoji_center(
1402
- #[case] line_width: u16,
1403
- #[case] buf_width: u16,
1404
- #[case] expected: &str,
1405
- ) {
1406
- // because the crab emoji is 2 characters wide, it will can cause the centering tests
1407
- // intersect with either the left or right part of the emoji, which causes the emoji to
1408
- // be not rendered. Checking for four different widths of the line is enough to cover
1409
- // all the possible cases.
1410
- let value = match line_width {
1411
- 6 => "ab🦀cd",
1412
- 7 => "ab🦀cde",
1413
- 8 => "ab🦀cdef",
1414
- 9 => "ab🦀cdefg",
1415
- _ => unreachable!(),
1416
- };
1417
- let line = Line::from(value).centered();
1418
- let mut buf = Buffer::empty(Rect::new(0, 0, buf_width, 1));
1419
- line.render(buf.area, &mut buf);
1420
- assert_eq!(buf, Buffer::with_lines([expected]));
1421
- }
1422
-
1423
- /// Ensures the rendering also works away from the 0x0 position.
1424
- ///
1425
- /// Particularly of note is that an emoji that is truncated will not overwrite the
1426
- /// characters that are already in the buffer. This is inentional (consider how a line
1427
- /// that is rendered on a border should not overwrite the border with a partial emoji).
1428
- #[rstest]
1429
- #[case::left(Alignment::Left, "XXa🦀bcXXX")]
1430
- #[case::center(Alignment::Center, "XX🦀bc🦀XX")]
1431
- #[case::right(Alignment::Right, "XXXbc🦀dXX")]
1432
- fn render_truncates_away_from_0x0(#[case] alignment: Alignment, #[case] expected: &str) {
1433
- let line = Line::from(vec![Span::raw("a🦀b"), Span::raw("c🦀d")]).alignment(alignment);
1434
- // Fill buffer with stuff to ensure the output is indeed padded
1435
- let mut buf = Buffer::filled(Rect::new(0, 0, 10, 1), Cell::new("X"));
1436
- let area = Rect::new(2, 0, 6, 1);
1437
- line.render(area, &mut buf);
1438
- assert_eq!(buf, Buffer::with_lines([expected]));
1439
- }
1440
-
1441
- /// When two spans are rendered after each other the first needs to be padded in accordance
1442
- /// to the skipped unicode width. In this case the first crab does not fit at width 6 which
1443
- /// takes a front white space.
1444
- #[rstest]
1445
- #[case::right_4(4, "c🦀d")]
1446
- #[case::right_5(5, "bc🦀d")]
1447
- #[case::right_6(6, "Xbc🦀d")]
1448
- #[case::right_7(7, "🦀bc🦀d")]
1449
- #[case::right_8(8, "a🦀bc🦀d")]
1450
- fn render_right_aligned_multi_span(#[case] buf_width: u16, #[case] expected: &str) {
1451
- let line = Line::from(vec![Span::raw("a🦀b"), Span::raw("c🦀d")]).right_aligned();
1452
- let area = Rect::new(0, 0, buf_width, 1);
1453
- // Fill buffer with stuff to ensure the output is indeed padded
1454
- let mut buf = Buffer::filled(area, Cell::new("X"));
1455
- line.render(buf.area, &mut buf);
1456
- assert_eq!(buf, Buffer::with_lines([expected]));
1457
- }
1458
-
1459
- /// Part of a regression test for <https://github.com/ratatui/ratatui/issues/1032> which
1460
- /// found panics with truncating lines that contained multi-byte characters.
1461
- ///
1462
- /// Flag emoji are actually two independent characters, so they can be truncated in the
1463
- /// middle of the emoji. This test documents just the emoji part of the test.
1464
- #[test]
1465
- fn flag_emoji() {
1466
- let str = "🇺🇸1234";
1467
- assert_eq!(str.len(), 12); // flag is 4 bytes
1468
- assert_eq!(str.chars().count(), 6); // flag is 2 chars
1469
- assert_eq!(str.graphemes(true).count(), 5); // flag is 1 grapheme
1470
- assert_eq!(str.width(), 6); // flag is 2 display width
1471
- }
1472
-
1473
- /// Part of a regression test for <https://github.com/ratatui/ratatui/issues/1032> which
1474
- /// found panics with truncating lines that contained multi-byte characters.
1475
- #[rstest]
1476
- #[case::flag_1(1, " ")]
1477
- #[case::flag_2(2, "🇺🇸")]
1478
- #[case::flag_3(3, "🇺🇸1")]
1479
- #[case::flag_4(4, "🇺🇸12")]
1480
- #[case::flag_5(5, "🇺🇸123")]
1481
- #[case::flag_6(6, "🇺🇸1234")]
1482
- #[case::flag_7(7, "🇺🇸1234 ")]
1483
- fn render_truncates_flag(#[case] buf_width: u16, #[case] expected: &str) {
1484
- let line = Line::from("🇺🇸1234");
1485
- let mut buf = Buffer::empty(Rect::new(0, 0, buf_width, 1));
1486
- line.render(buf.area, &mut buf);
1487
- assert_eq!(buf, Buffer::with_lines([expected]));
1488
- }
1489
-
1490
- // Buffer width is `u16`. A line can be longer.
1491
- #[rstest]
1492
- #[case::left(Alignment::Left, "This is some content with a some")]
1493
- #[case::right(Alignment::Right, "horribly long Line over u16::MAX")]
1494
- fn render_truncates_very_long_line_of_many_spans(
1495
- #[case] alignment: Alignment,
1496
- #[case] expected: &str,
1497
- ) {
1498
- let part = "This is some content with a somewhat long width to be repeated over and over again to create horribly long Line over u16::MAX";
1499
- let min_width = usize::from(u16::MAX).saturating_add(1);
1500
-
1501
- // width == len as only ASCII is used here
1502
- let factor = min_width.div_ceil(part.len());
1503
-
1504
- let line = Line::from(vec![Span::raw(part); factor]).alignment(alignment);
1505
-
1506
- dbg!(line.width());
1507
- assert!(line.width() >= min_width);
1508
-
1509
- let mut buf = Buffer::empty(Rect::new(0, 0, 32, 1));
1510
- line.render(buf.area, &mut buf);
1511
- assert_eq!(buf, Buffer::with_lines([expected]));
1512
- }
1513
-
1514
- // Buffer width is `u16`. A single span inside a line can be longer.
1515
- #[rstest]
1516
- #[case::left(Alignment::Left, "This is some content with a some")]
1517
- #[case::right(Alignment::Right, "horribly long Line over u16::MAX")]
1518
- fn render_truncates_very_long_single_span_line(
1519
- #[case] alignment: Alignment,
1520
- #[case] expected: &str,
1521
- ) {
1522
- let part = "This is some content with a somewhat long width to be repeated over and over again to create horribly long Line over u16::MAX";
1523
- let min_width = usize::from(u16::MAX).saturating_add(1);
1524
-
1525
- // width == len as only ASCII is used here
1526
- let factor = min_width.div_ceil(part.len());
1527
-
1528
- let line = Line::from(vec![Span::raw(part.repeat(factor))]).alignment(alignment);
1529
-
1530
- dbg!(line.width());
1531
- assert!(line.width() >= min_width);
1532
-
1533
- let mut buf = Buffer::empty(Rect::new(0, 0, 32, 1));
1534
- line.render(buf.area, &mut buf);
1535
- assert_eq!(buf, Buffer::with_lines([expected]));
1536
- }
1537
-
1538
- #[test]
1539
- fn render_with_newlines() {
1540
- let mut buf = Buffer::empty(Rect::new(0, 0, 11, 1));
1541
- Line::from("Hello\nworld!").render(Rect::new(0, 0, 11, 1), &mut buf);
1542
- assert_eq!(buf, Buffer::with_lines(["Helloworld!"]));
1543
- }
1544
- }
1545
-
1546
- mod iterators {
1547
- use super::*;
1548
-
1549
- /// a fixture used in the tests below to avoid repeating the same setup
1550
- #[fixture]
1551
- fn hello_world() -> Line<'static> {
1552
- Line::from(vec![
1553
- Span::styled("Hello ", Color::Blue),
1554
- Span::styled("world!", Color::Green),
1555
- ])
1556
- }
1557
-
1558
- #[rstest]
1559
- fn iter(hello_world: Line<'_>) {
1560
- let mut iter = hello_world.iter();
1561
- assert_eq!(iter.next(), Some(&Span::styled("Hello ", Color::Blue)));
1562
- assert_eq!(iter.next(), Some(&Span::styled("world!", Color::Green)));
1563
- assert_eq!(iter.next(), None);
1564
- }
1565
-
1566
- #[rstest]
1567
- fn iter_mut(mut hello_world: Line<'_>) {
1568
- let mut iter = hello_world.iter_mut();
1569
- assert_eq!(iter.next(), Some(&mut Span::styled("Hello ", Color::Blue)));
1570
- assert_eq!(iter.next(), Some(&mut Span::styled("world!", Color::Green)));
1571
- assert_eq!(iter.next(), None);
1572
- }
1573
-
1574
- #[rstest]
1575
- fn into_iter(hello_world: Line<'_>) {
1576
- let mut iter = hello_world.into_iter();
1577
- assert_eq!(iter.next(), Some(Span::styled("Hello ", Color::Blue)));
1578
- assert_eq!(iter.next(), Some(Span::styled("world!", Color::Green)));
1579
- assert_eq!(iter.next(), None);
1580
- }
1581
-
1582
- #[rstest]
1583
- fn into_iter_ref(hello_world: Line<'_>) {
1584
- let mut iter = (&hello_world).into_iter();
1585
- assert_eq!(iter.next(), Some(&Span::styled("Hello ", Color::Blue)));
1586
- assert_eq!(iter.next(), Some(&Span::styled("world!", Color::Green)));
1587
- assert_eq!(iter.next(), None);
1588
- }
1589
-
1590
- #[test]
1591
- fn into_iter_mut_ref() {
1592
- let mut hello_world = Line::from(vec![
1593
- Span::styled("Hello ", Color::Blue),
1594
- Span::styled("world!", Color::Green),
1595
- ]);
1596
- let mut iter = (&mut hello_world).into_iter();
1597
- assert_eq!(iter.next(), Some(&mut Span::styled("Hello ", Color::Blue)));
1598
- assert_eq!(iter.next(), Some(&mut Span::styled("world!", Color::Green)));
1599
- assert_eq!(iter.next(), None);
1600
- }
1601
-
1602
- #[rstest]
1603
- fn for_loop_ref(hello_world: Line<'_>) {
1604
- let mut result = String::new();
1605
- for span in &hello_world {
1606
- result.push_str(span.content.as_ref());
1607
- }
1608
- assert_eq!(result, "Hello world!");
1609
- }
1610
-
1611
- #[rstest]
1612
- fn for_loop_mut_ref() {
1613
- let mut hello_world = Line::from(vec![
1614
- Span::styled("Hello ", Color::Blue),
1615
- Span::styled("world!", Color::Green),
1616
- ]);
1617
- let mut result = String::new();
1618
- for span in &mut hello_world {
1619
- result.push_str(span.content.as_ref());
1620
- }
1621
- assert_eq!(result, "Hello world!");
1622
- }
1623
-
1624
- #[rstest]
1625
- fn for_loop_into(hello_world: Line<'_>) {
1626
- let mut result = String::new();
1627
- for span in hello_world {
1628
- result.push_str(span.content.as_ref());
1629
- }
1630
- assert_eq!(result, "Hello world!");
1631
- }
1632
- }
1633
-
1634
- #[rstest]
1635
- #[case::empty(Line::default(), "Line::default()")]
1636
- #[case::raw(Line::raw("Hello, world!"), r#"Line::from("Hello, world!")"#)]
1637
- #[case::styled(
1638
- Line::styled("Hello, world!", Color::Yellow),
1639
- r#"Line::from("Hello, world!").yellow()"#
1640
- )]
1641
- #[case::styled_complex(
1642
- Line::from(String::from("Hello, world!")).green().on_blue().bold().italic().not_dim(),
1643
- r#"Line::from("Hello, world!").green().on_blue().bold().italic().not_dim()"#
1644
- )]
1645
- #[case::styled_span(
1646
- Line::from(Span::styled("Hello, world!", Color::Yellow)),
1647
- r#"Line::from(Span::from("Hello, world!").yellow())"#
1648
- )]
1649
- #[case::styled_line_and_span(
1650
- Line::from(vec![
1651
- Span::styled("Hello", Color::Yellow),
1652
- Span::styled(" world!", Color::Green),
1653
- ]).italic(),
1654
- r#"Line::from_iter([Span::from("Hello").yellow(), Span::from(" world!").green()]).italic()"#
1655
- )]
1656
- #[case::spans_vec(
1657
- Line::from(vec![
1658
- Span::styled("Hello", Color::Blue),
1659
- Span::styled(" world!", Color::Green),
1660
- ]),
1661
- r#"Line::from_iter([Span::from("Hello").blue(), Span::from(" world!").green()])"#,
1662
- )]
1663
- #[case::left_aligned(
1664
- Line::from("Hello, world!").left_aligned(),
1665
- r#"Line::from("Hello, world!").left_aligned()"#
1666
- )]
1667
- #[case::centered(
1668
- Line::from("Hello, world!").centered(),
1669
- r#"Line::from("Hello, world!").centered()"#
1670
- )]
1671
- #[case::right_aligned(
1672
- Line::from("Hello, world!").right_aligned(),
1673
- r#"Line::from("Hello, world!").right_aligned()"#
1674
- )]
1675
- fn debug(#[case] line: Line, #[case] expected: &str) {
1676
- assert_eq!(format!("{line:?}"), expected);
1677
- }
1678
- }