Skip to main content

promkit_core/
terminal.rs

1use std::io::{self, Write};
2
3use crate::{
4    crossterm::{cursor, style, terminal},
5    grapheme::StyledGraphemes,
6};
7
8pub struct Terminal {
9    /// The current cursor position within the terminal.
10    pub position: (u16, u16),
11}
12
13impl Terminal {
14    pub fn draw(&mut self, graphemes: &[StyledGraphemes]) -> anyhow::Result<()> {
15        let (width, height) = terminal::size()?;
16        let visible_height = height.saturating_sub(self.position.1);
17
18        let viewable_rows = graphemes
19            .iter()
20            .map(|graphemes| graphemes.wrapped_lines(width as usize))
21            .filter(|rows| !rows.is_empty())
22            .collect::<Vec<Vec<StyledGraphemes>>>();
23
24        if height < viewable_rows.len() as u16 {
25            return Err(anyhow::anyhow!("Insufficient space to display all panes"));
26        }
27
28        crossterm::queue!(
29            io::stdout(),
30            cursor::MoveTo(self.position.0, self.position.1),
31            terminal::Clear(terminal::ClearType::FromCursorDown),
32        )?;
33
34        let mut used = 0;
35
36        let mut remaining_lines = visible_height;
37
38        for (pane_index, rows) in viewable_rows.iter().enumerate() {
39            let max_rows = 1
40                .max((height as usize).saturating_sub(used + viewable_rows.len() - 1 - pane_index));
41            let rows = rows.iter().take(max_rows).collect::<Vec<_>>();
42            let row_count = rows.len();
43            used += row_count;
44
45            for (row_index, row) in rows.iter().enumerate() {
46                crossterm::queue!(io::stdout(), style::Print(row.styled_display()))?;
47
48                remaining_lines = remaining_lines.saturating_sub(1);
49
50                // Determine if scrolling is needed:
51                // - We need to scroll if we've reached the bottom of the terminal (remaining_lines == 0)
52                // - AND we have more content to display (either more rows in current pane or more panes)
53                let is_last_pane = pane_index == viewable_rows.len() - 1;
54                let is_last_row_in_pane = row_index == row_count - 1;
55                let has_more_content = !(is_last_pane && is_last_row_in_pane);
56
57                if has_more_content && remaining_lines == 0 {
58                    crossterm::queue!(io::stdout(), terminal::ScrollUp(1))?;
59                    self.position.1 = self.position.1.saturating_sub(1);
60                }
61
62                crossterm::queue!(io::stdout(), cursor::MoveToNextLine(1))?;
63            }
64        }
65        io::stdout().flush()?;
66        Ok(())
67    }
68}