@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,1388 +0,0 @@
1
- use alloc::vec;
2
- use alloc::vec::Vec;
3
- use core::ops::{Index, IndexMut};
4
- use core::{cmp, fmt};
5
-
6
- use unicode_segmentation::UnicodeSegmentation;
7
- use unicode_width::UnicodeWidthStr;
8
-
9
- use crate::buffer::Cell;
10
- use crate::layout::{Position, Rect};
11
- use crate::style::Style;
12
- use crate::text::{Line, Span};
13
-
14
- /// A buffer that maps to the desired content of the terminal after the draw call
15
- ///
16
- /// No widget in the library interacts directly with the terminal. Instead each of them is required
17
- /// to draw their state to an intermediate buffer. It is basically a grid where each cell contains
18
- /// a grapheme, a foreground color and a background color. This grid will then be used to output
19
- /// the appropriate escape sequences and characters to draw the UI as the user has defined it.
20
- ///
21
- /// # Examples:
22
- ///
23
- /// ```
24
- /// use ratatui_core::buffer::{Buffer, Cell};
25
- /// use ratatui_core::layout::{Position, Rect};
26
- /// use ratatui_core::style::{Color, Style};
27
- ///
28
- /// # fn foo() -> Option<()> {
29
- /// let mut buf = Buffer::empty(Rect {
30
- /// x: 0,
31
- /// y: 0,
32
- /// width: 10,
33
- /// height: 5,
34
- /// });
35
- ///
36
- /// // indexing using Position
37
- /// buf[Position { x: 0, y: 0 }].set_symbol("A");
38
- /// assert_eq!(buf[Position { x: 0, y: 0 }].symbol(), "A");
39
- ///
40
- /// // indexing using (x, y) tuple (which is converted to Position)
41
- /// buf[(0, 1)].set_symbol("B");
42
- /// assert_eq!(buf[(0, 1)].symbol(), "x");
43
- ///
44
- /// // getting an Option instead of panicking if the position is outside the buffer
45
- /// let cell = buf.cell_mut(Position { x: 0, y: 2 })?;
46
- /// cell.set_symbol("C");
47
- ///
48
- /// let cell = buf.cell(Position { x: 0, y: 2 })?;
49
- /// assert_eq!(cell.symbol(), "C");
50
- ///
51
- /// buf.set_string(
52
- /// 3,
53
- /// 0,
54
- /// "string",
55
- /// Style::default().fg(Color::Red).bg(Color::White),
56
- /// );
57
- /// let cell = &buf[(5, 0)]; // cannot move out of buf, so we borrow it
58
- /// assert_eq!(cell.symbol(), "r");
59
- /// assert_eq!(cell.fg, Color::Red);
60
- /// assert_eq!(cell.bg, Color::White);
61
- /// # Some(())
62
- /// # }
63
- /// ```
64
- #[derive(Default, Clone, Eq, PartialEq, Hash)]
65
- #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
66
- pub struct Buffer {
67
- /// The area represented by this buffer
68
- pub area: Rect,
69
- /// The content of the buffer. The length of this Vec should always be equal to area.width *
70
- /// area.height
71
- pub content: Vec<Cell>,
72
- }
73
-
74
- impl Buffer {
75
- /// Returns a Buffer with all cells set to the default one
76
- #[must_use]
77
- pub fn empty(area: Rect) -> Self {
78
- Self::filled(area, Cell::EMPTY)
79
- }
80
-
81
- /// Returns a Buffer with all cells initialized with the attributes of the given Cell
82
- #[must_use]
83
- pub fn filled(area: Rect, cell: Cell) -> Self {
84
- let size = area.area() as usize;
85
- let content = vec![cell; size];
86
- Self { area, content }
87
- }
88
-
89
- /// Returns a Buffer containing the given lines
90
- #[must_use]
91
- pub fn with_lines<'a, Iter>(lines: Iter) -> Self
92
- where
93
- Iter: IntoIterator,
94
- Iter::Item: Into<Line<'a>>,
95
- {
96
- let lines = lines.into_iter().map(Into::into).collect::<Vec<_>>();
97
- let height = lines.len() as u16;
98
- let width = lines.iter().map(Line::width).max().unwrap_or_default() as u16;
99
- let mut buffer = Self::empty(Rect::new(0, 0, width, height));
100
- for (y, line) in lines.iter().enumerate() {
101
- buffer.set_line(0, y as u16, line, width);
102
- }
103
- buffer
104
- }
105
-
106
- /// Returns the content of the buffer as a slice
107
- pub fn content(&self) -> &[Cell] {
108
- &self.content
109
- }
110
-
111
- /// Returns the area covered by this buffer
112
- pub const fn area(&self) -> &Rect {
113
- &self.area
114
- }
115
-
116
- /// Returns a reference to the [`Cell`] at the given coordinates
117
- ///
118
- /// Callers should use [`Buffer[]`](Self::index) or [`Buffer::cell`] instead of this method.
119
- ///
120
- /// Note: idiomatically methods named `get` usually return `Option<&T>`, but this method panics
121
- /// instead. This is kept for backwards compatibility. See [`cell`](Self::cell) for a safe
122
- /// alternative.
123
- ///
124
- /// # Panics
125
- ///
126
- /// Panics if the index is out of bounds.
127
- #[track_caller]
128
- #[deprecated = "use `Buffer[(x, y)]` instead. To avoid panicking, use `Buffer::cell((x, y))`. Both methods take `impl Into<Position>`."]
129
- #[must_use]
130
- pub fn get(&self, x: u16, y: u16) -> &Cell {
131
- let i = self.index_of(x, y);
132
- &self.content[i]
133
- }
134
-
135
- /// Returns a mutable reference to the [`Cell`] at the given coordinates.
136
- ///
137
- /// Callers should use [`Buffer[]`](Self::index_mut) or [`Buffer::cell_mut`] instead of this
138
- /// method.
139
- ///
140
- /// Note: idiomatically methods named `get_mut` usually return `Option<&mut T>`, but this method
141
- /// panics instead. This is kept for backwards compatibility. See [`cell_mut`](Self::cell_mut)
142
- /// for a safe alternative.
143
- ///
144
- /// # Panics
145
- ///
146
- /// Panics if the position is outside the `Buffer`'s area.
147
- #[track_caller]
148
- #[deprecated = "use `Buffer[(x, y)]` instead. To avoid panicking, use `Buffer::cell_mut((x, y))`. Both methods take `impl Into<Position>`."]
149
- #[must_use]
150
- pub fn get_mut(&mut self, x: u16, y: u16) -> &mut Cell {
151
- let i = self.index_of(x, y);
152
- &mut self.content[i]
153
- }
154
-
155
- /// Returns a reference to the [`Cell`] at the given position or [`None`] if the position is
156
- /// outside the `Buffer`'s area.
157
- ///
158
- /// This method accepts any value that can be converted to [`Position`] (e.g. `(x, y)` or
159
- /// `Position::new(x, y)`).
160
- ///
161
- /// For a method that panics when the position is outside the buffer instead of returning
162
- /// `None`, use [`Buffer[]`](Self::index).
163
- ///
164
- /// # Examples
165
- ///
166
- /// ```rust
167
- /// use ratatui_core::buffer::{Buffer, Cell};
168
- /// use ratatui_core::layout::{Position, Rect};
169
- ///
170
- /// let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 10));
171
- ///
172
- /// assert_eq!(buffer.cell(Position::new(0, 0)), Some(&Cell::default()));
173
- /// assert_eq!(buffer.cell(Position::new(10, 10)), None);
174
- /// assert_eq!(buffer.cell((0, 0)), Some(&Cell::default()));
175
- /// assert_eq!(buffer.cell((10, 10)), None);
176
- /// ```
177
- #[must_use]
178
- pub fn cell<P: Into<Position>>(&self, position: P) -> Option<&Cell> {
179
- let position = position.into();
180
- let index = self.index_of_opt(position)?;
181
- self.content.get(index)
182
- }
183
-
184
- /// Returns a mutable reference to the [`Cell`] at the given position or [`None`] if the
185
- /// position is outside the `Buffer`'s area.
186
- ///
187
- /// This method accepts any value that can be converted to [`Position`] (e.g. `(x, y)` or
188
- /// `Position::new(x, y)`).
189
- ///
190
- /// For a method that panics when the position is outside the buffer instead of returning
191
- /// `None`, use [`Buffer[]`](Self::index_mut).
192
- ///
193
- /// # Examples
194
- ///
195
- /// ```rust
196
- /// use ratatui_core::buffer::{Buffer, Cell};
197
- /// use ratatui_core::layout::{Position, Rect};
198
- /// use ratatui_core::style::{Color, Style};
199
- /// let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 10));
200
- ///
201
- /// if let Some(cell) = buffer.cell_mut(Position::new(0, 0)) {
202
- /// cell.set_symbol("A");
203
- /// }
204
- /// if let Some(cell) = buffer.cell_mut((0, 0)) {
205
- /// cell.set_style(Style::default().fg(Color::Red));
206
- /// }
207
- /// ```
208
- #[must_use]
209
- pub fn cell_mut<P: Into<Position>>(&mut self, position: P) -> Option<&mut Cell> {
210
- let position = position.into();
211
- let index = self.index_of_opt(position)?;
212
- self.content.get_mut(index)
213
- }
214
-
215
- /// Returns the index in the `Vec<Cell>` for the given global (x, y) coordinates.
216
- ///
217
- /// Global coordinates are offset by the Buffer's area offset (`x`/`y`).
218
- ///
219
- /// Usage discouraged, as it exposes `self.content` as a linearly indexable array, which limits
220
- /// potential future abstractions. See <https://github.com/ratatui/ratatui/issues/1122>.
221
- ///
222
- /// # Examples
223
- ///
224
- /// ```
225
- /// use ratatui_core::buffer::Buffer;
226
- /// use ratatui_core::layout::Rect;
227
- ///
228
- /// let buffer = Buffer::empty(Rect::new(200, 100, 10, 10));
229
- /// // Global coordinates to the top corner of this buffer's area
230
- /// assert_eq!(buffer.index_of(200, 100), 0);
231
- /// ```
232
- ///
233
- /// # Panics
234
- ///
235
- /// Panics when given an coordinate that is outside of this Buffer's area.
236
- ///
237
- /// ```should_panic
238
- /// use ratatui_core::buffer::Buffer;
239
- /// use ratatui_core::layout::Rect;
240
- ///
241
- /// let buffer = Buffer::empty(Rect::new(200, 100, 10, 10));
242
- /// // Top coordinate is outside of the buffer in global coordinate space, as the Buffer's area
243
- /// // starts at (200, 100).
244
- /// buffer.index_of(0, 0); // Panics
245
- /// ```
246
- #[track_caller]
247
- #[must_use]
248
- pub fn index_of(&self, x: u16, y: u16) -> usize {
249
- self.index_of_opt(Position { x, y }).unwrap_or_else(|| {
250
- panic!(
251
- "index outside of buffer: the area is {area:?} but index is ({x}, {y})",
252
- area = self.area,
253
- )
254
- })
255
- }
256
-
257
- /// Returns the index in the `Vec<Cell>` for the given global (x, y) coordinates.
258
- ///
259
- /// Returns `None` if the given coordinates are outside of the Buffer's area.
260
- ///
261
- /// Note that this is private because of <https://github.com/ratatui/ratatui/issues/1122>
262
- #[must_use]
263
- const fn index_of_opt(&self, position: Position) -> Option<usize> {
264
- let area = self.area;
265
- if !area.contains(position) {
266
- return None;
267
- }
268
- // remove offset
269
- let y = (position.y - self.area.y) as usize;
270
- let x = (position.x - self.area.x) as usize;
271
- let width = self.area.width as usize;
272
- Some(y * width + x)
273
- }
274
-
275
- /// Returns the (global) coordinates of a cell given its index.
276
- ///
277
- /// Global coordinates are offset by the Buffer's area offset (`x`/`y`).
278
- ///
279
- /// Usage discouraged, as it exposes `self.content` as a linearly indexable array, which limits
280
- /// potential future abstractions. See <https://github.com/ratatui/ratatui/issues/1122>.
281
- ///
282
- /// # Examples
283
- ///
284
- /// ```
285
- /// use ratatui_core::buffer::Buffer;
286
- /// use ratatui_core::layout::Rect;
287
- ///
288
- /// let rect = Rect::new(200, 100, 10, 10);
289
- /// let buffer = Buffer::empty(rect);
290
- /// assert_eq!(buffer.pos_of(0), (200, 100));
291
- /// assert_eq!(buffer.pos_of(14), (204, 101));
292
- /// ```
293
- ///
294
- /// # Panics
295
- ///
296
- /// Panics when given an index that is outside the Buffer's content.
297
- ///
298
- /// ```should_panic
299
- /// use ratatui_core::buffer::Buffer;
300
- /// use ratatui_core::layout::Rect;
301
- ///
302
- /// let rect = Rect::new(0, 0, 10, 10); // 100 cells in total
303
- /// let buffer = Buffer::empty(rect);
304
- /// // Index 100 is the 101th cell, which lies outside of the area of this Buffer.
305
- /// buffer.pos_of(100); // Panics
306
- /// ```
307
- #[must_use]
308
- pub fn pos_of(&self, index: usize) -> (u16, u16) {
309
- debug_assert!(
310
- index < self.content.len(),
311
- "Trying to get the coords of a cell outside the buffer: i={index} len={}",
312
- self.content.len()
313
- );
314
- let x = index % self.area.width as usize + self.area.x as usize;
315
- let y = index / self.area.width as usize + self.area.y as usize;
316
- (
317
- u16::try_from(x).expect("x overflow. This should never happen as area.width is u16"),
318
- u16::try_from(y).expect("y overflow. This should never happen as area.height is u16"),
319
- )
320
- }
321
-
322
- /// Print a string, starting at the position (x, y)
323
- pub fn set_string<T, S>(&mut self, x: u16, y: u16, string: T, style: S)
324
- where
325
- T: AsRef<str>,
326
- S: Into<Style>,
327
- {
328
- self.set_stringn(x, y, string, usize::MAX, style);
329
- }
330
-
331
- /// Print at most the first n characters of a string if enough space is available
332
- /// until the end of the line. Skips zero-width graphemes and control characters.
333
- ///
334
- /// Use [`Buffer::set_string`] when the maximum amount of characters can be printed.
335
- pub fn set_stringn<T, S>(
336
- &mut self,
337
- mut x: u16,
338
- y: u16,
339
- string: T,
340
- max_width: usize,
341
- style: S,
342
- ) -> (u16, u16)
343
- where
344
- T: AsRef<str>,
345
- S: Into<Style>,
346
- {
347
- let max_width = max_width.try_into().unwrap_or(u16::MAX);
348
- let mut remaining_width = self.area.right().saturating_sub(x).min(max_width);
349
- let graphemes = UnicodeSegmentation::graphemes(string.as_ref(), true)
350
- .filter(|symbol| !symbol.contains(char::is_control))
351
- .map(|symbol| (symbol, symbol.width() as u16))
352
- .filter(|(_symbol, width)| *width > 0)
353
- .map_while(|(symbol, width)| {
354
- remaining_width = remaining_width.checked_sub(width)?;
355
- Some((symbol, width))
356
- });
357
- let style = style.into();
358
- for (symbol, width) in graphemes {
359
- self[(x, y)].set_symbol(symbol).set_style(style);
360
- let next_symbol = x + width;
361
- x += 1;
362
- // Reset following cells if multi-width (they would be hidden by the grapheme),
363
- while x < next_symbol {
364
- self[(x, y)].reset();
365
- x += 1;
366
- }
367
- }
368
- (x, y)
369
- }
370
-
371
- /// Print a line, starting at the position (x, y)
372
- pub fn set_line(&mut self, x: u16, y: u16, line: &Line<'_>, max_width: u16) -> (u16, u16) {
373
- let mut remaining_width = max_width;
374
- let mut x = x;
375
- for span in line {
376
- if remaining_width == 0 {
377
- break;
378
- }
379
- let pos = self.set_stringn(
380
- x,
381
- y,
382
- span.content.as_ref(),
383
- remaining_width as usize,
384
- line.style.patch(span.style),
385
- );
386
- let w = pos.0.saturating_sub(x);
387
- x = pos.0;
388
- remaining_width = remaining_width.saturating_sub(w);
389
- }
390
- (x, y)
391
- }
392
-
393
- /// Print a span, starting at the position (x, y)
394
- pub fn set_span(&mut self, x: u16, y: u16, span: &Span<'_>, max_width: u16) -> (u16, u16) {
395
- self.set_stringn(x, y, &span.content, max_width as usize, span.style)
396
- }
397
-
398
- /// Set the style of all cells in the given area.
399
- ///
400
- /// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
401
- /// your own type that implements [`Into<Style>`]).
402
- ///
403
- /// [`Color`]: crate::style::Color
404
- pub fn set_style<S: Into<Style>>(&mut self, area: Rect, style: S) {
405
- let style = style.into();
406
- let area = self.area.intersection(area);
407
- for y in area.top()..area.bottom() {
408
- for x in area.left()..area.right() {
409
- self[(x, y)].set_style(style);
410
- }
411
- }
412
- }
413
-
414
- /// Resize the buffer so that the mapped area matches the given area and that the buffer
415
- /// length is equal to area.width * area.height
416
- pub fn resize(&mut self, area: Rect) {
417
- let length = area.area() as usize;
418
- if self.content.len() > length {
419
- self.content.truncate(length);
420
- } else {
421
- self.content.resize(length, Cell::EMPTY);
422
- }
423
- self.area = area;
424
- }
425
-
426
- /// Reset all cells in the buffer
427
- pub fn reset(&mut self) {
428
- for cell in &mut self.content {
429
- cell.reset();
430
- }
431
- }
432
-
433
- /// Merge an other buffer into this one
434
- pub fn merge(&mut self, other: &Self) {
435
- let area = self.area.union(other.area);
436
- self.content.resize(area.area() as usize, Cell::EMPTY);
437
-
438
- // Move original content to the appropriate space
439
- let size = self.area.area() as usize;
440
- for i in (0..size).rev() {
441
- let (x, y) = self.pos_of(i);
442
- // New index in content
443
- let k = ((y - area.y) * area.width + x - area.x) as usize;
444
- if i != k {
445
- self.content[k] = self.content[i].clone();
446
- self.content[i].reset();
447
- }
448
- }
449
-
450
- // Push content of the other buffer into this one (may erase previous
451
- // data)
452
- let size = other.area.area() as usize;
453
- for i in 0..size {
454
- let (x, y) = other.pos_of(i);
455
- // New index in content
456
- let k = ((y - area.y) * area.width + x - area.x) as usize;
457
- self.content[k] = other.content[i].clone();
458
- }
459
- self.area = area;
460
- }
461
-
462
- /// Builds a minimal sequence of coordinates and Cells necessary to update the UI from
463
- /// self to other.
464
- ///
465
- /// We're assuming that buffers are well-formed, that is no double-width cell is followed by
466
- /// a non-blank cell.
467
- ///
468
- /// # Multi-width characters handling:
469
- ///
470
- /// ```text
471
- /// (Index:) `01`
472
- /// Prev: `コ`
473
- /// Next: `aa`
474
- /// Updates: `0: a, 1: a'
475
- /// ```
476
- ///
477
- /// ```text
478
- /// (Index:) `01`
479
- /// Prev: `a `
480
- /// Next: `コ`
481
- /// Updates: `0: コ` (double width symbol at index 0 - skip index 1)
482
- /// ```
483
- ///
484
- /// ```text
485
- /// (Index:) `012`
486
- /// Prev: `aaa`
487
- /// Next: `aコ`
488
- /// Updates: `0: a, 1: コ` (double width symbol at index 1 - skip index 2)
489
- /// ```
490
- pub fn diff<'a>(&self, other: &'a Self) -> Vec<(u16, u16, &'a Cell)> {
491
- let previous_buffer = &self.content;
492
- let next_buffer = &other.content;
493
-
494
- let mut updates: Vec<(u16, u16, &Cell)> = vec![];
495
- // Cells invalidated by drawing/replacing preceding multi-width characters:
496
- let mut invalidated: usize = 0;
497
- // Cells from the current buffer to skip due to preceding multi-width characters taking
498
- // their place (the skipped cells should be blank anyway), or due to per-cell-skipping:
499
- let mut to_skip: usize = 0;
500
- for (i, (current, previous)) in next_buffer.iter().zip(previous_buffer.iter()).enumerate() {
501
- if !current.skip && (current != previous || invalidated > 0) && to_skip == 0 {
502
- // If the current cell is multi-width, ensure the trailing cells are explicitly
503
- // cleared when they previously contained non-blank content. Some terminals do not
504
- // reliably clear the trailing cell(s) when printing a wide grapheme, which can
505
- // result in visual artifacts (e.g., leftover characters). Emit those clears
506
- // before reprinting the visible glyph so backends that translate the diff stream
507
- // directly into cursor moves + prints do not leave a blank trailing cell behind.
508
- let symbol = current.symbol();
509
- let cell_width = symbol.width();
510
- // Some terminals also leave stale text behind for standard wide glyphs
511
- // (for example CJK) during full-screen redraw churn, so emit the trailing
512
- // clears for every multi-width symbol when the trailing cells differ.
513
- if cell_width > 1 {
514
- for k in 1..cell_width {
515
- let j = i + k;
516
- // Make sure that we are still inside the buffer.
517
- if j >= next_buffer.len() || j >= previous_buffer.len() {
518
- break;
519
- }
520
- let prev_trailing = &previous_buffer[j];
521
- let next_trailing = &next_buffer[j];
522
- if !next_trailing.skip && prev_trailing != next_trailing {
523
- let (tx, ty) = self.pos_of(j);
524
- // Push an explicit update for the trailing cell first.
525
- // This is expected to be a blank cell, but we use the actual
526
- // content from the next buffer to handle cases where
527
- // the user has explicitly set something else.
528
- updates.push((tx, ty, next_trailing));
529
- }
530
- }
531
- }
532
-
533
- let (x, y) = self.pos_of(i);
534
- updates.push((x, y, &next_buffer[i]));
535
- }
536
-
537
- to_skip = current.symbol().width().saturating_sub(1);
538
-
539
- let affected_width = cmp::max(current.symbol().width(), previous.symbol().width());
540
- invalidated = cmp::max(affected_width, invalidated).saturating_sub(1);
541
- }
542
- updates
543
- }
544
- }
545
-
546
- impl<P: Into<Position>> Index<P> for Buffer {
547
- type Output = Cell;
548
-
549
- /// Returns a reference to the [`Cell`] at the given position.
550
- ///
551
- /// This method accepts any value that can be converted to [`Position`] (e.g. `(x, y)` or
552
- /// `Position::new(x, y)`).
553
- ///
554
- /// # Panics
555
- ///
556
- /// May panic if the given position is outside the buffer's area. For a method that returns
557
- /// `None` instead of panicking, use [`Buffer::cell`](Self::cell).
558
- ///
559
- /// # Examples
560
- ///
561
- /// ```
562
- /// use ratatui_core::buffer::{Buffer, Cell};
563
- /// use ratatui_core::layout::{Position, Rect};
564
- ///
565
- /// let buf = Buffer::empty(Rect::new(0, 0, 10, 10));
566
- /// let cell = &buf[(0, 0)];
567
- /// let cell = &buf[Position::new(0, 0)];
568
- /// ```
569
- fn index(&self, position: P) -> &Self::Output {
570
- let position = position.into();
571
- let index = self.index_of(position.x, position.y);
572
- &self.content[index]
573
- }
574
- }
575
-
576
- impl<P: Into<Position>> IndexMut<P> for Buffer {
577
- /// Returns a mutable reference to the [`Cell`] at the given position.
578
- ///
579
- /// This method accepts any value that can be converted to [`Position`] (e.g. `(x, y)` or
580
- /// `Position::new(x, y)`).
581
- ///
582
- /// # Panics
583
- ///
584
- /// May panic if the given position is outside the buffer's area. For a method that returns
585
- /// `None` instead of panicking, use [`Buffer::cell_mut`](Self::cell_mut).
586
- ///
587
- /// # Examples
588
- ///
589
- /// ```
590
- /// use ratatui_core::buffer::{Buffer, Cell};
591
- /// use ratatui_core::layout::{Position, Rect};
592
- ///
593
- /// let mut buf = Buffer::empty(Rect::new(0, 0, 10, 10));
594
- /// buf[(0, 0)].set_symbol("A");
595
- /// buf[Position::new(0, 0)].set_symbol("B");
596
- /// ```
597
- fn index_mut(&mut self, position: P) -> &mut Self::Output {
598
- let position = position.into();
599
- let index = self.index_of(position.x, position.y);
600
- &mut self.content[index]
601
- }
602
- }
603
-
604
- impl fmt::Debug for Buffer {
605
- /// Writes a debug representation of the buffer to the given formatter.
606
- ///
607
- /// The format is like a pretty printed struct, with the following fields:
608
- /// * `area`: displayed as `Rect { x: 1, y: 2, width: 3, height: 4 }`
609
- /// * `content`: displayed as a list of strings representing the content of the buffer
610
- /// * `styles`: displayed as a list of: `{ x: 1, y: 2, fg: Color::Red, bg: Color::Blue,
611
- /// modifier: Modifier::BOLD }` only showing a value when there is a change in style.
612
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
613
- f.write_fmt(format_args!("Buffer {{\n area: {:?}", &self.area))?;
614
-
615
- if self.area.is_empty() {
616
- return f.write_str("\n}");
617
- }
618
-
619
- f.write_str(",\n content: [\n")?;
620
- let mut last_style = None;
621
- let mut styles = vec![];
622
- for (y, line) in self.content.chunks(self.area.width as usize).enumerate() {
623
- let mut overwritten = vec![];
624
- let mut skip: usize = 0;
625
- f.write_str(" \"")?;
626
- for (x, c) in line.iter().enumerate() {
627
- if skip == 0 {
628
- f.write_str(c.symbol())?;
629
- } else {
630
- overwritten.push((x, c.symbol()));
631
- }
632
- skip = cmp::max(skip, c.symbol().width()).saturating_sub(1);
633
- #[cfg(feature = "underline-color")]
634
- {
635
- let style = (c.fg, c.bg, c.underline_color, c.modifier);
636
- if last_style != Some(style) {
637
- last_style = Some(style);
638
- styles.push((x, y, c.fg, c.bg, c.underline_color, c.modifier));
639
- }
640
- }
641
- #[cfg(not(feature = "underline-color"))]
642
- {
643
- let style = (c.fg, c.bg, c.modifier);
644
- if last_style != Some(style) {
645
- last_style = Some(style);
646
- styles.push((x, y, c.fg, c.bg, c.modifier));
647
- }
648
- }
649
- }
650
- f.write_str("\",")?;
651
- if !overwritten.is_empty() {
652
- f.write_fmt(format_args!(
653
- " // hidden by multi-width symbols: {overwritten:?}"
654
- ))?;
655
- }
656
- f.write_str("\n")?;
657
- }
658
- f.write_str(" ],\n styles: [\n")?;
659
- for s in styles {
660
- #[cfg(feature = "underline-color")]
661
- f.write_fmt(format_args!(
662
- " x: {}, y: {}, fg: {:?}, bg: {:?}, underline: {:?}, modifier: {:?},\n",
663
- s.0, s.1, s.2, s.3, s.4, s.5
664
- ))?;
665
- #[cfg(not(feature = "underline-color"))]
666
- f.write_fmt(format_args!(
667
- " x: {}, y: {}, fg: {:?}, bg: {:?}, modifier: {:?},\n",
668
- s.0, s.1, s.2, s.3, s.4
669
- ))?;
670
- }
671
- f.write_str(" ]\n}")?;
672
- Ok(())
673
- }
674
- }
675
-
676
- #[cfg(test)]
677
- mod tests {
678
- use alloc::format;
679
- use alloc::string::ToString;
680
- use core::iter;
681
- use std::{dbg, println};
682
-
683
- use itertools::Itertools;
684
- use rstest::{fixture, rstest};
685
-
686
- use super::*;
687
- use crate::style::{Color, Modifier, Stylize};
688
-
689
- #[test]
690
- fn debug_empty_buffer() {
691
- let buffer = Buffer::empty(Rect::ZERO);
692
- let result = format!("{buffer:?}");
693
- println!("{result}");
694
- let expected = "Buffer {\n area: Rect { x: 0, y: 0, width: 0, height: 0 }\n}";
695
- assert_eq!(result, expected);
696
- }
697
-
698
- #[cfg(feature = "underline-color")]
699
- #[test]
700
- fn debug_grapheme_override() {
701
- let buffer = Buffer::with_lines(["a🦀b"]);
702
- let result = format!("{buffer:?}");
703
- println!("{result}");
704
- let expected = indoc::indoc!(
705
- r#"
706
- Buffer {
707
- area: Rect { x: 0, y: 0, width: 4, height: 1 },
708
- content: [
709
- "a🦀b", // hidden by multi-width symbols: [(2, " ")]
710
- ],
711
- styles: [
712
- x: 0, y: 0, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
713
- ]
714
- }"#
715
- );
716
- assert_eq!(result, expected);
717
- }
718
-
719
- #[test]
720
- fn debug_some_example() {
721
- let mut buffer = Buffer::empty(Rect::new(0, 0, 12, 2));
722
- buffer.set_string(0, 0, "Hello World!", Style::default());
723
- buffer.set_string(
724
- 0,
725
- 1,
726
- "G'day World!",
727
- Style::default()
728
- .fg(Color::Green)
729
- .bg(Color::Yellow)
730
- .add_modifier(Modifier::BOLD),
731
- );
732
- let result = format!("{buffer:?}");
733
- println!("{result}");
734
- #[cfg(feature = "underline-color")]
735
- let expected = indoc::indoc!(
736
- r#"
737
- Buffer {
738
- area: Rect { x: 0, y: 0, width: 12, height: 2 },
739
- content: [
740
- "Hello World!",
741
- "G'day World!",
742
- ],
743
- styles: [
744
- x: 0, y: 0, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
745
- x: 0, y: 1, fg: Green, bg: Yellow, underline: Reset, modifier: BOLD,
746
- ]
747
- }"#
748
- );
749
- #[cfg(not(feature = "underline-color"))]
750
- let expected = indoc::indoc!(
751
- r#"
752
- Buffer {
753
- area: Rect { x: 0, y: 0, width: 12, height: 2 },
754
- content: [
755
- "Hello World!",
756
- "G'day World!",
757
- ],
758
- styles: [
759
- x: 0, y: 0, fg: Reset, bg: Reset, modifier: NONE,
760
- x: 0, y: 1, fg: Green, bg: Yellow, modifier: BOLD,
761
- ]
762
- }"#
763
- );
764
-
765
- assert_eq!(result, expected);
766
- }
767
-
768
- #[test]
769
- fn it_translates_to_and_from_coordinates() {
770
- let rect = Rect::new(200, 100, 50, 80);
771
- let buf = Buffer::empty(rect);
772
-
773
- // First cell is at the upper left corner.
774
- assert_eq!(buf.pos_of(0), (200, 100));
775
- assert_eq!(buf.index_of(200, 100), 0);
776
-
777
- // Last cell is in the lower right.
778
- assert_eq!(buf.pos_of(buf.content.len() - 1), (249, 179));
779
- assert_eq!(buf.index_of(249, 179), buf.content.len() - 1);
780
- }
781
-
782
- #[test]
783
- #[should_panic(expected = "outside the buffer")]
784
- fn pos_of_panics_on_out_of_bounds() {
785
- let rect = Rect::new(0, 0, 10, 10);
786
- let buf = Buffer::empty(rect);
787
-
788
- // There are a total of 100 cells; zero-indexed means that 100 would be the 101st cell.
789
- let _ = buf.pos_of(100);
790
- }
791
-
792
- #[rstest]
793
- #[case::left(9, 10)]
794
- #[case::top(10, 9)]
795
- #[case::right(20, 10)]
796
- #[case::bottom(10, 20)]
797
- #[should_panic(
798
- expected = "index outside of buffer: the area is Rect { x: 10, y: 10, width: 10, height: 10 } but index is"
799
- )]
800
- fn index_of_panics_on_out_of_bounds(#[case] x: u16, #[case] y: u16) {
801
- let _ = Buffer::empty(Rect::new(10, 10, 10, 10)).index_of(x, y);
802
- }
803
-
804
- #[test]
805
- fn test_cell() {
806
- let buf = Buffer::with_lines(["Hello", "World"]);
807
-
808
- let mut expected = Cell::default();
809
- expected.set_symbol("H");
810
-
811
- assert_eq!(buf.cell((0, 0)), Some(&expected));
812
- assert_eq!(buf.cell((10, 10)), None);
813
- assert_eq!(buf.cell(Position::new(0, 0)), Some(&expected));
814
- assert_eq!(buf.cell(Position::new(10, 10)), None);
815
- }
816
-
817
- #[test]
818
- fn test_cell_mut() {
819
- let mut buf = Buffer::with_lines(["Hello", "World"]);
820
-
821
- let mut expected = Cell::default();
822
- expected.set_symbol("H");
823
-
824
- assert_eq!(buf.cell_mut((0, 0)), Some(&mut expected));
825
- assert_eq!(buf.cell_mut((10, 10)), None);
826
- assert_eq!(buf.cell_mut(Position::new(0, 0)), Some(&mut expected));
827
- assert_eq!(buf.cell_mut(Position::new(10, 10)), None);
828
- }
829
-
830
- #[test]
831
- fn index() {
832
- let buf = Buffer::with_lines(["Hello", "World"]);
833
-
834
- let mut expected = Cell::default();
835
- expected.set_symbol("H");
836
-
837
- assert_eq!(buf[(0, 0)], expected);
838
- }
839
-
840
- #[rstest]
841
- #[case::left(9, 10)]
842
- #[case::top(10, 9)]
843
- #[case::right(20, 10)]
844
- #[case::bottom(10, 20)]
845
- #[should_panic(
846
- expected = "index outside of buffer: the area is Rect { x: 10, y: 10, width: 10, height: 10 } but index is"
847
- )]
848
- fn index_out_of_bounds_panics(#[case] x: u16, #[case] y: u16) {
849
- let rect = Rect::new(10, 10, 10, 10);
850
- let buf = Buffer::empty(rect);
851
- let _ = buf[(x, y)];
852
- }
853
-
854
- #[test]
855
- fn index_mut() {
856
- let mut buf = Buffer::with_lines(["Cat", "Dog"]);
857
- buf[(0, 0)].set_symbol("B");
858
- buf[Position::new(0, 1)].set_symbol("L");
859
- assert_eq!(buf, Buffer::with_lines(["Bat", "Log"]));
860
- }
861
-
862
- #[rstest]
863
- #[case::left(9, 10)]
864
- #[case::top(10, 9)]
865
- #[case::right(20, 10)]
866
- #[case::bottom(10, 20)]
867
- #[should_panic(
868
- expected = "index outside of buffer: the area is Rect { x: 10, y: 10, width: 10, height: 10 } but index is"
869
- )]
870
- fn index_mut_out_of_bounds_panics(#[case] x: u16, #[case] y: u16) {
871
- let mut buf = Buffer::empty(Rect::new(10, 10, 10, 10));
872
- buf[(x, y)].set_symbol("A");
873
- }
874
-
875
- #[test]
876
- fn set_string() {
877
- let area = Rect::new(0, 0, 5, 1);
878
- let mut buffer = Buffer::empty(area);
879
-
880
- // Zero-width
881
- buffer.set_stringn(0, 0, "aaa", 0, Style::default());
882
- assert_eq!(buffer, Buffer::with_lines([" "]));
883
-
884
- buffer.set_string(0, 0, "aaa", Style::default());
885
- assert_eq!(buffer, Buffer::with_lines(["aaa "]));
886
-
887
- // Width limit:
888
- buffer.set_stringn(0, 0, "bbbbbbbbbbbbbb", 4, Style::default());
889
- assert_eq!(buffer, Buffer::with_lines(["bbbb "]));
890
-
891
- buffer.set_string(0, 0, "12345", Style::default());
892
- assert_eq!(buffer, Buffer::with_lines(["12345"]));
893
-
894
- // Width truncation:
895
- buffer.set_string(0, 0, "123456", Style::default());
896
- assert_eq!(buffer, Buffer::with_lines(["12345"]));
897
-
898
- // multi-line
899
- buffer = Buffer::empty(Rect::new(0, 0, 5, 2));
900
- buffer.set_string(0, 0, "12345", Style::default());
901
- buffer.set_string(0, 1, "67890", Style::default());
902
- assert_eq!(buffer, Buffer::with_lines(["12345", "67890"]));
903
- }
904
-
905
- #[test]
906
- fn set_string_multi_width_overwrite() {
907
- let area = Rect::new(0, 0, 5, 1);
908
- let mut buffer = Buffer::empty(area);
909
-
910
- // multi-width overwrite
911
- buffer.set_string(0, 0, "aaaaa", Style::default());
912
- buffer.set_string(0, 0, "称号", Style::default());
913
- assert_eq!(buffer, Buffer::with_lines(["称号a"]));
914
- }
915
-
916
- #[test]
917
- fn set_string_zero_width() {
918
- assert_eq!("\u{200B}".width(), 0);
919
-
920
- let area = Rect::new(0, 0, 1, 1);
921
- let mut buffer = Buffer::empty(area);
922
-
923
- // Leading grapheme with zero width
924
- let s = "\u{200B}a";
925
- buffer.set_stringn(0, 0, s, 1, Style::default());
926
- assert_eq!(buffer, Buffer::with_lines(["a"]));
927
-
928
- // Trailing grapheme with zero with
929
- let s = "a\u{200B}";
930
- buffer.set_stringn(0, 0, s, 1, Style::default());
931
- assert_eq!(buffer, Buffer::with_lines(["a"]));
932
- }
933
-
934
- #[test]
935
- fn set_string_double_width() {
936
- let area = Rect::new(0, 0, 5, 1);
937
- let mut buffer = Buffer::empty(area);
938
- buffer.set_string(0, 0, "コン", Style::default());
939
- assert_eq!(buffer, Buffer::with_lines(["コン "]));
940
-
941
- // Only 1 space left.
942
- buffer.set_string(0, 0, "コンピ", Style::default());
943
- assert_eq!(buffer, Buffer::with_lines(["コン "]));
944
- }
945
-
946
- #[fixture]
947
- fn small_one_line_buffer() -> Buffer {
948
- Buffer::empty(Rect::new(0, 0, 5, 1))
949
- }
950
-
951
- #[rstest]
952
- #[case::empty("", " ")]
953
- #[case::one("1", "1 ")]
954
- #[case::full("12345", "12345")]
955
- #[case::overflow("123456", "12345")]
956
- fn set_line_raw(
957
- mut small_one_line_buffer: Buffer,
958
- #[case] content: &str,
959
- #[case] expected: &str,
960
- ) {
961
- let line = Line::raw(content);
962
- small_one_line_buffer.set_line(0, 0, &line, 5);
963
-
964
- // note: testing with empty / set_string here instead of with_lines because with_lines calls
965
- // set_line
966
- let mut expected_buffer = Buffer::empty(small_one_line_buffer.area);
967
- expected_buffer.set_string(0, 0, expected, Style::default());
968
- assert_eq!(small_one_line_buffer, expected_buffer);
969
- }
970
-
971
- #[rstest]
972
- #[case::empty("", " ")]
973
- #[case::one("1", "1 ")]
974
- #[case::full("12345", "12345")]
975
- #[case::overflow("123456", "12345")]
976
- fn set_line_styled(
977
- mut small_one_line_buffer: Buffer,
978
- #[case] content: &str,
979
- #[case] expected: &str,
980
- ) {
981
- let color = Color::Blue;
982
- let line = Line::styled(content, color);
983
- small_one_line_buffer.set_line(0, 0, &line, 5);
984
-
985
- // note: manually testing the contents here as the Buffer::with_lines calls set_line
986
- let actual_contents = small_one_line_buffer
987
- .content
988
- .iter()
989
- .map(Cell::symbol)
990
- .join("");
991
- let actual_styles = small_one_line_buffer
992
- .content
993
- .iter()
994
- .map(|c| c.fg)
995
- .collect_vec();
996
-
997
- // set_line only sets the style for non-empty cells (unlike Line::render which sets the
998
- // style for all cells)
999
- let expected_styles = iter::repeat_n(color, content.len().min(5))
1000
- .chain(iter::repeat_n(
1001
- Color::default(),
1002
- 5_usize.saturating_sub(content.len()),
1003
- ))
1004
- .collect_vec();
1005
- assert_eq!(actual_contents, expected);
1006
- assert_eq!(actual_styles, expected_styles);
1007
- }
1008
-
1009
- #[test]
1010
- fn set_style() {
1011
- let mut buffer = Buffer::with_lines(["aaaaa", "bbbbb", "ccccc"]);
1012
- buffer.set_style(Rect::new(0, 1, 5, 1), Style::new().red());
1013
- #[rustfmt::skip]
1014
- let expected = Buffer::with_lines([
1015
- "aaaaa".into(),
1016
- "bbbbb".red(),
1017
- "ccccc".into(),
1018
- ]);
1019
- assert_eq!(buffer, expected);
1020
- }
1021
-
1022
- #[test]
1023
- fn set_style_does_not_panic_when_out_of_area() {
1024
- let mut buffer = Buffer::with_lines(["aaaaa", "bbbbb", "ccccc"]);
1025
- buffer.set_style(Rect::new(0, 1, 10, 3), Style::new().red());
1026
- #[rustfmt::skip]
1027
- let expected = Buffer::with_lines([
1028
- "aaaaa".into(),
1029
- "bbbbb".red(),
1030
- "ccccc".red(),
1031
- ]);
1032
- assert_eq!(buffer, expected);
1033
- }
1034
-
1035
- #[test]
1036
- fn with_lines() {
1037
- #[rustfmt::skip]
1038
- let buffer = Buffer::with_lines([
1039
- "┌────────┐",
1040
- "│コンピュ│",
1041
- "│ーa 上で│",
1042
- "└────────┘",
1043
- ]);
1044
- assert_eq!(buffer.area.x, 0);
1045
- assert_eq!(buffer.area.y, 0);
1046
- assert_eq!(buffer.area.width, 10);
1047
- assert_eq!(buffer.area.height, 4);
1048
- }
1049
-
1050
- #[test]
1051
- fn diff_empty_empty() {
1052
- let area = Rect::new(0, 0, 40, 40);
1053
- let prev = Buffer::empty(area);
1054
- let next = Buffer::empty(area);
1055
- let diff = prev.diff(&next);
1056
- assert_eq!(diff, []);
1057
- }
1058
-
1059
- #[test]
1060
- fn diff_empty_filled() {
1061
- let area = Rect::new(0, 0, 40, 40);
1062
- let prev = Buffer::empty(area);
1063
- let next = Buffer::filled(area, Cell::new("a"));
1064
- let diff = prev.diff(&next);
1065
- assert_eq!(diff.len(), 40 * 40);
1066
- }
1067
-
1068
- #[test]
1069
- fn diff_filled_filled() {
1070
- let area = Rect::new(0, 0, 40, 40);
1071
- let prev = Buffer::filled(area, Cell::new("a"));
1072
- let next = Buffer::filled(area, Cell::new("a"));
1073
- let diff = prev.diff(&next);
1074
- assert_eq!(diff, []);
1075
- }
1076
-
1077
- #[test]
1078
- fn diff_single_width() {
1079
- let prev = Buffer::with_lines([
1080
- " ",
1081
- "┌Title─┐ ",
1082
- "│ │ ",
1083
- "│ │ ",
1084
- "└──────┘ ",
1085
- ]);
1086
- let next = Buffer::with_lines([
1087
- " ",
1088
- "┌TITLE─┐ ",
1089
- "│ │ ",
1090
- "│ │ ",
1091
- "└──────┘ ",
1092
- ]);
1093
- let diff = prev.diff(&next);
1094
- assert_eq!(
1095
- diff,
1096
- [
1097
- (2, 1, &Cell::new("I")),
1098
- (3, 1, &Cell::new("T")),
1099
- (4, 1, &Cell::new("L")),
1100
- (5, 1, &Cell::new("E")),
1101
- ]
1102
- );
1103
- }
1104
-
1105
- #[test]
1106
- fn diff_multi_width() {
1107
- #[rustfmt::skip]
1108
- let prev = Buffer::with_lines([
1109
- "┌Title─┐ ",
1110
- "└──────┘ ",
1111
- ]);
1112
- #[rustfmt::skip]
1113
- let next = Buffer::with_lines([
1114
- "┌称号──┐ ",
1115
- "└──────┘ ",
1116
- ]);
1117
- let diff = prev.diff(&next);
1118
- assert_eq!(
1119
- diff,
1120
- [
1121
- (1, 0, &Cell::new("称")),
1122
- // Skipped "i"
1123
- (3, 0, &Cell::new("号")),
1124
- // Skipped "l"
1125
- (5, 0, &Cell::new("─")),
1126
- ]
1127
- );
1128
- }
1129
-
1130
- #[test]
1131
- fn diff_multi_width_offset() {
1132
- let prev = Buffer::with_lines(["┌称号──┐"]);
1133
- let next = Buffer::with_lines(["┌─称号─┐"]);
1134
-
1135
- let diff = prev.diff(&next);
1136
- assert_eq!(
1137
- diff,
1138
- [
1139
- (1, 0, &Cell::new("─")),
1140
- (2, 0, &Cell::new("称")),
1141
- (4, 0, &Cell::new("号")),
1142
- ]
1143
- );
1144
- }
1145
-
1146
- #[test]
1147
- fn diff_skip() {
1148
- let prev = Buffer::with_lines(["123"]);
1149
- let mut next = Buffer::with_lines(["456"]);
1150
- for i in 1..3 {
1151
- next.content[i].set_skip(true);
1152
- }
1153
-
1154
- let diff = prev.diff(&next);
1155
- assert_eq!(diff, [(0, 0, &Cell::new("4"))],);
1156
- }
1157
-
1158
- #[rstest]
1159
- #[case(Rect::new(0, 0, 2, 2), Rect::new(0, 2, 2, 2), ["11", "11", "22", "22"])]
1160
- #[case(Rect::new(2, 2, 2, 2), Rect::new(0, 0, 2, 2), ["22 ", "22 ", " 11", " 11"])]
1161
- fn merge<'line, Lines>(#[case] one: Rect, #[case] two: Rect, #[case] expected: Lines)
1162
- where
1163
- Lines: IntoIterator,
1164
- Lines::Item: Into<Line<'line>>,
1165
- {
1166
- let mut one = Buffer::filled(one, Cell::new("1"));
1167
- let two = Buffer::filled(two, Cell::new("2"));
1168
- one.merge(&two);
1169
- assert_eq!(one, Buffer::with_lines(expected));
1170
- }
1171
-
1172
- #[test]
1173
- fn merge_with_offset() {
1174
- let mut one = Buffer::filled(
1175
- Rect {
1176
- x: 3,
1177
- y: 3,
1178
- width: 2,
1179
- height: 2,
1180
- },
1181
- Cell::new("1"),
1182
- );
1183
- let two = Buffer::filled(
1184
- Rect {
1185
- x: 1,
1186
- y: 1,
1187
- width: 3,
1188
- height: 4,
1189
- },
1190
- Cell::new("2"),
1191
- );
1192
- one.merge(&two);
1193
- let mut expected = Buffer::with_lines(["222 ", "222 ", "2221", "2221"]);
1194
- expected.area = Rect {
1195
- x: 1,
1196
- y: 1,
1197
- width: 4,
1198
- height: 4,
1199
- };
1200
- assert_eq!(one, expected);
1201
- }
1202
-
1203
- #[rstest]
1204
- #[case(false, true, [false, false, true, true, true, true])]
1205
- #[case(true, false, [true, true, false, false, false, false])]
1206
- fn merge_skip(#[case] skip_one: bool, #[case] skip_two: bool, #[case] expected: [bool; 6]) {
1207
- let mut one = {
1208
- let area = Rect {
1209
- x: 0,
1210
- y: 0,
1211
- width: 2,
1212
- height: 2,
1213
- };
1214
- let mut cell = Cell::new("1");
1215
- cell.skip = skip_one;
1216
- Buffer::filled(area, cell)
1217
- };
1218
- let two = {
1219
- let area = Rect {
1220
- x: 0,
1221
- y: 1,
1222
- width: 2,
1223
- height: 2,
1224
- };
1225
- let mut cell = Cell::new("2");
1226
- cell.skip = skip_two;
1227
- Buffer::filled(area, cell)
1228
- };
1229
- one.merge(&two);
1230
- let skipped = one.content().iter().map(|c| c.skip).collect::<Vec<_>>();
1231
- assert_eq!(skipped, expected);
1232
- }
1233
-
1234
- #[test]
1235
- fn with_lines_accepts_into_lines() {
1236
- use crate::style::Stylize;
1237
- let mut buf = Buffer::empty(Rect::new(0, 0, 3, 2));
1238
- buf.set_string(0, 0, "foo", Style::new().red());
1239
- buf.set_string(0, 1, "bar", Style::new().blue());
1240
- assert_eq!(buf, Buffer::with_lines(["foo".red(), "bar".blue()]));
1241
- }
1242
-
1243
- #[test]
1244
- fn control_sequence_rendered_full() {
1245
- let text = "I \x1b[0;36mwas\x1b[0m here!";
1246
-
1247
- let mut buffer = Buffer::filled(Rect::new(0, 0, 25, 3), Cell::new("x"));
1248
- buffer.set_string(1, 1, text, Style::new());
1249
-
1250
- let expected = Buffer::with_lines([
1251
- "xxxxxxxxxxxxxxxxxxxxxxxxx",
1252
- "xI [0;36mwas[0m here!xxxx",
1253
- "xxxxxxxxxxxxxxxxxxxxxxxxx",
1254
- ]);
1255
- assert_eq!(buffer, expected);
1256
- }
1257
-
1258
- #[test]
1259
- fn control_sequence_rendered_partially() {
1260
- let text = "I \x1b[0;36mwas\x1b[0m here!";
1261
-
1262
- let mut buffer = Buffer::filled(Rect::new(0, 0, 11, 3), Cell::new("x"));
1263
- buffer.set_string(1, 1, text, Style::new());
1264
-
1265
- #[rustfmt::skip]
1266
- let expected = Buffer::with_lines([
1267
- "xxxxxxxxxxx",
1268
- "xI [0;36mwa",
1269
- "xxxxxxxxxxx",
1270
- ]);
1271
- assert_eq!(buffer, expected);
1272
- }
1273
-
1274
- /// Emojis normally contain various characters which should stay part of the Emoji.
1275
- /// This should work fine by utilizing `unicode_segmentation` but a testcase is probably helpful
1276
- /// due to the nature of never perfect Unicode implementations and all of its quirks.
1277
- #[rstest]
1278
- // Shrug without gender or skintone. Has a width of 2 like all emojis have.
1279
- #[case::shrug("🤷", "🤷xxxxx")]
1280
- // Technically this is a (brown) bear, a zero-width joiner and a snowflake
1281
- // As it is joined its a single emoji and should therefore have a width of 2.
1282
- // Prior to unicode-width 0.2, this was incorrectly detected as width 4 for some reason
1283
- #[case::polarbear("🐻‍❄️", "🐻‍❄️xxxxx")]
1284
- // Technically this is an eye, a zero-width joiner and a speech bubble
1285
- // Both eye and speech bubble include a 'display as emoji' variation selector
1286
- // Prior to unicode-width 0.2, this was incorrectly detected as width 4 for some reason
1287
- #[case::eye_speechbubble("👁️‍🗨️", "👁️‍🗨️xxxxx")]
1288
- // Keyboard keycap emoji: base symbol + VS16 for emoji presentation
1289
- // This should render as a single grapheme with width 2.
1290
- #[case::keyboard_emoji("⌨️", "⌨️xxxxx")]
1291
- fn renders_emoji(#[case] input: &str, #[case] expected: &str) {
1292
- use unicode_width::UnicodeWidthChar;
1293
-
1294
- dbg!(input);
1295
- dbg!(input.len());
1296
- dbg!(
1297
- input
1298
- .graphemes(true)
1299
- .map(|symbol| (symbol, symbol.escape_unicode().to_string(), symbol.width()))
1300
- .collect::<Vec<_>>()
1301
- );
1302
- dbg!(
1303
- input
1304
- .chars()
1305
- .map(|char| (
1306
- char,
1307
- char.escape_unicode().to_string(),
1308
- char.width(),
1309
- char.is_control()
1310
- ))
1311
- .collect::<Vec<_>>()
1312
- );
1313
-
1314
- let mut buffer = Buffer::filled(Rect::new(0, 0, 7, 1), Cell::new("x"));
1315
- buffer.set_string(0, 0, input, Style::new());
1316
-
1317
- let expected = Buffer::with_lines([expected]);
1318
- assert_eq!(buffer, expected);
1319
- }
1320
-
1321
- /// Regression test for <https://github.com/ratatui/ratatui/issues/1441>
1322
- ///
1323
- /// Previously the `pos_of` function would incorrectly cast the index to a u16 value instead of
1324
- /// using the index as is. This caused incorrect rendering of any buffer with an length > 65535.
1325
- #[test]
1326
- fn index_pos_of_u16_max() {
1327
- let buffer = Buffer::empty(Rect::new(0, 0, 256, 256 + 1));
1328
- assert_eq!(buffer.index_of(255, 255), 65535);
1329
- assert_eq!(buffer.pos_of(65535), (255, 255));
1330
-
1331
- assert_eq!(buffer.index_of(0, 256), 65536);
1332
- assert_eq!(buffer.pos_of(65536), (0, 256)); // previously (0, 0)
1333
-
1334
- assert_eq!(buffer.index_of(1, 256), 65537);
1335
- assert_eq!(buffer.pos_of(65537), (1, 256)); // previously (1, 0)
1336
-
1337
- assert_eq!(buffer.index_of(255, 256), 65791);
1338
- assert_eq!(buffer.pos_of(65791), (255, 256)); // previously (255, 0)
1339
- }
1340
-
1341
- #[test]
1342
- fn diff_clears_trailing_cell_for_wide_grapheme() {
1343
- // Reproduce: write "ab", then overwrite with a wide emoji like "⌨️"
1344
- let prev = Buffer::with_lines(["ab"]); // width 2 area inferred
1345
- assert_eq!(prev.area.width, 2);
1346
-
1347
- let mut next = Buffer::with_lines([" "]); // start with blanks
1348
- next.set_string(0, 0, "⌨️", Style::new());
1349
-
1350
- // The next buffer contains a wide grapheme occupying cell 0 and implicitly cell 1.
1351
- // The debug formatting shows the hidden trailing space.
1352
- let expected_next = Buffer::with_lines(["⌨️"]);
1353
- assert_eq!(next, expected_next);
1354
-
1355
- // The diff should include an update for (0,0) to draw the emoji. Depending on
1356
- // terminal behavior, it may or may not be necessary to explicitly clear (1,0).
1357
- // At minimum, ensure the first cell is updated and nothing incorrect is emitted.
1358
- let diff = prev.diff(&next);
1359
- assert!(
1360
- diff.iter()
1361
- .any(|(x, y, c)| *x == 0 && *y == 0 && c.symbol() == "⌨️")
1362
- );
1363
- // And it should explicitly clear the trailing cell (1,0) to avoid leftovers on terminals
1364
- // that don't automatically clear the following cell for wide characters.
1365
- assert!(
1366
- diff.iter()
1367
- .any(|(x, y, c)| *x == 1 && *y == 0 && c.symbol() == " ")
1368
- );
1369
- }
1370
-
1371
- #[test]
1372
- fn diff_orders_trailing_clear_before_wide_grapheme_redraw() {
1373
- let prev = Buffer::with_lines(["ab"]);
1374
-
1375
- let mut next = Buffer::with_lines([" "]);
1376
- next.set_string(0, 0, "界", Style::new());
1377
-
1378
- let diff = prev.diff(&next);
1379
- assert_eq!(
1380
- diff,
1381
- [
1382
- (1, 0, &Cell::new(" ")),
1383
- (0, 0, &Cell::new("界")),
1384
- ],
1385
- "trailing clear must be emitted before the visible wide glyph so crossterm backends do not overdraw a blank after printing the glyph",
1386
- );
1387
- }
1388
- }