1use crate::cells::char_width;
13use crate::segment::{ControlType, Segment, Segments};
14use crate::style::Style;
15use crate::{Console, ConsoleOptions, Renderable};
16
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct Cell {
19 pub text: String,
21 pub style: Option<Style>,
23 pub continuation: bool,
25}
26
27impl Cell {
28 pub fn blank(style: Option<Style>) -> Self {
29 Self {
30 text: " ".to_string(),
31 style,
32 continuation: false,
33 }
34 }
35
36 pub fn continuation(style: Option<Style>) -> Self {
37 Self {
38 text: String::new(),
39 style,
40 continuation: true,
41 }
42 }
43
44 pub fn width(&self) -> usize {
45 if self.continuation {
46 0
47 } else {
48 crate::cell_len(&self.text)
49 }
50 }
51}
52
53#[derive(Debug, Clone, PartialEq, Eq)]
54pub struct ScreenBuffer {
55 pub width: usize,
56 pub height: usize,
57 default_style: Option<Style>,
58 cells: Vec<Cell>,
59}
60
61impl ScreenBuffer {
62 pub fn new(width: usize, height: usize, style: Option<Style>) -> Self {
63 let width = width.max(1);
64 let height = height.max(1);
65 Self {
66 width,
67 height,
68 default_style: style,
69 cells: vec![Cell::blank(style); width * height],
70 }
71 }
72
73 fn idx(&self, x: usize, y: usize) -> usize {
74 y * self.width + x
75 }
76
77 pub fn get(&self, x: usize, y: usize) -> &Cell {
78 &self.cells[self.idx(x, y)]
79 }
80
81 pub fn get_mut(&mut self, x: usize, y: usize) -> &mut Cell {
82 let idx = self.idx(x, y);
83 &mut self.cells[idx]
84 }
85
86 pub fn as_plain_lines(&self) -> Vec<String> {
87 let mut lines = Vec::with_capacity(self.height);
88 for y in 0..self.height {
89 let mut line = String::new();
90 for x in 0..self.width {
91 let cell = self.get(x, y);
92 if cell.continuation {
93 continue;
94 }
95 if cell.text.is_empty() {
96 line.push(' ');
97 } else {
98 line.push_str(&cell.text);
99 }
100 }
101 lines.push(crate::cells::set_cell_size(&line, self.width));
102 }
103 lines
104 }
105
106 pub fn from_renderable(
110 console: &Console,
111 options: &ConsoleOptions,
112 renderable: &dyn Renderable,
113 style: Option<Style>,
114 ) -> Self {
115 let (width, height) = options.size;
116 let lines = console.render_lines(renderable, Some(options), style, true, false);
117 let lines = Segment::set_shape(&lines, width, Some(height), style, false);
118 Self::from_lines(&lines, width, height, style)
119 }
120
121 pub fn from_lines(
125 lines: &[Vec<Segment>],
126 width: usize,
127 height: usize,
128 default_style: Option<Style>,
129 ) -> Self {
130 let mut buffer = ScreenBuffer::new(width, height, default_style);
131
132 for (y, line) in lines.iter().take(height).enumerate() {
133 buffer.write_line(y, line);
134 }
135
136 buffer
137 }
138
139 fn clear_line(&mut self, y: usize) {
140 for x in 0..self.width {
141 *self.get_mut(x, y) = Cell::blank(self.default_style);
142 }
143 }
144
145 fn write_line(&mut self, y: usize, line: &[Segment]) {
146 if y >= self.height {
147 return;
148 }
149 self.clear_line(y);
150
151 let mut x: usize = 0;
152 let mut last_non_zero: Option<(usize, usize)> = None; for seg in line {
155 if seg.control.is_some() {
156 continue;
157 }
158 let style = seg.style;
159 for ch in seg.text.chars() {
160 let w = char_width(ch);
161
162 if w == 0 {
163 if let Some((prev_x, prev_w)) = last_non_zero {
165 let cell = self.get_mut(prev_x, y);
166 cell.text.push(ch);
167 cell.style = style;
170 last_non_zero = Some((prev_x, prev_w));
172 }
173 continue;
174 }
175
176 if x >= self.width {
177 return;
178 }
179
180 if w == 2 && x + 1 >= self.width {
181 *self.get_mut(x, y) = Cell::blank(style);
183 x += 1;
184 last_non_zero = Some((x.saturating_sub(1), 1));
185 continue;
186 }
187
188 *self.get_mut(x, y) = Cell {
189 text: ch.to_string(),
190 style,
191 continuation: false,
192 };
193 last_non_zero = Some((x, w));
194
195 if w == 2 {
196 *self.get_mut(x + 1, y) = Cell::continuation(style);
197 x += 2;
198 } else {
199 x += 1;
200 }
201 }
202 }
203 }
204
205 fn write_line_at(&mut self, y: usize, start_x: usize, max_width: usize, line: &[Segment]) {
206 if y >= self.height {
207 return;
208 }
209 if start_x >= self.width || max_width == 0 {
210 return;
211 }
212
213 let mut x: usize = start_x;
214 let max_x = (start_x + max_width).min(self.width);
215 let mut last_non_zero: Option<(usize, usize)> = None; for seg in line {
218 if seg.control.is_some() {
219 continue;
220 }
221 let style = seg.style;
222 for ch in seg.text.chars() {
223 let w = char_width(ch);
224
225 if w == 0 {
226 if let Some((prev_x, prev_w)) = last_non_zero {
227 if prev_x >= start_x && prev_x < max_x {
229 let cell = self.get_mut(prev_x, y);
230 cell.text.push(ch);
231 cell.style = style;
232 last_non_zero = Some((prev_x, prev_w));
233 }
234 }
235 continue;
236 }
237
238 if x >= max_x {
239 return;
240 }
241
242 if w == 2 && x + 1 >= max_x {
243 *self.get_mut(x, y) = Cell::blank(style);
245 x += 1;
246 last_non_zero = Some((x.saturating_sub(1), 1));
247 continue;
248 }
249
250 *self.get_mut(x, y) = Cell {
251 text: ch.to_string(),
252 style,
253 continuation: false,
254 };
255 last_non_zero = Some((x, w));
256
257 if w == 2 {
258 *self.get_mut(x + 1, y) = Cell::continuation(style);
259 x += 2;
260 } else {
261 x += 1;
262 }
263 }
264 }
265 }
266
267 pub fn blit_lines(&mut self, x: usize, y: usize, width: usize, lines: &[Vec<Segment>]) {
272 if width == 0 {
273 return;
274 }
275 for (row, line) in lines.iter().enumerate() {
276 let yy = y + row;
277 if yy >= self.height {
278 break;
279 }
280 self.write_line_at(yy, x, width, line);
281 }
282 }
283
284 pub fn to_styled_lines(&self) -> Vec<Vec<Segment>> {
286 let mut lines: Vec<Vec<Segment>> = Vec::with_capacity(self.height);
287
288 for y in 0..self.height {
289 let mut line: Vec<Segment> = Vec::new();
290 let mut current_style: Option<Style> = None;
291 let mut run = String::new();
292
293 let flush = |line: &mut Vec<Segment>, run: &mut String, style: Option<Style>| {
294 if run.is_empty() {
295 return;
296 }
297 let mut seg = Segment::new(std::mem::take(run));
298 seg.style = style;
299 line.push(seg);
300 };
301
302 for x in 0..self.width {
303 let cell = self.get(x, y);
304 if cell.continuation {
305 continue;
306 }
307 let text = if cell.text.is_empty() {
308 " "
309 } else {
310 cell.text.as_str()
311 };
312 if cell.style == current_style {
313 run.push_str(text);
314 } else {
315 flush(&mut line, &mut run, current_style);
316 current_style = cell.style;
317 run.push_str(text);
318 }
319 }
320 flush(&mut line, &mut run, current_style);
321 lines.push(line);
322 }
323
324 lines
325 }
326
327 fn cell_span_width(&self, x: usize, y: usize) -> usize {
328 let cell = self.get(x, y);
329 if cell.continuation {
330 0
331 } else {
332 let w = cell.width();
333 if w == 0 { 1 } else { w }
334 }
335 }
336
337 fn diff_to_segments_impl(&self, previous: &ScreenBuffer, include_home: bool) -> Segments {
344 assert_eq!(self.width, previous.width, "buffer widths differ");
345 assert_eq!(self.height, previous.height, "buffer heights differ");
346
347 let mut out = Segments::new();
348 if include_home {
349 out.push(Segment::control(ControlType::Home));
350 }
351
352 let mut cursor_x: usize = 0;
353 let mut cursor_y: usize = 0;
354
355 for y in 0..self.height {
356 let mut x: usize = 0;
357
358 while x < self.width {
359 let curr = self.get(x, y);
360 let prev = previous.get(x, y);
361
362 if curr.continuation || prev.continuation {
364 x += 1;
365 continue;
366 }
367
368 if curr == prev {
369 x += 1;
370 continue;
371 }
372
373 let mut span = self
374 .cell_span_width(x, y)
375 .max(previous.cell_span_width(x, y))
376 .max(1);
377 span = span.min(self.width.saturating_sub(x));
378
379 let mut end_x = x + span;
381 while end_x < self.width {
382 let c = self.get(end_x, y);
383 let p = previous.get(end_x, y);
384 if c.continuation || p.continuation {
385 end_x += 1;
386 continue;
387 }
388 if c == p {
389 break;
390 }
391 let extra = self
392 .cell_span_width(end_x, y)
393 .max(previous.cell_span_width(end_x, y))
394 .max(1);
395 end_x = (end_x + extra).min(self.width);
396 }
397
398 if y != cursor_y {
400 if y > cursor_y {
401 out.push(Segment::control(ControlType::CursorDown(
402 (y - cursor_y) as u16,
403 )));
404 } else {
405 out.push(Segment::control(ControlType::CursorUp(
406 (cursor_y - y) as u16,
407 )));
408 }
409 cursor_y = y;
410 cursor_x = 0;
411 out.push(Segment::control(ControlType::CarriageReturn));
412 }
413
414 if x != cursor_x {
415 out.push(Segment::control(ControlType::CarriageReturn));
417 cursor_x = 0;
418 if x > 0 {
419 out.push(Segment::control(ControlType::CursorForward(x as u16)));
420 cursor_x = x;
421 }
422 }
423
424 let mut run_x = x;
426 while run_x < end_x {
427 let cell = self.get(run_x, y);
428 if cell.continuation {
429 run_x += 1;
430 continue;
431 }
432 let w = self.cell_span_width(run_x, y).max(1);
433 let text = if cell.text.is_empty() {
434 " ".to_string()
435 } else {
436 cell.text.clone()
437 };
438 let mut seg = Segment::new(text);
439 seg.style = cell.style;
440 out.push(seg);
441 cursor_x += w;
442 run_x += w;
443 }
444
445 x = end_x;
446 }
447 }
448
449 let target_y = self.height.saturating_sub(1);
451 if target_y != cursor_y {
452 if target_y > cursor_y {
453 out.push(Segment::control(ControlType::CursorDown(
454 (target_y - cursor_y) as u16,
455 )));
456 } else {
457 out.push(Segment::control(ControlType::CursorUp(
458 (cursor_y - target_y) as u16,
459 )));
460 }
461 }
462 out.push(Segment::control(ControlType::CarriageReturn));
463
464 out
465 }
466
467 pub fn diff_to_segments(&self, previous: &ScreenBuffer) -> Segments {
474 self.diff_to_segments_impl(previous, true)
475 }
476
477 pub fn diff_to_segments_from_origin(&self, previous: &ScreenBuffer) -> Segments {
482 self.diff_to_segments_impl(previous, false)
483 }
484}
485
486#[cfg(test)]
487mod tests {
488 use super::*;
489 use crate::Text;
490
491 fn apply_segments(mut buffer: ScreenBuffer, segments: &Segments) -> ScreenBuffer {
492 let mut x: usize = 0;
493 let mut y: usize = 0;
494 let mut last_non_zero: Option<(usize, usize)> = None; let width = buffer.width;
497 let height = buffer.height;
498
499 for seg in segments.iter() {
500 if let Some(ctrl) = &seg.control {
501 match ctrl {
502 ControlType::Home => {
503 x = 0;
504 y = 0;
505 }
506 ControlType::CarriageReturn => x = 0,
507 ControlType::CursorUp(n) => y = y.saturating_sub(*n as usize),
508 ControlType::CursorDown(n) => {
509 y = (y + *n as usize).min(height.saturating_sub(1))
510 }
511 ControlType::CursorForward(n) => {
512 x = (x + *n as usize).min(width.saturating_sub(1))
513 }
514 ControlType::CursorBackward(n) => x = x.saturating_sub(*n as usize),
515 _ => {}
516 }
517 continue;
518 }
519
520 for ch in seg.text.chars() {
521 let w = char_width(ch);
522 if x >= width || y >= height {
523 break;
524 }
525
526 if w == 0 {
527 if let Some((prev_x, prev_w)) = last_non_zero {
528 let cell = buffer.get_mut(prev_x, y);
529 cell.text.push(ch);
530 cell.style = seg.style;
531 last_non_zero = Some((prev_x, prev_w));
532 }
533 continue;
534 }
535
536 if w == 2 && x + 1 >= width {
537 break;
538 }
539 *buffer.get_mut(x, y) = Cell {
540 text: ch.to_string(),
541 style: seg.style,
542 continuation: false,
543 };
544 last_non_zero = Some((x, w));
545 if w == 2 {
546 *buffer.get_mut(x + 1, y) = Cell::continuation(seg.style);
547 x += 2;
548 } else {
549 x += 1;
550 }
551 }
552 }
553
554 buffer
555 }
556
557 #[test]
558 fn test_screen_buffer_from_renderable_plain() {
559 let console = Console::new();
560 let mut options = console.options().clone();
561 options.size = (5, 2);
562 options.max_width = 5;
563 options.max_height = 2;
564
565 let buf = ScreenBuffer::from_renderable(&console, &options, &Text::plain("hi"), None);
566 assert_eq!(buf.as_plain_lines()[0], "hi ");
567 assert_eq!(buf.as_plain_lines()[1], " ");
568 }
569
570 #[test]
571 fn test_screen_buffer_diff_applies() {
572 let console = Console::new();
573 let mut options = console.options().clone();
574 options.size = (10, 3);
575 options.max_width = 10;
576 options.max_height = 3;
577
578 let prev = ScreenBuffer::from_renderable(&console, &options, &Text::plain("A"), None);
579 let next = ScreenBuffer::from_renderable(&console, &options, &Text::plain("B"), None);
580
581 let diff = next.diff_to_segments(&prev);
582 let applied = apply_segments(prev.clone(), &diff);
583 assert_eq!(applied, next);
584 }
585
586 #[test]
587 fn test_screen_buffer_diff_handles_wide_char() {
588 let console = Console::new();
589 let mut options = console.options().clone();
590 options.size = (6, 1);
591 options.max_width = 6;
592 options.max_height = 1;
593
594 let prev = ScreenBuffer::from_renderable(&console, &options, &Text::plain("你"), None);
596 let next = ScreenBuffer::from_renderable(&console, &options, &Text::plain("a"), None);
597
598 let diff = next.diff_to_segments(&prev);
599 let applied = apply_segments(prev.clone(), &diff);
600 assert_eq!(applied, next);
601 }
602
603 #[test]
604 fn test_screen_buffer_diff_uses_no_newlines() {
605 let console = Console::new();
606 let mut options = console.options().clone();
607 options.size = (10, 2);
608 options.max_width = 10;
609 options.max_height = 2;
610
611 let prev = ScreenBuffer::from_renderable(&console, &options, &Text::plain("A"), None);
612 let next = ScreenBuffer::from_renderable(&console, &options, &Text::plain("B"), None);
613 let diff = next.diff_to_segments(&prev);
614
615 assert!(diff.iter().all(|s| !s.text.contains('\n')));
616 }
617}