1use crate::unicode::char_width;
2
3pub struct TerminalBuffer {
4 cells: Vec<Vec<Cell>>,
5 cursor_row: usize,
6 cursor_col: usize,
7 rows: usize,
8 cols: usize,
9 scrollback: Vec<Vec<Cell>>,
10 max_scrollback: usize,
11 scroll_top: usize,
13 scroll_bottom: usize,
14 pub window_title: String,
16}
17
18#[derive(Clone, Debug)]
19pub struct Cell {
20 pub ch: char,
21 pub fg: Color,
22 pub bg: Color,
23 pub bold: bool,
24 pub italic: bool,
25 pub underline: bool,
26 pub wide_continuation: bool,
29 pub combining: Vec<char>,
31}
32
33#[derive(Clone, Debug, Copy)]
34pub struct Color {
35 pub r: u8,
36 pub g: u8,
37 pub b: u8,
38}
39
40impl Color {
41 pub const WHITE: Self = Self {
42 r: 255,
43 g: 255,
44 b: 255,
45 };
46 pub const BLACK: Self = Self { r: 0, g: 0, b: 0 };
47 pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
48 Self { r, g, b }
49 }
50
51 pub fn from_ansi(code: u8) -> Self {
52 match code {
53 0 => Self::rgb(0, 0, 0),
54 1 => Self::rgb(205, 49, 49),
55 2 => Self::rgb(13, 188, 121),
56 3 => Self::rgb(229, 229, 16),
57 4 => Self::rgb(36, 114, 200),
58 5 => Self::rgb(188, 63, 188),
59 6 => Self::rgb(17, 168, 205),
60 7 => Self::rgb(229, 229, 229),
61 8 => Self::rgb(102, 102, 102),
62 9 => Self::rgb(241, 76, 76),
63 10 => Self::rgb(35, 209, 139),
64 11 => Self::rgb(245, 245, 67),
65 12 => Self::rgb(59, 142, 234),
66 13 => Self::rgb(214, 112, 214),
67 14 => Self::rgb(41, 184, 219),
68 15 => Self::rgb(255, 255, 255),
69 16..=231 => {
70 let idx = code - 16;
71 let r = (idx / 36) * 51;
72 let g = ((idx % 36) / 6) * 51;
73 let b = (idx % 6) * 51;
74 Self::rgb(r, g, b)
75 }
76 232..=255 => {
77 let gray = 8 + (code - 232) * 10;
78 Self::rgb(gray, gray, gray)
79 }
80 }
81 }
82}
83
84impl Default for Cell {
85 fn default() -> Self {
86 Self {
87 ch: ' ',
88 fg: Color::WHITE,
89 bg: Color::BLACK,
90 bold: false,
91 italic: false,
92 underline: false,
93 wide_continuation: false,
94 combining: Vec::new(),
95 }
96 }
97}
98
99impl TerminalBuffer {
100 pub fn new(rows: usize, cols: usize, max_scrollback: usize) -> Self {
101 let scroll_bottom = rows.saturating_sub(1);
102 Self {
103 cells: vec![vec![Cell::default(); cols]; rows],
104 cursor_row: 0,
105 cursor_col: 0,
106 rows,
107 cols,
108 scrollback: Vec::new(),
109 max_scrollback,
110 scroll_top: 0,
111 scroll_bottom,
112 window_title: String::new(),
113 }
114 }
115
116 pub fn write_char(&mut self, ch: char) {
125 match ch {
126 '\n' => {
127 self.cursor_col = 0;
128 self.advance_row();
129 }
130 '\r' => {
131 self.cursor_col = 0;
132 }
133 '\x08' => {
134 if self.cursor_col > 0 {
135 self.cursor_col -= 1;
136 }
137 }
138 '\t' => {
139 let next_tab = (self.cursor_col / 8 + 1) * 8;
140 self.cursor_col = next_tab.min(self.cols - 1);
141 }
142 _ => {
143 let w = char_width(ch);
144 match w {
145 0 => self.write_combining(ch),
146 1 => self.write_normal(ch),
147 2 => self.write_wide(ch),
148 _ => self.write_normal(ch), }
150 }
151 }
152 }
153
154 fn write_combining(&mut self, ch: char) {
155 if self.cursor_col > 0 {
158 let col = self.cursor_col - 1;
159 let row = self.cursor_row;
160 if row < self.rows && col < self.cols {
161 self.cells[row][col].combining.push(ch);
162 }
163 }
164 }
166
167 fn write_normal(&mut self, ch: char) {
168 if self.cursor_col >= self.cols {
169 self.cursor_col = 0;
170 self.advance_row();
171 }
172 let (row, col) = (self.cursor_row, self.cursor_col);
173 if row < self.rows && col < self.cols {
174 self.cells[row][col].ch = ch;
175 self.cells[row][col].wide_continuation = false;
176 self.cells[row][col].combining.clear();
177 }
178 self.cursor_col += 1;
179 }
180
181 fn write_wide(&mut self, ch: char) {
182 if self.cursor_col >= self.cols {
184 self.cursor_col = 0;
185 self.advance_row();
186 }
187 let (row, col) = (self.cursor_row, self.cursor_col);
188 if row < self.rows && col < self.cols {
189 self.cells[row][col].ch = ch;
190 self.cells[row][col].wide_continuation = false;
191 self.cells[row][col].combining.clear();
192 }
193 if col + 1 < self.cols {
194 self.cells[row][col + 1] = Cell {
196 ch: ' ',
197 wide_continuation: true,
198 ..Cell::default()
199 };
200 self.cursor_col += 2;
201 } else {
202 self.cursor_col += 1;
204 }
205 }
206
207 fn advance_row(&mut self) {
209 if self.cursor_row >= self.scroll_bottom {
210 self.scroll_region_up(1);
211 self.cursor_row = self.scroll_bottom;
213 } else {
214 self.cursor_row += 1;
215 }
216 }
217
218 pub fn scroll_up(&mut self) {
221 self.scroll_region_up(1);
222 }
223
224 pub fn scroll_region_up(&mut self, n: usize) {
226 let top = self.scroll_top;
227 let bot = self.scroll_bottom.min(self.rows - 1);
228 for _ in 0..n {
229 let removed = self.cells[top].clone();
230 self.cells.remove(top);
231 self.scrollback.push(removed);
232 if self.scrollback.len() > self.max_scrollback {
233 self.scrollback.remove(0);
234 }
235 self.cells.insert(bot, vec![Cell::default(); self.cols]);
237 }
238 }
239
240 pub fn scroll_region_down(&mut self, n: usize) {
243 let top = self.scroll_top;
244 let bot = self.scroll_bottom.min(self.rows - 1);
245 for _ in 0..n {
246 if bot < self.rows {
247 self.cells.remove(bot);
248 }
249 self.cells.insert(top, vec![Cell::default(); self.cols]);
250 }
251 }
252
253 pub fn cursor(&self) -> (usize, usize) {
254 (self.cursor_row, self.cursor_col)
255 }
256
257 pub fn cell(&self, row: usize, col: usize) -> &Cell {
258 &self.cells[row][col]
259 }
260
261 pub fn resize(&mut self, rows: usize, cols: usize) {
262 self.rows = rows;
263 self.cols = cols;
264 self.cells.resize(rows, vec![Cell::default(); cols]);
265 for row in &mut self.cells {
266 row.resize(cols, Cell::default());
267 }
268 if self.cursor_row >= rows {
269 self.cursor_row = rows - 1;
270 }
271 if self.cursor_col >= cols {
272 self.cursor_col = cols - 1;
273 }
274 if self.scroll_top >= rows {
276 self.scroll_top = 0;
277 }
278 if self.scroll_bottom >= rows {
279 self.scroll_bottom = rows - 1;
280 }
281 }
282
283 pub fn clear(&mut self) {
284 for row in &mut self.cells {
285 for cell in row.iter_mut() {
286 *cell = Cell::default();
287 }
288 }
289 self.cursor_row = 0;
290 self.cursor_col = 0;
291 }
292
293 pub fn dimensions(&self) -> (usize, usize) {
294 (self.rows, self.cols)
295 }
296
297 pub fn scrollback_len(&self) -> usize {
298 self.scrollback.len()
299 }
300
301 pub fn set_cursor(&mut self, row: usize, col: usize) {
302 self.cursor_row = row.min(self.rows.saturating_sub(1));
303 self.cursor_col = col.min(self.cols.saturating_sub(1));
304 }
305
306 pub fn set_scroll_region(&mut self, top: usize, bottom: usize) {
309 if top < bottom && bottom < self.rows {
310 self.scroll_top = top;
311 self.scroll_bottom = bottom;
312 }
313 self.cursor_row = self.scroll_top;
315 self.cursor_col = 0;
316 }
317
318 pub fn scroll_top(&self) -> usize {
319 self.scroll_top
320 }
321 pub fn scroll_bottom(&self) -> usize {
322 self.scroll_bottom
323 }
324
325 pub fn set_cell_styled(
326 &mut self,
327 row: usize,
328 col: usize,
329 ch: char,
330 fg: Color,
331 bg: Color,
332 bold: bool,
333 italic: bool,
334 underline: bool,
335 ) {
336 if row < self.rows && col < self.cols {
337 self.cells[row][col] = Cell {
338 ch,
339 fg,
340 bg,
341 bold,
342 italic,
343 underline,
344 wide_continuation: false,
345 combining: Vec::new(),
346 };
347 }
348 }
349
350 pub fn clear_below(&mut self) {
351 for col in self.cursor_col..self.cols {
352 self.cells[self.cursor_row][col] = Cell::default();
353 }
354 for row in (self.cursor_row + 1)..self.rows {
355 for col in 0..self.cols {
356 self.cells[row][col] = Cell::default();
357 }
358 }
359 }
360
361 pub fn clear_above(&mut self) {
362 for col in 0..=self.cursor_col.min(self.cols - 1) {
363 self.cells[self.cursor_row][col] = Cell::default();
364 }
365 for row in 0..self.cursor_row {
366 for col in 0..self.cols {
367 self.cells[row][col] = Cell::default();
368 }
369 }
370 }
371
372 pub fn clear_line(&mut self) {
373 for col in 0..self.cols {
374 self.cells[self.cursor_row][col] = Cell::default();
375 }
376 }
377
378 pub fn clear_line_right(&mut self) {
379 for col in self.cursor_col..self.cols {
380 self.cells[self.cursor_row][col] = Cell::default();
381 }
382 }
383
384 pub fn clear_line_left(&mut self) {
385 for col in 0..=self.cursor_col.min(self.cols - 1) {
386 self.cells[self.cursor_row][col] = Cell::default();
387 }
388 }
389}