ratatui_core/terminal/
terminal.rs

1use crate::backend::{Backend, ClearType};
2use crate::buffer::{Buffer, Cell};
3use crate::layout::{Position, Rect, Size};
4use 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)]
76pub struct Terminal<B>
77where
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)]
104pub struct Options {
105    /// Viewport used to draw to the terminal
106    pub viewport: Viewport,
107}
108
109impl<B> Drop for Terminal<B>
110where
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
125impl<B> Terminal<B>
126where
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
893fn 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}