ratatui/terminal/
terminal.rs

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