Skip to main content

altui_core/
buffer.rs

1use crate::{
2    layout::Rect,
3    style::{Color, Modifier, Style},
4    text::{Span, Spans},
5};
6use std::cmp::min;
7use unicode_segmentation::UnicodeSegmentation;
8use unicode_width::UnicodeWidthStr;
9
10/// A buffer cell
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct Cell {
13    pub symbol: String,
14    pub fg: Color,
15    pub bg: Color,
16    pub modifier: Modifier,
17}
18
19impl Cell {
20    pub fn set_symbol(&mut self, symbol: &str) -> &mut Cell {
21        self.symbol.clear();
22        self.symbol.push_str(symbol);
23        self
24    }
25
26    pub fn set_char(&mut self, ch: char) -> &mut Cell {
27        self.symbol.clear();
28        self.symbol.push(ch);
29        self
30    }
31
32    pub fn set_fg(&mut self, color: Color) -> &mut Cell {
33        self.fg = color;
34        self
35    }
36
37    pub fn set_bg(&mut self, color: Color) -> &mut Cell {
38        self.bg = color;
39        self
40    }
41
42    pub fn set_style(&mut self, style: Style) -> &mut Cell {
43        if let Some(c) = style.fg {
44            self.fg = c;
45        }
46        if let Some(c) = style.bg {
47            self.bg = c;
48        }
49        self.modifier.insert(style.add_modifier);
50        self.modifier.remove(style.sub_modifier);
51        self
52    }
53
54    pub fn style(&self) -> Style {
55        Style::default()
56            .fg(self.fg)
57            .bg(self.bg)
58            .add_modifier(self.modifier)
59    }
60
61    pub fn reset(&mut self) {
62        self.symbol.clear();
63        self.symbol.push(' ');
64        self.fg = Color::Reset;
65        self.bg = Color::Reset;
66        self.modifier = Modifier::empty();
67    }
68}
69
70impl Default for Cell {
71    fn default() -> Cell {
72        Cell {
73            symbol: " ".into(),
74            fg: Color::Reset,
75            bg: Color::Reset,
76            modifier: Modifier::empty(),
77        }
78    }
79}
80
81/// A buffer that maps to the desired content of the terminal after the draw call
82///
83/// No widget in the library interacts directly with the terminal. Instead each of them is required
84/// to draw their state to an intermediate buffer. It is basically a grid where each cell contains
85/// a grapheme, a foreground color and a background color. This grid will then be used to output
86/// the appropriate escape sequences and characters to draw the UI as the user has defined it.
87///
88/// # Examples:
89///
90/// ```
91/// use altui_core::buffer::{Buffer, Cell};
92/// use altui_core::layout::Rect;
93/// use altui_core::style::{Color, Style, Modifier};
94///
95/// let mut buf = Buffer::empty(Rect{x: 0, y: 0, width: 10, height: 5});
96/// buf.get_mut(0, 2).set_symbol("x");
97/// assert_eq!(buf.get(0, 2).symbol, "x");
98/// buf.set_string(3, 0, "string", Style::default().fg(Color::Red).bg(Color::White));
99/// assert_eq!(buf.get(5, 0), &Cell{
100///     symbol: String::from("r"),
101///     fg: Color::Red,
102///     bg: Color::White,
103///     modifier: Modifier::empty()
104/// });
105/// buf.get_mut(5, 0).set_char('x');
106/// assert_eq!(buf.get(5, 0).symbol, "x");
107/// ```
108#[derive(Debug, Clone, PartialEq, Eq, Default)]
109pub struct Buffer {
110    /// The area represented by this buffer
111    pub area: Rect,
112    /// The content of the buffer. The length of this Vec should always be equal to area.width *
113    /// area.height
114    pub content: Vec<Cell>,
115}
116
117impl Buffer {
118    /// Returns a Buffer with all cells set to the default one
119    pub fn empty(area: Rect) -> Buffer {
120        let cell: Cell = Default::default();
121        Buffer::filled(area, &cell)
122    }
123
124    /// Returns a Buffer with all cells initialized with the attributes of the given Cell
125    pub fn filled(area: Rect, cell: &Cell) -> Buffer {
126        let size = area.area() as usize;
127        let mut content = Vec::with_capacity(size);
128        for _ in 0..size {
129            content.push(cell.clone());
130        }
131        Buffer { area, content }
132    }
133
134    /// Returns a Buffer containing the given lines
135    pub fn with_lines<S>(lines: Vec<S>) -> Buffer
136    where
137        S: AsRef<str>,
138    {
139        let height = lines.len() as u16;
140        let width = lines
141            .iter()
142            .map(|i| i.as_ref().width() as u16)
143            .max()
144            .unwrap_or_default();
145        let mut buffer = Buffer::empty(Rect {
146            x: 0,
147            y: 0,
148            width,
149            height,
150        });
151        for (y, line) in lines.iter().enumerate() {
152            buffer.set_string(0, y as u16, line, Style::default());
153        }
154        buffer
155    }
156
157    /// Returns the content of the buffer as a slice
158    pub fn content(&self) -> &[Cell] {
159        &self.content
160    }
161
162    /// Returns the area covered by this buffer
163    pub fn area(&self) -> &Rect {
164        &self.area
165    }
166
167    /// Returns a reference to Cell at the given coordinates
168    pub fn get(&self, x: u16, y: u16) -> &Cell {
169        let i = self.index_of(x, y);
170        &self.content[i]
171    }
172
173    /// Returns a mutable reference to Cell at the given coordinates
174    pub fn get_mut(&mut self, x: u16, y: u16) -> &mut Cell {
175        let i = self.index_of(x, y);
176        &mut self.content[i]
177    }
178
179    /// Returns the index in the `Vec<Cell>` for the given global (x, y) coordinates.
180    ///
181    /// Global coordinates are offset by the Buffer's area offset (`x`/`y`).
182    ///
183    /// # Examples
184    ///
185    /// ```
186    /// # use altui_core::buffer::Buffer;
187    /// # use altui_core::layout::Rect;
188    /// let rect = Rect::new(200, 100, 10, 10);
189    /// let buffer = Buffer::empty(rect);
190    /// // Global coordinates to the top corner of this buffer's area
191    /// assert_eq!(buffer.index_of(200, 100), 0);
192    /// ```
193    ///
194    /// # Panics
195    ///
196    /// Panics when given an coordinate that is outside of this Buffer's area.
197    ///
198    /// ```should_panic
199    /// # use altui_core::buffer::Buffer;
200    /// # use altui_core::layout::Rect;
201    /// let rect = Rect::new(200, 100, 10, 10);
202    /// let buffer = Buffer::empty(rect);
203    /// // Top coordinate is outside of the buffer in global coordinate space, as the Buffer's area
204    /// // starts at (200, 100).
205    /// buffer.index_of(0, 0); // Panics
206    /// ```
207    pub fn index_of(&self, x: u16, y: u16) -> usize {
208        debug_assert!(
209            x >= self.area.left()
210                && x < self.area.right()
211                && y >= self.area.top()
212                && y < self.area.bottom(),
213            "Trying to access position outside the buffer: x={}, y={}, area={:?}",
214            x,
215            y,
216            self.area
217        );
218        ((y - self.area.y) * self.area.width + (x - self.area.x)) as usize
219    }
220
221    /// Returns the (global) coordinates of a cell given its index
222    ///
223    /// Global coordinates are offset by the Buffer's area offset (`x`/`y`).
224    ///
225    /// # Examples
226    ///
227    /// ```
228    /// # use altui_core::buffer::Buffer;
229    /// # use altui_core::layout::Rect;
230    /// let rect = Rect::new(200, 100, 10, 10);
231    /// let buffer = Buffer::empty(rect);
232    /// assert_eq!(buffer.pos_of(0), (200, 100));
233    /// assert_eq!(buffer.pos_of(14), (204, 101));
234    /// ```
235    ///
236    /// # Panics
237    ///
238    /// Panics when given an index that is outside the Buffer's content.
239    ///
240    /// ```should_panic
241    /// # use altui_core::buffer::Buffer;
242    /// # use altui_core::layout::Rect;
243    /// let rect = Rect::new(0, 0, 10, 10); // 100 cells in total
244    /// let buffer = Buffer::empty(rect);
245    /// // Index 100 is the 101th cell, which lies outside of the area of this Buffer.
246    /// buffer.pos_of(100); // Panics
247    /// ```
248    pub fn pos_of(&self, i: usize) -> (u16, u16) {
249        debug_assert!(
250            i < self.content.len(),
251            "Trying to get the coords of a cell outside the buffer: i={} len={}",
252            i,
253            self.content.len()
254        );
255        (
256            self.area.x + i as u16 % self.area.width,
257            self.area.y + i as u16 / self.area.width,
258        )
259    }
260
261    /// Print a string, starting at the position (x, y)
262    pub fn set_string<S>(&mut self, x: u16, y: u16, string: S, style: Style)
263    where
264        S: AsRef<str>,
265    {
266        self.set_stringn(x, y, string, usize::MAX, style);
267    }
268
269    /// Print at most the first n characters of a string if enough space is available
270    /// until the end of the line
271    pub fn set_stringn<S>(
272        &mut self,
273        x: u16,
274        y: u16,
275        string: S,
276        width: usize,
277        style: Style,
278    ) -> (u16, u16)
279    where
280        S: AsRef<str>,
281    {
282        let mut index = self.index_of(x, y);
283        let mut x_offset = x as usize;
284        let graphemes = UnicodeSegmentation::graphemes(string.as_ref(), true);
285        let max_offset = min(self.area.right() as usize, width.saturating_add(x as usize));
286        for s in graphemes {
287            let width = s.width();
288            if width == 0 || s.chars().all(|c| c.is_control()) {
289                continue;
290            }
291            // `x_offset + width > max_offset` could be integer overflow on 32-bit machines if we
292            // change dimenstions to usize or u32 and someone resizes the terminal to 1x2^32.
293            if width > max_offset.saturating_sub(x_offset) {
294                break;
295            }
296
297            self.content[index].set_symbol(s);
298            self.content[index].set_style(style);
299            // Reset following cells if multi-width (they would be hidden by the grapheme),
300            for i in index + 1..index + width {
301                self.content[i].reset();
302            }
303            index += width;
304            x_offset += width;
305        }
306        (x_offset as u16, y)
307    }
308
309    pub fn set_spans<'a>(&mut self, x: u16, y: u16, spans: &Spans<'a>, width: u16) -> (u16, u16) {
310        let mut remaining_width = width;
311        let mut x = x;
312        for span in &spans.0 {
313            if remaining_width == 0 {
314                break;
315            }
316            let pos = self.set_stringn(
317                x,
318                y,
319                span.content.as_ref(),
320                remaining_width as usize,
321                span.style,
322            );
323            let w = pos.0.saturating_sub(x);
324            x = pos.0;
325            remaining_width = remaining_width.saturating_sub(w);
326        }
327        (x, y)
328    }
329
330    pub fn set_span<'a>(&mut self, x: u16, y: u16, span: &Span<'a>, width: u16) -> (u16, u16) {
331        self.set_stringn(x, y, span.content.as_ref(), width as usize, span.style)
332    }
333
334    pub fn set_style(&mut self, area: Rect, style: Style) {
335        for y in area.top()..area.bottom() {
336            for x in area.left()..area.right() {
337                self.get_mut(x, y).set_style(style);
338            }
339        }
340    }
341
342    /// Resize the buffer so that the mapped area matches the given area and that the buffer
343    /// length is equal to area.width * area.height
344    pub fn resize(&mut self, area: Rect) {
345        let length = area.area() as usize;
346        if self.content.len() > length {
347            self.content.truncate(length);
348        } else {
349            self.content.resize(length, Default::default());
350        }
351        self.area = area;
352    }
353
354    /// Reset all cells in the buffer
355    pub fn reset(&mut self) {
356        for c in &mut self.content {
357            c.reset();
358        }
359    }
360
361    /// Merge an other buffer into this one
362    pub fn merge(&mut self, other: &Buffer) {
363        let area = self.area.union(other.area);
364        let cell: Cell = Default::default();
365        self.content.resize(area.area() as usize, cell.clone());
366
367        // Move original content to the appropriate space
368        let size = self.area.area() as usize;
369        for i in (0..size).rev() {
370            let (x, y) = self.pos_of(i);
371            // New index in content
372            let k = ((y - area.y) * area.width + x - area.x) as usize;
373            if i != k {
374                self.content[k] = self.content[i].clone();
375                self.content[i] = cell.clone();
376            }
377        }
378
379        // Push content of the other buffer into this one (may erase previous
380        // data)
381        let size = other.area.area() as usize;
382        for i in 0..size {
383            let (x, y) = other.pos_of(i);
384            // New index in content
385            let k = ((y - area.y) * area.width + x - area.x) as usize;
386            self.content[k] = other.content[i].clone();
387        }
388        self.area = area;
389    }
390
391    /// Builds a minimal sequence of coordinates and Cells necessary to update the UI from
392    /// self to other.
393    ///
394    /// We're assuming that buffers are well-formed, that is no double-width cell is followed by
395    /// a non-blank cell.
396    ///
397    /// # Multi-width characters handling:
398    ///
399    /// ```text
400    /// (Index:) `01`
401    /// Prev:    `コ`
402    /// Next:    `aa`
403    /// Updates: `0: a, 1: a'
404    /// ```
405    ///
406    /// ```text
407    /// (Index:) `01`
408    /// Prev:    `a `
409    /// Next:    `コ`
410    /// Updates: `0: コ` (double width symbol at index 0 - skip index 1)
411    /// ```
412    ///
413    /// ```text
414    /// (Index:) `012`
415    /// Prev:    `aaa`
416    /// Next:    `aコ`
417    /// Updates: `0: a, 1: コ` (double width symbol at index 1 - skip index 2)
418    /// ```
419    pub fn diff<'a>(&self, other: &'a Buffer) -> Vec<(u16, u16, &'a Cell)> {
420        let previous_buffer = &self.content;
421        let next_buffer = &other.content;
422        let width = self.area.width;
423
424        let mut updates: Vec<(u16, u16, &Cell)> = vec![];
425        // Cells invalidated by drawing/replacing preceeding multi-width characters:
426        let mut invalidated: usize = 0;
427        // Cells from the current buffer to skip due to preceeding multi-width characters taking their
428        // place (the skipped cells should be blank anyway):
429        let mut to_skip: usize = 0;
430        for (i, (current, previous)) in next_buffer.iter().zip(previous_buffer.iter()).enumerate() {
431            if (current != previous || invalidated > 0) && to_skip == 0 {
432                let x = i as u16 % width;
433                let y = i as u16 / width;
434                updates.push((x, y, &next_buffer[i]));
435            }
436
437            to_skip = current.symbol.width().saturating_sub(1);
438
439            let affected_width = std::cmp::max(current.symbol.width(), previous.symbol.width());
440            invalidated = std::cmp::max(affected_width, invalidated).saturating_sub(1);
441        }
442        updates
443    }
444}
445
446/// Assert that two buffers are equal by comparing their areas and content.
447///
448/// On panic, displays the areas or the content and a diff of the contents.
449#[macro_export]
450macro_rules! assert_buffer_eq {
451    ($actual_expr:expr, $expected_expr:expr) => {
452        match (&$actual_expr, &$expected_expr) {
453            (actual, expected) => {
454                if actual.area != expected.area {
455                    panic!(
456                        indoc::indoc!(
457                            "
458                            buffer areas not equal
459                            expected:  {:?}
460                            actual:    {:?}"
461                        ),
462                        expected, actual
463                    );
464                }
465                let diff = expected.diff(&actual);
466                if !diff.is_empty() {
467                    let nice_diff = diff
468                        .iter()
469                        .enumerate()
470                        .map(|(i, (x, y, cell))| {
471                            let expected_cell = expected.get(*x, *y);
472                            indoc::formatdoc! {"
473                                {i}: at ({x}, {y})
474                                  expected: {expected_cell:?}
475                                  actual:   {cell:?}
476                            "}
477                        })
478                        .collect::<Vec<String>>()
479                        .join("\n");
480                    panic!(
481                        indoc::indoc!(
482                            "
483                            buffer contents not equal
484                            expected: {:?}
485                            actual: {:?}
486                            diff:
487                            {}"
488                        ),
489                        expected, actual, nice_diff
490                    );
491                }
492                // shouldn't get here, but this guards against future behavior
493                // that changes equality but not area or content
494                assert_eq!(actual, expected, "buffers not equal");
495            }
496        }
497    };
498}
499
500#[cfg(test)]
501mod tests {
502    use super::*;
503
504    fn cell(s: &str) -> Cell {
505        let mut cell = Cell::default();
506        cell.set_symbol(s);
507        cell
508    }
509
510    #[test]
511    fn it_translates_to_and_from_coordinates() {
512        let rect = Rect::new(200, 100, 50, 80);
513        let buf = Buffer::empty(rect);
514
515        // First cell is at the upper left corner.
516        assert_eq!(buf.pos_of(0), (200, 100));
517        assert_eq!(buf.index_of(200, 100), 0);
518
519        // Last cell is in the lower right.
520        assert_eq!(buf.pos_of(buf.content.len() - 1), (249, 179));
521        assert_eq!(buf.index_of(249, 179), buf.content.len() - 1);
522    }
523
524    #[test]
525    #[should_panic(expected = "outside the buffer")]
526    fn pos_of_panics_on_out_of_bounds() {
527        let rect = Rect::new(0, 0, 10, 10);
528        let buf = Buffer::empty(rect);
529
530        // There are a total of 100 cells; zero-indexed means that 100 would be the 101st cell.
531        buf.pos_of(100);
532    }
533
534    #[test]
535    #[should_panic(expected = "outside the buffer")]
536    fn index_of_panics_on_out_of_bounds() {
537        let rect = Rect::new(0, 0, 10, 10);
538        let buf = Buffer::empty(rect);
539
540        // width is 10; zero-indexed means that 10 would be the 11th cell.
541        buf.index_of(10, 0);
542    }
543
544    #[test]
545    fn buffer_set_string() {
546        let area = Rect::new(0, 0, 5, 1);
547        let mut buffer = Buffer::empty(area);
548
549        // Zero-width
550        buffer.set_stringn(0, 0, "aaa", 0, Style::default());
551        assert_eq!(buffer, Buffer::with_lines(vec!["     "]));
552
553        buffer.set_string(0, 0, "aaa", Style::default());
554        assert_eq!(buffer, Buffer::with_lines(vec!["aaa  "]));
555
556        // Width limit:
557        buffer.set_stringn(0, 0, "bbbbbbbbbbbbbb", 4, Style::default());
558        assert_eq!(buffer, Buffer::with_lines(vec!["bbbb "]));
559
560        buffer.set_string(0, 0, "12345", Style::default());
561        assert_eq!(buffer, Buffer::with_lines(vec!["12345"]));
562
563        // Width truncation:
564        buffer.set_string(0, 0, "123456", Style::default());
565        assert_eq!(buffer, Buffer::with_lines(vec!["12345"]));
566    }
567
568    #[test]
569    fn buffer_set_string_zero_width() {
570        let area = Rect::new(0, 0, 1, 1);
571        let mut buffer = Buffer::empty(area);
572
573        // Leading grapheme with zero width
574        let s = "\u{1}a";
575        buffer.set_stringn(0, 0, s, 1, Style::default());
576        assert_eq!(buffer, Buffer::with_lines(vec!["a"]));
577
578        // Trailing grapheme with zero with
579        let s = "a\u{1}";
580        buffer.set_stringn(0, 0, s, 1, Style::default());
581        assert_eq!(buffer, Buffer::with_lines(vec!["a"]));
582    }
583
584    #[test]
585    fn buffer_set_string_double_width() {
586        let area = Rect::new(0, 0, 5, 1);
587        let mut buffer = Buffer::empty(area);
588        buffer.set_string(0, 0, "コン", Style::default());
589        assert_eq!(buffer, Buffer::with_lines(vec!["コン "]));
590
591        // Only 1 space left.
592        buffer.set_string(0, 0, "コンピ", Style::default());
593        assert_eq!(buffer, Buffer::with_lines(vec!["コン "]));
594    }
595
596    #[test]
597    fn buffer_with_lines() {
598        let buffer =
599            Buffer::with_lines(vec!["┌────────┐", "│コンピュ│", "│ーa 上で│", "└────────┘"]);
600        assert_eq!(buffer.area.x, 0);
601        assert_eq!(buffer.area.y, 0);
602        assert_eq!(buffer.area.width, 10);
603        assert_eq!(buffer.area.height, 4);
604    }
605
606    #[test]
607    fn buffer_diffing_empty_empty() {
608        let area = Rect::new(0, 0, 40, 40);
609        let prev = Buffer::empty(area);
610        let next = Buffer::empty(area);
611        let diff = prev.diff(&next);
612        assert_eq!(diff, vec![]);
613    }
614
615    #[test]
616    fn buffer_diffing_empty_filled() {
617        let area = Rect::new(0, 0, 40, 40);
618        let prev = Buffer::empty(area);
619        let next = Buffer::filled(area, Cell::default().set_symbol("a"));
620        let diff = prev.diff(&next);
621        assert_eq!(diff.len(), 40 * 40);
622    }
623
624    #[test]
625    fn buffer_diffing_filled_filled() {
626        let area = Rect::new(0, 0, 40, 40);
627        let prev = Buffer::filled(area, Cell::default().set_symbol("a"));
628        let next = Buffer::filled(area, Cell::default().set_symbol("a"));
629        let diff = prev.diff(&next);
630        assert_eq!(diff, vec![]);
631    }
632
633    #[test]
634    fn buffer_diffing_single_width() {
635        let prev = Buffer::with_lines(vec![
636            "          ",
637            "┌Title─┐  ",
638            "│      │  ",
639            "│      │  ",
640            "└──────┘  ",
641        ]);
642        let next = Buffer::with_lines(vec![
643            "          ",
644            "┌TITLE─┐  ",
645            "│      │  ",
646            "│      │  ",
647            "└──────┘  ",
648        ]);
649        let diff = prev.diff(&next);
650        assert_eq!(
651            diff,
652            vec![
653                (2, 1, &cell("I")),
654                (3, 1, &cell("T")),
655                (4, 1, &cell("L")),
656                (5, 1, &cell("E")),
657            ]
658        );
659    }
660
661    #[test]
662    #[rustfmt::skip]
663    fn buffer_diffing_multi_width() {
664        let prev = Buffer::with_lines(vec![
665            "┌Title─┐  ",
666            "└──────┘  ",
667        ]);
668        let next = Buffer::with_lines(vec![
669            "┌称号──┐  ",
670            "└──────┘  ",
671        ]);
672        let diff = prev.diff(&next);
673        assert_eq!(
674            diff,
675            vec![
676                (1, 0, &cell("称")),
677                // Skipped "i"
678                (3, 0, &cell("号")),
679                // Skipped "l"
680                (5, 0, &cell("─")),
681            ]
682        );
683    }
684
685    #[test]
686    fn buffer_diffing_multi_width_offset() {
687        let prev = Buffer::with_lines(vec!["┌称号──┐"]);
688        let next = Buffer::with_lines(vec!["┌─称号─┐"]);
689
690        let diff = prev.diff(&next);
691        assert_eq!(
692            diff,
693            vec![(1, 0, &cell("─")), (2, 0, &cell("称")), (4, 0, &cell("号")),]
694        );
695    }
696
697    #[test]
698    fn buffer_merge() {
699        let mut one = Buffer::filled(
700            Rect {
701                x: 0,
702                y: 0,
703                width: 2,
704                height: 2,
705            },
706            Cell::default().set_symbol("1"),
707        );
708        let two = Buffer::filled(
709            Rect {
710                x: 0,
711                y: 2,
712                width: 2,
713                height: 2,
714            },
715            Cell::default().set_symbol("2"),
716        );
717        one.merge(&two);
718        assert_eq!(one, Buffer::with_lines(vec!["11", "11", "22", "22"]));
719    }
720
721    #[test]
722    fn buffer_merge2() {
723        let mut one = Buffer::filled(
724            Rect {
725                x: 2,
726                y: 2,
727                width: 2,
728                height: 2,
729            },
730            Cell::default().set_symbol("1"),
731        );
732        let two = Buffer::filled(
733            Rect {
734                x: 0,
735                y: 0,
736                width: 2,
737                height: 2,
738            },
739            Cell::default().set_symbol("2"),
740        );
741        one.merge(&two);
742        assert_eq!(
743            one,
744            Buffer::with_lines(vec!["22  ", "22  ", "  11", "  11"])
745        );
746    }
747
748    #[test]
749    fn buffer_merge3() {
750        let mut one = Buffer::filled(
751            Rect {
752                x: 3,
753                y: 3,
754                width: 2,
755                height: 2,
756            },
757            Cell::default().set_symbol("1"),
758        );
759        let two = Buffer::filled(
760            Rect {
761                x: 1,
762                y: 1,
763                width: 3,
764                height: 4,
765            },
766            Cell::default().set_symbol("2"),
767        );
768        one.merge(&two);
769        let mut merged = Buffer::with_lines(vec!["222 ", "222 ", "2221", "2221"]);
770        merged.area = Rect {
771            x: 1,
772            y: 1,
773            width: 4,
774            height: 4,
775        };
776        assert_eq!(one, merged);
777    }
778}