@akiojin/gwt 6.30.3 → 9.0.1

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 (98) hide show
  1. package/.cargo/config.toml +2 -0
  2. package/.claude-plugin/marketplace.json +18 -0
  3. package/.coderabbit.yaml +8 -0
  4. package/.codex/skills/gwt-fix-issue/scripts/inspect_issue.py +833 -0
  5. package/.dockerignore +63 -0
  6. package/.gitattributes +27 -0
  7. package/.husky/commit-msg +2 -0
  8. package/.husky/pre-commit +9 -0
  9. package/.husky/pre-push +12 -0
  10. package/.markdownlint.json +18 -0
  11. package/.markdownlintignore +2 -0
  12. package/Dockerfile +58 -0
  13. package/README.ja.md +161 -484
  14. package/README.md +164 -444
  15. package/cliff.toml +56 -0
  16. package/clippy.toml +2 -0
  17. package/cmake/ci-disable-native.cmake +16 -0
  18. package/codecov.yml +16 -0
  19. package/commitlint.config.cjs +107 -0
  20. package/deny.toml +35 -0
  21. package/docker-compose.yml +59 -0
  22. package/messages/errors.toml +52 -0
  23. package/package.json +12 -22
  24. package/rustfmt.toml +8 -0
  25. package/scripts/check-e2e-coverage-threshold.mjs +238 -0
  26. package/scripts/entrypoint.sh +36 -25
  27. package/scripts/install-linux-deps.sh +46 -0
  28. package/scripts/postinstall.js +79 -227
  29. package/scripts/release_issue_refs.py +317 -0
  30. package/scripts/run-local-backend-tests-on-commit.sh +15 -0
  31. package/scripts/run-local-e2e-coverage-on-commit.sh +69 -0
  32. package/scripts/run-local-e2e-on-commit.sh +60 -0
  33. package/scripts/test-all.sh +13 -0
  34. package/scripts/test_release_issue_refs.py +257 -0
  35. package/scripts/validate-skill-frontmatter.sh +108 -0
  36. package/scripts/verify-ci-node-toolchain.sh +76 -0
  37. package/scripts/verify-husky-hooks.sh +6 -0
  38. package/scripts/voice-eval.sh +48 -0
  39. package/tests/voice_eval/README.md +53 -0
  40. package/tests/voice_eval/manifest.template.json +55 -0
  41. package/tests/voice_eval/samples/.gitkeep +1 -0
  42. package/tests/voice_eval/script-ja.txt +10 -0
  43. package/vendor/ratatui-core/src/backend/test.rs +1077 -0
  44. package/vendor/ratatui-core/src/backend.rs +405 -0
  45. package/vendor/ratatui-core/src/buffer/assert.rs +71 -0
  46. package/vendor/ratatui-core/src/buffer/buffer.rs +1388 -0
  47. package/vendor/ratatui-core/src/buffer/cell.rs +377 -0
  48. package/vendor/ratatui-core/src/buffer.rs +9 -0
  49. package/vendor/ratatui-core/src/layout/alignment.rs +89 -0
  50. package/vendor/ratatui-core/src/layout/constraint.rs +526 -0
  51. package/vendor/ratatui-core/src/layout/direction.rs +63 -0
  52. package/vendor/ratatui-core/src/layout/flex.rs +212 -0
  53. package/vendor/ratatui-core/src/layout/layout.rs +2838 -0
  54. package/vendor/ratatui-core/src/layout/margin.rs +79 -0
  55. package/vendor/ratatui-core/src/layout/offset.rs +66 -0
  56. package/vendor/ratatui-core/src/layout/position.rs +253 -0
  57. package/vendor/ratatui-core/src/layout/rect/iter.rs +356 -0
  58. package/vendor/ratatui-core/src/layout/rect/ops.rs +136 -0
  59. package/vendor/ratatui-core/src/layout/rect.rs +1114 -0
  60. package/vendor/ratatui-core/src/layout/size.rs +147 -0
  61. package/vendor/ratatui-core/src/layout.rs +333 -0
  62. package/vendor/ratatui-core/src/lib.rs +82 -0
  63. package/vendor/ratatui-core/src/style/anstyle.rs +348 -0
  64. package/vendor/ratatui-core/src/style/color.rs +788 -0
  65. package/vendor/ratatui-core/src/style/palette/material.rs +608 -0
  66. package/vendor/ratatui-core/src/style/palette/tailwind.rs +653 -0
  67. package/vendor/ratatui-core/src/style/palette.rs +6 -0
  68. package/vendor/ratatui-core/src/style/palette_conversion.rs +82 -0
  69. package/vendor/ratatui-core/src/style/stylize.rs +668 -0
  70. package/vendor/ratatui-core/src/style.rs +1069 -0
  71. package/vendor/ratatui-core/src/symbols/bar.rs +51 -0
  72. package/vendor/ratatui-core/src/symbols/block.rs +51 -0
  73. package/vendor/ratatui-core/src/symbols/border.rs +709 -0
  74. package/vendor/ratatui-core/src/symbols/braille.rs +21 -0
  75. package/vendor/ratatui-core/src/symbols/half_block.rs +3 -0
  76. package/vendor/ratatui-core/src/symbols/line.rs +259 -0
  77. package/vendor/ratatui-core/src/symbols/marker.rs +82 -0
  78. package/vendor/ratatui-core/src/symbols/merge.rs +748 -0
  79. package/vendor/ratatui-core/src/symbols/pixel.rs +30 -0
  80. package/vendor/ratatui-core/src/symbols/scrollbar.rs +46 -0
  81. package/vendor/ratatui-core/src/symbols/shade.rs +5 -0
  82. package/vendor/ratatui-core/src/symbols.rs +15 -0
  83. package/vendor/ratatui-core/src/terminal/frame.rs +192 -0
  84. package/vendor/ratatui-core/src/terminal/terminal.rs +926 -0
  85. package/vendor/ratatui-core/src/terminal/viewport.rs +58 -0
  86. package/vendor/ratatui-core/src/terminal.rs +40 -0
  87. package/vendor/ratatui-core/src/text/grapheme.rs +84 -0
  88. package/vendor/ratatui-core/src/text/line.rs +1678 -0
  89. package/vendor/ratatui-core/src/text/masked.rs +149 -0
  90. package/vendor/ratatui-core/src/text/span.rs +904 -0
  91. package/vendor/ratatui-core/src/text/text.rs +1434 -0
  92. package/vendor/ratatui-core/src/text.rs +64 -0
  93. package/vendor/ratatui-core/src/widgets/stateful_widget.rs +193 -0
  94. package/vendor/ratatui-core/src/widgets/widget.rs +174 -0
  95. package/vendor/ratatui-core/src/widgets.rs +9 -0
  96. package/bin/gwt.js +0 -131
  97. package/scripts/postinstall.test.js +0 -71
  98. package/scripts/release-download.js +0 -66
@@ -0,0 +1,926 @@
1
+ use crate::backend::{Backend, ClearType};
2
+ use crate::buffer::{Buffer, Cell};
3
+ use crate::layout::{Position, Rect, Size};
4
+ use crate::terminal::{CompletedFrame, Frame, TerminalOptions, Viewport};
5
+
6
+ /// An interface to interact and draw [`Frame`]s on the user's terminal.
7
+ ///
8
+ /// This is the main entry point for Ratatui. It is responsible for drawing and maintaining the
9
+ /// state of the buffers, cursor and viewport.
10
+ ///
11
+ /// The [`Terminal`] is generic over a [`Backend`] implementation which is used to interface with
12
+ /// the underlying terminal library. The [`Backend`] trait is implemented for three popular Rust
13
+ /// terminal libraries: [Crossterm], [Termion] and [Termwiz]. See the [`backend`] module for more
14
+ /// information.
15
+ ///
16
+ /// The `Terminal` struct maintains two buffers: the current and the previous.
17
+ /// When the widgets are drawn, the changes are accumulated in the current buffer.
18
+ /// At the end of each draw pass, the two buffers are compared, and only the changes
19
+ /// between these buffers are written to the terminal, avoiding any redundant operations.
20
+ /// After flushing these changes, the buffers are swapped to prepare for the next draw cycle.
21
+ ///
22
+ /// The terminal also has a viewport which is the area of the terminal that is currently visible to
23
+ /// the user. It can be either fullscreen, inline or fixed. See [`Viewport`] for more information.
24
+ ///
25
+ /// Applications should detect terminal resizes and call [`Terminal::draw`] to redraw the
26
+ /// application with the new size. This will automatically resize the internal buffers to match the
27
+ /// new size for inline and fullscreen viewports. Fixed viewports are not resized automatically.
28
+ ///
29
+ /// # Initialization
30
+ ///
31
+ /// For most applications, consider using the convenience functions `ratatui::run()`,
32
+ /// `ratatui::init()`, and `ratatui::restore()` (available since version 0.28.1) along with the
33
+ /// `DefaultTerminal` type alias instead of constructing `Terminal` instances manually. These
34
+ /// functions handle the common setup and teardown tasks automatically. Manual construction
35
+ /// using `Terminal::new()` or `Terminal::with_options()` is still supported for applications
36
+ /// that need fine-grained control over initialization.
37
+ ///
38
+ /// # Examples
39
+ ///
40
+ /// ## Using convenience functions (recommended for most applications)
41
+ ///
42
+ /// ```rust,ignore
43
+ /// // Modern approach using convenience functions
44
+ /// ratatui::run(|terminal| {
45
+ /// terminal.draw(|frame| {
46
+ /// let area = frame.area();
47
+ /// frame.render_widget(Paragraph::new("Hello World!"), area);
48
+ /// })?;
49
+ /// Ok(())
50
+ /// })?;
51
+ /// ```
52
+ ///
53
+ /// ## Manual construction (for fine-grained control)
54
+ ///
55
+ /// ```rust,ignore
56
+ /// use std::io::stdout;
57
+ ///
58
+ /// use ratatui::{backend::CrosstermBackend, widgets::Paragraph, Terminal};
59
+ ///
60
+ /// let backend = CrosstermBackend::new(stdout());
61
+ /// let mut terminal = Terminal::new(backend)?;
62
+ /// terminal.draw(|frame| {
63
+ /// let area = frame.area();
64
+ /// frame.render_widget(Paragraph::new("Hello World!"), area);
65
+ /// })?;
66
+ /// # std::io::Result::Ok(())
67
+ /// ```
68
+ ///
69
+ /// [Crossterm]: https://crates.io/crates/crossterm
70
+ /// [Termion]: https://crates.io/crates/termion
71
+ /// [Termwiz]: https://crates.io/crates/termwiz
72
+ /// [`backend`]: crate::backend
73
+ /// [`Backend`]: crate::backend::Backend
74
+ /// [`Buffer`]: crate::buffer::Buffer
75
+ #[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
76
+ pub struct Terminal<B>
77
+ where
78
+ B: Backend,
79
+ {
80
+ /// The backend used to interface with the terminal
81
+ backend: B,
82
+ /// Holds the results of the current and previous draw calls. The two are compared at the end
83
+ /// of each draw pass to output the necessary updates to the terminal
84
+ buffers: [Buffer; 2],
85
+ /// Index of the current buffer in the previous array
86
+ current: usize,
87
+ /// Whether the cursor is currently hidden
88
+ hidden_cursor: bool,
89
+ /// Viewport
90
+ viewport: Viewport,
91
+ /// Area of the viewport
92
+ viewport_area: Rect,
93
+ /// Last known area of the terminal. Used to detect if the internal buffers have to be resized.
94
+ last_known_area: Rect,
95
+ /// Last known position of the cursor. Used to find the new area when the viewport is inlined
96
+ /// and the terminal resized.
97
+ last_known_cursor_pos: Position,
98
+ /// Number of frames rendered up until current time.
99
+ frame_count: usize,
100
+ }
101
+
102
+ /// Options to pass to [`Terminal::with_options`]
103
+ #[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
104
+ pub struct Options {
105
+ /// Viewport used to draw to the terminal
106
+ pub viewport: Viewport,
107
+ }
108
+
109
+ impl<B> Drop for Terminal<B>
110
+ where
111
+ B: Backend,
112
+ {
113
+ fn drop(&mut self) {
114
+ // Attempt to restore the cursor state
115
+ if self.hidden_cursor {
116
+ #[allow(unused_variables)]
117
+ if let Err(err) = self.show_cursor() {
118
+ #[cfg(feature = "std")]
119
+ std::eprintln!("Failed to show the cursor: {err}");
120
+ }
121
+ }
122
+ }
123
+ }
124
+
125
+ impl<B> Terminal<B>
126
+ where
127
+ B: Backend,
128
+ {
129
+ /// Creates a new [`Terminal`] with the given [`Backend`] with a full screen viewport.
130
+ ///
131
+ /// Note that unlike `ratatui::init`, this does not install a panic hook, so it is recommended
132
+ /// to do that manually when using this function, otherwise any panic messages will be printed
133
+ /// to the alternate screen and the terminal may be left in an unusable state.
134
+ ///
135
+ /// See [how to set up panic hooks](https://ratatui.rs/recipes/apps/panic-hooks/) and
136
+ /// [`better-panic` example](https://ratatui.rs/recipes/apps/better-panic/) for more
137
+ /// information.
138
+ ///
139
+ /// # Example
140
+ ///
141
+ /// ```rust,ignore
142
+ /// use std::io::stdout;
143
+ ///
144
+ /// use ratatui::{backend::CrosstermBackend, Terminal};
145
+ ///
146
+ /// let backend = CrosstermBackend::new(stdout());
147
+ /// let terminal = Terminal::new(backend)?;
148
+ ///
149
+ /// // Optionally set up a panic hook to restore the terminal on panic.
150
+ /// let old_hook = std::panic::take_hook();
151
+ /// std::panic::set_hook(Box::new(move |info| {
152
+ /// ratatui::restore();
153
+ /// old_hook(info);
154
+ /// }));
155
+ /// # std::io::Result::Ok(())
156
+ /// ```
157
+ pub fn new(backend: B) -> Result<Self, B::Error> {
158
+ Self::with_options(
159
+ backend,
160
+ TerminalOptions {
161
+ viewport: Viewport::Fullscreen,
162
+ },
163
+ )
164
+ }
165
+
166
+ /// Creates a new [`Terminal`] with the given [`Backend`] and [`TerminalOptions`].
167
+ ///
168
+ /// # Example
169
+ ///
170
+ /// ```rust,ignore
171
+ /// use std::io::stdout;
172
+ ///
173
+ /// use ratatui::{backend::CrosstermBackend, layout::Rect, Terminal, TerminalOptions, Viewport};
174
+ ///
175
+ /// let backend = CrosstermBackend::new(stdout());
176
+ /// let viewport = Viewport::Fixed(Rect::new(0, 0, 10, 10));
177
+ /// let terminal = Terminal::with_options(backend, TerminalOptions { viewport })?;
178
+ /// # std::io::Result::Ok(())
179
+ /// ```
180
+ pub fn with_options(mut backend: B, options: TerminalOptions) -> Result<Self, B::Error> {
181
+ let area = match options.viewport {
182
+ Viewport::Fullscreen | Viewport::Inline(_) => backend.size()?.into(),
183
+ Viewport::Fixed(area) => area,
184
+ };
185
+ let (viewport_area, cursor_pos) = match options.viewport {
186
+ Viewport::Fullscreen => (area, Position::ORIGIN),
187
+ Viewport::Inline(height) => {
188
+ compute_inline_size(&mut backend, height, area.as_size(), 0)?
189
+ }
190
+ Viewport::Fixed(area) => (area, area.as_position()),
191
+ };
192
+ Ok(Self {
193
+ backend,
194
+ buffers: [Buffer::empty(viewport_area), Buffer::empty(viewport_area)],
195
+ current: 0,
196
+ hidden_cursor: false,
197
+ viewport: options.viewport,
198
+ viewport_area,
199
+ last_known_area: area,
200
+ last_known_cursor_pos: cursor_pos,
201
+ frame_count: 0,
202
+ })
203
+ }
204
+
205
+ /// Get a Frame object which provides a consistent view into the terminal state for rendering.
206
+ ///
207
+ /// # Note
208
+ ///
209
+ /// This exists to support more advanced use cases. Most cases should be fine using
210
+ /// [`Terminal::draw`].
211
+ ///
212
+ /// [`Terminal::get_frame`] should be used when you need direct access to the frame buffer
213
+ /// outside of draw closure, for example:
214
+ ///
215
+ /// - Unit testing widgets
216
+ /// - Buffer state inspection
217
+ /// - Cursor manipulation
218
+ /// - Multiple rendering passes/Buffer Manipulation
219
+ /// - Custom frame lifecycle management
220
+ /// - Buffer exporting
221
+ ///
222
+ /// # Example
223
+ ///
224
+ /// Getting the buffer and asserting on some cells after rendering a widget.
225
+ ///
226
+ /// ```rust,ignore
227
+ /// use ratatui::{backend::TestBackend, Terminal};
228
+ /// use ratatui::widgets::Paragraph;
229
+ /// let backend = TestBackend::new(30, 5);
230
+ /// let mut terminal = Terminal::new(backend).unwrap();
231
+ /// {
232
+ /// let mut frame = terminal.get_frame();
233
+ /// frame.render_widget(Paragraph::new("Hello"), frame.area());
234
+ /// }
235
+ /// // When not using `draw`, present the buffer manually:
236
+ /// terminal.flush().unwrap();
237
+ /// terminal.swap_buffers();
238
+ /// terminal.backend_mut().flush().unwrap();
239
+ /// ```
240
+ pub const fn get_frame(&mut self) -> Frame<'_> {
241
+ let count = self.frame_count;
242
+ Frame {
243
+ cursor_position: None,
244
+ viewport_area: self.viewport_area,
245
+ buffer: self.current_buffer_mut(),
246
+ count,
247
+ }
248
+ }
249
+
250
+ /// Gets the current buffer as a mutable reference.
251
+ pub const fn current_buffer_mut(&mut self) -> &mut Buffer {
252
+ &mut self.buffers[self.current]
253
+ }
254
+
255
+ /// Gets the backend
256
+ pub const fn backend(&self) -> &B {
257
+ &self.backend
258
+ }
259
+
260
+ /// Gets the backend as a mutable reference
261
+ pub const fn backend_mut(&mut self) -> &mut B {
262
+ &mut self.backend
263
+ }
264
+
265
+ /// Obtains a difference between the previous and the current buffer and passes it to the
266
+ /// current backend for drawing.
267
+ pub fn flush(&mut self) -> Result<(), B::Error> {
268
+ let previous_buffer = &self.buffers[1 - self.current];
269
+ let current_buffer = &self.buffers[self.current];
270
+ let updates = previous_buffer.diff(current_buffer);
271
+ if let Some((col, row, _)) = updates.last() {
272
+ self.last_known_cursor_pos = Position { x: *col, y: *row };
273
+ }
274
+ self.backend.draw(updates.into_iter())
275
+ }
276
+
277
+ /// Updates the Terminal so that internal buffers match the requested area.
278
+ ///
279
+ /// Requested area will be saved to remain consistent when rendering. This leads to a full clear
280
+ /// of the screen.
281
+ pub fn resize(&mut self, area: Rect) -> Result<(), B::Error> {
282
+ let next_area = match self.viewport {
283
+ Viewport::Inline(height) => {
284
+ let offset_in_previous_viewport = self
285
+ .last_known_cursor_pos
286
+ .y
287
+ .saturating_sub(self.viewport_area.top());
288
+ compute_inline_size(
289
+ &mut self.backend,
290
+ height,
291
+ area.as_size(),
292
+ offset_in_previous_viewport,
293
+ )?
294
+ .0
295
+ }
296
+ Viewport::Fixed(_) | Viewport::Fullscreen => area,
297
+ };
298
+ self.set_viewport_area(next_area);
299
+ self.clear()?;
300
+
301
+ self.last_known_area = area;
302
+ Ok(())
303
+ }
304
+
305
+ fn set_viewport_area(&mut self, area: Rect) {
306
+ self.buffers[self.current].resize(area);
307
+ self.buffers[1 - self.current].resize(area);
308
+ self.viewport_area = area;
309
+ }
310
+
311
+ /// Queries the backend for size and resizes if it doesn't match the previous size.
312
+ pub fn autoresize(&mut self) -> Result<(), B::Error> {
313
+ // fixed viewports do not get autoresized
314
+ if matches!(self.viewport, Viewport::Fullscreen | Viewport::Inline(_)) {
315
+ let area = self.size()?.into();
316
+ if area != self.last_known_area {
317
+ self.resize(area)?;
318
+ }
319
+ }
320
+ Ok(())
321
+ }
322
+
323
+ /// Draws a single frame to the terminal.
324
+ ///
325
+ /// Returns a [`CompletedFrame`] if successful, otherwise a [`std::io::Error`].
326
+ ///
327
+ /// If the render callback passed to this method can fail, use [`try_draw`] instead.
328
+ ///
329
+ /// Applications should call `draw` or [`try_draw`] in a loop to continuously render the
330
+ /// terminal. These methods are the main entry points for drawing to the terminal.
331
+ ///
332
+ /// [`try_draw`]: Terminal::try_draw
333
+ ///
334
+ /// This method will:
335
+ ///
336
+ /// - autoresize the terminal if necessary
337
+ /// - call the render callback, passing it a [`Frame`] reference to render to
338
+ /// - flush the current internal state by copying the current buffer to the backend
339
+ /// - move the cursor to the last known position if it was set during the rendering closure
340
+ /// - return a [`CompletedFrame`] with the current buffer and the area of the terminal
341
+ ///
342
+ /// The [`CompletedFrame`] returned by this method can be useful for debugging or testing
343
+ /// purposes, but it is often not used in regular applications.
344
+ ///
345
+ /// The render callback should fully render the entire frame when called, including areas that
346
+ /// are unchanged from the previous frame. This is because each frame is compared to the
347
+ /// previous frame to determine what has changed, and only the changes are written to the
348
+ /// terminal. If the render callback does not fully render the frame, the terminal will not be
349
+ /// in a consistent state.
350
+ ///
351
+ /// # Examples
352
+ ///
353
+ /// ```rust,ignore
354
+ /// # let backend = ratatui::backend::TestBackend::new(10, 10);
355
+ /// # let mut terminal = ratatui::Terminal::new(backend)?;
356
+ /// use ratatui::{layout::Position, widgets::Paragraph};
357
+ ///
358
+ /// // with a closure
359
+ /// terminal.draw(|frame| {
360
+ /// let area = frame.area();
361
+ /// frame.render_widget(Paragraph::new("Hello World!"), area);
362
+ /// frame.set_cursor_position(Position { x: 0, y: 0 });
363
+ /// })?;
364
+ ///
365
+ /// // or with a function
366
+ /// terminal.draw(render)?;
367
+ ///
368
+ /// fn render(frame: &mut ratatui::Frame) {
369
+ /// frame.render_widget(Paragraph::new("Hello World!"), frame.area());
370
+ /// }
371
+ /// # std::io::Result::Ok(())
372
+ /// ```
373
+ pub fn draw<F>(&mut self, render_callback: F) -> Result<CompletedFrame<'_>, B::Error>
374
+ where
375
+ F: FnOnce(&mut Frame),
376
+ {
377
+ self.try_draw(|frame| {
378
+ render_callback(frame);
379
+ Ok::<(), B::Error>(())
380
+ })
381
+ }
382
+
383
+ /// Tries to draw a single frame to the terminal.
384
+ ///
385
+ /// Returns [`Result::Ok`] containing a [`CompletedFrame`] if successful, otherwise
386
+ /// [`Result::Err`] containing the [`std::io::Error`] that caused the failure.
387
+ ///
388
+ /// This is the equivalent of [`Terminal::draw`] but the render callback is a function or
389
+ /// closure that returns a `Result` instead of nothing.
390
+ ///
391
+ /// Applications should call `try_draw` or [`draw`] in a loop to continuously render the
392
+ /// terminal. These methods are the main entry points for drawing to the terminal.
393
+ ///
394
+ /// [`draw`]: Terminal::draw
395
+ ///
396
+ /// This method will:
397
+ ///
398
+ /// - autoresize the terminal if necessary
399
+ /// - call the render callback, passing it a [`Frame`] reference to render to
400
+ /// - flush the current internal state by copying the current buffer to the backend
401
+ /// - move the cursor to the last known position if it was set during the rendering closure
402
+ /// - return a [`CompletedFrame`] with the current buffer and the area of the terminal
403
+ ///
404
+ /// The render callback passed to `try_draw` can return any [`Result`] with an error type that
405
+ /// can be converted into an [`std::io::Error`] using the [`Into`] trait. This makes it possible
406
+ /// to use the `?` operator to propagate errors that occur during rendering. If the render
407
+ /// callback returns an error, the error will be returned from `try_draw` as an
408
+ /// [`std::io::Error`] and the terminal will not be updated.
409
+ ///
410
+ /// The [`CompletedFrame`] returned by this method can be useful for debugging or testing
411
+ /// purposes, but it is often not used in regular applications.
412
+ ///
413
+ /// The render callback should fully render the entire frame when called, including areas that
414
+ /// are unchanged from the previous frame. This is because each frame is compared to the
415
+ /// previous frame to determine what has changed, and only the changes are written to the
416
+ /// terminal. If the render function does not fully render the frame, the terminal will not be
417
+ /// in a consistent state.
418
+ ///
419
+ /// # Examples
420
+ ///
421
+ /// ```ignore
422
+ /// # use ratatui::layout::Position;;
423
+ /// # let backend = ratatui::backend::TestBackend::new(10, 10);
424
+ /// # let mut terminal = ratatui::Terminal::new(backend)?;
425
+ /// use std::io;
426
+ ///
427
+ /// use ratatui::widgets::Paragraph;
428
+ ///
429
+ /// // with a closure
430
+ /// terminal.try_draw(|frame| {
431
+ /// let value: u8 = "not a number".parse().map_err(io::Error::other)?;
432
+ /// let area = frame.area();
433
+ /// frame.render_widget(Paragraph::new("Hello World!"), area);
434
+ /// frame.set_cursor_position(Position { x: 0, y: 0 });
435
+ /// io::Result::Ok(())
436
+ /// })?;
437
+ ///
438
+ /// // or with a function
439
+ /// terminal.try_draw(render)?;
440
+ ///
441
+ /// fn render(frame: &mut ratatui::Frame) -> io::Result<()> {
442
+ /// let value: u8 = "not a number".parse().map_err(io::Error::other)?;
443
+ /// frame.render_widget(Paragraph::new("Hello World!"), frame.area());
444
+ /// Ok(())
445
+ /// }
446
+ /// # io::Result::Ok(())
447
+ /// ```
448
+ pub fn try_draw<F, E>(&mut self, render_callback: F) -> Result<CompletedFrame<'_>, B::Error>
449
+ where
450
+ F: FnOnce(&mut Frame) -> Result<(), E>,
451
+ E: Into<B::Error>,
452
+ {
453
+ // Autoresize - otherwise we get glitches if shrinking or potential desync between widgets
454
+ // and the terminal (if growing), which may OOB.
455
+ self.autoresize()?;
456
+
457
+ let mut frame = self.get_frame();
458
+
459
+ render_callback(&mut frame).map_err(Into::into)?;
460
+
461
+ // We can't change the cursor position right away because we have to flush the frame to
462
+ // stdout first. But we also can't keep the frame around, since it holds a &mut to
463
+ // Buffer. Thus, we're taking the important data out of the Frame and dropping it.
464
+ let cursor_position = frame.cursor_position;
465
+
466
+ // Draw to stdout
467
+ self.flush()?;
468
+
469
+ match cursor_position {
470
+ None => self.hide_cursor()?,
471
+ Some(position) => {
472
+ self.show_cursor()?;
473
+ self.set_cursor_position(position)?;
474
+ }
475
+ }
476
+
477
+ self.swap_buffers();
478
+
479
+ // Flush
480
+ self.backend.flush()?;
481
+
482
+ let completed_frame = CompletedFrame {
483
+ buffer: &self.buffers[1 - self.current],
484
+ area: self.last_known_area,
485
+ count: self.frame_count,
486
+ };
487
+
488
+ // increment frame count before returning from draw
489
+ self.frame_count = self.frame_count.wrapping_add(1);
490
+
491
+ Ok(completed_frame)
492
+ }
493
+
494
+ /// Hides the cursor.
495
+ pub fn hide_cursor(&mut self) -> Result<(), B::Error> {
496
+ self.backend.hide_cursor()?;
497
+ self.hidden_cursor = true;
498
+ Ok(())
499
+ }
500
+
501
+ /// Shows the cursor.
502
+ pub fn show_cursor(&mut self) -> Result<(), B::Error> {
503
+ self.backend.show_cursor()?;
504
+ self.hidden_cursor = false;
505
+ Ok(())
506
+ }
507
+
508
+ /// Gets the current cursor position.
509
+ ///
510
+ /// This is the position of the cursor after the last draw call and is returned as a tuple of
511
+ /// `(x, y)` coordinates.
512
+ #[deprecated = "use `get_cursor_position()` instead which returns `Result<Position>`"]
513
+ pub fn get_cursor(&mut self) -> Result<(u16, u16), B::Error> {
514
+ let Position { x, y } = self.get_cursor_position()?;
515
+ Ok((x, y))
516
+ }
517
+
518
+ /// Sets the cursor position.
519
+ #[deprecated = "use `set_cursor_position((x, y))` instead which takes `impl Into<Position>`"]
520
+ pub fn set_cursor(&mut self, x: u16, y: u16) -> Result<(), B::Error> {
521
+ self.set_cursor_position(Position { x, y })
522
+ }
523
+
524
+ /// Gets the current cursor position.
525
+ ///
526
+ /// This is the position of the cursor after the last draw call.
527
+ pub fn get_cursor_position(&mut self) -> Result<Position, B::Error> {
528
+ self.backend.get_cursor_position()
529
+ }
530
+
531
+ /// Sets the cursor position.
532
+ pub fn set_cursor_position<P: Into<Position>>(&mut self, position: P) -> Result<(), B::Error> {
533
+ let position = position.into();
534
+ self.backend.set_cursor_position(position)?;
535
+ self.last_known_cursor_pos = position;
536
+ Ok(())
537
+ }
538
+
539
+ /// Clear the terminal and force a full redraw on the next draw call.
540
+ pub fn clear(&mut self) -> Result<(), B::Error> {
541
+ match self.viewport {
542
+ Viewport::Fullscreen => self.backend.clear_region(ClearType::All)?,
543
+ Viewport::Inline(_) => {
544
+ self.backend
545
+ .set_cursor_position(self.viewport_area.as_position())?;
546
+ self.backend.clear_region(ClearType::AfterCursor)?;
547
+ }
548
+ Viewport::Fixed(_) => {
549
+ let area = self.viewport_area;
550
+ for y in area.top()..area.bottom() {
551
+ self.backend.set_cursor_position(Position { x: 0, y })?;
552
+ self.backend.clear_region(ClearType::AfterCursor)?;
553
+ }
554
+ }
555
+ }
556
+ // Reset the back buffer to make sure the next update will redraw everything.
557
+ self.buffers[1 - self.current].reset();
558
+ Ok(())
559
+ }
560
+
561
+ /// Clears the inactive buffer and swaps it with the current buffer
562
+ pub fn swap_buffers(&mut self) {
563
+ self.buffers[1 - self.current].reset();
564
+ self.current = 1 - self.current;
565
+ }
566
+
567
+ /// Queries the real size of the backend.
568
+ pub fn size(&self) -> Result<Size, B::Error> {
569
+ self.backend.size()
570
+ }
571
+
572
+ /// Insert some content before the current inline viewport. This has no effect when the
573
+ /// viewport is not inline.
574
+ ///
575
+ /// The `draw_fn` closure will be called to draw into a writable `Buffer` that is `height`
576
+ /// lines tall. The content of that `Buffer` will then be inserted before the viewport.
577
+ ///
578
+ /// If the viewport isn't yet at the bottom of the screen, inserted lines will push it towards
579
+ /// the bottom. Once the viewport is at the bottom of the screen, inserted lines will scroll
580
+ /// the area of the screen above the viewport upwards.
581
+ ///
582
+ /// Before:
583
+ /// ```ignore
584
+ /// +---------------------+
585
+ /// | pre-existing line 1 |
586
+ /// | pre-existing line 2 |
587
+ /// +---------------------+
588
+ /// | viewport |
589
+ /// +---------------------+
590
+ /// | |
591
+ /// | |
592
+ /// +---------------------+
593
+ /// ```
594
+ ///
595
+ /// After inserting 2 lines:
596
+ /// ```ignore
597
+ /// +---------------------+
598
+ /// | pre-existing line 1 |
599
+ /// | pre-existing line 2 |
600
+ /// | inserted line 1 |
601
+ /// | inserted line 2 |
602
+ /// +---------------------+
603
+ /// | viewport |
604
+ /// +---------------------+
605
+ /// +---------------------+
606
+ /// ```
607
+ ///
608
+ /// After inserting 2 more lines:
609
+ /// ```ignore
610
+ /// +---------------------+
611
+ /// | pre-existing line 2 |
612
+ /// | inserted line 1 |
613
+ /// | inserted line 2 |
614
+ /// | inserted line 3 |
615
+ /// | inserted line 4 |
616
+ /// +---------------------+
617
+ /// | viewport |
618
+ /// +---------------------+
619
+ /// ```
620
+ ///
621
+ /// If more lines are inserted than there is space on the screen, then the top lines will go
622
+ /// directly into the terminal's scrollback buffer. At the limit, if the viewport takes up the
623
+ /// whole screen, all lines will be inserted directly into the scrollback buffer.
624
+ ///
625
+ /// # Examples
626
+ ///
627
+ /// ## Insert a single line before the current viewport
628
+ ///
629
+ /// ```rust,ignore
630
+ /// use ratatui::{
631
+ /// backend::TestBackend,
632
+ /// style::{Color, Style},
633
+ /// text::{Line, Span},
634
+ /// widgets::{Paragraph, Widget},
635
+ /// Terminal,
636
+ /// };
637
+ /// # let backend = TestBackend::new(10, 10);
638
+ /// # let mut terminal = Terminal::new(backend).unwrap();
639
+ /// terminal.insert_before(1, |buf| {
640
+ /// Paragraph::new(Line::from(vec![
641
+ /// Span::raw("This line will be added "),
642
+ /// Span::styled("before", Style::default().fg(Color::Blue)),
643
+ /// Span::raw(" the current viewport"),
644
+ /// ]))
645
+ /// .render(buf.area, buf);
646
+ /// });
647
+ /// ```
648
+ pub fn insert_before<F>(&mut self, height: u16, draw_fn: F) -> Result<(), B::Error>
649
+ where
650
+ F: FnOnce(&mut Buffer),
651
+ {
652
+ match self.viewport {
653
+ #[cfg(feature = "scrolling-regions")]
654
+ Viewport::Inline(_) => self.insert_before_scrolling_regions(height, draw_fn),
655
+ #[cfg(not(feature = "scrolling-regions"))]
656
+ Viewport::Inline(_) => self.insert_before_no_scrolling_regions(height, draw_fn),
657
+ _ => Ok(()),
658
+ }
659
+ }
660
+
661
+ /// Implement `Self::insert_before` using standard backend capabilities.
662
+ #[cfg(not(feature = "scrolling-regions"))]
663
+ fn insert_before_no_scrolling_regions(
664
+ &mut self,
665
+ height: u16,
666
+ draw_fn: impl FnOnce(&mut Buffer),
667
+ ) -> Result<(), B::Error> {
668
+ // The approach of this function is to first render all of the lines to insert into a
669
+ // temporary buffer, and then to loop drawing chunks from the buffer to the screen. drawing
670
+ // this buffer onto the screen.
671
+ let area = Rect {
672
+ x: 0,
673
+ y: 0,
674
+ width: self.viewport_area.width,
675
+ height,
676
+ };
677
+ let mut buffer = Buffer::empty(area);
678
+ draw_fn(&mut buffer);
679
+ let mut buffer = buffer.content.as_slice();
680
+
681
+ // Use i32 variables so we don't have worry about overflowed u16s when adding, or about
682
+ // negative results when subtracting.
683
+ let mut drawn_height: i32 = self.viewport_area.top().into();
684
+ let mut buffer_height: i32 = height.into();
685
+ let viewport_height: i32 = self.viewport_area.height.into();
686
+ let screen_height: i32 = self.last_known_area.height.into();
687
+
688
+ // The algorithm here is to loop, drawing large chunks of text (up to a screen-full at a
689
+ // time), until the remainder of the buffer plus the viewport fits on the screen. We choose
690
+ // this loop condition because it guarantees that we can write the remainder of the buffer
691
+ // with just one call to Self::draw_lines().
692
+ while buffer_height + viewport_height > screen_height {
693
+ // We will draw as much of the buffer as possible on this iteration in order to make
694
+ // forward progress. So we have:
695
+ //
696
+ // to_draw = min(buffer_height, screen_height)
697
+ //
698
+ // We may need to scroll the screen up to make room to draw. We choose the minimal
699
+ // possible scroll amount so we don't end up with the viewport sitting in the middle of
700
+ // the screen when this function is done. The amount to scroll by is:
701
+ //
702
+ // scroll_up = max(0, drawn_height + to_draw - screen_height)
703
+ //
704
+ // We want `scroll_up` to be enough so that, after drawing, we have used the whole
705
+ // screen (drawn_height - scroll_up + to_draw = screen_height). However, there might
706
+ // already be enough room on the screen to draw without scrolling (drawn_height +
707
+ // to_draw <= screen_height). In this case, we just don't scroll at all.
708
+ let to_draw = buffer_height.min(screen_height);
709
+ let scroll_up = 0.max(drawn_height + to_draw - screen_height);
710
+ self.scroll_up(scroll_up as u16)?;
711
+ buffer = self.draw_lines((drawn_height - scroll_up) as u16, to_draw as u16, buffer)?;
712
+ drawn_height += to_draw - scroll_up;
713
+ buffer_height -= to_draw;
714
+ }
715
+
716
+ // There is now enough room on the screen for the remaining buffer plus the viewport,
717
+ // though we may still need to scroll up some of the existing text first. It's possible
718
+ // that by this point we've drained the buffer, but we may still need to scroll up to make
719
+ // room for the viewport.
720
+ //
721
+ // We want to scroll up the exact amount that will leave us completely filling the screen.
722
+ // However, it's possible that the viewport didn't start on the bottom of the screen and
723
+ // the added lines weren't enough to push it all the way to the bottom. We deal with this
724
+ // case by just ensuring that our scroll amount is non-negative.
725
+ //
726
+ // We want:
727
+ // screen_height = drawn_height - scroll_up + buffer_height + viewport_height
728
+ // Or, equivalently:
729
+ // scroll_up = drawn_height + buffer_height + viewport_height - screen_height
730
+ let scroll_up = 0.max(drawn_height + buffer_height + viewport_height - screen_height);
731
+ self.scroll_up(scroll_up as u16)?;
732
+ self.draw_lines(
733
+ (drawn_height - scroll_up) as u16,
734
+ buffer_height as u16,
735
+ buffer,
736
+ )?;
737
+ drawn_height += buffer_height - scroll_up;
738
+
739
+ self.set_viewport_area(Rect {
740
+ y: drawn_height as u16,
741
+ ..self.viewport_area
742
+ });
743
+
744
+ // Clear the viewport off the screen. We didn't clear earlier for two reasons. First, it
745
+ // wasn't necessary because the buffer we drew out of isn't sparse, so it overwrote
746
+ // whatever was on the screen. Second, there is a weird bug with tmux where a full screen
747
+ // clear plus immediate scrolling causes some garbage to go into the scrollback.
748
+ self.clear()?;
749
+
750
+ Ok(())
751
+ }
752
+
753
+ /// Implement `Self::insert_before` using scrolling regions.
754
+ ///
755
+ /// If a terminal supports scrolling regions, it means that we can define a subset of rows of
756
+ /// the screen, and then tell the terminal to scroll up or down just within that region. The
757
+ /// rows outside of the region are not affected.
758
+ ///
759
+ /// This function utilizes this feature to avoid having to redraw the viewport. This is done
760
+ /// either by splitting the screen at the top of the viewport, and then creating a gap by
761
+ /// either scrolling the viewport down, or scrolling the area above it up. The lines to insert
762
+ /// are then drawn into the gap created.
763
+ #[cfg(feature = "scrolling-regions")]
764
+ fn insert_before_scrolling_regions(
765
+ &mut self,
766
+ mut height: u16,
767
+ draw_fn: impl FnOnce(&mut Buffer),
768
+ ) -> Result<(), B::Error> {
769
+ // The approach of this function is to first render all of the lines to insert into a
770
+ // temporary buffer, and then to loop drawing chunks from the buffer to the screen. drawing
771
+ // this buffer onto the screen.
772
+ let area = Rect {
773
+ x: 0,
774
+ y: 0,
775
+ width: self.viewport_area.width,
776
+ height,
777
+ };
778
+ let mut buffer = Buffer::empty(area);
779
+ draw_fn(&mut buffer);
780
+ let mut buffer = buffer.content.as_slice();
781
+
782
+ // Handle the special case where the viewport takes up the whole screen.
783
+ if self.viewport_area.height == self.last_known_area.height {
784
+ // "Borrow" the top line of the viewport. Draw over it, then immediately scroll it into
785
+ // scrollback. Do this repeatedly until the whole buffer has been put into scrollback.
786
+ let mut first = true;
787
+ while !buffer.is_empty() {
788
+ buffer = if first {
789
+ self.draw_lines(0, 1, buffer)?
790
+ } else {
791
+ self.draw_lines_over_cleared(0, 1, buffer)?
792
+ };
793
+ first = false;
794
+ self.backend.scroll_region_up(0..1, 1)?;
795
+ }
796
+
797
+ // Redraw the top line of the viewport.
798
+ let width = self.viewport_area.width as usize;
799
+ let top_line = self.buffers[1 - self.current].content[0..width].to_vec();
800
+ self.draw_lines_over_cleared(0, 1, &top_line)?;
801
+ return Ok(());
802
+ }
803
+
804
+ // Handle the case where the viewport isn't yet at the bottom of the screen.
805
+ {
806
+ let viewport_top = self.viewport_area.top();
807
+ let viewport_bottom = self.viewport_area.bottom();
808
+ let screen_bottom = self.last_known_area.bottom();
809
+ if viewport_bottom < screen_bottom {
810
+ let to_draw = height.min(screen_bottom - viewport_bottom);
811
+ self.backend
812
+ .scroll_region_down(viewport_top..viewport_bottom + to_draw, to_draw)?;
813
+ buffer = self.draw_lines_over_cleared(viewport_top, to_draw, buffer)?;
814
+ self.set_viewport_area(Rect {
815
+ y: viewport_top + to_draw,
816
+ ..self.viewport_area
817
+ });
818
+ height -= to_draw;
819
+ }
820
+ }
821
+
822
+ let viewport_top = self.viewport_area.top();
823
+ while height > 0 {
824
+ let to_draw = height.min(viewport_top);
825
+ self.backend.scroll_region_up(0..viewport_top, to_draw)?;
826
+ buffer = self.draw_lines_over_cleared(viewport_top - to_draw, to_draw, buffer)?;
827
+ height -= to_draw;
828
+ }
829
+
830
+ Ok(())
831
+ }
832
+
833
+ /// Draw lines at the given vertical offset. The slice of cells must contain enough cells
834
+ /// for the requested lines. A slice of the unused cells are returned.
835
+ fn draw_lines<'a>(
836
+ &mut self,
837
+ y_offset: u16,
838
+ lines_to_draw: u16,
839
+ cells: &'a [Cell],
840
+ ) -> Result<&'a [Cell], B::Error> {
841
+ let width: usize = self.last_known_area.width.into();
842
+ let (to_draw, remainder) = cells.split_at(width * lines_to_draw as usize);
843
+ if lines_to_draw > 0 {
844
+ let iter = to_draw
845
+ .iter()
846
+ .enumerate()
847
+ .map(|(i, c)| ((i % width) as u16, y_offset + (i / width) as u16, c));
848
+ self.backend.draw(iter)?;
849
+ self.backend.flush()?;
850
+ }
851
+ Ok(remainder)
852
+ }
853
+
854
+ /// Draw lines at the given vertical offset, assuming that the lines they are replacing on the
855
+ /// screen are cleared. The slice of cells must contain enough cells for the requested lines. A
856
+ /// slice of the unused cells are returned.
857
+ #[cfg(feature = "scrolling-regions")]
858
+ fn draw_lines_over_cleared<'a>(
859
+ &mut self,
860
+ y_offset: u16,
861
+ lines_to_draw: u16,
862
+ cells: &'a [Cell],
863
+ ) -> Result<&'a [Cell], B::Error> {
864
+ let width: usize = self.last_known_area.width.into();
865
+ let (to_draw, remainder) = cells.split_at(width * lines_to_draw as usize);
866
+ if lines_to_draw > 0 {
867
+ let area = Rect::new(0, y_offset, width as u16, y_offset + lines_to_draw);
868
+ let old = Buffer::empty(area);
869
+ let new = Buffer {
870
+ area,
871
+ content: to_draw.to_vec(),
872
+ };
873
+ self.backend.draw(old.diff(&new).into_iter())?;
874
+ self.backend.flush()?;
875
+ }
876
+ Ok(remainder)
877
+ }
878
+
879
+ /// Scroll the whole screen up by the given number of lines.
880
+ #[cfg(not(feature = "scrolling-regions"))]
881
+ fn scroll_up(&mut self, lines_to_scroll: u16) -> Result<(), B::Error> {
882
+ if lines_to_scroll > 0 {
883
+ self.set_cursor_position(Position::new(
884
+ 0,
885
+ self.last_known_area.height.saturating_sub(1),
886
+ ))?;
887
+ self.backend.append_lines(lines_to_scroll)?;
888
+ }
889
+ Ok(())
890
+ }
891
+ }
892
+
893
+ fn compute_inline_size<B: Backend>(
894
+ backend: &mut B,
895
+ height: u16,
896
+ size: Size,
897
+ offset_in_previous_viewport: u16,
898
+ ) -> Result<(Rect, Position), B::Error> {
899
+ let pos = backend.get_cursor_position()?;
900
+ let mut row = pos.y;
901
+
902
+ let max_height = size.height.min(height);
903
+
904
+ let lines_after_cursor = height
905
+ .saturating_sub(offset_in_previous_viewport)
906
+ .saturating_sub(1);
907
+
908
+ backend.append_lines(lines_after_cursor)?;
909
+
910
+ let available_lines = size.height.saturating_sub(row).saturating_sub(1);
911
+ let missing_lines = lines_after_cursor.saturating_sub(available_lines);
912
+ if missing_lines > 0 {
913
+ row = row.saturating_sub(missing_lines);
914
+ }
915
+ row = row.saturating_sub(offset_in_previous_viewport);
916
+
917
+ Ok((
918
+ Rect {
919
+ x: 0,
920
+ y: row,
921
+ width: size.width,
922
+ height: max_height,
923
+ },
924
+ pos,
925
+ ))
926
+ }