@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,1077 +0,0 @@
1
- //! This module provides the `TestBackend` implementation for the [`Backend`] trait.
2
- //! It is used in the integration tests to verify the correctness of the library.
3
-
4
- use alloc::string::String;
5
- use alloc::vec;
6
- use core::fmt::{self, Write};
7
- use core::iter;
8
-
9
- use unicode_width::UnicodeWidthStr;
10
-
11
- use crate::backend::{Backend, ClearType, WindowSize};
12
- use crate::buffer::{Buffer, Cell};
13
- use crate::layout::{Position, Rect, Size};
14
-
15
- /// A [`Backend`] implementation used for integration testing that renders to an memory buffer.
16
- ///
17
- /// Note: that although many of the integration and unit tests in ratatui are written using this
18
- /// backend, it is preferable to write unit tests for widgets directly against the buffer rather
19
- /// than using this backend. This backend is intended for integration tests that test the entire
20
- /// terminal UI.
21
- ///
22
- /// # Example
23
- ///
24
- /// ```rust,ignore
25
- /// use ratatui::backend::{Backend, TestBackend};
26
- ///
27
- /// let mut backend = TestBackend::new(10, 2);
28
- /// backend.clear()?;
29
- /// backend.assert_buffer_lines([" "; 2]);
30
- /// # Result::Ok(())
31
- /// ```
32
- #[derive(Debug, Clone, Eq, PartialEq, Hash)]
33
- #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
34
- pub struct TestBackend {
35
- buffer: Buffer,
36
- scrollback: Buffer,
37
- cursor: bool,
38
- pos: (u16, u16),
39
- }
40
-
41
- /// Returns a string representation of the given buffer for debugging purpose.
42
- ///
43
- /// This function is used to visualize the buffer content in a human-readable format.
44
- /// It iterates through the buffer content and appends each cell's symbol to the view string.
45
- /// If a cell is hidden by a multi-width symbol, it is added to the overwritten vector and
46
- /// displayed at the end of the line.
47
- fn buffer_view(buffer: &Buffer) -> String {
48
- let mut view = String::with_capacity(buffer.content.len() + buffer.area.height as usize * 3);
49
- for cells in buffer.content.chunks(buffer.area.width as usize) {
50
- let mut overwritten = vec![];
51
- let mut skip: usize = 0;
52
- view.push('"');
53
- for (x, c) in cells.iter().enumerate() {
54
- if skip == 0 {
55
- view.push_str(c.symbol());
56
- } else {
57
- overwritten.push((x, c.symbol()));
58
- }
59
- skip = core::cmp::max(skip, c.symbol().width()).saturating_sub(1);
60
- }
61
- view.push('"');
62
- if !overwritten.is_empty() {
63
- write!(&mut view, " Hidden by multi-width symbols: {overwritten:?}").unwrap();
64
- }
65
- view.push('\n');
66
- }
67
- view
68
- }
69
-
70
- impl TestBackend {
71
- /// Creates a new `TestBackend` with the specified width and height.
72
- pub fn new(width: u16, height: u16) -> Self {
73
- Self {
74
- buffer: Buffer::empty(Rect::new(0, 0, width, height)),
75
- scrollback: Buffer::empty(Rect::new(0, 0, width, 0)),
76
- cursor: false,
77
- pos: (0, 0),
78
- }
79
- }
80
-
81
- /// Creates a new `TestBackend` with the specified lines as the initial screen state.
82
- ///
83
- /// The backend's screen size is determined from the initial lines.
84
- #[must_use]
85
- pub fn with_lines<'line, Lines>(lines: Lines) -> Self
86
- where
87
- Lines: IntoIterator,
88
- Lines::Item: Into<crate::text::Line<'line>>,
89
- {
90
- let buffer = Buffer::with_lines(lines);
91
- let scrollback = Buffer::empty(Rect {
92
- width: buffer.area.width,
93
- ..Rect::ZERO
94
- });
95
- Self {
96
- buffer,
97
- scrollback,
98
- cursor: false,
99
- pos: (0, 0),
100
- }
101
- }
102
-
103
- /// Returns a reference to the internal buffer of the `TestBackend`.
104
- pub const fn buffer(&self) -> &Buffer {
105
- &self.buffer
106
- }
107
-
108
- /// Returns a reference to the internal scrollback buffer of the `TestBackend`.
109
- ///
110
- /// The scrollback buffer represents the part of the screen that is currently hidden from view,
111
- /// but that could be accessed by scrolling back in the terminal's history. This would normally
112
- /// be done using the terminal's scrollbar or an equivalent keyboard shortcut.
113
- ///
114
- /// The scrollback buffer starts out empty. Lines are appended when they scroll off the top of
115
- /// the main buffer. This happens when lines are appended to the bottom of the main buffer
116
- /// using [`Backend::append_lines`].
117
- ///
118
- /// The scrollback buffer has a maximum height of [`u16::MAX`]. If lines are appended to the
119
- /// bottom of the scrollback buffer when it is at its maximum height, a corresponding number of
120
- /// lines will be removed from the top.
121
- pub const fn scrollback(&self) -> &Buffer {
122
- &self.scrollback
123
- }
124
-
125
- /// Resizes the `TestBackend` to the specified width and height.
126
- pub fn resize(&mut self, width: u16, height: u16) {
127
- self.buffer.resize(Rect::new(0, 0, width, height));
128
- let scrollback_height = self.scrollback.area.height;
129
- self.scrollback
130
- .resize(Rect::new(0, 0, width, scrollback_height));
131
- }
132
-
133
- /// Asserts that the `TestBackend`'s buffer is equal to the expected buffer.
134
- ///
135
- /// This is a shortcut for `assert_eq!(self.buffer(), &expected)`.
136
- ///
137
- /// # Panics
138
- ///
139
- /// When they are not equal, a panic occurs with a detailed error message showing the
140
- /// differences between the expected and actual buffers.
141
- #[expect(deprecated)]
142
- #[track_caller]
143
- pub fn assert_buffer(&self, expected: &Buffer) {
144
- // TODO: use assert_eq!()
145
- crate::assert_buffer_eq!(&self.buffer, expected);
146
- }
147
-
148
- /// Asserts that the `TestBackend`'s scrollback buffer is equal to the expected buffer.
149
- ///
150
- /// This is a shortcut for `assert_eq!(self.scrollback(), &expected)`.
151
- ///
152
- /// # Panics
153
- ///
154
- /// When they are not equal, a panic occurs with a detailed error message showing the
155
- /// differences between the expected and actual buffers.
156
- #[track_caller]
157
- pub fn assert_scrollback(&self, expected: &Buffer) {
158
- assert_eq!(&self.scrollback, expected);
159
- }
160
-
161
- /// Asserts that the `TestBackend`'s scrollback buffer is empty.
162
- ///
163
- /// # Panics
164
- ///
165
- /// When the scrollback buffer is not equal, a panic occurs with a detailed error message
166
- /// showing the differences between the expected and actual buffers.
167
- pub fn assert_scrollback_empty(&self) {
168
- let expected = Buffer {
169
- area: Rect {
170
- width: self.scrollback.area.width,
171
- ..Rect::ZERO
172
- },
173
- content: vec![],
174
- };
175
- self.assert_scrollback(&expected);
176
- }
177
-
178
- /// Asserts that the `TestBackend`'s buffer is equal to the expected lines.
179
- ///
180
- /// This is a shortcut for `assert_eq!(self.buffer(), &Buffer::with_lines(expected))`.
181
- ///
182
- /// # Panics
183
- ///
184
- /// When they are not equal, a panic occurs with a detailed error message showing the
185
- /// differences between the expected and actual buffers.
186
- #[track_caller]
187
- pub fn assert_buffer_lines<'line, Lines>(&self, expected: Lines)
188
- where
189
- Lines: IntoIterator,
190
- Lines::Item: Into<crate::text::Line<'line>>,
191
- {
192
- self.assert_buffer(&Buffer::with_lines(expected));
193
- }
194
-
195
- /// Asserts that the `TestBackend`'s scrollback buffer is equal to the expected lines.
196
- ///
197
- /// This is a shortcut for `assert_eq!(self.scrollback(), &Buffer::with_lines(expected))`.
198
- ///
199
- /// # Panics
200
- ///
201
- /// When they are not equal, a panic occurs with a detailed error message showing the
202
- /// differences between the expected and actual buffers.
203
- #[track_caller]
204
- pub fn assert_scrollback_lines<'line, Lines>(&self, expected: Lines)
205
- where
206
- Lines: IntoIterator,
207
- Lines::Item: Into<crate::text::Line<'line>>,
208
- {
209
- self.assert_scrollback(&Buffer::with_lines(expected));
210
- }
211
-
212
- /// Asserts that the `TestBackend`'s cursor position is equal to the expected one.
213
- ///
214
- /// This is a shortcut for `assert_eq!(self.get_cursor_position().unwrap(), expected)`.
215
- ///
216
- /// # Panics
217
- ///
218
- /// When they are not equal, a panic occurs with a detailed error message showing the
219
- /// differences between the expected and actual position.
220
- #[track_caller]
221
- pub fn assert_cursor_position<P: Into<Position>>(&mut self, position: P) {
222
- let actual = self.get_cursor_position().unwrap();
223
- assert_eq!(actual, position.into());
224
- }
225
- }
226
-
227
- impl fmt::Display for TestBackend {
228
- /// Formats the `TestBackend` for display by calling the `buffer_view` function
229
- /// on its internal buffer.
230
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
231
- write!(f, "{}", buffer_view(&self.buffer))
232
- }
233
- }
234
-
235
- type Result<T, E = core::convert::Infallible> = core::result::Result<T, E>;
236
-
237
- impl Backend for TestBackend {
238
- type Error = core::convert::Infallible;
239
-
240
- fn draw<'a, I>(&mut self, content: I) -> Result<()>
241
- where
242
- I: Iterator<Item = (u16, u16, &'a Cell)>,
243
- {
244
- for (x, y, c) in content {
245
- self.buffer[(x, y)] = c.clone();
246
- }
247
- Ok(())
248
- }
249
-
250
- fn hide_cursor(&mut self) -> Result<()> {
251
- self.cursor = false;
252
- Ok(())
253
- }
254
-
255
- fn show_cursor(&mut self) -> Result<()> {
256
- self.cursor = true;
257
- Ok(())
258
- }
259
-
260
- fn get_cursor_position(&mut self) -> Result<Position> {
261
- Ok(self.pos.into())
262
- }
263
-
264
- fn set_cursor_position<P: Into<Position>>(&mut self, position: P) -> Result<()> {
265
- self.pos = position.into().into();
266
- Ok(())
267
- }
268
-
269
- fn clear(&mut self) -> Result<()> {
270
- self.buffer.reset();
271
- Ok(())
272
- }
273
-
274
- fn clear_region(&mut self, clear_type: ClearType) -> Result<()> {
275
- let region = match clear_type {
276
- ClearType::All => return self.clear(),
277
- ClearType::AfterCursor => {
278
- let index = self.buffer.index_of(self.pos.0, self.pos.1) + 1;
279
- &mut self.buffer.content[index..]
280
- }
281
- ClearType::BeforeCursor => {
282
- let index = self.buffer.index_of(self.pos.0, self.pos.1);
283
- &mut self.buffer.content[..index]
284
- }
285
- ClearType::CurrentLine => {
286
- let line_start_index = self.buffer.index_of(0, self.pos.1);
287
- let line_end_index = self.buffer.index_of(self.buffer.area.width - 1, self.pos.1);
288
- &mut self.buffer.content[line_start_index..=line_end_index]
289
- }
290
- ClearType::UntilNewLine => {
291
- let index = self.buffer.index_of(self.pos.0, self.pos.1);
292
- let line_end_index = self.buffer.index_of(self.buffer.area.width - 1, self.pos.1);
293
- &mut self.buffer.content[index..=line_end_index]
294
- }
295
- };
296
- for cell in region {
297
- cell.reset();
298
- }
299
- Ok(())
300
- }
301
-
302
- /// Inserts n line breaks at the current cursor position.
303
- ///
304
- /// After the insertion, the cursor x position will be incremented by 1 (unless it's already
305
- /// at the end of line). This is a common behaviour of terminals in raw mode.
306
- ///
307
- /// If the number of lines to append is fewer than the number of lines in the buffer after the
308
- /// cursor y position then the cursor is moved down by n rows.
309
- ///
310
- /// If the number of lines to append is greater than the number of lines in the buffer after
311
- /// the cursor y position then that number of empty lines (at most the buffer's height in this
312
- /// case but this limit is instead replaced with scrolling in most backend implementations) will
313
- /// be added after the current position and the cursor will be moved to the last row.
314
- fn append_lines(&mut self, line_count: u16) -> Result<()> {
315
- let Position { x: cur_x, y: cur_y } = self.get_cursor_position()?;
316
- let Rect { width, height, .. } = self.buffer.area;
317
-
318
- // the next column ensuring that we don't go past the last column
319
- let new_cursor_x = cur_x.saturating_add(1).min(width.saturating_sub(1));
320
-
321
- let max_y = height.saturating_sub(1);
322
- let lines_after_cursor = max_y.saturating_sub(cur_y);
323
-
324
- if line_count > lines_after_cursor {
325
- // We need to insert blank lines at the bottom and scroll the lines from the top into
326
- // scrollback.
327
- let scroll_by: usize = (line_count - lines_after_cursor).into();
328
- let width: usize = self.buffer.area.width.into();
329
- let cells_to_scrollback = self.buffer.content.len().min(width * scroll_by);
330
-
331
- append_to_scrollback(
332
- &mut self.scrollback,
333
- self.buffer.content.splice(
334
- 0..cells_to_scrollback,
335
- iter::repeat_with(Default::default).take(cells_to_scrollback),
336
- ),
337
- );
338
- self.buffer.content.rotate_left(cells_to_scrollback);
339
- append_to_scrollback(
340
- &mut self.scrollback,
341
- iter::repeat_with(Default::default).take(width * scroll_by - cells_to_scrollback),
342
- );
343
- }
344
-
345
- let new_cursor_y = cur_y.saturating_add(line_count).min(max_y);
346
- self.set_cursor_position(Position::new(new_cursor_x, new_cursor_y))?;
347
-
348
- Ok(())
349
- }
350
-
351
- fn size(&self) -> Result<Size> {
352
- Ok(self.buffer.area.as_size())
353
- }
354
-
355
- fn window_size(&mut self) -> Result<WindowSize> {
356
- // Some arbitrary window pixel size, probably doesn't need much testing.
357
- const WINDOW_PIXEL_SIZE: Size = Size {
358
- width: 640,
359
- height: 480,
360
- };
361
- Ok(WindowSize {
362
- columns_rows: self.buffer.area.as_size(),
363
- pixels: WINDOW_PIXEL_SIZE,
364
- })
365
- }
366
-
367
- fn flush(&mut self) -> Result<()> {
368
- Ok(())
369
- }
370
-
371
- #[cfg(feature = "scrolling-regions")]
372
- fn scroll_region_up(&mut self, region: core::ops::Range<u16>, scroll_by: u16) -> Result<()> {
373
- let width: usize = self.buffer.area.width.into();
374
- let cell_region_start = width * region.start.min(self.buffer.area.height) as usize;
375
- let cell_region_end = width * region.end.min(self.buffer.area.height) as usize;
376
- let cell_region_len = cell_region_end - cell_region_start;
377
- let cells_to_scroll_by = width * scroll_by as usize;
378
-
379
- // Deal with the simple case where nothing needs to be copied into scrollback.
380
- if cell_region_start > 0 {
381
- if cells_to_scroll_by >= cell_region_len {
382
- // The scroll amount is large enough to clear the whole region.
383
- self.buffer.content[cell_region_start..cell_region_end].fill_with(Default::default);
384
- } else {
385
- // Scroll up by rotating, then filling in the bottom with empty cells.
386
- self.buffer.content[cell_region_start..cell_region_end]
387
- .rotate_left(cells_to_scroll_by);
388
- self.buffer.content[cell_region_end - cells_to_scroll_by..cell_region_end]
389
- .fill_with(Default::default);
390
- }
391
- return Ok(());
392
- }
393
-
394
- // The rows inserted into the scrollback will first come from the buffer, and if that is
395
- // insufficient, will then be blank rows.
396
- let cells_from_region = cell_region_len.min(cells_to_scroll_by);
397
- append_to_scrollback(
398
- &mut self.scrollback,
399
- self.buffer.content.splice(
400
- 0..cells_from_region,
401
- iter::repeat_with(Default::default).take(cells_from_region),
402
- ),
403
- );
404
- if cells_to_scroll_by < cell_region_len {
405
- // Rotate the remaining cells to the front of the region.
406
- self.buffer.content[cell_region_start..cell_region_end].rotate_left(cells_from_region);
407
- } else {
408
- // Splice cleared out the region. Insert empty rows in scrollback.
409
- append_to_scrollback(
410
- &mut self.scrollback,
411
- iter::repeat_with(Default::default).take(cells_to_scroll_by - cell_region_len),
412
- );
413
- }
414
- Ok(())
415
- }
416
-
417
- #[cfg(feature = "scrolling-regions")]
418
- fn scroll_region_down(&mut self, region: core::ops::Range<u16>, scroll_by: u16) -> Result<()> {
419
- let width: usize = self.buffer.area.width.into();
420
- let cell_region_start = width * region.start.min(self.buffer.area.height) as usize;
421
- let cell_region_end = width * region.end.min(self.buffer.area.height) as usize;
422
- let cell_region_len = cell_region_end - cell_region_start;
423
- let cells_to_scroll_by = width * scroll_by as usize;
424
-
425
- if cells_to_scroll_by >= cell_region_len {
426
- // The scroll amount is large enough to clear the whole region.
427
- self.buffer.content[cell_region_start..cell_region_end].fill_with(Default::default);
428
- } else {
429
- // Scroll up by rotating, then filling in the top with empty cells.
430
- self.buffer.content[cell_region_start..cell_region_end]
431
- .rotate_right(cells_to_scroll_by);
432
- self.buffer.content[cell_region_start..cell_region_start + cells_to_scroll_by]
433
- .fill_with(Default::default);
434
- }
435
- Ok(())
436
- }
437
- }
438
-
439
- /// Append the provided cells to the bottom of a scrollback buffer. The number of cells must be a
440
- /// multiple of the buffer's width. If the scrollback buffer ends up larger than 65535 lines tall,
441
- /// then lines will be removed from the top to get it down to size.
442
- fn append_to_scrollback(scrollback: &mut Buffer, cells: impl IntoIterator<Item = Cell>) {
443
- scrollback.content.extend(cells);
444
- let width = scrollback.area.width as usize;
445
- let new_height = (scrollback.content.len() / width).min(u16::MAX as usize);
446
- let keep_from = scrollback
447
- .content
448
- .len()
449
- .saturating_sub(width * u16::MAX as usize);
450
- scrollback.content.drain(0..keep_from);
451
- scrollback.area.height = new_height as u16;
452
- }
453
-
454
- #[cfg(test)]
455
- mod tests {
456
- use alloc::format;
457
-
458
- use itertools::Itertools as _;
459
-
460
- use super::*;
461
-
462
- #[test]
463
- fn new() {
464
- assert_eq!(
465
- TestBackend::new(10, 2),
466
- TestBackend {
467
- buffer: Buffer::with_lines([" "; 2]),
468
- scrollback: Buffer::empty(Rect::new(0, 0, 10, 0)),
469
- cursor: false,
470
- pos: (0, 0),
471
- }
472
- );
473
- }
474
- #[test]
475
- fn test_buffer_view() {
476
- let buffer = Buffer::with_lines(["aaaa"; 2]);
477
- assert_eq!(buffer_view(&buffer), "\"aaaa\"\n\"aaaa\"\n");
478
- }
479
-
480
- #[test]
481
- fn buffer_view_with_overwrites() {
482
- let multi_byte_char = "👨‍👩‍👧‍👦"; // renders 2 wide
483
- let buffer = Buffer::with_lines([multi_byte_char]);
484
- assert_eq!(
485
- buffer_view(&buffer),
486
- format!(
487
- r#""{multi_byte_char}" Hidden by multi-width symbols: [(1, " ")]
488
- "#,
489
- )
490
- );
491
- }
492
-
493
- #[test]
494
- fn buffer() {
495
- let backend = TestBackend::new(10, 2);
496
- backend.assert_buffer_lines([" "; 2]);
497
- }
498
-
499
- #[test]
500
- fn resize() {
501
- let mut backend = TestBackend::new(10, 2);
502
- backend.resize(5, 5);
503
- backend.assert_buffer_lines([" "; 5]);
504
- }
505
-
506
- #[test]
507
- fn assert_buffer() {
508
- let backend = TestBackend::new(10, 2);
509
- backend.assert_buffer_lines([" "; 2]);
510
- }
511
-
512
- #[test]
513
- #[should_panic = "buffer contents not equal"]
514
- fn assert_buffer_panics() {
515
- let backend = TestBackend::new(10, 2);
516
- backend.assert_buffer_lines(["aaaaaaaaaa"; 2]);
517
- }
518
-
519
- #[test]
520
- #[should_panic = "assertion `left == right` failed"]
521
- fn assert_scrollback_panics() {
522
- let backend = TestBackend::new(10, 2);
523
- backend.assert_scrollback_lines(["aaaaaaaaaa"; 2]);
524
- }
525
-
526
- #[test]
527
- fn display() {
528
- let backend = TestBackend::new(10, 2);
529
- assert_eq!(format!("{backend}"), "\" \"\n\" \"\n");
530
- }
531
-
532
- #[test]
533
- fn draw() {
534
- let mut backend = TestBackend::new(10, 2);
535
- let cell = Cell::new("a");
536
- backend.draw([(0, 0, &cell)].into_iter()).unwrap();
537
- backend.draw([(0, 1, &cell)].into_iter()).unwrap();
538
- backend.assert_buffer_lines(["a "; 2]);
539
- }
540
-
541
- #[test]
542
- fn hide_cursor() {
543
- let mut backend = TestBackend::new(10, 2);
544
- backend.hide_cursor().unwrap();
545
- assert!(!backend.cursor);
546
- }
547
-
548
- #[test]
549
- fn show_cursor() {
550
- let mut backend = TestBackend::new(10, 2);
551
- backend.show_cursor().unwrap();
552
- assert!(backend.cursor);
553
- }
554
-
555
- #[test]
556
- fn get_cursor_position() {
557
- let mut backend = TestBackend::new(10, 2);
558
- assert_eq!(backend.get_cursor_position().unwrap(), Position::ORIGIN);
559
- }
560
-
561
- #[test]
562
- fn assert_cursor_position() {
563
- let mut backend = TestBackend::new(10, 2);
564
- backend.assert_cursor_position(Position::ORIGIN);
565
- }
566
-
567
- #[test]
568
- fn set_cursor_position() {
569
- let mut backend = TestBackend::new(10, 10);
570
- backend
571
- .set_cursor_position(Position { x: 5, y: 5 })
572
- .unwrap();
573
- assert_eq!(backend.pos, (5, 5));
574
- }
575
-
576
- #[test]
577
- fn clear() {
578
- let mut backend = TestBackend::new(4, 2);
579
- let cell = Cell::new("a");
580
- backend.draw([(0, 0, &cell)].into_iter()).unwrap();
581
- backend.draw([(0, 1, &cell)].into_iter()).unwrap();
582
- backend.clear().unwrap();
583
- backend.assert_buffer_lines([" ", " "]);
584
- }
585
-
586
- #[test]
587
- fn clear_region_all() {
588
- let mut backend = TestBackend::with_lines([
589
- "aaaaaaaaaa",
590
- "aaaaaaaaaa",
591
- "aaaaaaaaaa",
592
- "aaaaaaaaaa",
593
- "aaaaaaaaaa",
594
- ]);
595
-
596
- backend.clear_region(ClearType::All).unwrap();
597
- backend.assert_buffer_lines([
598
- " ",
599
- " ",
600
- " ",
601
- " ",
602
- " ",
603
- ]);
604
- }
605
-
606
- #[test]
607
- fn clear_region_after_cursor() {
608
- let mut backend = TestBackend::with_lines([
609
- "aaaaaaaaaa",
610
- "aaaaaaaaaa",
611
- "aaaaaaaaaa",
612
- "aaaaaaaaaa",
613
- "aaaaaaaaaa",
614
- ]);
615
-
616
- backend
617
- .set_cursor_position(Position { x: 3, y: 2 })
618
- .unwrap();
619
- backend.clear_region(ClearType::AfterCursor).unwrap();
620
- backend.assert_buffer_lines([
621
- "aaaaaaaaaa",
622
- "aaaaaaaaaa",
623
- "aaaa ",
624
- " ",
625
- " ",
626
- ]);
627
- }
628
-
629
- #[test]
630
- fn clear_region_before_cursor() {
631
- let mut backend = TestBackend::with_lines([
632
- "aaaaaaaaaa",
633
- "aaaaaaaaaa",
634
- "aaaaaaaaaa",
635
- "aaaaaaaaaa",
636
- "aaaaaaaaaa",
637
- ]);
638
-
639
- backend
640
- .set_cursor_position(Position { x: 5, y: 3 })
641
- .unwrap();
642
- backend.clear_region(ClearType::BeforeCursor).unwrap();
643
- backend.assert_buffer_lines([
644
- " ",
645
- " ",
646
- " ",
647
- " aaaaa",
648
- "aaaaaaaaaa",
649
- ]);
650
- }
651
-
652
- #[test]
653
- fn clear_region_current_line() {
654
- let mut backend = TestBackend::with_lines([
655
- "aaaaaaaaaa",
656
- "aaaaaaaaaa",
657
- "aaaaaaaaaa",
658
- "aaaaaaaaaa",
659
- "aaaaaaaaaa",
660
- ]);
661
-
662
- backend
663
- .set_cursor_position(Position { x: 3, y: 1 })
664
- .unwrap();
665
- backend.clear_region(ClearType::CurrentLine).unwrap();
666
- backend.assert_buffer_lines([
667
- "aaaaaaaaaa",
668
- " ",
669
- "aaaaaaaaaa",
670
- "aaaaaaaaaa",
671
- "aaaaaaaaaa",
672
- ]);
673
- }
674
-
675
- #[test]
676
- fn clear_region_until_new_line() {
677
- let mut backend = TestBackend::with_lines([
678
- "aaaaaaaaaa",
679
- "aaaaaaaaaa",
680
- "aaaaaaaaaa",
681
- "aaaaaaaaaa",
682
- "aaaaaaaaaa",
683
- ]);
684
-
685
- backend
686
- .set_cursor_position(Position { x: 3, y: 0 })
687
- .unwrap();
688
- backend.clear_region(ClearType::UntilNewLine).unwrap();
689
- backend.assert_buffer_lines([
690
- "aaa ",
691
- "aaaaaaaaaa",
692
- "aaaaaaaaaa",
693
- "aaaaaaaaaa",
694
- "aaaaaaaaaa",
695
- ]);
696
- }
697
-
698
- #[test]
699
- fn append_lines_not_at_last_line() {
700
- let mut backend = TestBackend::with_lines([
701
- "aaaaaaaaaa",
702
- "bbbbbbbbbb",
703
- "cccccccccc",
704
- "dddddddddd",
705
- "eeeeeeeeee",
706
- ]);
707
-
708
- backend.set_cursor_position(Position::ORIGIN).unwrap();
709
-
710
- // If the cursor is not at the last line in the terminal the addition of a
711
- // newline simply moves the cursor down and to the right
712
-
713
- backend.append_lines(1).unwrap();
714
- backend.assert_cursor_position(Position { x: 1, y: 1 });
715
-
716
- backend.append_lines(1).unwrap();
717
- backend.assert_cursor_position(Position { x: 2, y: 2 });
718
-
719
- backend.append_lines(1).unwrap();
720
- backend.assert_cursor_position(Position { x: 3, y: 3 });
721
-
722
- backend.append_lines(1).unwrap();
723
- backend.assert_cursor_position(Position { x: 4, y: 4 });
724
-
725
- // As such the buffer should remain unchanged
726
- backend.assert_buffer_lines([
727
- "aaaaaaaaaa",
728
- "bbbbbbbbbb",
729
- "cccccccccc",
730
- "dddddddddd",
731
- "eeeeeeeeee",
732
- ]);
733
- backend.assert_scrollback_empty();
734
- }
735
-
736
- #[test]
737
- fn append_lines_at_last_line() {
738
- let mut backend = TestBackend::with_lines([
739
- "aaaaaaaaaa",
740
- "bbbbbbbbbb",
741
- "cccccccccc",
742
- "dddddddddd",
743
- "eeeeeeeeee",
744
- ]);
745
-
746
- // If the cursor is at the last line in the terminal the addition of a
747
- // newline will scroll the contents of the buffer
748
- backend
749
- .set_cursor_position(Position { x: 0, y: 4 })
750
- .unwrap();
751
-
752
- backend.append_lines(1).unwrap();
753
-
754
- backend.assert_buffer_lines([
755
- "bbbbbbbbbb",
756
- "cccccccccc",
757
- "dddddddddd",
758
- "eeeeeeeeee",
759
- " ",
760
- ]);
761
- backend.assert_scrollback_lines(["aaaaaaaaaa"]);
762
-
763
- // It also moves the cursor to the right, as is common of the behaviour of
764
- // terminals in raw-mode
765
- backend.assert_cursor_position(Position { x: 1, y: 4 });
766
- }
767
-
768
- #[test]
769
- fn append_multiple_lines_not_at_last_line() {
770
- let mut backend = TestBackend::with_lines([
771
- "aaaaaaaaaa",
772
- "bbbbbbbbbb",
773
- "cccccccccc",
774
- "dddddddddd",
775
- "eeeeeeeeee",
776
- ]);
777
-
778
- backend.set_cursor_position(Position::ORIGIN).unwrap();
779
-
780
- // If the cursor is not at the last line in the terminal the addition of multiple
781
- // newlines simply moves the cursor n lines down and to the right by 1
782
-
783
- backend.append_lines(4).unwrap();
784
- backend.assert_cursor_position(Position { x: 1, y: 4 });
785
-
786
- // As such the buffer should remain unchanged
787
- backend.assert_buffer_lines([
788
- "aaaaaaaaaa",
789
- "bbbbbbbbbb",
790
- "cccccccccc",
791
- "dddddddddd",
792
- "eeeeeeeeee",
793
- ]);
794
- backend.assert_scrollback_empty();
795
- }
796
-
797
- #[test]
798
- fn append_multiple_lines_past_last_line() {
799
- let mut backend = TestBackend::with_lines([
800
- "aaaaaaaaaa",
801
- "bbbbbbbbbb",
802
- "cccccccccc",
803
- "dddddddddd",
804
- "eeeeeeeeee",
805
- ]);
806
-
807
- backend
808
- .set_cursor_position(Position { x: 0, y: 3 })
809
- .unwrap();
810
-
811
- backend.append_lines(3).unwrap();
812
- backend.assert_cursor_position(Position { x: 1, y: 4 });
813
-
814
- backend.assert_buffer_lines([
815
- "cccccccccc",
816
- "dddddddddd",
817
- "eeeeeeeeee",
818
- " ",
819
- " ",
820
- ]);
821
- backend.assert_scrollback_lines(["aaaaaaaaaa", "bbbbbbbbbb"]);
822
- }
823
-
824
- #[test]
825
- fn append_multiple_lines_where_cursor_at_end_appends_height_lines() {
826
- let mut backend = TestBackend::with_lines([
827
- "aaaaaaaaaa",
828
- "bbbbbbbbbb",
829
- "cccccccccc",
830
- "dddddddddd",
831
- "eeeeeeeeee",
832
- ]);
833
-
834
- backend
835
- .set_cursor_position(Position { x: 0, y: 4 })
836
- .unwrap();
837
-
838
- backend.append_lines(5).unwrap();
839
- backend.assert_cursor_position(Position { x: 1, y: 4 });
840
-
841
- backend.assert_buffer_lines([
842
- " ",
843
- " ",
844
- " ",
845
- " ",
846
- " ",
847
- ]);
848
- backend.assert_scrollback_lines([
849
- "aaaaaaaaaa",
850
- "bbbbbbbbbb",
851
- "cccccccccc",
852
- "dddddddddd",
853
- "eeeeeeeeee",
854
- ]);
855
- }
856
-
857
- #[test]
858
- fn append_multiple_lines_where_cursor_appends_height_lines() {
859
- let mut backend = TestBackend::with_lines([
860
- "aaaaaaaaaa",
861
- "bbbbbbbbbb",
862
- "cccccccccc",
863
- "dddddddddd",
864
- "eeeeeeeeee",
865
- ]);
866
-
867
- backend.set_cursor_position(Position::ORIGIN).unwrap();
868
-
869
- backend.append_lines(5).unwrap();
870
- backend.assert_cursor_position(Position { x: 1, y: 4 });
871
-
872
- backend.assert_buffer_lines([
873
- "bbbbbbbbbb",
874
- "cccccccccc",
875
- "dddddddddd",
876
- "eeeeeeeeee",
877
- " ",
878
- ]);
879
- backend.assert_scrollback_lines(["aaaaaaaaaa"]);
880
- }
881
-
882
- #[test]
883
- fn append_multiple_lines_where_cursor_at_end_appends_more_than_height_lines() {
884
- let mut backend = TestBackend::with_lines([
885
- "aaaaaaaaaa",
886
- "bbbbbbbbbb",
887
- "cccccccccc",
888
- "dddddddddd",
889
- "eeeeeeeeee",
890
- ]);
891
-
892
- backend
893
- .set_cursor_position(Position { x: 0, y: 4 })
894
- .unwrap();
895
-
896
- backend.append_lines(8).unwrap();
897
- backend.assert_cursor_position(Position { x: 1, y: 4 });
898
-
899
- backend.assert_buffer_lines([
900
- " ",
901
- " ",
902
- " ",
903
- " ",
904
- " ",
905
- ]);
906
- backend.assert_scrollback_lines([
907
- "aaaaaaaaaa",
908
- "bbbbbbbbbb",
909
- "cccccccccc",
910
- "dddddddddd",
911
- "eeeeeeeeee",
912
- " ",
913
- " ",
914
- " ",
915
- ]);
916
- }
917
-
918
- #[test]
919
- fn append_lines_truncates_beyond_u16_max() -> Result<()> {
920
- let mut backend = TestBackend::new(10, 5);
921
-
922
- // Fill the scrollback with 65535 + 10 lines.
923
- let row_count = u16::MAX as usize + 10;
924
- for row in 0..=row_count {
925
- if row > 4 {
926
- backend.set_cursor_position(Position { x: 0, y: 4 })?;
927
- backend.append_lines(1)?;
928
- }
929
- let cells = format!("{row:>10}").chars().map(Cell::from).collect_vec();
930
- let content = cells
931
- .iter()
932
- .enumerate()
933
- .map(|(column, cell)| (column as u16, 4.min(row) as u16, cell));
934
- backend.draw(content)?;
935
- }
936
-
937
- // check that the buffer contains the last 5 lines appended
938
- backend.assert_buffer_lines([
939
- " 65541",
940
- " 65542",
941
- " 65543",
942
- " 65544",
943
- " 65545",
944
- ]);
945
-
946
- // TODO: ideally this should be something like:
947
- // let lines = (6..=65545).map(|row| format!("{row:>10}"));
948
- // backend.assert_scrollback_lines(lines);
949
- // but there's some truncation happening in Buffer::with_lines that needs to be fixed
950
- assert_eq!(
951
- Buffer {
952
- area: Rect::new(0, 0, 10, 5),
953
- content: backend.scrollback.content[0..10 * 5].to_vec(),
954
- },
955
- Buffer::with_lines([
956
- " 6",
957
- " 7",
958
- " 8",
959
- " 9",
960
- " 10",
961
- ]),
962
- "first 5 lines of scrollback should have been truncated"
963
- );
964
-
965
- assert_eq!(
966
- Buffer {
967
- area: Rect::new(0, 0, 10, 5),
968
- content: backend.scrollback.content[10 * 65530..10 * 65535].to_vec(),
969
- },
970
- Buffer::with_lines([
971
- " 65536",
972
- " 65537",
973
- " 65538",
974
- " 65539",
975
- " 65540",
976
- ]),
977
- "last 5 lines of scrollback should have been appended"
978
- );
979
-
980
- // These checks come after the content checks as otherwise we won't see the failing content
981
- // when these checks fail.
982
- // Make sure the scrollback is the right size.
983
- assert_eq!(backend.scrollback.area.width, 10);
984
- assert_eq!(backend.scrollback.area.height, 65535);
985
- assert_eq!(backend.scrollback.content.len(), 10 * 65535);
986
- Ok(())
987
- }
988
-
989
- #[test]
990
- fn size() {
991
- let backend = TestBackend::new(10, 2);
992
- assert_eq!(backend.size().unwrap(), Size::new(10, 2));
993
- }
994
-
995
- #[test]
996
- fn flush() {
997
- let mut backend = TestBackend::new(10, 2);
998
- backend.flush().unwrap();
999
- }
1000
-
1001
- #[cfg(feature = "scrolling-regions")]
1002
- mod scrolling_regions {
1003
- use rstest::rstest;
1004
-
1005
- use super::*;
1006
-
1007
- const A: &str = "aaaa";
1008
- const B: &str = "bbbb";
1009
- const C: &str = "cccc";
1010
- const D: &str = "dddd";
1011
- const E: &str = "eeee";
1012
- const S: &str = " ";
1013
-
1014
- #[rstest]
1015
- #[case([A, B, C, D, E], 0..5, 0, [], [A, B, C, D, E])]
1016
- #[case([A, B, C, D, E], 0..5, 2, [A, B], [C, D, E, S, S])]
1017
- #[case([A, B, C, D, E], 0..5, 5, [A, B, C, D, E], [S, S, S, S, S])]
1018
- #[case([A, B, C, D, E], 0..5, 7, [A, B, C, D, E, S, S], [S, S, S, S, S])]
1019
- #[case([A, B, C, D, E], 0..3, 0, [], [A, B, C, D, E])]
1020
- #[case([A, B, C, D, E], 0..3, 2, [A, B], [C, S, S, D, E])]
1021
- #[case([A, B, C, D, E], 0..3, 3, [A, B, C], [S, S, S, D, E])]
1022
- #[case([A, B, C, D, E], 0..3, 4, [A, B, C, S], [S, S, S, D, E])]
1023
- #[case([A, B, C, D, E], 1..4, 0, [], [A, B, C, D, E])]
1024
- #[case([A, B, C, D, E], 1..4, 2, [], [A, D, S, S, E])]
1025
- #[case([A, B, C, D, E], 1..4, 3, [], [A, S, S, S, E])]
1026
- #[case([A, B, C, D, E], 1..4, 4, [], [A, S, S, S, E])]
1027
- #[case([A, B, C, D, E], 0..0, 0, [], [A, B, C, D, E])]
1028
- #[case([A, B, C, D, E], 0..0, 2, [S, S], [A, B, C, D, E])]
1029
- #[case([A, B, C, D, E], 2..2, 0, [], [A, B, C, D, E])]
1030
- #[case([A, B, C, D, E], 2..2, 2, [], [A, B, C, D, E])]
1031
- fn scroll_region_up<const L: usize, const M: usize, const N: usize>(
1032
- #[case] initial_screen: [&'static str; L],
1033
- #[case] range: core::ops::Range<u16>,
1034
- #[case] scroll_by: u16,
1035
- #[case] expected_scrollback: [&'static str; M],
1036
- #[case] expected_buffer: [&'static str; N],
1037
- ) {
1038
- let mut backend = TestBackend::with_lines(initial_screen);
1039
- backend.scroll_region_up(range, scroll_by).unwrap();
1040
- if expected_scrollback.is_empty() {
1041
- backend.assert_scrollback_empty();
1042
- } else {
1043
- backend.assert_scrollback_lines(expected_scrollback);
1044
- }
1045
- backend.assert_buffer_lines(expected_buffer);
1046
- }
1047
-
1048
- #[rstest]
1049
- #[case([A, B, C, D, E], 0..5, 0, [A, B, C, D, E])]
1050
- #[case([A, B, C, D, E], 0..5, 2, [S, S, A, B, C])]
1051
- #[case([A, B, C, D, E], 0..5, 5, [S, S, S, S, S])]
1052
- #[case([A, B, C, D, E], 0..5, 7, [S, S, S, S, S])]
1053
- #[case([A, B, C, D, E], 0..3, 0, [A, B, C, D, E])]
1054
- #[case([A, B, C, D, E], 0..3, 2, [S, S, A, D, E])]
1055
- #[case([A, B, C, D, E], 0..3, 3, [S, S, S, D, E])]
1056
- #[case([A, B, C, D, E], 0..3, 4, [S, S, S, D, E])]
1057
- #[case([A, B, C, D, E], 1..4, 0, [A, B, C, D, E])]
1058
- #[case([A, B, C, D, E], 1..4, 2, [A, S, S, B, E])]
1059
- #[case([A, B, C, D, E], 1..4, 3, [A, S, S, S, E])]
1060
- #[case([A, B, C, D, E], 1..4, 4, [A, S, S, S, E])]
1061
- #[case([A, B, C, D, E], 0..0, 0, [A, B, C, D, E])]
1062
- #[case([A, B, C, D, E], 0..0, 2, [A, B, C, D, E])]
1063
- #[case([A, B, C, D, E], 2..2, 0, [A, B, C, D, E])]
1064
- #[case([A, B, C, D, E], 2..2, 2, [A, B, C, D, E])]
1065
- fn scroll_region_down<const M: usize, const N: usize>(
1066
- #[case] initial_screen: [&'static str; M],
1067
- #[case] range: core::ops::Range<u16>,
1068
- #[case] scroll_by: u16,
1069
- #[case] expected_buffer: [&'static str; N],
1070
- ) {
1071
- let mut backend = TestBackend::with_lines(initial_screen);
1072
- backend.scroll_region_down(range, scroll_by).unwrap();
1073
- backend.assert_scrollback_empty();
1074
- backend.assert_buffer_lines(expected_buffer);
1075
- }
1076
- }
1077
- }