1use crate::{
3 Face, Glyph, Image, ImageHandler, KittyImageHandler, Position, Size, Surface, SurfaceMut,
4 SurfaceMutView, SurfaceOwned, SurfaceView, Terminal, TerminalCaps, TerminalCommand,
5 TerminalEvent, TerminalSize, TerminalWaker,
6 decoder::{Decoder, TTYCommandDecoder, Utf8Decoder},
7 encoder::{Encoder, TTYEncoder},
8 error::Error,
9 view::{
10 BoxConstraint, IntoView, Layout, Text, Tree, TreeId, TreeMut, View, ViewContext,
11 ViewLayoutStore, ViewMutLayout,
12 },
13};
14use rasterize::RGBA;
15use std::{
16 cmp::{max, min},
17 collections::HashMap,
18 fmt::Debug,
19 io::{BufWriter, Write},
20};
21use unicode_width::UnicodeWidthChar;
22
23#[derive(Debug, Clone, PartialEq, Eq, Hash)]
25pub enum CellKind {
26 Char(char),
28 Image(Image),
30 Glyph(Glyph),
32}
33
34#[derive(Debug, Clone, PartialEq, Eq, Hash)]
36pub struct Cell {
37 face: Face,
39 kind: CellKind,
41}
42
43impl Cell {
44 pub fn new_char(face: Face, character: char) -> Self {
46 Self {
47 face,
48 kind: CellKind::Char(character),
49 }
50 }
51
52 pub fn new_image(image: Image) -> Self {
54 Self {
55 face: Default::default(),
56 kind: CellKind::Image(image),
57 }
58 }
59
60 pub fn new_glyph(face: Face, glyph: Glyph) -> Self {
62 Self {
63 face,
64 kind: CellKind::Glyph(glyph),
65 }
66 }
67
68 pub fn size(&self, ctx: &ViewContext) -> Size {
70 match &self.kind {
71 CellKind::Char(ch) => Size::new(1, ch.width().unwrap_or(0)),
72 CellKind::Glyph(glyph) => {
73 if ctx.has_glyphs() {
74 glyph.size()
75 } else {
76 Size {
77 height: 1,
78 width: glyph
79 .fallback_str()
80 .chars()
81 .map(|c| c.width().unwrap_or(0))
82 .sum(),
83 }
84 }
85 }
86 CellKind::Image(image) => image.size_cells(ctx.pixels_per_cell()),
87 }
88 }
89
90 pub fn face(&self) -> Face {
92 self.face
93 }
94
95 pub fn with_face(self, face: Face) -> Self {
97 Cell { face, ..self }
98 }
99
100 pub fn kind(&self) -> &CellKind {
102 &self.kind
103 }
104
105 pub fn overlay(&mut self, other: Cell) -> &mut Self {
106 self.face = self.face.overlay(&other.face);
107 self.kind = other.kind;
108 self
109 }
110
111 pub fn layout(
121 &self,
122 ctx: &ViewContext,
123 max_width: usize,
124 wraps: bool,
125 size: &mut Size,
126 cursor: &mut Position,
127 ) -> Option<Position> {
128 if let CellKind::Char(character) = &self.kind {
130 match character {
131 '\n' => {
132 size.width = max(size.width, cursor.col);
133 size.height = max(size.height, cursor.row + 1);
134 cursor.col = 0;
135 cursor.row += 1;
136 return None;
137 }
138 '\r' => {
139 cursor.col = 0;
140 return None;
141 }
142 '\t' => {
143 cursor.col += (8 - cursor.col % 8).min(max_width.saturating_sub(cursor.col));
144 size.width = max(size.width, cursor.col);
145 return None;
146 }
147 _ => {}
148 }
149 }
150
151 let cell_size = self.size(ctx);
153 if cell_size.height == 0 || cell_size.width == 0 {
154 return None;
155 }
156
157 if cursor.col + cell_size.width <= max_width {
158 let pos = *cursor;
160 cursor.col += cell_size.width;
161 size.width = max(size.width, cursor.col);
162 size.height = max(size.height, cursor.row + cell_size.height);
163 Some(pos)
164 } else if !wraps {
165 None
166 } else {
167 cursor.row += 1;
169 cursor.col = 0;
170 size.height = max(size.height, cursor.row);
171 let pos = *cursor;
173 cursor.col = min(cell_size.width, max_width);
174 size.width = max(size.width, cursor.col);
175 size.height = max(size.height, cursor.row + cell_size.height);
176 Some(pos)
177 }
178 }
179}
180
181impl Default for Cell {
182 fn default() -> Self {
183 Self::new_char(Face::default(), ' ')
184 }
185}
186
187#[derive(Clone, Copy, Default, PartialEq, Eq, Hash)]
188enum CellMark {
189 #[default]
191 Empty,
192 Ignored,
194 Damaged,
196}
197
198pub type TerminalSurface<'a> = SurfaceMutView<'a, Cell>;
199
200pub struct TerminalRenderer {
205 size: TerminalSize,
207 front: SurfaceOwned<Cell>,
209 back: SurfaceOwned<Cell>,
211
212 marks: SurfaceOwned<CellMark>,
214 images: Vec<(Position, Face, Image)>,
216
217 glyph_cache: HashMap<Cell, Image>,
219 frame_count: usize,
221}
222
223impl TerminalRenderer {
224 pub fn new<T: Terminal + ?Sized>(term: &mut T, clear: bool) -> Result<Self, Error> {
226 let size = term.size()?;
227 let mark = if clear {
228 CellMark::Damaged
229 } else {
230 CellMark::Empty
231 };
232 Ok(Self {
233 size,
234 front: SurfaceOwned::new(size.cells),
235 back: SurfaceOwned::new(size.cells),
236 marks: SurfaceOwned::new_with(size.cells, |_| mark),
237 images: Vec::new(),
238 glyph_cache: HashMap::new(),
239 frame_count: 0,
240 })
241 }
242
243 pub fn clear<T: Terminal + ?Sized>(&mut self, term: &mut T) -> Result<(), Error> {
245 for (pos, cell) in self.back.iter().with_position() {
247 if let CellKind::Image(img) = &cell.kind {
248 term.execute(TerminalCommand::ImageErase(img.clone(), Some(pos)))?;
249 }
250 }
251
252 self.marks.fill(CellMark::Damaged);
253 self.front.fill(Cell::default());
254 self.back.fill(Cell::default());
255
256 Ok(())
257 }
258
259 pub fn surface(&mut self) -> TerminalSurface<'_> {
261 self.front.as_mut()
262 }
263
264 #[tracing::instrument(name = "[TerminalRenderer.frame]", level="debug", skip_all, fields(frame_count = %self.frame_count))]
267 pub fn frame<T: Terminal + ?Sized>(&mut self, term: &mut T) -> Result<(), Error> {
268 self.images.clear();
270 self.marks.fill(CellMark::Empty);
271
272 for ((pos, old), new) in self.back.iter().with_position().zip(self.front.iter_mut()) {
278 if let CellKind::Glyph(glyph) = &new.kind {
280 let image = match self.glyph_cache.get(new) {
281 Some(image) => image.clone(),
282 None => {
283 let image = glyph.rasterize(new.face, self.size);
284 self.glyph_cache.insert(new.clone(), image.clone());
285 image
286 }
287 };
288 new.kind = CellKind::Image(image);
289 }
290
291 if old == new && self.marks.get(pos) != Some(&CellMark::Damaged) {
294 if let CellKind::Image(image) = &new.kind {
296 let size = image.size_cells(self.size.pixels_per_cell());
297 self.marks
298 .view_mut(
299 pos.row..pos.row + size.height,
300 pos.col..pos.col + size.width,
301 )
302 .fill(CellMark::Ignored);
303 }
304 continue;
305 }
306
307 if let CellKind::Image(image) = &old.kind {
309 term.execute(TerminalCommand::ImageErase(image.clone(), Some(pos)))?;
310 let size = image.size_cells(self.size.pixels_per_cell());
311 self.marks
312 .view_mut(
313 pos.row..pos.row + size.height,
314 pos.col..pos.col + size.width,
315 )
316 .fill(CellMark::Damaged);
317 }
318
319 if let CellKind::Image(image) = &new.kind {
321 self.images.push((pos, new.face, image.clone()));
322 let size = image.size_cells(self.size.pixels_per_cell());
323 self.marks
324 .view_mut(
325 pos.row..pos.row + size.height,
326 pos.col..pos.col + size.width,
327 )
328 .fill(CellMark::Ignored);
329 }
330 }
331
332 let mut face = Face::default().with_bg(Some(RGBA::new(1, 2, 3, 255)));
336 let mut cursor = Position::new(123_456, 654_123);
337
338 let mut pos = Position::origin();
339 while pos.row < self.front.height() {
340 while pos.col < self.front.width() {
341 let offset = self.front.shape().offset(pos);
343 let new = &self.front.data()[offset];
344 let old = &self.back.data()[offset];
345 let mark = self.marks.data()[offset];
346
347 if mark != CellMark::Damaged && (mark == CellMark::Ignored || old == new) {
349 pos.col += 1;
350 continue;
351 }
352 let CellKind::Char(character) = &new.kind else {
353 pos.col += 1;
354 continue;
355 };
356 let character_width = character.width().unwrap_or(0);
357 if character_width == 0 {
358 pos.col += 1;
359 continue;
360 }
361
362 if face != new.face {
364 face = new.face;
365 term.execute(TerminalCommand::Face(face))?;
366 }
367 if cursor != pos {
368 cursor = pos;
369 term.execute(TerminalCommand::CursorTo(cursor))?;
370 }
371
372 if matches!(character, ' ') {
373 let mut repeats = 1;
375 for col in pos.col + 1..self.front.width() {
376 let pos = Position::new(pos.row, col);
377 let Some(next) = self.front.get(pos) else {
378 break;
379 };
380 let next_mark = self.marks.get(pos).copied().unwrap_or_default();
381 if next == new && next_mark != CellMark::Ignored {
382 repeats += 1;
383 } else {
384 break;
385 }
386 }
387 pos.col += repeats;
388 if repeats > 4 {
390 term.execute(TerminalCommand::EraseChars(repeats))?;
392 } else {
393 cursor.col += repeats;
394 for _ in 0..repeats {
395 term.execute(TerminalCommand::Char(' '))?;
396 }
397 }
398 } else {
399 term.execute(TerminalCommand::Char(*character))?;
400 cursor.col += character_width;
401 pos.col += character_width;
402 }
403 }
404 pos.col = 0;
405 pos.row += 1;
406 }
407
408 for (pos, face, image) in self.images.drain(..) {
410 term.execute(TerminalCommand::Face(face))?;
413 let size = image.size_cells(self.size.pixels_per_cell());
414 for row in pos.row..pos.row + size.height {
415 term.execute(TerminalCommand::CursorTo(Position::new(row, pos.col)))?;
416 term.execute(TerminalCommand::EraseChars(size.width))?;
417 }
418
419 term.execute(TerminalCommand::CursorTo(pos))?;
421 term.execute(TerminalCommand::Image(image, pos))?;
422 }
423
424 self.frame_count += 1;
426 std::mem::swap(&mut self.front, &mut self.back);
427 self.front.clear();
428
429 Ok(())
430 }
431}
432
433pub trait CellWrite {
434 fn face(&self) -> Face;
436
437 fn set_face(&mut self, face: Face) -> Face;
439
440 fn with_face(mut self, face: Face) -> Self
442 where
443 Self: Sized,
444 {
445 self.set_face(face);
446 self
447 }
448
449 fn wraps(&self) -> bool;
451
452 fn set_wraps(&mut self, wraps: bool) -> bool;
454
455 fn with_wraps(mut self, wraps: bool) -> Self
457 where
458 Self: Sized,
459 {
460 self.set_wraps(wraps);
461 self
462 }
463
464 fn scope(&mut self, scope: impl FnOnce(&mut Self)) -> &mut Self {
466 let face = self.face();
467 let wraps = self.wraps();
468 scope(self);
469 self.set_wraps(wraps);
470 self.set_face(face);
471 self
472 }
473
474 fn put_cell(&mut self, cell: Cell) -> bool;
478
479 fn with_cell(mut self, cell: Cell) -> Self
480 where
481 Self: Sized,
482 {
483 self.put_cell(cell);
484 self
485 }
486
487 fn put_char(&mut self, character: char) -> bool {
489 self.put_cell(Cell::new_char(self.face(), character))
490 }
491
492 fn with_char(mut self, character: char) -> Self
493 where
494 Self: Sized,
495 {
496 self.put_char(character);
497 self
498 }
499
500 fn put_glyph(&mut self, glyph: Glyph) -> bool {
502 self.put_cell(Cell::new_glyph(self.face(), glyph))
503 }
504
505 fn with_glyph(mut self, glyph: Glyph) -> Self
506 where
507 Self: Sized,
508 {
509 self.put_glyph(glyph);
510 self
511 }
512
513 fn put_image(&mut self, image: Image) -> bool {
515 self.put_cell(Cell::new_image(image))
516 }
517
518 fn with_image(mut self, image: Image) -> Self
519 where
520 Self: Sized,
521 {
522 self.put_image(image);
523 self
524 }
525
526 fn put_fmt<T>(&mut self, value: &T, mut face: Option<Face>) -> &mut Self
528 where
529 T: std::fmt::Display + ?Sized,
530 {
531 face = face.map(|face| self.set_face(face));
532 let _ = write!(self.utf8_writer(), "{}", value);
533 face.map(|face| self.set_face(face));
534 self
535 }
536
537 fn with_fmt<T>(mut self, value: &T, face: Option<Face>) -> Self
538 where
539 T: std::fmt::Display + ?Sized,
540 Self: Sized,
541 {
542 self.put_fmt(value, face);
543 self
544 }
545
546 fn put_text(&mut self, text: &Text) -> &mut Self {
548 text.cells().iter().cloned().for_each(|cell| {
549 self.put_cell(cell);
550 });
551 self
552 }
553
554 fn with_text(mut self, text: &Text) -> Self
555 where
556 Self: Sized,
557 {
558 self.put_text(text);
559 self
560 }
561
562 fn utf8_writer(self) -> Utf8CellWriter<Self>
566 where
567 Self: Sized,
568 {
569 Utf8CellWriter {
570 parent: self,
571 decoder: Utf8Decoder::new(),
572 }
573 }
574
575 fn tty_writer(self) -> TTYCellWriter<Self>
579 where
580 Self: Sized,
581 {
582 TTYCellWriter {
583 parent: self,
584 decoder: TTYCommandDecoder::new(),
585 }
586 }
587
588 fn by_ref(&mut self) -> &mut Self
590 where
591 Self: Sized,
592 {
593 self
594 }
595}
596
597impl<W: CellWrite + ?Sized> CellWrite for &mut W {
598 fn face(&self) -> Face {
599 (**self).face()
600 }
601
602 fn set_face(&mut self, face: Face) -> Face {
603 (**self).set_face(face)
604 }
605
606 fn wraps(&self) -> bool {
607 (**self).wraps()
608 }
609
610 fn set_wraps(&mut self, wraps: bool) -> bool {
611 (**self).set_wraps(wraps)
612 }
613
614 fn put_cell(&mut self, cell: Cell) -> bool {
615 (**self).put_cell(cell)
616 }
617}
618
619pub struct Utf8CellWriter<W> {
620 parent: W,
621 decoder: Utf8Decoder,
622}
623
624impl<W> Utf8CellWriter<W> {
625 pub fn parent(&mut self) -> &mut W {
626 &mut self.parent
627 }
628}
629
630impl<W> std::io::Write for Utf8CellWriter<W>
631where
632 W: CellWrite,
633{
634 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
635 let mut cur = std::io::Cursor::new(buf);
636 while let Some(ch) = self.decoder.decode(&mut cur)? {
637 if !self.parent.put_char(ch) {
638 return Ok(buf.len());
639 }
640 }
641 Ok(cur.position() as usize)
642 }
643
644 fn flush(&mut self) -> std::io::Result<()> {
645 Ok(())
646 }
647}
648
649pub struct TTYCellWriter<W> {
650 parent: W,
651 decoder: TTYCommandDecoder,
652}
653
654impl<W> TTYCellWriter<W> {
655 pub fn parent(&mut self) -> &mut W {
656 &mut self.parent
657 }
658}
659
660impl<W> std::io::Write for TTYCellWriter<W>
661where
662 W: CellWrite,
663{
664 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
665 let mut cur = std::io::Cursor::new(buf);
666 while let Some(cmd) = self
667 .decoder
668 .decode(&mut cur)
669 .map_err(std::io::Error::other)?
670 {
671 match cmd {
672 TerminalCommand::Char(c) => {
673 self.parent.put_char(c);
674 }
675 TerminalCommand::FaceModify(face_modify) => {
676 self.parent.set_face(face_modify.apply(self.parent.face()));
677 }
678 TerminalCommand::Image(image, _) => {
679 self.parent.put_image(image);
680 }
681 _ => continue,
682 }
683 }
684 Ok(cur.position() as usize)
685 }
686
687 fn flush(&mut self) -> std::io::Result<()> {
688 Ok(())
689 }
690}
691
692pub trait TerminalSurfaceExt: SurfaceMut<Item = Cell> {
694 fn draw_box(&mut self, face: Option<Face>);
696
697 fn draw_check_pattern(&mut self, face: Face);
699
700 fn draw_view(
702 &mut self,
703 ctx: &ViewContext,
704 layout_store: Option<&mut ViewLayoutStore>,
705 view: impl IntoView,
706 ) -> Result<TreeId, Error>;
707
708 fn erase(&mut self, face: Face);
710
711 fn writer(&mut self, ctx: &ViewContext) -> TerminalWriter<'_>;
713
714 fn debug(&self) -> TerminalSurfaceDebug<'_>;
717}
718
719impl<S> TerminalSurfaceExt for S
720where
721 S: SurfaceMut<Item = Cell>,
722{
723 fn draw_box(&mut self, face: Option<Face>) {
725 if self.width() < 2 || self.height() < 2 {
726 return;
727 }
728 let face = face.unwrap_or_default();
729
730 let h = Cell::new_char(face, '─');
731 let v = Cell::new_char(face, '│');
732 self.view_mut(0, 1..-1).fill(h.clone());
733 self.view_mut(-1, 1..-1).fill(h);
734 self.view_mut(1..-1, 0).fill(v.clone());
735 self.view_mut(1..-1, -1).fill(v);
736
737 self.view_mut(0, 0).fill(Cell::new_char(face, '┌'));
738 self.view_mut(0, -1).fill(Cell::new_char(face, '┐'));
739 self.view_mut(-1, -1).fill(Cell::new_char(face, '┘'));
740 self.view_mut(-1, 0).fill(Cell::new_char(face, '└'));
741 }
742
743 fn draw_check_pattern(&mut self, face: Face) {
745 self.fill_with(|pos, _| {
746 Cell::new_char(
747 face,
748 if pos.col & 1 == 1 {
749 '\u{2580}' } else {
751 '\u{2584}' },
753 )
754 })
755 }
756
757 fn draw_view(
759 &mut self,
760 ctx: &ViewContext,
761 layout_store: Option<&mut ViewLayoutStore>,
762 view: impl IntoView,
763 ) -> Result<TreeId, Error> {
764 let view = view.into_view();
765
766 let mut layout_store_vec = ViewLayoutStore::new();
767 let layout_store = layout_store.unwrap_or(&mut layout_store_vec);
768 layout_store.clear();
769
770 let mut layout = ViewMutLayout::new(layout_store, Layout::default());
771
772 view.layout(ctx, BoxConstraint::loose(self.size()), layout.view_mut())?;
773 view.render(ctx, self.as_mut(), layout.view())?;
774 Ok(layout.id())
775 }
776
777 fn erase(&mut self, face: Face) {
779 self.fill_with(|_, cell| Cell::new_char(cell.face.overlay(&face), ' '));
780 }
781
782 fn writer(&mut self, ctx: &ViewContext) -> TerminalWriter<'_> {
784 TerminalWriter::new(ctx.clone(), self)
785 }
786
787 fn debug(&self) -> TerminalSurfaceDebug<'_> {
790 TerminalSurfaceDebug {
791 surf: self.as_ref(),
792 }
793 }
794}
795
796pub struct TerminalWriter<'a> {
798 ctx: ViewContext,
799 wraps: bool,
800 face: Face, cursor: Position, size: Size, surf: SurfaceMutView<'a, Cell>,
804 decoder: crate::decoder::Utf8Decoder,
805}
806
807impl<'a> TerminalWriter<'a> {
808 pub fn new<S>(ctx: ViewContext, surf: &'a mut S) -> Self
809 where
810 S: SurfaceMut<Item = Cell> + ?Sized,
811 {
812 Self {
813 ctx,
814 wraps: true,
815 face: Default::default(),
816 cursor: Position::origin(),
817 size: Size::empty(),
818 surf: surf.as_mut(),
819 decoder: crate::decoder::Utf8Decoder::new(),
820 }
821 }
822
823 pub fn cursor(&self) -> Position {
825 self.cursor
826 }
827
828 pub fn set_cursor(&mut self, pos: Position) -> &mut Self {
830 self.cursor = Position {
831 col: min(pos.col, self.size().width),
832 row: min(pos.row, self.size().height),
833 };
834 self
835 }
836
837 pub fn size(&self) -> Size {
839 self.surf.size()
840 }
841}
842
843impl CellWrite for TerminalWriter<'_> {
844 fn face(&self) -> Face {
845 self.face
846 }
847
848 fn set_face(&mut self, face: Face) -> Face {
849 std::mem::replace(&mut self.face, face)
850 }
851
852 fn wraps(&self) -> bool {
853 self.wraps
854 }
855
856 fn set_wraps(&mut self, wraps: bool) -> bool {
857 std::mem::replace(&mut self.wraps, wraps)
858 }
859
860 fn put_cell(&mut self, cell: Cell) -> bool {
861 if !self.ctx.has_glyphs() {
862 if let CellKind::Glyph(glyph) = &cell.kind {
864 return glyph
865 .fallback_str()
866 .chars()
867 .all(|c| self.put_cell(Cell::new_char(cell.face, c)));
868 }
869 }
870
871 let face = self.face.overlay(&cell.face);
872 let cursor_start = self.cursor;
873 if let Some(pos) = cell.layout(
874 &self.ctx,
875 self.size().width,
876 self.wraps,
877 &mut self.size,
878 &mut self.cursor,
879 ) {
880 if let Some(cell_ref) = self.surf.get_mut(pos) {
882 cell_ref.overlay(cell.with_face(face));
883 true
884 } else {
885 false
886 }
887 } else if cursor_start != self.cursor {
888 let shape = self.surf.shape();
891 let data = self.surf.data_mut();
892
893 let start = shape.offset(cursor_start);
894 let end = shape.offset(self.cursor);
895
896 for row in cursor_start.row..min(self.cursor.row + 1, shape.height) {
897 for col in 0..shape.width {
898 let offset = shape.offset(Position::new(row, col));
899 if (start..end).contains(&offset) {
900 let cell = &mut data[offset];
901 cell.face = cell.face.overlay(&face);
902 }
903 }
904 }
905 true
906 } else {
907 true
908 }
909 }
910}
911
912impl std::io::Write for TerminalWriter<'_> {
913 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
914 let mut cur = std::io::Cursor::new(buf);
915 while let Some(ch) = self.decoder.decode(&mut cur)? {
916 if !self.put_char(ch) {
917 return Ok(buf.len());
918 }
919 }
920 Ok(cur.position() as usize)
921 }
922
923 fn flush(&mut self) -> std::io::Result<()> {
924 Ok(())
925 }
926}
927
928pub struct TerminalSurfaceDebug<'a> {
929 surf: SurfaceView<'a, Cell>,
930}
931
932impl TerminalSurfaceDebug<'_> {
933 pub fn save<W: Write + Send>(&self, output: W) -> Result<W, Error> {
935 let capabilities = TerminalCaps {
937 depth: crate::encoder::ColorDepth::TrueColor,
938 glyphs: true,
939 kitty_keyboard: false,
940 };
941
942 let size = Size {
944 width: self.surf.width() + 2,
945 height: self.surf.height() + 2,
946 };
947
948 let ctx = ViewContext::dummy();
950 let pixels_per_cell = ctx.pixels_per_cell();
951 let mut term = TerminalDebug {
952 size: TerminalSize {
953 cells: size,
954 pixels: Size {
955 height: pixels_per_cell.height * size.height,
956 width: pixels_per_cell.width * size.width,
957 },
958 },
959 encoder: TTYEncoder::new(capabilities.clone()),
960 image_handler: KittyImageHandler::new().quiet(),
961 output,
962 capabilities,
963 face: Default::default(),
964 };
965
966 let scroll = size.height as i32;
968 term.execute_many([
969 TerminalCommand::Scroll(scroll),
970 TerminalCommand::CursorMove {
971 row: -scroll,
972 col: 0,
973 },
974 TerminalCommand::CursorSave,
975 ])?;
976
977 let mut renderer = TerminalRenderer::new(&mut term, true)?;
979 let mut surf = renderer.surface();
980
981 surf.draw_box(Some("fg=#665c54".parse()?));
983 write!(
984 surf.view_mut(0, 2..-1).writer(&ctx),
985 "{}x{}",
986 size.height - 2,
987 size.width - 2,
988 )?;
989
990 surf.view_mut(1..-1, 1..-1)
992 .iter_mut()
993 .zip(self.surf.iter())
994 .for_each(|(dst, src)| *dst = src.clone());
995 renderer.frame(&mut term)?;
996
997 term.execute_many([
999 TerminalCommand::Face(Face::default()),
1000 TerminalCommand::CursorRestore,
1001 TerminalCommand::CursorMove {
1002 row: size.height as i32,
1003 col: 0,
1004 },
1005 ])?;
1006
1007 Ok(term.output)
1008 }
1009
1010 pub fn save_to_file(&self, path: impl AsRef<std::path::Path>) -> Result<(), Error> {
1012 let file = BufWriter::new(std::fs::File::create(path)?);
1013 self.save(file)?;
1014 Ok(())
1015 }
1016}
1017
1018impl Debug for TerminalSurfaceDebug<'_> {
1019 fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1020 let mut cur = std::io::Cursor::new(self.save(Vec::new()).map_err(|_| std::fmt::Error)?);
1021 let mut decoder = crate::decoder::Utf8Decoder::new();
1022 writeln!(fmt)?;
1023 while let Some(chr) = decoder.decode(&mut cur).map_err(|_| std::fmt::Error)? {
1024 write!(fmt, "{}", chr)?;
1025 }
1026 Ok(())
1027 }
1028}
1029
1030struct TerminalDebug<W> {
1031 size: TerminalSize,
1032 encoder: TTYEncoder,
1033 image_handler: KittyImageHandler,
1034 capabilities: TerminalCaps,
1035 output: W,
1036 face: Face,
1037}
1038
1039impl<W: Write + Send> Terminal for TerminalDebug<W> {
1040 fn execute(&mut self, cmd: TerminalCommand) -> Result<(), Error> {
1041 use TerminalCommand::*;
1042 match cmd {
1043 Image(img, pos) => self.image_handler.draw(&mut self.output, &img, pos)?,
1044 ImageErase(img, pos) => self.image_handler.erase(&mut self.output, &img, pos)?,
1045 Face(face) => {
1046 self.face = face;
1047 self.encoder.encode(&mut self.output, cmd)?;
1048 }
1049 CursorTo(pos) => {
1050 self.encoder.encode(&mut self.output, CursorRestore)?;
1052 self.encoder.encode(&mut self.output, Face(self.face))?;
1053 self.encoder.encode(
1054 &mut self.output,
1055 CursorMove {
1056 row: pos.row as i32,
1057 col: pos.col as i32,
1058 },
1059 )?
1060 }
1061 cmd => self.encoder.encode(&mut self.output, cmd)?,
1062 }
1063 Ok(())
1064 }
1065
1066 fn poll(
1067 &mut self,
1068 _timeout: Option<std::time::Duration>,
1069 ) -> Result<Option<TerminalEvent>, Error> {
1070 Ok(None)
1071 }
1072
1073 fn size(&self) -> Result<TerminalSize, Error> {
1074 Ok(self.size)
1075 }
1076
1077 fn position(&mut self) -> Result<Position, Error> {
1078 Ok(Position::new(0, 0))
1079 }
1080
1081 fn waker(&self) -> TerminalWaker {
1082 TerminalWaker::new(|| Ok(()))
1083 }
1084
1085 fn frames_pending(&self) -> usize {
1086 0
1087 }
1088
1089 fn frames_drop(&mut self) {}
1090
1091 fn dyn_ref(&mut self) -> &mut dyn Terminal {
1092 self
1093 }
1094
1095 fn capabilities(&self) -> &TerminalCaps {
1096 &self.capabilities
1097 }
1098}
1099
1100impl<W: Write> Write for TerminalDebug<W> {
1101 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
1102 self.output.write(buf)
1103 }
1104
1105 fn flush(&mut self) -> std::io::Result<()> {
1106 Ok(())
1107 }
1108}
1109
1110#[cfg(test)]
1111mod tests {
1112 use super::*;
1113 use crate::encoder::ColorDepth::TrueColor;
1114
1115 struct DummyTerminal {
1116 size: TerminalSize,
1117 cmds: Vec<TerminalCommand>,
1118 buffer: Vec<u8>,
1119 capabiliets: TerminalCaps,
1120 }
1121
1122 impl DummyTerminal {
1123 fn new(size: Size) -> Self {
1124 Self {
1125 size: TerminalSize {
1126 cells: size,
1127 pixels: Size {
1128 height: size.height * 20,
1129 width: size.width * 10,
1130 },
1131 },
1132 cmds: Default::default(),
1133 buffer: Default::default(),
1134 capabiliets: TerminalCaps::default(),
1135 }
1136 }
1137
1138 fn clear(&mut self) {
1139 self.cmds.clear();
1140 self.buffer.clear();
1141 }
1142 }
1143
1144 impl Write for DummyTerminal {
1145 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
1146 self.buffer.write(buf)
1147 }
1148
1149 fn flush(&mut self) -> std::io::Result<()> {
1150 Ok(())
1151 }
1152 }
1153
1154 impl Terminal for DummyTerminal {
1155 fn execute(&mut self, cmd: TerminalCommand) -> Result<(), Error> {
1156 self.cmds.push(cmd);
1157 Ok(())
1158 }
1159
1160 fn poll(
1161 &mut self,
1162 _timeout: Option<std::time::Duration>,
1163 ) -> Result<Option<TerminalEvent>, Error> {
1164 Ok(None)
1165 }
1166
1167 fn size(&self) -> Result<TerminalSize, Error> {
1168 Ok(self.size)
1169 }
1170
1171 fn position(&mut self) -> Result<Position, Error> {
1172 Ok(Position::new(0, 0))
1173 }
1174
1175 fn waker(&self) -> TerminalWaker {
1176 TerminalWaker::new(|| Ok(()))
1177 }
1178
1179 fn frames_pending(&self) -> usize {
1180 0
1181 }
1182
1183 fn frames_drop(&mut self) {}
1184
1185 fn dyn_ref(&mut self) -> &mut dyn Terminal {
1186 self
1187 }
1188
1189 fn capabilities(&self) -> &TerminalCaps {
1190 &self.capabiliets
1191 }
1192 }
1193
1194 #[test]
1195 fn test_render_commands() -> Result<(), Error> {
1196 use TerminalCommand::*;
1197
1198 let purple = "bg=#d3869b".parse()?;
1199 let red = "bg=#fb4934".parse()?;
1200
1201 let mut term = DummyTerminal::new(Size::new(3, 7));
1202 let mut render = TerminalRenderer::new(&mut term, false)?;
1203 let ctx = ViewContext::new(&term)?;
1204
1205 let mut view = render.surface().view_owned(.., 1..);
1206 let mut writer = view.writer(&ctx).with_face(purple);
1207 writer.set_cursor(Position::new(0, 4));
1208
1209 assert_eq!(writer.cursor(), Position::new(0, 4));
1211 write!(writer, "TEST")?;
1212 assert_eq!(writer.cursor(), Position::new(1, 2));
1213 print!(
1214 "[render] writer with offset: {:?}",
1215 render.surface().debug()
1216 );
1217 render.frame(&mut term)?;
1218 assert_eq!(
1219 term.cmds,
1220 vec![
1221 Face(purple),
1222 CursorTo(Position::new(0, 5)),
1223 Char('T'),
1224 Char('E'),
1225 CursorTo(Position::new(1, 1)),
1226 Char('S'),
1227 Char('T'),
1228 ]
1229 );
1230 term.clear();
1231
1232 render
1234 .surface()
1235 .view_owned(1..2, 1..-1)
1236 .fill(Cell::new_char(red, ' '));
1237 print!("[render] erase: {:?}", render.surface().debug());
1238 render.frame(&mut term)?;
1239 assert_eq!(
1240 term.cmds,
1241 vec![
1242 Face(Default::default()),
1243 CursorTo(Position::new(0, 5)),
1245 Char(' '),
1246 Char(' '),
1247 Face(red),
1249 CursorTo(Position::new(1, 1)),
1250 EraseChars(5)
1251 ]
1252 );
1253 term.clear();
1254
1255 let mut image_surf = SurfaceOwned::new(Size::new(3, 3));
1257 let purple_color = "#d3869b".parse()?;
1258 let green_color = "#b8bb26".parse()?;
1259 image_surf.fill_with(|pos, _| {
1260 if (pos.row + pos.col) % 2 == 0 {
1261 purple_color
1262 } else {
1263 green_color
1264 }
1265 });
1266 let image_ascii = crate::Image::from(image_surf).ascii_view();
1267 render
1268 .surface()
1269 .view_mut(1.., 2..)
1270 .draw_view(&ctx, None, image_ascii)?;
1271 print!("[render] ascii image: {:?}", render.surface().debug());
1272 render.frame(&mut term)?;
1273 assert_eq!(
1274 term.cmds,
1275 vec![
1276 Face(Default::default()),
1277 CursorTo(Position { row: 1, col: 1 }),
1278 Char(' '),
1279 Face("fg=#d3869b, bg=#b8bb26".parse()?),
1280 Char('▀'),
1281 Face("fg=#b8bb26, bg=#d3869b".parse()?),
1282 Char('▀'),
1283 Face("fg=#d3869b, bg=#b8bb26".parse()?),
1284 Char('▀'),
1285 Face(Default::default()),
1286 Char(' '),
1287 Char(' '),
1288 Face("fg=#d3869b".parse()?),
1289 CursorTo(Position { row: 2, col: 2 }),
1290 Char('▀'),
1291 Face("fg=#b8bb26".parse()?),
1292 Char('▀'),
1293 Face("fg=#d3869b".parse()?),
1294 Char('▀')
1295 ]
1296 );
1297 term.clear();
1298
1299 let mut render = TerminalRenderer::new(&mut term, false)?;
1301 let mut view = render.surface().view_owned(.., 1..-1);
1302 let mut writer = view.writer(&ctx).with_face(purple);
1303 assert_eq!(writer.cursor(), Position::new(0, 0));
1304 write!(&mut writer, "one\ntwo")?;
1305 assert_eq!(writer.cursor(), Position::new(1, 3));
1306 print!("[render] writer new line: {:?}", render.surface().debug());
1307 render.frame(&mut term)?;
1308 assert_eq!(
1309 term.cmds,
1310 vec![
1311 Face(purple),
1312 CursorTo(Position::new(0, 1)),
1313 Char('o'),
1314 Char('n'),
1315 Char('e'),
1316 Char(' '),
1317 Char(' '),
1318 CursorTo(Position::new(1, 1)),
1319 Char('t'),
1320 Char('w'),
1321 Char('o')
1322 ]
1323 );
1324 term.clear();
1325
1326 let mut render = TerminalRenderer::new(&mut term, false)?;
1328 let mut view = render.surface().view_owned(.., 1..-1);
1329 let mut writer = view.writer(&ctx).with_face(purple);
1330 write!(&mut writer, " one\ntwo")?;
1331 print!(
1332 "[render] writer new line at the end of line: {:?}",
1333 render.surface().debug()
1334 );
1335 render.frame(&mut term)?;
1336 assert_eq!(
1337 term.cmds,
1338 vec![
1339 Face(purple),
1340 CursorTo(Position::new(0, 1)),
1341 Char(' '),
1342 Char(' '),
1343 Char('o'),
1344 Char('n'),
1345 Char('e'),
1346 CursorTo(Position::new(1, 1)),
1347 Char('t'),
1348 Char('w'),
1349 Char('o'),
1350 ]
1351 );
1352 term.clear();
1353
1354 let gray = "bg=#504945".parse()?;
1356 let mut render = TerminalRenderer::new(&mut term, false)?;
1357 let mut view = render.surface().view_owned(.., 1..);
1358 let mut writer = view.writer(&ctx).with_face(gray);
1359 write!(&mut writer, "🤩 awesome 😻|")?;
1360 print!(
1361 "[render] double with characters: {:?}",
1362 render.surface().debug()
1363 );
1364 render.frame(&mut term)?;
1365 assert_eq!(
1366 term.cmds,
1367 vec![
1368 Face(gray),
1369 CursorTo(Position::new(0, 1)),
1370 Char('🤩'),
1371 Char(' '),
1372 Char('a'),
1373 Char('w'),
1374 Char('e'),
1375 CursorTo(Position::new(1, 1)),
1376 Char('s'),
1377 Char('o'),
1378 Char('m'),
1379 Char('e'),
1380 Char(' '),
1381 CursorTo(Position::new(2, 1)),
1382 Char('😻'),
1383 Char('|')
1384 ]
1385 );
1386 term.clear();
1387
1388 Ok(())
1389 }
1390
1391 #[test]
1392 fn test_render_image() -> Result<(), Box<dyn std::error::Error>> {
1393 use TerminalCommand::*;
1394
1395 const ICON: &str = r#"
1396 {
1397 "view_box": [0, 0, 24, 24],
1398 "size": [2, 5],
1399 "path": "M10,17L5,12L6.41,10.58L10,14.17L17.59,6.58L19,8M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z"
1400 }
1401 "#;
1402 let face = "bg=#665c54,fg=#b8bb26".parse()?;
1403 let icon = Cell::new_glyph(face, serde_json::from_str(ICON)?);
1404
1405 let mut term = DummyTerminal::new(Size::new(5, 10));
1406 let mut render = TerminalRenderer::new(&mut term, false)?;
1407 term.clear();
1408
1409 let mut view = render.surface();
1410 view.set(Position::new(1, 2), icon.clone());
1411 print!("[render] image: {:?}", render.surface().debug());
1412 render.frame(&mut term)?;
1413 let img = render
1414 .glyph_cache
1415 .get(&icon)
1416 .expect("image is not rendered")
1417 .clone();
1418 assert_eq!(
1419 term.cmds,
1420 vec![
1421 Face(face),
1422 CursorTo(Position::new(1, 2)),
1423 EraseChars(5),
1424 CursorTo(Position::new(2, 2)),
1425 EraseChars(5),
1426 CursorTo(Position::new(1, 2)),
1427 Image(img.clone(), Position::new(1, 2))
1428 ]
1429 );
1430 term.clear();
1431
1432 let mut view = render.surface();
1433 view.set(Position::new(2, 3), icon.clone());
1434 print!("[render] image move: {:?}", render.surface().debug());
1435 render.frame(&mut term)?;
1436 assert_eq!(
1437 term.cmds,
1438 vec![
1439 ImageErase(img.clone(), Some(Position::new(1, 2))),
1441 Face(crate::Face::default()),
1442 CursorTo(Position::new(1, 2)),
1443 EraseChars(8),
1444 CursorTo(Position::new(2, 2)),
1445 Char(' '),
1446 Face(face),
1448 CursorTo(Position::new(2, 3)),
1449 EraseChars(5),
1450 CursorTo(Position::new(3, 3)),
1451 EraseChars(5),
1452 CursorTo(Position::new(2, 3)),
1453 Image(img, Position::new(2, 3)),
1454 ]
1455 );
1456 term.clear();
1457
1458 Ok(())
1459 }
1460
1461 #[test]
1462 fn test_cell_layout_wrap() {
1463 let max_width = 10;
1464 let ctx = &ViewContext {
1465 pixels_per_cell: Size::new(10, 10),
1466 has_glyphs: true,
1467 color_depth: TrueColor,
1468 };
1469 let face = Face::default();
1470
1471 let mut size = Size::default();
1472 let mut cursor = Position::default();
1473
1474 let pos = Cell::new_char(face, '\n').layout(ctx, max_width, true, &mut size, &mut cursor);
1476 assert!(pos.is_none());
1477 assert_eq!(cursor, Position::new(1, 0));
1478 assert_eq!(size, Size::new(1, 0));
1479
1480 for c in "test".chars() {
1482 Cell::new_char(face, c).layout(ctx, max_width, true, &mut size, &mut cursor);
1483 }
1484 assert_eq!(cursor, Position::new(1, 4));
1485 assert_eq!(size, Size::new(2, 4));
1486
1487 let pos = Cell::new_char(face, '\n').layout(ctx, max_width, true, &mut size, &mut cursor);
1489 assert!(pos.is_none());
1490 assert_eq!(cursor, Position::new(2, 0));
1491 assert_eq!(size, Size::new(2, 4));
1492
1493 let pos = Cell::new_char(face, ' ').layout(ctx, max_width, true, &mut size, &mut cursor);
1495 assert_eq!(pos, Some(Position::new(2, 0)));
1496 assert_eq!(cursor, Position::new(2, 1));
1497 assert_eq!(size, Size::new(3, 4));
1498
1499 let pos = Cell::new_char(face, '🤩').layout(ctx, max_width, true, &mut size, &mut cursor);
1501 assert_eq!(pos, Some(Position::new(2, 1)));
1502 assert_eq!(cursor, Position::new(2, 3));
1503 assert_eq!(size, Size::new(3, 4));
1504
1505 let pos = Cell::new_char(face, '\t').layout(ctx, max_width, true, &mut size, &mut cursor);
1507 assert!(pos.is_none());
1508 assert_eq!(cursor, Position::new(2, 8));
1509 assert_eq!(size, Size::new(3, 8));
1510
1511 let pos = Cell::new_char(face, '\0').layout(ctx, max_width, true, &mut size, &mut cursor);
1513 assert!(pos.is_none());
1514 assert_eq!(cursor, Position::new(2, 8));
1515 assert_eq!(size, Size::new(3, 8));
1516
1517 let pos = Cell::new_char(face, 'P').layout(ctx, max_width, true, &mut size, &mut cursor);
1519 assert_eq!(pos, Some(Position::new(2, 8)));
1520 assert_eq!(cursor, Position::new(2, 9));
1521 assert_eq!(size, Size::new(3, 9));
1522
1523 let pos = Cell::new_char(face, '🥳').layout(ctx, max_width, true, &mut size, &mut cursor);
1525 assert_eq!(pos, Some(Position::new(3, 0)));
1526 assert_eq!(cursor, Position::new(3, 2));
1527 assert_eq!(size, Size::new(4, 9));
1528
1529 let glyph = Glyph::new(
1531 rasterize::Path::empty(),
1532 Default::default(),
1533 None,
1534 Size::new(1, 3),
1535 " ".to_owned(),
1536 None,
1537 );
1538 let pos = Cell::new_glyph(face, glyph).layout(ctx, max_width, true, &mut size, &mut cursor);
1539 assert_eq!(pos, Some(Position::new(3, 2)));
1540 assert_eq!(cursor, Position::new(3, 5));
1541 assert_eq!(size, Size::new(4, 9));
1542
1543 let image = Image::from(SurfaceOwned::new(Size::new(20, 30)));
1545 let image_cell = Cell::new_image(image);
1546 assert_eq!(image_cell.size(ctx), Size::new(2, 3));
1547 let pos = image_cell.layout(ctx, max_width, true, &mut size, &mut cursor);
1548 assert_eq!(pos, Some(Position::new(3, 5)));
1549 assert_eq!(cursor, Position::new(3, 8));
1550 assert_eq!(size, Size::new(5, 9));
1551
1552 let pos = image_cell.layout(ctx, max_width, true, &mut size, &mut cursor);
1554 assert_eq!(pos, Some(Position::new(4, 0)));
1555 assert_eq!(cursor, Position::new(4, 3));
1556 assert_eq!(size, Size::new(6, 9));
1557 }
1558
1559 #[test]
1560 fn test_cell_layout_nowrap() {
1561 let max_width = 5;
1562 let ctx = &ViewContext {
1563 pixels_per_cell: Size::new(10, 10),
1564 has_glyphs: true,
1565 color_depth: TrueColor,
1566 };
1567 let face = Face::default();
1568
1569 let mut size = Size::default();
1570 let mut cursor = Position::default();
1571
1572 for (index, c) in "test".chars().enumerate() {
1574 let pos = Cell::new_char(face, c).layout(ctx, max_width, false, &mut size, &mut cursor);
1575 assert_eq!(pos, Some(Position::new(0, index)));
1576 }
1577 assert_eq!(cursor, Position::new(0, 4));
1578 assert_eq!(size, Size::new(1, 4));
1579
1580 let pos = Cell::new_char(face, '_').layout(ctx, max_width, false, &mut size, &mut cursor);
1582 assert_eq!(pos, Some(Position::new(0, 4)));
1583 assert_eq!(cursor, Position::new(0, 5));
1584 assert_eq!(size, Size::new(1, 5));
1585
1586 let pos = Cell::new_char(face, '_').layout(ctx, max_width, false, &mut size, &mut cursor);
1588 assert_eq!(pos, None);
1589 assert_eq!(cursor, Position::new(0, 5));
1590 assert_eq!(size, Size::new(1, 5));
1591
1592 let pos = Cell::new_char(face, '\n').layout(ctx, max_width, false, &mut size, &mut cursor);
1594 assert_eq!(pos, None);
1595 assert_eq!(cursor, Position::new(1, 0));
1596 assert_eq!(size, Size::new(1, 5));
1597
1598 for (index, c) in "****".chars().enumerate() {
1600 let pos = Cell::new_char(face, c).layout(ctx, max_width, false, &mut size, &mut cursor);
1601 assert_eq!(pos, Some(Position::new(1, index)));
1602 assert_eq!(cursor, Position::new(1, 1 + index));
1603 assert_eq!(size, Size::new(2, 5));
1604 }
1605
1606 let pos = Cell::new_char(face, '\r').layout(ctx, max_width, false, &mut size, &mut cursor);
1608 assert_eq!(pos, None);
1609 assert_eq!(cursor, Position::new(1, 0));
1610 assert_eq!(size, Size::new(2, 5));
1611 }
1612
1613 #[test]
1614 fn test_writer_tab() -> Result<(), Error> {
1615 let guide_face = "bg=#cc241d,fg=#fbf1c7".parse()?;
1616 let underline_face = "bg=#b8bb26,fg=#fbf1c7".parse()?;
1617 let tab_face = "bg=#458588,fg=#fbf1c7".parse()?;
1618 let mark_face = "bg=#b16286,fg=#fbf1c7".parse()?;
1619
1620 let mut term = DummyTerminal::new(Size::new(30, 21));
1621 let mut render = TerminalRenderer::new(&mut term, false)?;
1622 let ctx = ViewContext::new(&term)?;
1623
1624 {
1625 let mut view = render.surface().view_owned(1.., 0).transpose();
1626 let mut writer = view.writer(&ctx).with_face(guide_face);
1627 write!(writer, "012345678901234567890123456789")?;
1628 }
1629
1630 let mut view = render.surface().view_owned(.., 1..);
1631 let mut writer = view.writer(&ctx).with_face(guide_face);
1632 writeln!(writer, "01234567890123456789")?;
1633 writer.set_face(Face::default());
1634
1635 let mut cols = Vec::new();
1636 for index in 0..22 {
1637 writer.set_face(underline_face);
1638 (0..index).for_each(|_| {
1639 writer.put_char('_');
1640 });
1641 writer.set_face(tab_face);
1642 writer.put_char('\t');
1643 writer.set_face(mark_face);
1644 writer.put_char('X');
1645 cols.push(writer.cursor().col);
1646 writer.set_face(Face::default());
1647 writer.put_char('\n');
1648 }
1649 print!("[render] tab writer: {:?}", render.surface().debug());
1650 for (line, col) in cols.into_iter().enumerate() {
1651 assert_eq!((col - 1) % 8, 0, "column {} % 8 != 0 at line {}", col, line);
1652 }
1653 Ok(())
1654 }
1655
1656 #[derive(Default)]
1657 struct DummyCellWrite {
1658 face: Face,
1659 wraps: bool,
1660 cells: Vec<Cell>,
1661 }
1662
1663 impl DummyCellWrite {
1664 fn take(&mut self) -> Vec<Cell> {
1665 std::mem::take(&mut self.cells)
1666 }
1667 }
1668
1669 impl CellWrite for DummyCellWrite {
1670 fn face(&self) -> Face {
1671 self.face
1672 }
1673
1674 fn set_face(&mut self, face: Face) -> Face {
1675 std::mem::replace(&mut self.face, face)
1676 }
1677
1678 fn wraps(&self) -> bool {
1679 self.wraps
1680 }
1681
1682 fn set_wraps(&mut self, wraps: bool) -> bool {
1683 std::mem::replace(&mut self.wraps, wraps)
1684 }
1685
1686 fn put_cell(&mut self, cell: Cell) -> bool {
1687 self.cells.push(cell);
1688 true
1689 }
1690 }
1691
1692 #[test]
1693 fn test_cell_tty_writer() -> Result<(), Error> {
1694 let mut target = DummyCellWrite::default();
1695
1696 write!(target.by_ref().tty_writer(), "\x1b[91mA\x1b[mB")?;
1697 assert_eq!(
1698 target.take(),
1699 vec![
1700 Cell::new_char("fg=#ff0000".parse()?, 'A'),
1701 Cell::new_char(Face::default(), 'B'),
1702 ]
1703 );
1704
1705 Ok(())
1706 }
1707}