1use crate::styles::*;
2use fltk::enums::Color;
3
4#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5pub struct Style {
6 pub fg: Color,
7 pub bg: Color,
8 pub bold: bool,
9 pub faint: bool,
10 pub italic: bool,
11 pub underline: bool,
12 pub strikethrough: bool,
13 pub overline: bool,
14 pub inverse: bool,
15}
16
17impl Default for Style {
18 fn default() -> Self {
19 Self {
20 fg: WHITE,
21 bg: BLACK,
22 bold: false,
23 faint: false,
24 italic: false,
25 underline: false,
26 strikethrough: false,
27 overline: false,
28 inverse: false,
29 }
30 }
31}
32
33impl Style {
34 pub fn new(bg: Color, fg: Color) -> Self {
35 Self {
36 fg,
37 bg,
38 bold: false,
39 faint: false,
40 italic: false,
41 underline: false,
42 strikethrough: false,
43 overline: false,
44 inverse: false,
45 }
46 }
47}
48
49#[derive(Clone, Copy, Debug)]
50pub struct Cell {
51 pub ch: char,
52 pub style: Style,
53}
54
55impl Cell {
56 pub fn new(ch: char, style: Style) -> Self {
57 Self { ch, style }
58 }
59}
60
61pub struct CellBuffer {
62 lines: Vec<Vec<Cell>>, pub max_lines: usize,
64 pub cur_style: Style,
65 pub default_bg: Color,
66 pub default_fg: Color,
67 dirty_start: Option<usize>,
68 dirty_end: Option<usize>,
69 dirty_cols: Vec<Option<(usize, usize)>>,
70 cursor_row: usize,
71 cursor_col: usize,
72 cols: usize,
73 rows: usize,
74}
75
76impl CellBuffer {
77 pub fn new(max_lines: usize, default_bg: Color, default_fg: Color) -> Self {
78 Self {
79 lines: vec![Vec::new()],
80 max_lines,
81 cur_style: Style {
82 fg: default_fg,
83 bg: default_bg,
84 ..Default::default()
85 },
86 default_bg,
87 default_fg,
88 dirty_start: None,
89 dirty_end: None,
90 dirty_cols: vec![None],
91 cursor_row: 0,
92 cursor_col: 0,
93 cols: 80,
94 rows: 24,
95 }
96 }
97
98 pub fn set_style(&mut self, style: Style) {
99 self.cur_style = style;
100 }
101
102 pub fn push_char(&mut self, ch: char) {
103 if let Some(line) = self.lines.last_mut() {
104 let col = line.len();
105 line.push(Cell::new(ch, self.cur_style));
106 let idx = self.lines.len().saturating_sub(1);
107 self.mark_dirty_line(idx);
108 self.mark_dirty_cols(idx, col, col);
109 self.cursor_row = idx;
110 self.cursor_col = col + 1;
111 }
112 }
113
114 pub fn newline(&mut self) {
115 self.lines.push(Vec::new());
116 self.dirty_cols.push(None);
117 if self.lines.len() > self.max_lines {
118 let overflow = self.lines.len() - self.max_lines;
119 self.lines.drain(0..overflow);
120 if overflow > 0 {
121 if self.dirty_cols.len() >= overflow {
122 self.dirty_cols.drain(0..overflow);
123 }
124 self.cursor_row = self.cursor_row.saturating_sub(overflow);
125 }
126 }
127 let idx = self.lines.len().saturating_sub(1);
128 self.mark_dirty_line(idx);
129 self.mark_dirty_cols(idx, 0, 0);
130 self.cursor_row = idx;
131 self.cursor_col = 0;
132 }
133
134 pub fn clear_line_right(&mut self) {
135 if let Some(line) = self.lines.last_mut() {
136 let before = line.len();
137 line.clear();
138 let idx = self.lines.len().saturating_sub(1);
139 self.mark_dirty_line(idx);
140 if before > 0 {
141 self.mark_dirty_cols(idx, 0, before - 1);
142 }
143 self.cursor_row = idx;
144 self.cursor_col = 0;
145 }
146 }
147
148 pub fn snapshot(&self) -> Vec<Vec<Cell>> {
149 self.lines.clone()
150 }
151
152 pub fn set_dimensions(&mut self, cols: usize, rows: usize) {
153 self.cols = cols.max(1);
154 self.rows = rows.max(1);
155 let st = self.screen_top();
157 let sb = st + self.rows.saturating_sub(1);
158 if self.cursor_row < st {
159 self.cursor_row = st;
160 self.cursor_col = 0;
161 } else if self.cursor_row > sb {
162 self.cursor_row = sb;
163 self.cursor_col = 0;
164 }
165 }
166
167 fn screen_top(&self) -> usize {
168 self.lines.len().saturating_sub(self.rows)
169 }
170
171 fn screen_bottom(&self) -> usize {
172 self.lines.len().saturating_sub(1).max(
173 self.screen_top()
174 .saturating_add(self.rows.saturating_sub(1)),
175 )
176 }
177
178 fn mark_dirty_line(&mut self, idx: usize) {
179 self.dirty_start = Some(self.dirty_start.map(|s| s.min(idx)).unwrap_or(idx));
180 self.dirty_end = Some(self.dirty_end.map(|e| e.max(idx)).unwrap_or(idx));
181 }
182
183 fn mark_dirty_cols(&mut self, idx: usize, start: usize, end: usize) {
184 if idx >= self.dirty_cols.len() {
185 self.dirty_cols.resize(idx + 1, None);
186 }
187 self.dirty_cols[idx] = match self.dirty_cols[idx] {
188 Some((s, e)) => Some((s.min(start), e.max(end))),
189 None => Some((start, end)),
190 };
191 }
192
193 pub fn take_dirty(&mut self) -> Option<(usize, usize)> {
194 match (self.dirty_start.take(), self.dirty_end.take()) {
195 (Some(s), Some(e)) if s <= e => Some((s, e)),
196 _ => None,
197 }
198 }
199
200 pub fn take_dirty_areas(&mut self) -> Vec<(usize, usize, usize)> {
201 let mut areas = Vec::new();
202 for (i, rng) in self.dirty_cols.iter_mut().enumerate() {
203 if let Some((s, e)) = rng.take() {
204 areas.push((i, s, e));
205 }
206 }
207 areas
208 }
209
210 pub fn cursor(&self) -> (usize, usize) {
211 (self.cursor_row, self.cursor_col)
212 }
213 pub fn set_cursor(&mut self, row: usize, col: usize) {
214 self.cursor_row = row;
215 self.cursor_col = col;
216 }
217
218 fn ensure_row(&mut self, row: usize) {
219 while self.lines.len() <= row {
220 self.lines.push(Vec::new());
221 self.dirty_cols.push(None);
222 }
223 }
224
225 fn ensure_col(&mut self, row: usize, col: usize) {
226 self.ensure_row(row);
227 let line = &mut self.lines[row];
228 if line.len() <= col {
229 let pad = col + 1 - line.len();
230 for _ in 0..pad {
231 line.push(Cell::new(' ', self.cur_style));
232 }
233 }
234 }
235
236 pub fn write_char(&mut self, ch: char) {
237 if self.cols > 0 && self.cursor_col >= self.cols {
239 self.line_feed();
240 }
241 let (row, col) = (self.cursor_row, self.cursor_col);
242 self.ensure_col(row, col);
243 if let Some(cell) = self.lines[row].get_mut(col) {
244 cell.ch = ch;
245 cell.style = self.cur_style;
246 }
247 self.mark_dirty_line(row);
248 self.mark_dirty_cols(row, col, col);
249 self.cursor_col = self.cursor_col.saturating_add(1);
250 }
251
252 pub fn carriage_return(&mut self) {
253 self.cursor_col = 0;
254 }
255
256 pub fn line_feed(&mut self) {
257 self.cursor_row = self.cursor_row.saturating_add(1);
258 self.cursor_col = 0;
259 self.ensure_row(self.cursor_row);
260 self.mark_dirty_line(self.cursor_row);
261 }
262
263 pub fn move_cursor_rel(&mut self, drow: isize, dcol: isize) {
264 let st = self.screen_top();
265 let sb = st + self.rows.saturating_sub(1);
266 let nr = (self.cursor_row as isize + drow).clamp(st as isize, sb as isize) as usize;
267 let mut nc = (self.cursor_col as isize + dcol).max(0) as usize;
268 if self.cols > 0 {
270 nc = nc.min(self.cols.saturating_sub(1));
271 }
272 self.cursor_row = nr;
273 self.cursor_col = nc;
274 self.ensure_row(nr);
275 }
276
277 pub fn move_cursor_abs(&mut self, row1: usize, col1: usize) {
278 let st = self.screen_top();
280 let row = st + row1.min(self.rows.saturating_sub(1));
281 let col = if self.cols > 0 {
282 col1.min(self.cols.saturating_sub(1))
283 } else {
284 col1
285 };
286 self.cursor_row = row;
287 self.cursor_col = col;
288 self.ensure_row(row);
289 }
290
291 pub fn clear_eol(&mut self) {
292 self.ensure_row(self.cursor_row);
293 let col = self.cursor_col;
294 if let Some(line) = self.lines.get_mut(self.cursor_row) {
295 if col < line.len() {
296 let end = line.len() - 1;
297 line.truncate(col);
298 self.mark_dirty_line(self.cursor_row);
299 self.mark_dirty_cols(self.cursor_row, col, end);
300 }
301 }
302 }
303
304 pub fn clear_eol_0(&mut self) {
306 self.ensure_row(self.cursor_row);
307 let col = self.cursor_col;
308 if let Some(line) = self.lines.get_mut(self.cursor_row) {
309 if col < line.len() {
310 let end = line.len().saturating_sub(1);
311 for i in col..=end {
312 if let Some(cell) = line.get_mut(i) {
313 cell.ch = ' ';
314 cell.style = self.cur_style;
315 }
316 }
317 self.mark_dirty_line(self.cursor_row);
318 self.mark_dirty_cols(self.cursor_row, col, end);
319 }
320 }
321 }
322
323 pub fn clear_eol_1(&mut self) {
325 let row = self.cursor_row;
326 self.ensure_row(row);
327 let col = self.cursor_col;
328 self.ensure_col(row, col);
330 if let Some(line) = self.lines.get_mut(row) {
331 if !line.is_empty() {
332 let end = col.min(line.len().saturating_sub(1));
333 for i in 0..=end {
334 if let Some(cell) = line.get_mut(i) {
335 cell.ch = ' ';
336 cell.style = self.cur_style;
337 }
338 }
339 self.mark_dirty_line(row);
340 self.mark_dirty_cols(row, 0, end);
341 }
342 }
343 }
344
345 pub fn clear_eol_2(&mut self) {
347 if let Some(line) = self.lines.get_mut(self.cursor_row) {
348 let len = line.len();
349 if len > 0 {
350 for c in line.iter_mut() {
351 c.ch = ' ';
352 c.style = self.cur_style;
353 }
354 self.mark_dirty_line(self.cursor_row);
355 self.mark_dirty_cols(self.cursor_row, 0, len.saturating_sub(1));
356 }
357 }
358 }
359
360 pub fn clear_ed_0(&mut self) {
361 self.clear_eol_0();
363 let row = self.cursor_row;
364 let sb = self.screen_top() + self.rows.saturating_sub(1);
365 let end_row = sb.min(self.lines.len().saturating_sub(1));
366 if row < end_row {
367 for r in row + 1..=end_row {
368 let len = self.lines[r].len();
369 if len > 0 {
370 self.lines[r].clear();
371 self.mark_dirty_line(r);
372 self.mark_dirty_cols(r, 0, len.saturating_sub(1));
373 }
374 }
375 }
376 }
377
378 pub fn clear_ed_1(&mut self) {
379 let row = self.cursor_row;
381 let st = self.screen_top();
382 for r in st..row {
384 let len = self.lines.get(r).map(|l| l.len()).unwrap_or(0);
385 if len > 0 {
386 if let Some(line) = self.lines.get_mut(r) {
387 line.clear();
388 }
389 self.mark_dirty_line(r);
390 self.mark_dirty_cols(r, 0, len.saturating_sub(1));
391 }
392 }
393 self.ensure_row(row);
395 let col = self.cursor_col;
396 self.ensure_col(row, col);
398 if let Some(line) = self.lines.get_mut(row) {
399 let end = col.min(line.len().saturating_sub(1));
400 for i in 0..=end {
401 if let Some(cell) = line.get_mut(i) {
402 cell.ch = ' ';
403 cell.style = self.cur_style;
404 }
405 }
406 self.mark_dirty_line(row);
407 if end < usize::MAX {
408 self.mark_dirty_cols(row, 0, end);
409 }
410 }
411 }
412
413 pub fn clear_ed_2(&mut self) {
414 let st = self.screen_top();
416 let sb = st + self.rows.saturating_sub(1);
417 let end_row = sb.min(self.lines.len().saturating_sub(1));
418 for r in st..=end_row {
419 let len = self.lines.get(r).map(|l| l.len()).unwrap_or(0);
420 if let Some(line) = self.lines.get_mut(r) {
421 if len > 0 {
422 line.clear();
423 }
424 }
425 if len > 0 {
426 self.mark_dirty_line(r);
427 self.mark_dirty_cols(r, 0, len.saturating_sub(1));
428 }
429 }
430 self.cursor_row = st;
432 self.cursor_col = 0;
433 }
434
435 pub fn clear_scrollback(&mut self) {
436 let st = self.screen_top();
438 if st > 0 {
439 let keep: Vec<Vec<Cell>> = self.lines.split_off(st);
440 self.lines = keep;
441 self.dirty_cols = vec![None; self.lines.len()];
442 self.cursor_row = self.cursor_row.saturating_sub(st);
443 self.mark_dirty_line(0);
444 if !self.lines.is_empty() {
445 let last_len = self.lines[0].len();
446 self.mark_dirty_cols(0, 0, last_len.saturating_sub(1));
447 }
448 }
449 }
450
451 pub fn insert_blanks(&mut self, count: usize) {
452 self.ensure_row(self.cursor_row);
453 let col = self.cursor_col;
454 if let Some(line) = self.lines.get_mut(self.cursor_row) {
455 let blanks = std::iter::repeat_n(Cell::new(' ', self.cur_style), count);
456 if col >= line.len() {
457 line.extend(blanks);
458 } else {
459 line.splice(col..col, blanks);
460 }
461 let end = col + count;
462 self.mark_dirty_line(self.cursor_row);
463 self.mark_dirty_cols(self.cursor_row, col, end);
464 }
465 }
466
467 pub fn delete_chars(&mut self, count: usize) {
468 self.ensure_row(self.cursor_row);
469 let col = self.cursor_col;
470 if let Some(line) = self.lines.get_mut(self.cursor_row) {
471 if col < line.len() {
472 let end = (col + count).min(line.len());
473 line.drain(col..end);
474 self.mark_dirty_line(self.cursor_row);
475 self.mark_dirty_cols(self.cursor_row, col, end.saturating_sub(1));
476 }
477 }
478 }
479
480 pub fn insert_lines(&mut self, count: usize) {
481 let row = self.cursor_row.min(self.lines.len());
482 for _ in 0..count {
483 self.lines.insert(row, Vec::new());
484 self.dirty_cols.insert(row, None);
485 }
486 if self.lines.len() > self.max_lines {
488 let overflow = self.lines.len() - self.max_lines;
489 for _ in 0..overflow {
490 self.lines.pop();
491 self.dirty_cols.pop();
492 }
493 }
494 let end = (row + count).min(self.lines.len().saturating_sub(1));
496 self.mark_dirty_line(row);
497 self.mark_dirty_line(end);
498 }
499
500 pub fn delete_lines(&mut self, count: usize) {
501 let row = self.cursor_row.min(self.lines.len());
502 let end = (row + count).min(self.lines.len());
503 if row < end {
504 self.lines.drain(row..end);
505 self.dirty_cols.drain(row..end);
506 if self.lines.len() < self.max_lines {
508 self.lines.push(Vec::new());
509 self.dirty_cols.push(None);
510 }
511 self.mark_dirty_line(row);
512 }
513 }
514
515 pub fn tab(&mut self) {
517 let next = ((self.cursor_col / 8) + 1) * 8;
518 if next > self.cursor_col {
519 self.ensure_col(self.cursor_row, next.saturating_sub(1));
521 self.mark_dirty_line(self.cursor_row);
523 self.mark_dirty_cols(self.cursor_row, self.cursor_col, next.saturating_sub(1));
524 self.cursor_col = next;
525 }
526 }
527
528 pub fn erase_chars(&mut self, count: usize) {
530 if count == 0 {
531 return;
532 }
533 let row = self.cursor_row;
534 let start = self.cursor_col;
535 let end = start.saturating_add(count).saturating_sub(1);
536 self.ensure_col(row, end);
537 if let Some(line) = self.lines.get_mut(row) {
538 let last = end.min(line.len().saturating_sub(1));
539 for i in start..=last {
540 if let Some(cell) = line.get_mut(i) {
541 cell.ch = ' ';
542 cell.style = self.cur_style;
543 }
544 }
545 self.mark_dirty_line(row);
546 self.mark_dirty_cols(row, start, last);
547 }
548 }
549}