Skip to main content

strat9_kernel/arch/x86_64/vga/
scrollback.rs

1//! Scrollback buffer for the framebuffer terminal.
2//!
3//! Implements a ring-buffer of character cells for scrollback, plus
4//! a "current" partial row that accumulates characters before a newline
5//! finalises it into the ring.
6
7use alloc::vec::Vec;
8
9/// Maximum number of scrollback lines retained.
10pub(crate) const MAX_SCROLLBACK: usize = 500;
11
12/// A single character cell stored in the scrollback buffer.
13#[derive(Clone, Copy)]
14pub(crate) struct SbCell {
15    pub ch: char,
16    pub fg: u32, // packed pixel colour
17    pub bg: u32, // packed pixel colour
18}
19
20/// Ring-buffer scrollback store.
21///
22/// `rows` holds the completed rows (capped at `MAX_SCROLLBACK + visible_rows`).
23/// `cur_row` accumulates the line currently being typed.
24/// `row_head` is the ring-buffer insertion index.
25pub(crate) struct ScrollbackBuffer {
26    /// Completed rows (ring buffer).
27    pub rows: Vec<Vec<SbCell>>,
28    /// Current (partial) row being accumulated.
29    pub cur_row: Vec<SbCell>,
30    /// Ring-buffer head index.
31    pub row_head: usize,
32}
33
34impl ScrollbackBuffer {
35    /// Create an empty scrollback.
36    pub const fn new() -> Self {
37        Self {
38            rows: Vec::new(),
39            cur_row: Vec::new(),
40            row_head: 0,
41        }
42    }
43
44    /// Maximum number of rows the buffer can hold before trimming.
45    #[inline]
46    pub fn capacity(&self, visible_rows: usize) -> usize {
47        MAX_SCROLLBACK + visible_rows + 1
48    }
49
50    /// Access a completed row by logical (oldest→newest) index.
51    pub fn row_at(&self, logical_idx: usize) -> Option<&Vec<SbCell>> {
52        if logical_idx >= self.rows.len() {
53            return None;
54        }
55        let phys = (self.row_head + logical_idx) % self.rows.len();
56        self.rows.get(phys)
57    }
58
59    /// Push a completed row into the ring, growing or wrapping as needed.
60    pub fn push_row(&mut self, row: Vec<SbCell>, visible_rows: usize) {
61        let cap = self.capacity(visible_rows);
62        if cap == 0 {
63            return;
64        }
65        if self.rows.len() < cap {
66            self.rows.push(row);
67            return;
68        }
69        if self.rows.is_empty() {
70            self.rows.push(row);
71            self.row_head = 0;
72            return;
73        }
74        self.rows[self.row_head] = row;
75        self.row_head = (self.row_head + 1) % self.rows.len();
76    }
77
78    /// Trim the buffer so it does not exceed `MAX_SCROLLBACK + visible_rows`.
79    #[inline]
80    pub fn trim(&mut self, visible_rows: usize) {
81        let cap = self.capacity(visible_rows);
82        if self.rows.len() <= cap {
83            return;
84        }
85        let remove_count = self.rows.len() - cap;
86        self.rows.drain(..remove_count);
87        self.row_head = 0;
88    }
89
90    /// Mirror a single character into the current (partial) row,
91    /// finalising it into the ring when a newline or overflow occurs.
92    ///
93    /// `cols` = current terminal width in columns.
94    /// `fg` / `bg` = packed foreground / background colours for the cell.
95    pub fn mirror_char(&mut self, c: char, cols: usize, fg: u32, bg: u32, visible_rows: usize) {
96        match c {
97            '\n' => {
98                let row = core::mem::take(&mut self.cur_row);
99                self.push_row(row, visible_rows);
100                self.trim(visible_rows);
101            }
102            '\r' => {
103                self.cur_row.clear();
104            }
105            '\t' => {
106                let stop = (self.cur_row.len() + 4) & !3;
107                let end = stop.min(cols);
108                while self.cur_row.len() < end {
109                    self.cur_row.push(SbCell { ch: ' ', fg, bg });
110                }
111                if self.cur_row.len() >= cols {
112                    let row = core::mem::take(&mut self.cur_row);
113                    self.push_row(row, visible_rows);
114                    self.trim(visible_rows);
115                }
116            }
117            '\u{8}' => {
118                self.cur_row.pop();
119            }
120            '\0' => {}
121            ch => {
122                self.cur_row.push(SbCell { ch, fg, bg });
123                if self.cur_row.len() >= cols {
124                    let row = core::mem::take(&mut self.cur_row);
125                    self.push_row(row, visible_rows);
126                    self.trim(visible_rows);
127                }
128            }
129        }
130    }
131}