1use crate::cell::Cell;
4use crate::geometry::Size;
5
6#[derive(Clone, Debug)]
8pub struct ScreenBuffer {
9 cells: Vec<Cell>,
10 width: u16,
11 height: u16,
12}
13
14impl ScreenBuffer {
15 pub fn new(size: Size) -> Self {
17 let len = usize::from(size.width) * usize::from(size.height);
18 Self {
19 cells: vec![Cell::blank(); len],
20 width: size.width,
21 height: size.height,
22 }
23 }
24
25 pub fn size(&self) -> Size {
27 Size::new(self.width, self.height)
28 }
29
30 pub fn width(&self) -> u16 {
32 self.width
33 }
34
35 pub fn height(&self) -> u16 {
37 self.height
38 }
39
40 pub fn clear(&mut self) {
42 for cell in &mut self.cells {
43 *cell = Cell::blank();
44 }
45 }
46
47 pub fn resize(&mut self, size: Size) {
49 self.width = size.width;
50 self.height = size.height;
51 let len = usize::from(size.width) * usize::from(size.height);
52 self.cells.clear();
53 self.cells.resize(len, Cell::blank());
54 }
55
56 pub fn get(&self, x: u16, y: u16) -> Option<&Cell> {
58 if x < self.width && y < self.height {
59 let idx = self.index(x, y);
60 self.cells.get(idx)
61 } else {
62 None
63 }
64 }
65
66 pub fn get_mut(&mut self, x: u16, y: u16) -> Option<&mut Cell> {
68 if x < self.width && y < self.height {
69 let idx = self.index(x, y);
70 self.cells.get_mut(idx)
71 } else {
72 None
73 }
74 }
75
76 pub fn set(&mut self, x: u16, y: u16, cell: Cell) {
87 if x >= self.width || y >= self.height {
88 return;
89 }
90
91 let is_wide = cell.is_wide();
92
93 if is_wide && x + 1 >= self.width {
96 let idx = self.index(x, y);
97 if let Some(c) = self.cells.get_mut(idx) {
98 *c = Cell::blank();
99 }
100 return;
101 }
102
103 let idx = self.index(x, y);
106 if let Some(existing) = self.cells.get(idx)
107 && existing.is_continuation()
108 && x > 0
109 {
110 let prev_idx = self.index(x - 1, y);
111 if let Some(prev) = self.cells.get_mut(prev_idx) {
112 *prev = Cell::blank();
113 }
114 }
115
116 if let Some(existing) = self.cells.get(idx)
119 && existing.is_wide()
120 {
121 let next_x = x + 1;
122 if next_x < self.width {
123 let next_idx = self.index(next_x, y);
124 if let Some(cont) = self.cells.get_mut(next_idx) {
125 *cont = Cell::blank();
126 }
127 }
128 }
129
130 if let Some(c) = self.cells.get_mut(idx) {
132 *c = cell;
133 }
134
135 if is_wide {
137 let next_x = x + 1;
138 if next_x < self.width {
139 let next_idx = self.index(next_x, y);
142 if let Some(next_cell) = self.cells.get(next_idx)
143 && next_cell.is_wide()
144 {
145 let after_next = next_x + 1;
146 if after_next < self.width {
147 let after_idx = self.index(after_next, y);
148 if let Some(after_cell) = self.cells.get_mut(after_idx) {
149 *after_cell = Cell::blank();
150 }
151 }
152 }
153 if let Some(c) = self.cells.get_mut(next_idx) {
154 *c = Cell::continuation();
155 }
156 }
157 }
158 }
159
160 pub fn get_row(&self, y: u16) -> Option<&[Cell]> {
162 if y < self.height {
163 let start = self.index(0, y);
164 let end = start + usize::from(self.width);
165 Some(&self.cells[start..end])
166 } else {
167 None
168 }
169 }
170
171 pub fn diff(&self, previous: &ScreenBuffer) -> Vec<CellChange> {
174 if self.width != previous.width || self.height != previous.height {
176 return self.full_diff();
177 }
178
179 let mut changes = Vec::new();
180 for y in 0..self.height {
181 for x in 0..self.width {
182 let idx = self.index(x, y);
183 let current = &self.cells[idx];
184 let prev = &previous.cells[idx];
185 if current != prev {
186 changes.push(CellChange {
187 x,
188 y,
189 cell: current.clone(),
190 });
191 }
192 }
193 }
194 changes
195 }
196
197 fn full_diff(&self) -> Vec<CellChange> {
199 let mut changes = Vec::new();
200 for y in 0..self.height {
201 for x in 0..self.width {
202 let idx = self.index(x, y);
203 let cell = &self.cells[idx];
204 changes.push(CellChange {
205 x,
206 y,
207 cell: cell.clone(),
208 });
209 }
210 }
211 changes
212 }
213
214 fn index(&self, x: u16, y: u16) -> usize {
216 usize::from(y) * usize::from(self.width) + usize::from(x)
217 }
218}
219
220#[derive(Clone, Debug, PartialEq, Eq)]
222pub struct CellChange {
223 pub x: u16,
225 pub y: u16,
227 pub cell: Cell,
229}
230
231#[cfg(test)]
232mod tests {
233 use super::*;
234 use crate::color::{Color, NamedColor};
235 use crate::style::Style;
236
237 #[test]
238 fn new_buffer_all_blank() {
239 let buf = ScreenBuffer::new(Size::new(10, 5));
240 assert_eq!(buf.width(), 10);
241 assert_eq!(buf.height(), 5);
242 for y in 0..5 {
243 for x in 0..10 {
244 let cell = buf.get(x, y);
245 assert!(cell.is_some());
246 assert!(cell.is_some_and(|c| c.is_blank()));
247 }
248 }
249 }
250
251 #[test]
252 fn set_and_get() {
253 let mut buf = ScreenBuffer::new(Size::new(10, 5));
254 let style = Style::new().fg(Color::Named(NamedColor::Red));
255 let cell = Cell::new("A", style.clone());
256 buf.set(3, 2, cell.clone());
257 let got = buf.get(3, 2);
258 assert_eq!(got, Some(&cell));
259 }
260
261 #[test]
262 fn wide_char_sets_continuation() {
263 let mut buf = ScreenBuffer::new(Size::new(10, 5));
264 let wide = Cell::new("\u{4e16}", Style::default()); buf.set(3, 1, wide.clone());
266 assert_eq!(buf.get(3, 1), Some(&wide));
267 let cont = buf.get(4, 1);
269 assert!(cont.is_some());
270 assert_eq!(cont.map(|c| c.width), Some(0));
271 }
272
273 #[test]
274 fn wide_char_at_right_edge() {
275 let mut buf = ScreenBuffer::new(Size::new(5, 1));
276 let wide = Cell::new("\u{4e16}", Style::default());
277 buf.set(4, 0, wide);
280 let cell = buf.get(4, 0);
281 assert!(cell.is_some());
282 match cell {
283 Some(c) => {
284 assert!(c.is_blank(), "Wide char at last column should become blank");
285 }
286 None => unreachable!(),
287 }
288 }
289
290 #[test]
291 fn out_of_bounds_returns_none() {
292 let buf = ScreenBuffer::new(Size::new(5, 3));
293 assert!(buf.get(5, 0).is_none());
294 assert!(buf.get(0, 3).is_none());
295 assert!(buf.get(100, 100).is_none());
296 }
297
298 #[test]
299 fn out_of_bounds_set_is_noop() {
300 let mut buf = ScreenBuffer::new(Size::new(5, 3));
301 buf.set(10, 10, Cell::new("X", Style::default()));
302 }
304
305 #[test]
306 fn get_row() {
307 let buf = ScreenBuffer::new(Size::new(5, 3));
308 let row = buf.get_row(0);
309 assert!(row.is_some());
310 assert_eq!(row.map(|r| r.len()), Some(5));
311 assert!(buf.get_row(3).is_none());
312 }
313
314 #[test]
315 fn clear_resets_all_cells() {
316 let mut buf = ScreenBuffer::new(Size::new(5, 3));
317 buf.set(2, 1, Cell::new("X", Style::new().bold(true)));
318 buf.clear();
319 for y in 0..3 {
320 for x in 0..5 {
321 assert!(buf.get(x, y).is_some_and(|c| c.is_blank()));
322 }
323 }
324 }
325
326 #[test]
327 fn resize_fills_with_blank() {
328 let mut buf = ScreenBuffer::new(Size::new(5, 3));
329 buf.set(2, 1, Cell::new("X", Style::default()));
330 buf.resize(Size::new(10, 8));
331 assert_eq!(buf.width(), 10);
332 assert_eq!(buf.height(), 8);
333 for y in 0..8 {
334 for x in 0..10 {
335 assert!(buf.get(x, y).is_some_and(|c| c.is_blank()));
336 }
337 }
338 }
339
340 #[test]
341 fn diff_no_changes() {
342 let buf1 = ScreenBuffer::new(Size::new(5, 3));
343 let buf2 = ScreenBuffer::new(Size::new(5, 3));
344 let changes = buf1.diff(&buf2);
345 assert!(changes.is_empty());
346 }
347
348 #[test]
349 fn diff_single_change() {
350 let mut current = ScreenBuffer::new(Size::new(5, 3));
351 let previous = ScreenBuffer::new(Size::new(5, 3));
352 current.set(2, 1, Cell::new("A", Style::default()));
353 let changes = current.diff(&previous);
354 assert_eq!(changes.len(), 1);
355 assert_eq!(changes[0].x, 2);
356 assert_eq!(changes[0].y, 1);
357 assert_eq!(changes[0].cell.grapheme, "A");
358 }
359
360 #[test]
361 fn diff_style_change() {
362 let mut current = ScreenBuffer::new(Size::new(5, 3));
363 let mut previous = ScreenBuffer::new(Size::new(5, 3));
364 previous.set(0, 0, Cell::new("A", Style::default()));
365 current.set(0, 0, Cell::new("A", Style::new().bold(true)));
366 let changes = current.diff(&previous);
367 assert_eq!(changes.len(), 1);
368 }
369
370 #[test]
371 fn diff_size_mismatch_full_redraw() {
372 let current = ScreenBuffer::new(Size::new(5, 3));
373 let previous = ScreenBuffer::new(Size::new(10, 8));
374 let changes = current.diff(&previous);
375 assert_eq!(changes.len(), 15); }
378
379 #[test]
380 fn diff_wide_char_change() {
381 let mut current = ScreenBuffer::new(Size::new(10, 1));
382 let previous = ScreenBuffer::new(Size::new(10, 1));
383 current.set(3, 0, Cell::new("\u{4e16}", Style::default())); let changes = current.diff(&previous);
385 assert_eq!(changes.len(), 2);
387 }
388
389 #[test]
392 fn overwrite_continuation_blanks_preceding_wide() {
393 let mut buf = ScreenBuffer::new(Size::new(10, 1));
394 buf.set(3, 0, Cell::new("\u{4e16}", Style::default()));
396 buf.set(4, 0, Cell::new("X", Style::default()));
398 match buf.get(3, 0) {
400 Some(c) => assert!(c.is_blank(), "Preceding wide char should be blanked"),
401 None => unreachable!(),
402 }
403 match buf.get(4, 0) {
405 Some(c) => assert_eq!(c.grapheme, "X"),
406 None => unreachable!(),
407 }
408 }
409
410 #[test]
411 fn overwrite_wide_with_narrow_blanks_continuation() {
412 let mut buf = ScreenBuffer::new(Size::new(10, 1));
413 buf.set(3, 0, Cell::new("\u{4e16}", Style::default()));
415 buf.set(3, 0, Cell::new("A", Style::default()));
417 match buf.get(3, 0) {
419 Some(c) => assert_eq!(c.grapheme, "A"),
420 None => unreachable!(),
421 }
422 match buf.get(4, 0) {
424 Some(c) => assert!(c.is_blank(), "Old continuation should be blanked"),
425 None => unreachable!(),
426 }
427 }
428
429 #[test]
430 fn wide_char_last_column_replaced_with_space() {
431 let mut buf = ScreenBuffer::new(Size::new(10, 1));
432 buf.set(9, 0, Cell::new("\u{4e16}", Style::default()));
434 match buf.get(9, 0) {
435 Some(c) => {
436 assert!(c.is_blank(), "Wide char at last column should become space");
437 }
438 None => unreachable!(),
439 }
440 }
441
442 #[test]
443 fn wide_char_second_to_last_fits() {
444 let mut buf = ScreenBuffer::new(Size::new(10, 1));
445 let wide = Cell::new("\u{4e16}", Style::default());
447 buf.set(8, 0, wide.clone());
448 match buf.get(8, 0) {
449 Some(c) => {
450 assert_eq!(c.grapheme, "\u{4e16}");
451 assert_eq!(c.width, 2);
452 }
453 None => unreachable!(),
454 }
455 match buf.get(9, 0) {
456 Some(c) => assert!(c.is_continuation()),
457 None => unreachable!(),
458 }
459 }
460
461 #[test]
462 fn set_narrow_over_narrow_no_side_effects() {
463 let mut buf = ScreenBuffer::new(Size::new(10, 1));
464 buf.set(3, 0, Cell::new("A", Style::default()));
465 buf.set(3, 0, Cell::new("B", Style::default()));
466 match buf.get(3, 0) {
467 Some(c) => assert_eq!(c.grapheme, "B"),
468 None => unreachable!(),
469 }
470 match buf.get(2, 0) {
472 Some(c) => assert!(c.is_blank()),
473 None => unreachable!(),
474 }
475 match buf.get(4, 0) {
476 Some(c) => assert!(c.is_blank()),
477 None => unreachable!(),
478 }
479 }
480
481 #[test]
482 fn set_wide_over_wide_old_continuation_cleaned() {
483 let mut buf = ScreenBuffer::new(Size::new(10, 1));
484 buf.set(2, 0, Cell::new("\u{4e16}", Style::default()));
486 buf.set(2, 0, Cell::new("\u{754c}", Style::default()));
488 match buf.get(2, 0) {
489 Some(c) => {
490 assert_eq!(c.grapheme, "\u{754c}");
491 assert_eq!(c.width, 2);
492 }
493 None => unreachable!(),
494 }
495 match buf.get(3, 0) {
496 Some(c) => assert!(c.is_continuation()),
497 None => unreachable!(),
498 }
499 }
500
501 #[test]
502 fn multiple_wide_chars_in_sequence() {
503 let mut buf = ScreenBuffer::new(Size::new(10, 1));
504 buf.set(0, 0, Cell::new("\u{4e16}", Style::default())); buf.set(2, 0, Cell::new("\u{754c}", Style::default())); buf.set(4, 0, Cell::new("\u{4eba}", Style::default())); for col in [0, 2, 4] {
510 match buf.get(col, 0) {
511 Some(c) => assert_eq!(c.width, 2),
512 None => unreachable!(),
513 }
514 }
515 for col in [1, 3, 5] {
516 match buf.get(col, 0) {
517 Some(c) => assert!(c.is_continuation()),
518 None => unreachable!(),
519 }
520 }
521 }
522
523 #[test]
524 fn overwrite_middle_of_adjacent_wide_chars() {
525 let mut buf = ScreenBuffer::new(Size::new(10, 1));
526 buf.set(0, 0, Cell::new("\u{4e16}", Style::default()));
528 buf.set(2, 0, Cell::new("\u{754c}", Style::default()));
529 buf.set(1, 0, Cell::new("X", Style::default()));
531 match buf.get(0, 0) {
533 Some(c) => assert!(c.is_blank(), "First wide char should be blanked"),
534 None => unreachable!(),
535 }
536 match buf.get(1, 0) {
538 Some(c) => assert_eq!(c.grapheme, "X"),
539 None => unreachable!(),
540 }
541 match buf.get(2, 0) {
543 Some(c) => {
544 assert_eq!(c.grapheme, "\u{754c}");
545 assert_eq!(c.width, 2);
546 }
547 None => unreachable!(),
548 }
549 }
550
551 #[test]
552 fn wide_char_at_column_zero() {
553 let mut buf = ScreenBuffer::new(Size::new(10, 1));
554 buf.set(0, 0, Cell::new("\u{4e16}", Style::default()));
555 match buf.get(0, 0) {
556 Some(c) => {
557 assert_eq!(c.grapheme, "\u{4e16}");
558 assert_eq!(c.width, 2);
559 }
560 None => unreachable!(),
561 }
562 match buf.get(1, 0) {
563 Some(c) => assert!(c.is_continuation()),
564 None => unreachable!(),
565 }
566 }
567
568 #[test]
569 fn wide_char_continuation_exactly_at_last_column() {
570 let mut buf = ScreenBuffer::new(Size::new(6, 1));
572 buf.set(4, 0, Cell::new("\u{4e16}", Style::default()));
573 match buf.get(4, 0) {
574 Some(c) => {
575 assert_eq!(c.grapheme, "\u{4e16}");
576 assert_eq!(c.width, 2);
577 }
578 None => unreachable!(),
579 }
580 match buf.get(5, 0) {
581 Some(c) => assert!(c.is_continuation()),
582 None => unreachable!(),
583 }
584 }
585
586 #[test]
589 fn get_row_with_cjk_primary_and_continuation() {
590 let mut buf = ScreenBuffer::new(Size::new(10, 1));
592 buf.set(0, 0, Cell::new("\u{4e16}", Style::default())); buf.set(2, 0, Cell::new("\u{754c}", Style::default())); buf.set(4, 0, Cell::new("\u{4eba}", Style::default())); let row = buf.get_row(0);
597 assert!(row.is_some());
598 match row {
599 Some(cells) => {
600 assert_eq!(cells.len(), 10);
601 assert_eq!(cells[0].grapheme, "\u{4e16}");
603 assert_eq!(cells[0].width, 2);
604 assert_eq!(cells[2].grapheme, "\u{754c}");
605 assert_eq!(cells[2].width, 2);
606 assert_eq!(cells[4].grapheme, "\u{4eba}");
607 assert_eq!(cells[4].width, 2);
608 assert!(cells[1].is_continuation());
610 assert!(cells[3].is_continuation());
611 assert!(cells[5].is_continuation());
612 assert!(cells[6].is_blank());
614 assert!(cells[7].is_blank());
615 }
616 None => unreachable!(),
617 }
618 }
619
620 #[test]
621 fn diff_with_wide_char_produces_two_change_entries() {
622 let mut current = ScreenBuffer::new(Size::new(10, 1));
623 let previous = ScreenBuffer::new(Size::new(10, 1));
624 current.set(0, 0, Cell::new("\u{4e16}", Style::default()));
626 current.set(4, 0, Cell::new("\u{754c}", Style::default()));
627 let changes = current.diff(&previous);
628 assert_eq!(changes.len(), 4);
630 assert_eq!(changes[0].x, 0);
632 assert_eq!(changes[0].cell.width, 2);
633 assert_eq!(changes[1].x, 1);
634 assert_eq!(changes[1].cell.width, 0); assert_eq!(changes[2].x, 4);
637 assert_eq!(changes[2].cell.width, 2);
638 assert_eq!(changes[3].x, 5);
639 assert_eq!(changes[3].cell.width, 0); }
641
642 #[test]
643 fn clear_after_wide_char_writes_all_blank() {
644 let mut buf = ScreenBuffer::new(Size::new(10, 2));
645 buf.set(0, 0, Cell::new("\u{4e16}", Style::default()));
647 buf.set(2, 0, Cell::new("\u{754c}", Style::default()));
648 buf.set(0, 1, Cell::new("\u{1f600}", Style::default())); match buf.get(0, 0) {
651 Some(c) => assert!(!c.is_blank()),
652 None => unreachable!(),
653 }
654 buf.clear();
656 for y in 0..2 {
658 for x in 0..10 {
659 match buf.get(x, y) {
660 Some(c) => assert!(c.is_blank(), "Cell ({x},{y}) should be blank after clear"),
661 None => unreachable!(),
662 }
663 }
664 }
665 }
666}