Skip to main content

tattoy_wezterm_surface/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2use crate::line::CellRef;
3use alloc::borrow::Cow;
4use core::cmp::min;
5use finl_unicode::grapheme_clusters::Graphemes;
6#[cfg(feature = "use_serde")]
7use serde::{Deserialize, Serialize};
8use wezterm_cell::color::ColorAttribute;
9#[cfg(feature = "use_image")]
10use wezterm_cell::image::ImageCell;
11use wezterm_cell::{Cell, CellAttributes};
12use wezterm_dynamic::{FromDynamic, ToDynamic};
13
14extern crate alloc;
15use crate::alloc::borrow::ToOwned;
16use crate::alloc::string::ToString;
17use alloc::string::String;
18use alloc::vec;
19use alloc::vec::Vec;
20
21pub mod cellcluster;
22pub mod change;
23pub mod hyperlink;
24pub mod line;
25
26pub use self::change::{Change, LineAttribute};
27#[cfg(feature = "use_image")]
28pub use self::change::{Image, TextureCoordinate};
29pub use self::line::Line;
30
31/// Position holds 0-based positioning information, where
32/// Absolute(0) is the start of the line or column,
33/// Relative(0) is the current position in the line or
34/// column and EndRelative(0) is the end position in the
35/// line or column.
36#[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))]
37#[derive(Debug, Clone, Copy, Eq, PartialEq)]
38pub enum Position {
39    /// Negative values move up, positive values down, 0 means no change
40    Relative(isize),
41    /// Relative to the start of the line or top of the screen
42    Absolute(usize),
43    /// Relative to the end of line or bottom of screen
44    EndRelative(usize),
45}
46
47#[cfg_attr(
48    feature = "use_serde",
49    derive(Serialize, Deserialize, schemars::JsonSchema)
50)]
51#[derive(Debug, Clone, Hash, Copy, PartialEq, Eq, FromDynamic, ToDynamic)]
52pub enum CursorVisibility {
53    Hidden,
54    Visible,
55}
56
57impl Default for CursorVisibility {
58    fn default() -> CursorVisibility {
59        CursorVisibility::Visible
60    }
61}
62
63#[cfg_attr(
64    feature = "use_serde",
65    derive(Serialize, Deserialize, schemars::JsonSchema)
66)]
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, FromDynamic, ToDynamic)]
68pub enum CursorShape {
69    Default,
70    BlinkingBlock,
71    SteadyBlock,
72    BlinkingUnderline,
73    SteadyUnderline,
74    BlinkingBar,
75    SteadyBar,
76}
77
78impl Default for CursorShape {
79    fn default() -> CursorShape {
80        CursorShape::Default
81    }
82}
83
84impl CursorShape {
85    pub fn is_blinking(self) -> bool {
86        matches!(
87            self,
88            Self::BlinkingBlock | Self::BlinkingUnderline | Self::BlinkingBar
89        )
90    }
91}
92
93/// SequenceNo indicates a logical position within a stream of changes.
94/// The sequence is only meaningful within a given `Surface` instance.
95pub type SequenceNo = usize;
96pub const SEQ_ZERO: SequenceNo = 0;
97
98/// The `Surface` type represents the contents of a terminal screen.
99/// It is not directly connected to a terminal device.
100/// It consists of a buffer and a log of changes.  You can accumulate
101/// updates to the screen by adding instances of the `Change` enum
102/// that describe the updates.
103///
104/// When ready to render the `Surface` to a `Terminal`, you can use
105/// the `get_changes` method to return an optimized stream of `Change`s
106/// since the last render and then pass it to an instance of `Renderer`.
107///
108/// `Surface`s can also be composited together; this is useful when
109/// building up a UI with layers or widgets: each widget can be its
110/// own `Surface` instance and have its content maintained independently
111/// from the other widgets on the screen and can then be copied into
112/// the target `Surface` buffer for rendering.
113///
114/// To support more efficient updates in the composite use case, a
115/// `draw_from_screen` method is available; the intent is to have one
116/// `Surface` be hold the data that was last rendered, and a second `Surface`
117/// of the same size that is repeatedly redrawn from the composite
118/// of the widgets.  `draw_from_screen` is used to extract the smallest
119/// difference between the updated screen and apply those changes to
120/// the render target, and then use `get_changes` to render those without
121/// repainting the world on each update.
122#[derive(Default, Clone)]
123pub struct Surface {
124    width: usize,
125    height: usize,
126    lines: Vec<Line>,
127    attributes: CellAttributes,
128    xpos: usize,
129    ypos: usize,
130    seqno: SequenceNo,
131    changes: Vec<Change>,
132    cursor_shape: Option<CursorShape>,
133    cursor_visibility: CursorVisibility,
134    cursor_color: ColorAttribute,
135    title: String,
136    ignore_high_repaint_cost: bool,
137}
138
139#[derive(Default)]
140struct DiffState {
141    changes: Vec<Change>,
142    /// Keep track of the cursor position that the change stream
143    /// selects for updates so that we can avoid emitting redundant
144    /// position changes.
145    cursor: Option<(usize, usize)>,
146    /// Similarly, we keep track of the cell attributes that we have
147    /// activated for change stream to avoid over-emitting.
148    /// Tracking the cursor and attributes in this way helps to coalesce
149    /// lines of text into simpler strings.
150    attr: Option<CellAttributes>,
151}
152
153impl DiffState {
154    #[inline]
155    fn diff_cells(&mut self, col_num: usize, row_num: usize, cell: CellRef, other_cell: CellRef) {
156        if cell.same_contents(&other_cell) {
157            return;
158        }
159
160        self.set_cell(col_num, row_num, other_cell);
161    }
162
163    #[inline]
164    fn set_cell(&mut self, col_num: usize, row_num: usize, other_cell: CellRef) {
165        self.cursor = match self.cursor.take() {
166            Some((cursor_row, cursor_col)) if cursor_row == row_num && cursor_col == col_num => {
167                // It is on the current column, so we don't need
168                // to explicitly move it.  Move the cursor by the
169                // width of the text we're about to add.
170                Some((row_num, col_num + other_cell.width()))
171            }
172            _ => {
173                // Need to explicitly move the cursor
174                self.changes.push(Change::CursorPosition {
175                    y: Position::Absolute(row_num),
176                    x: Position::Absolute(col_num),
177                });
178                // and update the position for next time
179                Some((row_num, col_num + other_cell.width()))
180            }
181        };
182
183        // we could get fancy and try to minimize the update traffic
184        // by computing a series of AttributeChange values here.
185        // For now, let's just record the new value
186        self.attr = match self.attr.take() {
187            Some(ref attr) if attr == other_cell.attrs() => {
188                // Active attributes match, so we don't need
189                // to emit a change for them
190                Some(attr.clone())
191            }
192            _ => {
193                // Attributes are different
194                self.changes
195                    .push(Change::AllAttributes(other_cell.attrs().clone()));
196                Some(other_cell.attrs().clone())
197            }
198        };
199        // A little bit of bloat in the code to avoid runs of single
200        // character Text entries; just append to the string.
201        let result_len = self.changes.len();
202        if result_len > 0 && self.changes[result_len - 1].is_text() {
203            if let Some(Change::Text(ref mut prefix)) = self.changes.get_mut(result_len - 1) {
204                prefix.push_str(other_cell.str());
205            }
206        } else {
207            self.changes
208                .push(Change::Text(other_cell.str().to_string()));
209        }
210    }
211}
212
213impl Surface {
214    /// Create a new Surface with the specified width and height.
215    pub fn new(width: usize, height: usize) -> Self {
216        let mut scr = Surface {
217            width,
218            height,
219            ..Default::default()
220        };
221        scr.resize(width, height);
222        scr
223    }
224
225    /// Returns the (width, height) of the surface
226    pub fn dimensions(&self) -> (usize, usize) {
227        (self.width, self.height)
228    }
229
230    pub fn cursor_position(&self) -> (usize, usize) {
231        (self.xpos, self.ypos)
232    }
233
234    pub fn cursor_shape(&self) -> Option<CursorShape> {
235        self.cursor_shape
236    }
237
238    pub fn cursor_visibility(&self) -> CursorVisibility {
239        self.cursor_visibility
240    }
241
242    pub fn title(&self) -> &str {
243        &self.title
244    }
245
246    /// Resize the Surface to the specified width and height.
247    /// If the width and/or height are smaller than previously, the rows and/or
248    /// columns are truncated.  If the width and/or height are larger than
249    /// previously then an appropriate number of cells are added to the
250    /// buffer and filled with default attributes.
251    /// The resize event invalidates the change stream, discarding it and
252    /// causing a subsequent `get_changes` call to yield a full repaint.
253    /// If the cursor position would be outside the bounds of the newly resized
254    /// screen, it will be moved to be within the new bounds.
255    pub fn resize(&mut self, width: usize, height: usize) {
256        // We need to invalidate the change stream prior to this
257        // event, so we nominally generate an entry for the resize
258        // here.  Since rendering a resize doesn't make sense, we
259        // don't record a Change entry.  Instead what we do is
260        // increment the sequence number and then flush the whole
261        // stream.  The next call to get_changes() will perform a
262        // full repaint, and that is what we want.
263        // We only do this if we have any changes buffered.
264        if !self.changes.is_empty() {
265            self.seqno += 1;
266            self.changes.clear();
267        }
268
269        self.lines
270            .resize(height, Line::with_width(width, self.seqno));
271        for line in &mut self.lines {
272            line.resize(width, self.seqno);
273        }
274        self.width = width;
275        self.height = height;
276
277        // Ensure that the cursor position is well-defined
278        self.xpos = compute_position_change(self.xpos, &Position::Relative(0), self.width);
279        self.ypos = compute_position_change(self.ypos, &Position::Relative(0), self.height);
280    }
281
282    /// Efficiently apply a series of changes
283    /// Returns the sequence number at the end of the change.
284    pub fn add_changes(&mut self, mut changes: Vec<Change>) -> SequenceNo {
285        let seq = self.seqno.saturating_sub(1) + changes.len();
286
287        for change in &changes {
288            self.apply_change(&change);
289        }
290
291        self.seqno += changes.len();
292        self.changes.append(&mut changes);
293
294        seq
295    }
296
297    /// Apply a change and return the sequence number at the end of the change.
298    pub fn add_change<C: Into<Change>>(&mut self, change: C) -> SequenceNo {
299        let seq = self.seqno;
300        self.seqno += 1;
301        let change = change.into();
302        self.apply_change(&change);
303        self.changes.push(change);
304        seq
305    }
306
307    fn apply_change(&mut self, change: &Change) {
308        match change {
309            Change::AllAttributes(attr) => self.attributes = attr.clone(),
310            Change::Text(text) => self.print_text(text),
311            Change::Attribute(change) => self.attributes.apply_change(change),
312            Change::CursorPosition { x, y } => self.set_cursor_pos(x, y),
313            Change::ClearScreen(color) => self.clear_screen(*color),
314            Change::ClearToEndOfLine(color) => self.clear_eol(*color),
315            Change::ClearToEndOfScreen(color) => self.clear_eos(*color),
316            Change::CursorColor(color) => self.cursor_color = *color,
317            Change::CursorShape(shape) => self.cursor_shape = Some(*shape),
318            Change::CursorVisibility(visibility) => self.cursor_visibility = *visibility,
319            #[cfg(feature = "use_image")]
320            Change::Image(image) => self.add_image(image),
321            Change::Title(text) => self.title = text.to_owned(),
322            Change::ScrollRegionUp {
323                first_row,
324                region_size,
325                scroll_count,
326            } => self.scroll_region_up(*first_row, *region_size, *scroll_count),
327            Change::ScrollRegionDown {
328                first_row,
329                region_size,
330                scroll_count,
331            } => self.scroll_region_down(*first_row, *region_size, *scroll_count),
332            Change::LineAttribute(attr) => self.line_attribute(attr),
333        }
334    }
335
336    #[cfg(feature = "use_image")]
337    fn add_image(&mut self, image: &Image) {
338        use ordered_float::NotNan;
339
340        let xsize = (image.bottom_right.x - image.top_left.x) / image.width as f32;
341        let ysize = (image.bottom_right.y - image.top_left.y) / image.height as f32;
342
343        if self.ypos + image.height > self.height {
344            let scroll = (self.ypos + image.height) - self.height;
345            for _ in 0..scroll {
346                self.scroll_screen_up();
347            }
348            self.ypos -= scroll;
349        }
350
351        let mut ypos = NotNan::new(0.0).unwrap();
352        for y in 0..image.height {
353            let mut xpos = NotNan::new(0.0).unwrap();
354            for x in 0..image.width {
355                self.lines[self.ypos + y].set_cell(
356                    self.xpos + x,
357                    Cell::new(
358                        ' ',
359                        self.attributes
360                            .clone()
361                            .set_image(Box::new(ImageCell::new(
362                                TextureCoordinate::new(
363                                    image.top_left.x + xpos,
364                                    image.top_left.y + ypos,
365                                ),
366                                TextureCoordinate::new(
367                                    image.top_left.x + xpos + xsize,
368                                    image.top_left.y + ypos + ysize,
369                                ),
370                                image.image.clone(),
371                            )))
372                            .clone(),
373                    ),
374                    self.seqno,
375                );
376
377                xpos += xsize;
378            }
379            ypos += ysize;
380        }
381
382        self.xpos += image.width;
383    }
384
385    fn clear_screen(&mut self, color: ColorAttribute) {
386        self.attributes = CellAttributes::default().set_background(color).clone();
387        let cleared = Cell::new(' ', self.attributes.clone());
388        for line in &mut self.lines {
389            line.fill_range(0..self.width, &cleared, self.seqno);
390        }
391        self.xpos = 0;
392        self.ypos = 0;
393    }
394
395    fn clear_eos(&mut self, color: ColorAttribute) {
396        self.attributes = CellAttributes::default().set_background(color).clone();
397        let cleared = Cell::new(' ', self.attributes.clone());
398        self.lines[self.ypos].fill_range(self.xpos..self.width, &cleared, self.seqno);
399        for line in &mut self.lines.iter_mut().skip(self.ypos + 1) {
400            line.fill_range(0..self.width, &cleared, self.seqno);
401        }
402    }
403
404    fn clear_eol(&mut self, color: ColorAttribute) {
405        self.attributes = CellAttributes::default().set_background(color).clone();
406        let cleared = Cell::new(' ', self.attributes.clone());
407        self.lines[self.ypos].fill_range(self.xpos..self.width, &cleared, self.seqno);
408    }
409
410    fn scroll_screen_up(&mut self) {
411        self.lines.remove(0);
412        self.lines.push(Line::with_width(self.width, self.seqno));
413    }
414
415    fn scroll_region_up(&mut self, start: usize, size: usize, count: usize) {
416        // Replace the first lines with empty lines
417        for index in start..start + min(count, size) {
418            self.lines[index] = Line::with_width(self.width, self.seqno);
419        }
420        // Rotate the remaining lines up the surface.
421        if 0 < count && count < size {
422            self.lines[start..start + size].rotate_left(count);
423        }
424    }
425
426    fn scroll_region_down(&mut self, start: usize, size: usize, count: usize) {
427        // Replace the last lines with empty lines
428        for index in start + size - min(count, size)..start + size {
429            self.lines[index] = Line::with_width(self.width, self.seqno);
430        }
431        // Rotate the remaining lines down the surface.
432        if 0 < count && count < size {
433            self.lines[start..start + size].rotate_right(count);
434        }
435    }
436
437    fn line_attribute(&mut self, attr: &LineAttribute) {
438        let line = &mut self.lines[self.ypos];
439        match attr {
440            LineAttribute::DoubleHeightTopHalfLine => line.set_double_height_top(self.seqno),
441            LineAttribute::DoubleHeightBottomHalfLine => line.set_double_height_bottom(self.seqno),
442            LineAttribute::DoubleWidthLine => line.set_double_width(self.seqno),
443            LineAttribute::SingleWidthLine => line.set_single_width(self.seqno),
444        }
445    }
446
447    fn print_text(&mut self, text: &str) {
448        for g in Graphemes::new(text) {
449            if g == "\r\n" {
450                self.xpos = 0;
451                let new_y = self.ypos + 1;
452                if new_y >= self.height {
453                    self.scroll_screen_up();
454                } else {
455                    self.ypos = new_y;
456                }
457                continue;
458            }
459
460            if g == "\r" {
461                self.xpos = 0;
462                continue;
463            }
464
465            if g == "\n" {
466                let new_y = self.ypos + 1;
467                if new_y >= self.height {
468                    self.scroll_screen_up();
469                } else {
470                    self.ypos = new_y;
471                }
472                continue;
473            }
474
475            if self.xpos >= self.width {
476                let new_y = self.ypos + 1;
477                if new_y >= self.height {
478                    self.scroll_screen_up();
479                } else {
480                    self.ypos = new_y;
481                }
482                self.xpos = 0;
483            }
484
485            let cell = Cell::new_grapheme(g, self.attributes.clone(), None);
486            // the max(1) here is to ensure that we advance to the next cell
487            // position for zero-width graphemes.  We want to make sure that
488            // they occupy a cell so that we can re-emit them when we output them.
489            // If we didn't do this, then we'd effectively filter them out from
490            // the model, which seems like a lossy design choice.
491            let width = cell.width().max(1);
492
493            self.lines[self.ypos].set_cell(self.xpos, cell, self.seqno);
494
495            // Increment the position now; we'll defer processing
496            // wrapping until the next printed character, otherwise
497            // we'll eagerly scroll when we reach the right margin.
498            self.xpos += width;
499        }
500    }
501
502    fn set_cursor_pos(&mut self, x: &Position, y: &Position) {
503        self.xpos = compute_position_change(self.xpos, x, self.width);
504        self.ypos = compute_position_change(self.ypos, y, self.height);
505    }
506
507    /// Returns the entire contents of the screen as a string.
508    /// Only the character data is returned.  The end of each line is
509    /// returned as a \n character.
510    /// This function exists primarily for testing purposes.
511    pub fn screen_chars_to_string(&self) -> String {
512        let mut s = String::new();
513
514        for line in &self.lines {
515            for cell in line.visible_cells() {
516                s.push_str(cell.str());
517            }
518            s.push('\n');
519        }
520
521        s
522    }
523
524    /// Returns the cell data for the screen.
525    /// This is intended to be used for testing purposes.
526    pub fn screen_cells(&mut self) -> Vec<&mut [Cell]> {
527        let mut lines = Vec::new();
528        for line in &mut self.lines {
529            lines.push(line.cells_mut());
530        }
531        lines
532    }
533
534    pub fn make_screen_cells(&mut self) {
535        for line in &mut self.lines {
536            line.make_cells();
537        }
538    }
539
540    pub fn get_screen_cells(&self) -> Vec<&[Cell]> {
541        let mut lines = Vec::new();
542        for line in &self.lines {
543            lines.push(line.cells());
544        }
545        lines
546    }
547
548    pub fn screen_lines(&self) -> Vec<Cow<Line>> {
549        self.lines.iter().map(|line| Cow::Borrowed(line)).collect()
550    }
551
552    /// Returns a stream of changes suitable to update the screen
553    /// to match the model.  The input `seq` argument should be 0
554    /// on the first call, or in any situation where the screen
555    /// contents may have been invalidated, otherwise it should
556    /// be set to the `SequenceNo` returned by the most recent call
557    /// to `get_changes`.
558    /// `get_changes` will use a heuristic to decide on the lower
559    /// cost approach to updating the screen and return some sequence
560    /// of `Change` entries that will update the display accordingly.
561    /// The worst case is that this function will fabricate a sequence
562    /// of Change entries to paint the screen from scratch.
563    pub fn get_changes(&self, seq: SequenceNo) -> (SequenceNo, Cow<[Change]>) {
564        // Do we have continuity in the sequence numbering?
565        let first = self.seqno.saturating_sub(self.changes.len());
566        if seq == 0 || first > seq || self.seqno == 0 {
567            // No, we have folded away some data, we'll need a full paint
568            return (self.seqno, Cow::Owned(self.repaint_all()));
569        }
570
571        let mut is_repaint = false;
572
573        if !self.ignore_high_repaint_cost {
574            // Approximate cost to render the change screen
575            let delta_cost = self.seqno - seq;
576            // Approximate cost to repaint from scratch
577            let full_cost = self.estimate_full_paint_cost();
578
579            is_repaint = delta_cost > full_cost
580        }
581
582        if is_repaint {
583            (self.seqno, Cow::Owned(self.repaint_all()))
584        } else {
585            (self.seqno, Cow::Borrowed(&self.changes[seq - first..]))
586        }
587    }
588
589    pub fn has_changes(&self, seq: SequenceNo) -> bool {
590        self.seqno != seq
591    }
592
593    pub fn current_seqno(&self) -> SequenceNo {
594        self.seqno
595    }
596
597    /// After having called `get_changes` and processed the resultant
598    /// change stream, the caller can then pass the returned `SequenceNo`
599    /// value to this call to prune the list of changes and free up
600    /// resources from the change log.
601    pub fn flush_changes_older_than(&mut self, seq: SequenceNo) {
602        let first = self.seqno.saturating_sub(self.changes.len());
603        let idx = seq.saturating_sub(first);
604        if idx > self.changes.len() {
605            return;
606        }
607        self.changes = self.changes.split_off(idx);
608    }
609
610    /// Without allocating resources, estimate how many Change entries
611    /// we would produce in repaint_all for the current state.
612    fn estimate_full_paint_cost(&self) -> usize {
613        // assume 1 per cell with 20% overhead for attribute changes
614        3 + (((self.width * self.height) as f64) * 1.2) as usize
615    }
616
617    fn repaint_all(&self) -> Vec<Change> {
618        let mut result = vec![
619            // Home the cursor and clear the screen to defaults.  Hide the
620            // cursor while we're repainting.
621            Change::CursorVisibility(CursorVisibility::Hidden),
622            Change::ClearScreen(Default::default()),
623        ];
624
625        if !self.title.is_empty() {
626            result.push(Change::Title(self.title.to_owned()));
627        }
628
629        let mut attr = CellAttributes::default();
630
631        let crlf = Change::CursorPosition {
632            x: Position::Absolute(0),
633            y: Position::Relative(1),
634        };
635
636        // Walk backwards through the lines; the goal is to determine
637        // if the screen ends with a number of clear lines that we
638        // can coalesce together as a ClearToEndOfScreen op.
639        // We track the index (from the end) of the last matching
640        // run, together with the color of that run.
641        let mut trailing_color = None;
642        let mut trailing_idx = None;
643
644        for (idx, line) in self.lines.iter().rev().enumerate() {
645            let changes = line.changes(&attr);
646            if changes.is_empty() {
647                // The line recorded no changes; this means that the line
648                // consists of spaces and the default background color
649                match trailing_color {
650                    Some(other) if other != Default::default() => {
651                        // Color doesn't match up, so we have to stop
652                        // looking for the ClearToEndOfScreen run here
653                        break;
654                    }
655                    // Color does match
656                    Some(_) => continue,
657                    // we don't have a run, we should start one
658                    None => {
659                        trailing_color = Some(Default::default());
660                        trailing_idx = Some(idx);
661                        continue;
662                    }
663                }
664            } else {
665                let last_change = changes.len() - 1;
666                match (&changes[last_change], trailing_color) {
667                    (&Change::ClearToEndOfLine(ref color), None) => {
668                        trailing_color = Some(*color);
669                        trailing_idx = Some(idx);
670                    }
671                    (&Change::ClearToEndOfLine(ref color), Some(other)) => {
672                        if other == *color {
673                            trailing_idx = Some(idx);
674                            continue;
675                        } else {
676                            break;
677                        }
678                    }
679                    _ => break,
680                }
681            }
682        }
683
684        for (idx, line) in self.lines.iter().enumerate() {
685            match trailing_idx {
686                Some(t) if self.height - t == idx => {
687                    let color =
688                        trailing_color.expect("didn't set trailing_color along with trailing_idx");
689
690                    // The first in the sequence of the ClearToEndOfLine may
691                    // be batched up here; let's remove it if that is the case.
692                    let last_result = result.len() - 1;
693                    match result[last_result] {
694                        Change::ClearToEndOfLine(col) if col == color => {
695                            result.remove(last_result);
696                        }
697                        _ => {}
698                    }
699
700                    result.push(Change::ClearToEndOfScreen(color));
701                    break;
702                }
703                _ => {}
704            }
705
706            let mut changes = line.changes(&attr);
707
708            if idx != 0 {
709                // We emit a relative move at the end of each
710                // line with the theory that this will translate
711                // to a short \r\n sequence rather than the longer
712                // absolute cursor positioning sequence
713                result.push(crlf.clone());
714            }
715
716            result.append(&mut changes);
717            if let Some(c) = line.visible_cells().last() {
718                attr = c.attrs().clone();
719            }
720        }
721
722        // Remove any trailing sequence of cursor movements, as we're
723        // going to just finish up with an absolute move anyway.
724        loop {
725            let result_len = result.len();
726            if result_len == 0 {
727                break;
728            }
729            match result[result_len - 1] {
730                Change::CursorPosition { .. } => {
731                    result.remove(result_len - 1);
732                }
733                _ => break,
734            }
735        }
736
737        // Place the cursor at its intended position, but only if we moved the
738        // cursor.  We don't explicitly track movement but can infer it from the
739        // size of the results: results will have an initial ClearScreen entry
740        // that homes the cursor and a CursorShape entry that hides the cursor.
741        // If the screen is otherwise blank there will be no further entries
742        // and we don't need to emit cursor movement.  However, in the
743        // optimization passes above, we may have removed some number of
744        // movement entries, so let's be sure to check the cursor position to
745        // make sure that we don't fail to emit movement.
746
747        let moved_cursor = result.len() != 2;
748        if moved_cursor || self.xpos != 0 || self.ypos != 0 {
749            result.push(Change::CursorPosition {
750                x: Position::Absolute(self.xpos),
751                y: Position::Absolute(self.ypos),
752            });
753        }
754
755        // Set the intended cursor shape.  We hid the cursor at the start
756        // of the repaint, so no need to hide it again.
757        if self.cursor_visibility != CursorVisibility::Hidden {
758            result.push(Change::CursorVisibility(CursorVisibility::Visible));
759            if let Some(shape) = self.cursor_shape {
760                result.push(Change::CursorShape(shape));
761            }
762        }
763
764        result
765    }
766
767    /// Computes the change stream required to make the region within `self`
768    /// at coordinates `x`, `y` and size `width`, `height` look like the
769    /// same sized region within `other` at coordinates `other_x`, `other_y`.
770    ///
771    /// `other` and `self` may be the same, causing regions within the same
772    /// `Surface` to be differenced; this is used by the `copy_region` method.
773    ///
774    /// The returned list of `Change`s can be passed to the `add_changes` method
775    /// to make the region within self match the region within other.
776    #[allow(clippy::too_many_arguments)]
777    pub fn diff_region(
778        &self,
779        x: usize,
780        y: usize,
781        width: usize,
782        height: usize,
783        other: &Surface,
784        other_x: usize,
785        other_y: usize,
786    ) -> Vec<Change> {
787        let mut diff_state = DiffState::default();
788
789        for ((row_num, line), other_line) in self
790            .lines
791            .iter()
792            .enumerate()
793            .skip(y)
794            .take_while(|(row_num, _)| *row_num < y + height)
795            .zip(other.lines.iter().skip(other_y))
796        {
797            diff_line(
798                &mut diff_state,
799                line,
800                row_num,
801                other_line,
802                x,
803                width,
804                other_x,
805            );
806        }
807
808        diff_state.changes
809    }
810
811    pub fn diff_lines(&self, other_lines: Vec<&Line>) -> Vec<Change> {
812        let mut diff_state = DiffState::default();
813        for ((row_num, line), other_line) in self.lines.iter().enumerate().zip(other_lines.iter()) {
814            diff_line(&mut diff_state, line, row_num, other_line, 0, line.len(), 0);
815        }
816        diff_state.changes
817    }
818
819    pub fn diff_against_numbered_line(&self, row_num: usize, other_line: &Line) -> Vec<Change> {
820        let mut diff_state = DiffState::default();
821        if let Some(line) = self.lines.get(row_num) {
822            diff_line(&mut diff_state, line, row_num, other_line, 0, line.len(), 0);
823        }
824        diff_state.changes
825    }
826
827    /// Computes the change stream required to make `self` have the same
828    /// screen contents as `other`.
829    pub fn diff_screens(&self, other: &Surface) -> Vec<Change> {
830        self.diff_region(0, 0, self.width, self.height, other, 0, 0)
831    }
832
833    /// Draw the contents of `other` into self at the specified coordinates.
834    /// The required updates are recorded as Change entries as well as stored
835    /// in the screen line/cell data.
836    /// Saves the cursor position and attributes that were in effect prior to
837    /// calling `draw_from_screen` and restores them after applying the changes
838    /// from the other surface.
839    pub fn draw_from_screen(&mut self, other: &Surface, x: usize, y: usize) -> SequenceNo {
840        let attrs = self.attributes.clone();
841        let cursor = (self.xpos, self.ypos);
842        let changes = self.diff_region(x, y, other.width, other.height, other, 0, 0);
843        let seq = self.add_changes(changes);
844        self.xpos = cursor.0;
845        self.ypos = cursor.1;
846        self.attributes = attrs;
847        seq
848    }
849
850    /// Copy the contents of the specified region to the same sized
851    /// region elsewhere in the screen display.
852    /// The regions may overlap.
853    /// # Panics
854    /// The destination region must be the same size as the source
855    /// (which is implied by the function parameters) and must fit
856    /// within the width and height of the Surface or this operation
857    /// will panic.
858    pub fn copy_region(
859        &mut self,
860        src_x: usize,
861        src_y: usize,
862        width: usize,
863        height: usize,
864        dest_x: usize,
865        dest_y: usize,
866    ) -> SequenceNo {
867        let changes = self.diff_region(dest_x, dest_y, width, height, self, src_x, src_y);
868        self.add_changes(changes)
869    }
870
871    /// Normally, if rendering changes is too expensive, then a full repaint is done. However, this
872    /// involves clearing the terminal screen, which can cause flickering in some circumstances. If
873    /// you're willing to pay the potential extra cost for rendering then set this to `true`.
874    pub fn ignore_high_repaint_cost(&mut self, value: bool) {
875        self.ignore_high_repaint_cost = value;
876    }
877}
878
879/// Populate `diff_state` with changes to replace contents of `line` in range [x,x+width)
880/// with the contents of `other_line` in range [other_x,other_x+width).
881fn diff_line(
882    diff_state: &mut DiffState,
883    line: &Line,
884    row_num: usize,
885    other_line: &Line,
886    x: usize,
887    width: usize,
888    other_x: usize,
889) {
890    let mut cells = line
891        .visible_cells()
892        .skip_while(|cell| cell.cell_index() < x)
893        .take_while(|cell| cell.cell_index() < x + width)
894        .peekable();
895    let other_cells = other_line
896        .visible_cells()
897        .skip_while(|cell| cell.cell_index() < other_x)
898        .take_while(|cell| cell.cell_index() < other_x + width);
899
900    for other_cell in other_cells {
901        let rel_x = other_cell.cell_index() - other_x;
902        let mut comparison_cell = None;
903
904        // Advance the `cells` iterator to try to find the visible cell in `line` in the equivalent
905        // position to `other_cell`. If there is no visible cell in equivalent position, advance
906        // one past and wait for next iteration.
907        while let Some(cell) = cells.peek() {
908            let cell_rel_x = cell.cell_index() - x;
909
910            if cell_rel_x == rel_x {
911                comparison_cell = Some(*cell);
912                break;
913            } else if cell_rel_x > rel_x {
914                break;
915            }
916
917            cells.next();
918        }
919
920        // If we find a cell in the equivalent position, diff against it. If not, we know
921        // there is a multi-cell grapheme in `line` that partially overlaps `other_cell`,
922        // so we have to overwrite anyway.
923        if let Some(comparison_cell) = comparison_cell {
924            diff_state.diff_cells(x + rel_x, row_num, comparison_cell, other_cell);
925        } else {
926            diff_state.set_cell(x + rel_x, row_num, other_cell);
927        }
928    }
929}
930
931/// Applies a Position update to either the x or y position.
932/// The value is clamped to be in the range: 0..limit
933fn compute_position_change(current: usize, pos: &Position, limit: usize) -> usize {
934    use self::Position::*;
935    match pos {
936        Relative(delta) => {
937            if *delta >= 0 {
938                min(
939                    current.saturating_add(*delta as usize),
940                    limit.saturating_sub(1),
941                )
942            } else {
943                current.saturating_sub((*delta).abs() as usize)
944            }
945        }
946        Absolute(abs) => min(*abs, limit.saturating_sub(1)),
947        EndRelative(delta) => limit.saturating_sub(*delta),
948    }
949}
950
951#[cfg(test)]
952mod test {
953    use super::*;
954    use alloc::sync::Arc;
955    use wezterm_cell::color::AnsiColor;
956    use wezterm_cell::image::ImageData;
957    use wezterm_cell::{AttributeChange, Intensity};
958
959    // The \x20's look a little awkward, but we can't use a plain
960    // space in the first chararcter of a multi-line continuation;
961    // it gets eaten up and ignored.
962
963    #[test]
964    fn basic_print() {
965        let mut s = Surface::new(4, 3);
966        assert_eq!(
967            s.screen_chars_to_string(),
968            "\x20\x20\x20\x20\n\
969             \x20\x20\x20\x20\n\
970             \x20\x20\x20\x20\n"
971        );
972
973        s.add_change("w00t");
974        assert_eq!(
975            s.screen_chars_to_string(),
976            "w00t\n\
977             \x20\x20\x20\x20\n\
978             \x20\x20\x20\x20\n"
979        );
980
981        s.add_change("foo");
982        assert_eq!(
983            s.screen_chars_to_string(),
984            "w00t\n\
985             foo\x20\n\
986             \x20\x20\x20\x20\n"
987        );
988
989        s.add_change("baar");
990        assert_eq!(
991            s.screen_chars_to_string(),
992            "w00t\n\
993             foob\n\
994             aar\x20\n"
995        );
996
997        s.add_change("baz");
998        assert_eq!(
999            s.screen_chars_to_string(),
1000            "foob\n\
1001             aarb\n\
1002             az\x20\x20\n"
1003        );
1004    }
1005
1006    #[test]
1007    fn newline() {
1008        let mut s = Surface::new(4, 4);
1009        s.add_change("bloo\rwat\n hey\r\nho");
1010        assert_eq!(
1011            s.screen_chars_to_string(),
1012            "wato\n\
1013             \x20\x20\x20\x20\n\
1014             hey \n\
1015             ho  \n"
1016        );
1017    }
1018
1019    #[test]
1020    fn clear_screen() {
1021        let mut s = Surface::new(2, 2);
1022        s.add_change("hello");
1023        assert_eq!(s.xpos, 1);
1024        assert_eq!(s.ypos, 1);
1025        s.add_change(Change::ClearScreen(Default::default()));
1026        assert_eq!(s.xpos, 0);
1027        assert_eq!(s.ypos, 0);
1028        assert_eq!(s.screen_chars_to_string(), "  \n  \n");
1029    }
1030
1031    #[test]
1032    fn clear_eol() {
1033        let mut s = Surface::new(3, 3);
1034        s.add_change("helwowfoo");
1035        s.add_change(Change::ClearToEndOfLine(Default::default()));
1036        assert_eq!(s.screen_chars_to_string(), "hel\nwow\nfoo\n");
1037        s.add_change(Change::CursorPosition {
1038            x: Position::Absolute(0),
1039            y: Position::Absolute(0),
1040        });
1041        s.add_change(Change::ClearToEndOfLine(Default::default()));
1042        assert_eq!(s.screen_chars_to_string(), "   \nwow\nfoo\n");
1043        s.add_change(Change::CursorPosition {
1044            x: Position::Absolute(1),
1045            y: Position::Absolute(1),
1046        });
1047        s.add_change(Change::ClearToEndOfLine(Default::default()));
1048        assert_eq!(s.screen_chars_to_string(), "   \nw\nfoo\n");
1049    }
1050
1051    #[test]
1052    fn clear_eos() {
1053        let mut s = Surface::new(3, 3);
1054        s.add_change("helwowfoo");
1055        s.add_change(Change::ClearToEndOfScreen(Default::default()));
1056        assert_eq!(s.screen_chars_to_string(), "hel\nwow\nfoo\n");
1057        s.add_change(Change::CursorPosition {
1058            x: Position::Absolute(1),
1059            y: Position::Absolute(1),
1060        });
1061        s.add_change(Change::ClearToEndOfScreen(Default::default()));
1062        assert_eq!(s.screen_chars_to_string(), "hel\nw\n   \n");
1063
1064        let (_seq, changes) = s.get_changes(0);
1065        assert_eq!(
1066            &[
1067                Change::CursorVisibility(CursorVisibility::Hidden),
1068                Change::ClearScreen(Default::default()),
1069                Change::Text("hel".into()),
1070                Change::CursorPosition {
1071                    x: Position::Absolute(0),
1072                    y: Position::Relative(1),
1073                },
1074                Change::Text("w".into()),
1075                Change::CursorPosition {
1076                    x: Position::Absolute(1),
1077                    y: Position::Absolute(1),
1078                },
1079                Change::CursorVisibility(CursorVisibility::Visible),
1080            ],
1081            &*changes
1082        );
1083    }
1084
1085    #[test]
1086    fn clear_eos_back_color() {
1087        let mut s = Surface::new(3, 3);
1088        s.add_change(Change::ClearScreen(AnsiColor::Red.into()));
1089        s.add_change("helwowfoo");
1090        assert_eq!(s.screen_chars_to_string(), "hel\nwow\nfoo\n");
1091        s.add_change(Change::CursorPosition {
1092            x: Position::Absolute(1),
1093            y: Position::Absolute(1),
1094        });
1095        s.add_change(Change::ClearToEndOfScreen(AnsiColor::Red.into()));
1096        assert_eq!(s.screen_chars_to_string(), "hel\nw  \n   \n");
1097
1098        let (_seq, changes) = s.get_changes(0);
1099        assert_eq!(
1100            &[
1101                Change::CursorVisibility(CursorVisibility::Hidden),
1102                Change::ClearScreen(Default::default()),
1103                Change::AllAttributes(
1104                    CellAttributes::default()
1105                        .set_background(AnsiColor::Red)
1106                        .clone()
1107                ),
1108                Change::Text("hel".into()),
1109                Change::CursorPosition {
1110                    x: Position::Absolute(0),
1111                    y: Position::Relative(1),
1112                },
1113                Change::Text("w".into()),
1114                Change::ClearToEndOfScreen(AnsiColor::Red.into()),
1115                Change::CursorPosition {
1116                    x: Position::Absolute(1),
1117                    y: Position::Absolute(1),
1118                },
1119                Change::CursorVisibility(CursorVisibility::Visible),
1120            ],
1121            &*changes
1122        );
1123    }
1124
1125    #[test]
1126    fn clear_eol_opt() {
1127        let mut s = Surface::new(3, 3);
1128        s.add_change(Change::Attribute(AttributeChange::Background(
1129            AnsiColor::Red.into(),
1130        )));
1131        s.add_change("111   333");
1132        let (_seq, changes) = s.get_changes(0);
1133        assert_eq!(
1134            &[
1135                Change::CursorVisibility(CursorVisibility::Hidden),
1136                Change::ClearScreen(Default::default()),
1137                Change::AllAttributes(
1138                    CellAttributes::default()
1139                        .set_background(AnsiColor::Red)
1140                        .clone()
1141                ),
1142                Change::Text("111".into()),
1143                Change::CursorPosition {
1144                    x: Position::Absolute(0),
1145                    y: Position::Relative(1),
1146                },
1147                Change::ClearToEndOfLine(AnsiColor::Red.into()),
1148                Change::CursorPosition {
1149                    x: Position::Absolute(0),
1150                    y: Position::Relative(1),
1151                },
1152                Change::Text("333".into()),
1153                Change::CursorPosition {
1154                    x: Position::Absolute(3),
1155                    y: Position::Absolute(2),
1156                },
1157                Change::CursorVisibility(CursorVisibility::Visible),
1158            ],
1159            &*changes
1160        );
1161    }
1162
1163    #[test]
1164    fn clear_and_move_cursor() {
1165        let mut s = Surface::new(4, 3);
1166        s.add_change(Change::CursorPosition {
1167            x: Position::Absolute(3),
1168            y: Position::Absolute(2),
1169        });
1170        let (_seq, changes) = s.get_changes(0);
1171        assert_eq!(
1172            &[
1173                Change::CursorVisibility(CursorVisibility::Hidden),
1174                Change::ClearScreen(Default::default()),
1175                Change::CursorPosition {
1176                    x: Position::Absolute(3),
1177                    y: Position::Absolute(2),
1178                },
1179                Change::CursorVisibility(CursorVisibility::Visible),
1180            ],
1181            &*changes
1182        );
1183    }
1184
1185    #[test]
1186    fn cursor_movement() {
1187        let mut s = Surface::new(4, 3);
1188        s.add_change(Change::CursorPosition {
1189            x: Position::Absolute(3),
1190            y: Position::Absolute(2),
1191        });
1192        s.add_change("X");
1193        assert_eq!(
1194            s.screen_chars_to_string(),
1195            "\x20\x20\x20\x20\n\
1196             \x20\x20\x20\x20\n\
1197             \x20\x20\x20X\n"
1198        );
1199
1200        s.add_change(Change::CursorPosition {
1201            x: Position::Relative(-2),
1202            y: Position::Relative(-1),
1203        });
1204        s.add_change("-");
1205        assert_eq!(
1206            s.screen_chars_to_string(),
1207            "\x20\x20\x20\x20\n\
1208             \x20\x20-\x20\n\
1209             \x20\x20\x20X\n"
1210        );
1211
1212        s.add_change(Change::CursorPosition {
1213            x: Position::Relative(1),
1214            y: Position::Relative(-1),
1215        });
1216        s.add_change("-");
1217        assert_eq!(
1218            s.screen_chars_to_string(),
1219            "\x20\x20\x20-\n\
1220             \x20\x20-\x20\n\
1221             \x20\x20\x20X\n"
1222        );
1223    }
1224
1225    #[test]
1226    fn attribute_setting() {
1227        use wezterm_cell::Intensity;
1228
1229        let mut s = Surface::new(3, 1);
1230        s.add_change("n");
1231        s.add_change(AttributeChange::Intensity(Intensity::Bold));
1232        s.add_change("b");
1233
1234        let mut bold = CellAttributes::default();
1235        bold.set_intensity(Intensity::Bold);
1236
1237        assert_eq!(
1238            s.screen_cells(),
1239            [[
1240                Cell::new('n', CellAttributes::default()),
1241                Cell::new('b', bold),
1242                Cell::default(),
1243            ]]
1244        );
1245    }
1246
1247    #[test]
1248    fn empty_changes() {
1249        let s = Surface::new(4, 3);
1250
1251        let empty = &[
1252            Change::CursorVisibility(CursorVisibility::Hidden),
1253            Change::ClearScreen(Default::default()),
1254            Change::CursorVisibility(CursorVisibility::Visible),
1255        ];
1256
1257        let (seq, changes) = s.get_changes(0);
1258        assert_eq!(seq, 0);
1259        assert_eq!(empty, &*changes);
1260
1261        // Using an invalid sequence number should get us the full
1262        // repaint also.
1263        let (seq, changes) = s.get_changes(1);
1264        assert_eq!(seq, 0);
1265        assert_eq!(empty, &*changes);
1266    }
1267
1268    #[test]
1269    fn add_changes_empty() {
1270        let mut s = Surface::new(2, 2);
1271        let last_seq = s.add_change("foo");
1272        assert_eq!(0, last_seq);
1273        assert_eq!(last_seq, s.add_changes(vec![]));
1274        assert_eq!(last_seq + 1, s.add_changes(vec![Change::Text("a".into())]));
1275    }
1276
1277    #[test]
1278    fn resize_delta_flush() {
1279        let mut s = Surface::new(4, 3);
1280        s.add_change("a");
1281        let (seq, _) = s.get_changes(0);
1282        s.resize(2, 2);
1283
1284        let full = &[
1285            Change::CursorVisibility(CursorVisibility::Hidden),
1286            Change::ClearScreen(Default::default()),
1287            Change::Text("a".to_string()),
1288            Change::CursorPosition {
1289                x: Position::Absolute(1),
1290                y: Position::Absolute(0),
1291            },
1292            Change::CursorVisibility(CursorVisibility::Visible),
1293        ];
1294
1295        let (_seq, changes) = s.get_changes(seq);
1296        // The resize causes get_changes to return a full repaint
1297        assert_eq!(full, &*changes);
1298    }
1299
1300    #[test]
1301    fn dont_lose_first_char_on_attr_change() {
1302        let mut s = Surface::new(2, 2);
1303        s.add_change(Change::Attribute(AttributeChange::Foreground(
1304            AnsiColor::Maroon.into(),
1305        )));
1306        s.add_change("ab");
1307        let (_seq, changes) = s.get_changes(0);
1308        assert_eq!(
1309            &[
1310                Change::CursorVisibility(CursorVisibility::Hidden),
1311                Change::ClearScreen(Default::default()),
1312                Change::AllAttributes(
1313                    CellAttributes::default()
1314                        .set_foreground(AnsiColor::Maroon)
1315                        .clone()
1316                ),
1317                Change::Text("ab".into()),
1318                Change::CursorPosition {
1319                    x: Position::Absolute(2),
1320                    y: Position::Absolute(0),
1321                },
1322                Change::CursorVisibility(CursorVisibility::Visible),
1323            ],
1324            &*changes
1325        );
1326    }
1327
1328    #[test]
1329    fn resize_cursor_position() {
1330        let mut s = Surface::new(4, 4);
1331
1332        s.add_change(" a");
1333        s.add_change(Change::CursorPosition {
1334            x: Position::Absolute(3),
1335            y: Position::Absolute(3),
1336        });
1337
1338        assert_eq!(s.xpos, 3);
1339        assert_eq!(s.ypos, 3);
1340        s.resize(2, 2);
1341        assert_eq!(s.xpos, 1);
1342        assert_eq!(s.ypos, 1);
1343
1344        let full = &[
1345            Change::CursorVisibility(CursorVisibility::Hidden),
1346            Change::ClearScreen(Default::default()),
1347            Change::Text(" a".to_string()),
1348            Change::CursorPosition {
1349                x: Position::Absolute(1),
1350                y: Position::Absolute(1),
1351            },
1352            Change::CursorVisibility(CursorVisibility::Visible),
1353        ];
1354
1355        let (_seq, changes) = s.get_changes(0);
1356        assert_eq!(full, &*changes);
1357    }
1358
1359    #[test]
1360    fn delta_change() {
1361        let mut s = Surface::new(4, 3);
1362        // flushing nothing should be a NOP
1363        s.flush_changes_older_than(0);
1364
1365        // check that using an invalid index doesn't panic
1366        s.flush_changes_older_than(1);
1367
1368        let initial = &[
1369            Change::CursorVisibility(CursorVisibility::Hidden),
1370            Change::ClearScreen(Default::default()),
1371            Change::Text("a".to_string()),
1372            Change::CursorPosition {
1373                x: Position::Absolute(1),
1374                y: Position::Absolute(0),
1375            },
1376            Change::CursorVisibility(CursorVisibility::Visible),
1377        ];
1378
1379        let seq_pos = {
1380            let next_seq = s.add_change("a");
1381            let (seq, changes) = s.get_changes(0);
1382            assert_eq!(seq, next_seq + 1);
1383            assert_eq!(initial, &*changes);
1384            seq
1385        };
1386
1387        let seq_pos = {
1388            let next_seq = s.add_change("b");
1389            let (seq, changes) = s.get_changes(seq_pos);
1390            assert_eq!(seq, next_seq + 1);
1391            assert_eq!(&[Change::Text("b".to_string())], &*changes);
1392            seq
1393        };
1394
1395        // prep some deltas for the loop to test below
1396        {
1397            s.add_change(Change::Attribute(AttributeChange::Intensity(
1398                Intensity::Bold,
1399            )));
1400            s.add_change("c");
1401            s.add_change(Change::Attribute(AttributeChange::Intensity(
1402                Intensity::Normal,
1403            )));
1404            s.add_change("d");
1405        }
1406
1407        // Do this three times to ennsure that the behavior is consistent
1408        // across multiple flush calls
1409        for _ in 0..3 {
1410            {
1411                let (_seq, changes) = s.get_changes(seq_pos);
1412
1413                assert_eq!(
1414                    &[
1415                        Change::Attribute(AttributeChange::Intensity(Intensity::Bold)),
1416                        Change::Text("c".to_string()),
1417                        Change::Attribute(AttributeChange::Intensity(Intensity::Normal)),
1418                        Change::Text("d".to_string()),
1419                    ],
1420                    &*changes
1421                );
1422            }
1423
1424            // Flush the changes so that the next iteration is run on a pruned
1425            // set of changes.  It should not change the outcome of the body
1426            // of the loop.
1427            s.flush_changes_older_than(seq_pos);
1428        }
1429    }
1430
1431    #[test]
1432    fn diff_screens() {
1433        let mut s = Surface::new(4, 3);
1434        s.add_change("w00t");
1435        s.add_change("foo");
1436        s.add_change("baar");
1437        s.add_change("baz");
1438        assert_eq!(
1439            s.screen_chars_to_string(),
1440            "foob\n\
1441             aarb\n\
1442             az  \n"
1443        );
1444
1445        let s2 = Surface::new(2, 2);
1446
1447        {
1448            // We want to sample the top left corner
1449            let changes = s2.diff_region(0, 0, 2, 2, &s, 0, 0);
1450            assert_eq!(
1451                vec![
1452                    Change::CursorPosition {
1453                        x: Position::Absolute(0),
1454                        y: Position::Absolute(0),
1455                    },
1456                    Change::AllAttributes(CellAttributes::default()),
1457                    Change::Text("fo".into()),
1458                    Change::CursorPosition {
1459                        x: Position::Absolute(0),
1460                        y: Position::Absolute(1),
1461                    },
1462                    Change::Text("aa".into()),
1463                ],
1464                changes
1465            );
1466        }
1467
1468        // Throw in some attribute changes too
1469        s.add_change(Change::CursorPosition {
1470            x: Position::Absolute(1),
1471            y: Position::Absolute(1),
1472        });
1473        s.add_change(Change::Attribute(AttributeChange::Intensity(
1474            Intensity::Bold,
1475        )));
1476        s.add_change("XO");
1477
1478        {
1479            let changes = s2.diff_region(0, 0, 2, 2, &s, 1, 1);
1480            assert_eq!(
1481                vec![
1482                    Change::CursorPosition {
1483                        x: Position::Absolute(0),
1484                        y: Position::Absolute(0),
1485                    },
1486                    Change::AllAttributes(
1487                        CellAttributes::default()
1488                            .set_intensity(Intensity::Bold)
1489                            .clone(),
1490                    ),
1491                    Change::Text("XO".into()),
1492                    Change::CursorPosition {
1493                        x: Position::Absolute(0),
1494                        y: Position::Absolute(1),
1495                    },
1496                    Change::AllAttributes(CellAttributes::default()),
1497                    Change::Text("z".into()),
1498                    /* There's no change for the final character
1499                     * position because it is a space in both regions. */
1500                ],
1501                changes
1502            );
1503        }
1504    }
1505
1506    #[test]
1507    fn draw_screens() {
1508        let mut s = Surface::new(4, 4);
1509
1510        let mut s1 = Surface::new(2, 2);
1511        s1.add_change("1234");
1512
1513        let mut s2 = Surface::new(2, 2);
1514        s2.add_change("XYZA");
1515
1516        s.draw_from_screen(&s1, 0, 0);
1517        s.draw_from_screen(&s2, 2, 2);
1518
1519        assert_eq!(
1520            s.screen_chars_to_string(),
1521            "12  \n\
1522             34  \n\
1523             \x20\x20XY\n\
1524             \x20\x20ZA\n"
1525        );
1526    }
1527
1528    #[test]
1529    fn draw_colored_region() {
1530        let mut dest = Surface::new(4, 4);
1531        dest.add_change("A");
1532        let mut src = Surface::new(2, 2);
1533        src.add_change(Change::ClearScreen(AnsiColor::Blue.into()));
1534        dest.draw_from_screen(&src, 2, 2);
1535
1536        assert_eq!(
1537            dest.screen_chars_to_string(),
1538            "A   \n\
1539             \x20   \n\
1540             \x20   \n\
1541             \x20   \n"
1542        );
1543
1544        let blue_space = Cell::new(
1545            ' ',
1546            CellAttributes::default()
1547                .set_background(AnsiColor::Blue)
1548                .clone(),
1549        );
1550
1551        assert_eq!(
1552            dest.screen_cells(),
1553            [
1554                [
1555                    Cell::new('A', CellAttributes::default()),
1556                    Cell::default(),
1557                    Cell::default(),
1558                    Cell::default(),
1559                ],
1560                [
1561                    Cell::default(),
1562                    Cell::default(),
1563                    Cell::default(),
1564                    Cell::default(),
1565                ],
1566                [
1567                    Cell::default(),
1568                    Cell::default(),
1569                    blue_space.clone(),
1570                    blue_space.clone(),
1571                ],
1572                [
1573                    Cell::default(),
1574                    Cell::default(),
1575                    blue_space.clone(),
1576                    blue_space.clone(),
1577                ]
1578            ]
1579        );
1580
1581        assert_eq!(dest.xpos, 1);
1582        assert_eq!(dest.ypos, 0);
1583        assert_eq!(dest.attributes, Default::default());
1584        dest.add_change("B");
1585
1586        assert_eq!(
1587            dest.screen_chars_to_string(),
1588            "AB  \n\
1589             \x20   \n\
1590             \x20   \n\
1591             \x20   \n"
1592        );
1593    }
1594
1595    #[test]
1596    fn copy_region() {
1597        let mut s = Surface::new(4, 3);
1598        s.add_change("w00t");
1599        s.add_change("foo");
1600        s.add_change("baar");
1601        s.add_change("baz");
1602        assert_eq!(
1603            s.screen_chars_to_string(),
1604            "foob\n\
1605             aarb\n\
1606             az  \n"
1607        );
1608
1609        // Copy top left to bottom left
1610        s.copy_region(0, 0, 2, 2, 2, 1);
1611        assert_eq!(
1612            s.screen_chars_to_string(),
1613            "foob\n\
1614             aafo\n\
1615             azaa\n"
1616        );
1617    }
1618
1619    #[test]
1620    fn double_width() {
1621        let mut s = Surface::new(4, 1);
1622        s.add_change("🤷12");
1623        assert_eq!(s.screen_chars_to_string(), "🤷12\n");
1624        s.add_change(Change::CursorPosition {
1625            x: Position::Absolute(1),
1626            y: Position::Absolute(0),
1627        });
1628        s.add_change("a🤷");
1629        assert_eq!(s.screen_chars_to_string(), " a🤷\n");
1630        s.add_change(Change::CursorPosition {
1631            x: Position::Absolute(2),
1632            y: Position::Absolute(0),
1633        });
1634        s.add_change("x");
1635        assert_eq!(s.screen_chars_to_string(), " ax \n");
1636    }
1637
1638    #[test]
1639    fn draw_double_width() {
1640        let mut s = Surface::new(4, 1);
1641        s.add_change("か a");
1642        assert_eq!(s.screen_chars_to_string(), "か a\n");
1643
1644        let mut s2 = Surface::new(4, 1);
1645        s2.draw_from_screen(&s, 0, 0);
1646        // Verify no issue when the second visible cells on both sides
1647        // are identical (' 's) but they are at different cell indices.
1648        assert_eq!(s2.screen_chars_to_string(), "か a\n");
1649
1650        let s3 = Surface::new(4, 1);
1651        s2.draw_from_screen(&s3, 0, 0);
1652        // Verify same but in other direction
1653        assert_eq!(s2.screen_chars_to_string(), "    \n");
1654
1655        let mut s4 = Surface::new(4, 1);
1656        s4.add_change("abcd");
1657        s.draw_from_screen(&s4, 0, 0);
1658        // Verify that all overlapping cells are updated when cell widths
1659        // differ on each side.
1660        assert_eq!(s.screen_chars_to_string(), "abcd\n");
1661    }
1662
1663    #[test]
1664    fn diff_cursor_double_width() {
1665        let mut s = Surface::new(3, 1);
1666        s.add_change("かa");
1667
1668        let s2 = Surface::new(3, 1);
1669        let changes = s2.diff_region(0, 0, 3, 1, &s, 0, 0);
1670
1671        assert_eq!(
1672            changes
1673                .iter()
1674                .filter(|change| matches!(change, Change::CursorPosition { .. }))
1675                .count(),
1676            1
1677        );
1678    }
1679
1680    #[test]
1681    fn zero_width() {
1682        let mut s = Surface::new(4, 1);
1683        // https://en.wikipedia.org/wiki/Zero-width_space
1684        s.add_change("A\u{200b}B");
1685        assert_eq!(s.screen_chars_to_string(), "A\u{200b}B \n");
1686    }
1687
1688    #[test]
1689    fn images() {
1690        // a dummy image blob with nonsense content
1691        let data = Arc::new(ImageData::with_raw_data(vec![]));
1692        let mut s = Surface::new(2, 2);
1693        s.add_change(Change::Image(Image {
1694            top_left: TextureCoordinate::new_f32(0.0, 0.0),
1695            bottom_right: TextureCoordinate::new_f32(1.0, 1.0),
1696            image: data.clone(),
1697            width: 4,
1698            height: 2,
1699        }));
1700
1701        // We're checking that we slice the image up and assign the correct
1702        // texture coordinates for each cell.  The width and height are
1703        // different from each other to help ensure that the right terms
1704        // are used by add_image() function.
1705        assert_eq!(
1706            s.screen_cells(),
1707            [
1708                [
1709                    Cell::new(
1710                        ' ',
1711                        CellAttributes::default()
1712                            .set_image(Box::new(ImageCell::new(
1713                                TextureCoordinate::new_f32(0.0, 0.0),
1714                                TextureCoordinate::new_f32(0.25, 0.5),
1715                                data.clone()
1716                            )))
1717                            .clone()
1718                    ),
1719                    Cell::new(
1720                        ' ',
1721                        CellAttributes::default()
1722                            .set_image(Box::new(ImageCell::new(
1723                                TextureCoordinate::new_f32(0.25, 0.0),
1724                                TextureCoordinate::new_f32(0.5, 0.5),
1725                                data.clone()
1726                            )))
1727                            .clone()
1728                    ),
1729                    Cell::new(
1730                        ' ',
1731                        CellAttributes::default()
1732                            .set_image(Box::new(ImageCell::new(
1733                                TextureCoordinate::new_f32(0.5, 0.0),
1734                                TextureCoordinate::new_f32(0.75, 0.5),
1735                                data.clone()
1736                            )))
1737                            .clone()
1738                    ),
1739                    Cell::new(
1740                        ' ',
1741                        CellAttributes::default()
1742                            .set_image(Box::new(ImageCell::new(
1743                                TextureCoordinate::new_f32(0.75, 0.0),
1744                                TextureCoordinate::new_f32(1.0, 0.5),
1745                                data.clone()
1746                            )))
1747                            .clone()
1748                    ),
1749                ],
1750                [
1751                    Cell::new(
1752                        ' ',
1753                        CellAttributes::default()
1754                            .set_image(Box::new(ImageCell::new(
1755                                TextureCoordinate::new_f32(0.0, 0.5),
1756                                TextureCoordinate::new_f32(0.25, 1.0),
1757                                data.clone()
1758                            )))
1759                            .clone()
1760                    ),
1761                    Cell::new(
1762                        ' ',
1763                        CellAttributes::default()
1764                            .set_image(Box::new(ImageCell::new(
1765                                TextureCoordinate::new_f32(0.25, 0.5),
1766                                TextureCoordinate::new_f32(0.5, 1.0),
1767                                data.clone()
1768                            )))
1769                            .clone()
1770                    ),
1771                    Cell::new(
1772                        ' ',
1773                        CellAttributes::default()
1774                            .set_image(Box::new(ImageCell::new(
1775                                TextureCoordinate::new_f32(0.5, 0.5),
1776                                TextureCoordinate::new_f32(0.75, 1.0),
1777                                data.clone()
1778                            )))
1779                            .clone()
1780                    ),
1781                    Cell::new(
1782                        ' ',
1783                        CellAttributes::default()
1784                            .set_image(Box::new(ImageCell::new(
1785                                TextureCoordinate::new_f32(0.75, 0.5),
1786                                TextureCoordinate::new_f32(1.0, 1.0),
1787                                data.clone()
1788                            )))
1789                            .clone()
1790                    ),
1791                ],
1792            ]
1793        );
1794
1795        // Check that starting at not the texture origin coordinates
1796        // gives reasonable values in the resultant cell
1797        let mut other = Surface::new(1, 1);
1798        other.add_change(Change::Image(Image {
1799            top_left: TextureCoordinate::new_f32(0.25, 0.3),
1800            bottom_right: TextureCoordinate::new_f32(0.75, 0.8),
1801            image: data.clone(),
1802            width: 1,
1803            height: 1,
1804        }));
1805        assert_eq!(
1806            other.screen_cells(),
1807            [[Cell::new(
1808                ' ',
1809                CellAttributes::default()
1810                    .set_image(Box::new(ImageCell::new(
1811                        TextureCoordinate::new_f32(0.25, 0.3),
1812                        TextureCoordinate::new_f32(0.75, 0.8),
1813                        data.clone()
1814                    )))
1815                    .clone()
1816            ),]]
1817        );
1818    }
1819}