promkit_core/
terminal.rs

1use std::io::{self, Write};
2
3use crate::{
4    Pane,
5    crossterm::{cursor, style, terminal},
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, panes: &[Pane]) -> anyhow::Result<()> {
15        let height = terminal::size()?.1;
16
17        let viewable_panes = panes
18            .iter()
19            .filter(|pane| !pane.is_empty())
20            .collect::<Vec<&Pane>>();
21
22        if height < viewable_panes.len() as u16 {
23            return Err(anyhow::anyhow!("Insufficient space to display all panes"));
24        }
25
26        crossterm::queue!(
27            io::stdout(),
28            cursor::MoveTo(self.position.0, self.position.1),
29            terminal::Clear(terminal::ClearType::FromCursorDown),
30        )?;
31
32        let mut used = 0;
33
34        let mut remaining_lines = height.saturating_sub(self.position.1);
35
36        for (pane_index, pane) in viewable_panes.iter().enumerate() {
37            // We need to ensure each pane gets at least 1 row
38            let max_rows = 1.max(
39                (height as usize).saturating_sub(used + viewable_panes.len() - 1 - pane_index),
40            );
41
42            let rows = pane.extract(max_rows);
43            used += rows.len();
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_panes.len() - 1;
54                let is_last_row_in_pane = row_index == rows.len() - 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}