Skip to main content

ratatui_core/buffer/
buffer.rs

1use alloc::vec;
2use alloc::vec::Vec;
3use core::ops::{Index, IndexMut};
4use core::{cmp, fmt};
5
6use unicode_segmentation::UnicodeSegmentation;
7
8use crate::buffer::{BufferDiff, Cell, CellWidth};
9use crate::layout::{Position, Rect};
10use crate::style::Style;
11use crate::text::{Line, Span};
12
13/// A buffer that maps to the desired content of the terminal after the draw call
14///
15/// No widget in the library interacts directly with the terminal. Instead each of them is required
16/// to draw their state to an intermediate buffer. It is basically a grid where each cell contains
17/// a grapheme, a foreground color and a background color. This grid will then be used to output
18/// the appropriate escape sequences and characters to draw the UI as the user has defined it.
19///
20/// # Examples:
21///
22/// ```
23/// use ratatui_core::buffer::{Buffer, Cell};
24/// use ratatui_core::layout::{Position, Rect};
25/// use ratatui_core::style::{Color, Style};
26///
27/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
28/// let mut buf = Buffer::empty(Rect {
29///     x: 0,
30///     y: 0,
31///     width: 10,
32///     height: 5,
33/// });
34///
35/// // indexing using Position
36/// buf[Position { x: 0, y: 0 }].set_symbol("A");
37/// assert_eq!(buf[Position { x: 0, y: 0 }].symbol(), "A");
38///
39/// // indexing using (x, y) tuple (which is converted to Position)
40/// buf[(0, 1)].set_symbol("B");
41/// assert_eq!(buf[(0, 1)].symbol(), "B");
42///
43/// // getting an Option instead of panicking if the position is outside the buffer
44/// let cell = buf
45///     .cell_mut(Position { x: 0, y: 2 })
46///     .ok_or("cell not found")?;
47/// cell.set_symbol("C");
48///
49/// let cell = buf.cell(Position { x: 0, y: 2 }).ok_or("cell not found")?;
50/// assert_eq!(cell.symbol(), "C");
51///
52/// buf.set_string(
53///     3,
54///     0,
55///     "string",
56///     Style::default().fg(Color::Red).bg(Color::White),
57/// );
58/// let cell = &buf[(5, 0)]; // cannot move out of buf, so we borrow it
59/// assert_eq!(cell.symbol(), "r");
60/// assert_eq!(cell.fg, Color::Red);
61/// assert_eq!(cell.bg, Color::White);
62/// # Ok(())
63/// # }
64/// ```
65#[derive(Default, Clone, Eq, PartialEq, Hash)]
66#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
67pub struct Buffer {
68    /// The area represented by this buffer
69    pub area: Rect,
70    /// The content of the buffer. The length of this Vec should always be equal to area.width *
71    /// area.height
72    pub content: Vec<Cell>,
73}
74
75impl Buffer {
76    /// Returns a Buffer with all cells set to the default one
77    #[must_use]
78    pub fn empty(area: Rect) -> Self {
79        Self::filled(area, Cell::EMPTY)
80    }
81
82    /// Returns a Buffer with all cells initialized with the attributes of the given Cell
83    #[must_use]
84    pub fn filled(area: Rect, cell: Cell) -> Self {
85        let size = area.area() as usize;
86        let content = vec![cell; size];
87        Self { area, content }
88    }
89
90    /// Returns a Buffer containing the given lines
91    #[must_use]
92    pub fn with_lines<'a, Iter>(lines: Iter) -> Self
93    where
94        Iter: IntoIterator,
95        Iter::Item: Into<Line<'a>>,
96    {
97        let lines = lines.into_iter().map(Into::into).collect::<Vec<_>>();
98        let height = lines.len() as u16;
99        let width = lines.iter().map(Line::width).max().unwrap_or_default() as u16;
100        let mut buffer = Self::empty(Rect::new(0, 0, width, height));
101        for (y, line) in lines.iter().enumerate() {
102            buffer.set_line(0, y as u16, line, width);
103        }
104        buffer
105    }
106
107    /// Returns the content of the buffer as a slice
108    pub fn content(&self) -> &[Cell] {
109        &self.content
110    }
111
112    /// Returns the area covered by this buffer
113    pub const fn area(&self) -> &Rect {
114        &self.area
115    }
116
117    /// Returns a reference to the [`Cell`] at the given coordinates
118    ///
119    /// Callers should use [`Buffer[]`](Self::index) or [`Buffer::cell`] instead of this method.
120    ///
121    /// Note: idiomatically methods named `get` usually return `Option<&T>`, but this method panics
122    /// instead. This is kept for backwards compatibility. See [`cell`](Self::cell) for a safe
123    /// alternative.
124    ///
125    /// # Panics
126    ///
127    /// Panics if the index is out of bounds.
128    #[track_caller]
129    #[deprecated = "use `Buffer[(x, y)]` instead. To avoid panicking, use `Buffer::cell((x, y))`. Both methods take `impl Into<Position>`."]
130    #[must_use]
131    pub fn get(&self, x: u16, y: u16) -> &Cell {
132        let i = self.index_of(x, y);
133        &self.content[i]
134    }
135
136    /// Returns a mutable reference to the [`Cell`] at the given coordinates.
137    ///
138    /// Callers should use [`Buffer[]`](Self::index_mut) or [`Buffer::cell_mut`] instead of this
139    /// method.
140    ///
141    /// Note: idiomatically methods named `get_mut` usually return `Option<&mut T>`, but this method
142    /// panics instead. This is kept for backwards compatibility. See [`cell_mut`](Self::cell_mut)
143    /// for a safe alternative.
144    ///
145    /// # Panics
146    ///
147    /// Panics if the position is outside the `Buffer`'s area.
148    #[track_caller]
149    #[deprecated = "use `Buffer[(x, y)]` instead. To avoid panicking, use `Buffer::cell_mut((x, y))`. Both methods take `impl Into<Position>`."]
150    #[must_use]
151    pub fn get_mut(&mut self, x: u16, y: u16) -> &mut Cell {
152        let i = self.index_of(x, y);
153        &mut self.content[i]
154    }
155
156    /// Returns a reference to the [`Cell`] at the given position or [`None`] if the position is
157    /// outside the `Buffer`'s area.
158    ///
159    /// This method accepts any value that can be converted to [`Position`] (e.g. `(x, y)` or
160    /// `Position::new(x, y)`).
161    ///
162    /// For a method that panics when the position is outside the buffer instead of returning
163    /// `None`, use [`Buffer[]`](Self::index).
164    ///
165    /// # Examples
166    ///
167    /// ```rust
168    /// use ratatui_core::buffer::{Buffer, Cell};
169    /// use ratatui_core::layout::{Position, Rect};
170    ///
171    /// let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 10));
172    ///
173    /// assert_eq!(buffer.cell(Position::new(0, 0)), Some(&Cell::default()));
174    /// assert_eq!(buffer.cell(Position::new(10, 10)), None);
175    /// assert_eq!(buffer.cell((0, 0)), Some(&Cell::default()));
176    /// assert_eq!(buffer.cell((10, 10)), None);
177    /// ```
178    #[must_use]
179    pub fn cell<P: Into<Position>>(&self, position: P) -> Option<&Cell> {
180        let position = position.into();
181        let index = self.index_of_opt(position)?;
182        self.content.get(index)
183    }
184
185    /// Returns a mutable reference to the [`Cell`] at the given position or [`None`] if the
186    /// position is outside the `Buffer`'s area.
187    ///
188    /// This method accepts any value that can be converted to [`Position`] (e.g. `(x, y)` or
189    /// `Position::new(x, y)`).
190    ///
191    /// For a method that panics when the position is outside the buffer instead of returning
192    /// `None`, use [`Buffer[]`](Self::index_mut).
193    ///
194    /// # Examples
195    ///
196    /// ```rust
197    /// use ratatui_core::buffer::{Buffer, Cell};
198    /// use ratatui_core::layout::{Position, Rect};
199    /// use ratatui_core::style::{Color, Style};
200    /// let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 10));
201    ///
202    /// if let Some(cell) = buffer.cell_mut(Position::new(0, 0)) {
203    ///     cell.set_symbol("A");
204    /// }
205    /// if let Some(cell) = buffer.cell_mut((0, 0)) {
206    ///     cell.set_style(Style::default().fg(Color::Red));
207    /// }
208    /// ```
209    #[must_use]
210    pub fn cell_mut<P: Into<Position>>(&mut self, position: P) -> Option<&mut Cell> {
211        let position = position.into();
212        let index = self.index_of_opt(position)?;
213        self.content.get_mut(index)
214    }
215
216    /// Returns the index in the `Vec<Cell>` for the given global (x, y) coordinates.
217    ///
218    /// Global coordinates are offset by the Buffer's area offset (`x`/`y`).
219    ///
220    /// Usage discouraged, as it exposes `self.content` as a linearly indexable array, which limits
221    /// potential future abstractions. See <https://github.com/ratatui/ratatui/issues/1122>.
222    ///
223    /// # Examples
224    ///
225    /// ```
226    /// use ratatui_core::buffer::Buffer;
227    /// use ratatui_core::layout::Rect;
228    ///
229    /// let buffer = Buffer::empty(Rect::new(200, 100, 10, 10));
230    /// // Global coordinates to the top corner of this buffer's area
231    /// assert_eq!(buffer.index_of(200, 100), 0);
232    /// ```
233    ///
234    /// # Panics
235    ///
236    /// Panics when given an coordinate that is outside of this Buffer's area.
237    ///
238    /// ```should_panic
239    /// use ratatui_core::buffer::Buffer;
240    /// use ratatui_core::layout::Rect;
241    ///
242    /// let buffer = Buffer::empty(Rect::new(200, 100, 10, 10));
243    /// // Top coordinate is outside of the buffer in global coordinate space, as the Buffer's area
244    /// // starts at (200, 100).
245    /// buffer.index_of(0, 0); // Panics
246    /// ```
247    #[track_caller]
248    #[must_use]
249    pub fn index_of(&self, x: u16, y: u16) -> usize {
250        self.index_of_opt(Position { x, y }).unwrap_or_else(|| {
251            panic!(
252                "index outside of buffer: the area is {area:?} but index is ({x}, {y})",
253                area = self.area,
254            )
255        })
256    }
257
258    /// Returns the index in the `Vec<Cell>` for the given global (x, y) coordinates.
259    ///
260    /// Returns `None` if the given coordinates are outside of the Buffer's area.
261    ///
262    /// Note that this is private because of <https://github.com/ratatui/ratatui/issues/1122>
263    #[must_use]
264    const fn index_of_opt(&self, position: Position) -> Option<usize> {
265        let area = self.area;
266        if !area.contains(position) {
267            return None;
268        }
269        // remove offset
270        let y = (position.y - self.area.y) as usize;
271        let x = (position.x - self.area.x) as usize;
272        let width = self.area.width as usize;
273        Some(y * width + x)
274    }
275
276    /// Returns the (global) coordinates of a cell given its index.
277    ///
278    /// Global coordinates are offset by the Buffer's area offset (`x`/`y`).
279    ///
280    /// Usage discouraged, as it exposes `self.content` as a linearly indexable array, which limits
281    /// potential future abstractions. See <https://github.com/ratatui/ratatui/issues/1122>.
282    ///
283    /// # Examples
284    ///
285    /// ```
286    /// use ratatui_core::buffer::Buffer;
287    /// use ratatui_core::layout::Rect;
288    ///
289    /// let rect = Rect::new(200, 100, 10, 10);
290    /// let buffer = Buffer::empty(rect);
291    /// assert_eq!(buffer.pos_of(0), (200, 100));
292    /// assert_eq!(buffer.pos_of(14), (204, 101));
293    /// ```
294    ///
295    /// # Panics
296    ///
297    /// Panics when given an index that is outside the Buffer's content.
298    ///
299    /// ```should_panic
300    /// use ratatui_core::buffer::Buffer;
301    /// use ratatui_core::layout::Rect;
302    ///
303    /// let rect = Rect::new(0, 0, 10, 10); // 100 cells in total
304    /// let buffer = Buffer::empty(rect);
305    /// // Index 100 is the 101th cell, which lies outside of the area of this Buffer.
306    /// buffer.pos_of(100); // Panics
307    /// ```
308    #[must_use]
309    pub fn pos_of(&self, index: usize) -> (u16, u16) {
310        debug_assert!(
311            index < self.content.len(),
312            "Trying to get the coords of a cell outside the buffer: i={index} len={}",
313            self.content.len()
314        );
315        let x = index % self.area.width as usize + self.area.x as usize;
316        let y = index / self.area.width as usize + self.area.y as usize;
317        (
318            u16::try_from(x).expect("x overflow. This should never happen as area.width is u16"),
319            u16::try_from(y).expect("y overflow. This should never happen as area.height is u16"),
320        )
321    }
322
323    /// Print a string, starting at the position (x, y)
324    pub fn set_string<T, S>(&mut self, x: u16, y: u16, string: T, style: S)
325    where
326        T: AsRef<str>,
327        S: Into<Style>,
328    {
329        self.set_stringn(x, y, string, usize::MAX, style);
330    }
331
332    /// Print at most the first n characters of a string if enough space is available
333    /// until the end of the line. Skips zero-width graphemes and control characters.
334    ///
335    /// Use [`Buffer::set_string`] when the maximum amount of characters can be printed.
336    pub fn set_stringn<T, S>(
337        &mut self,
338        mut x: u16,
339        y: u16,
340        string: T,
341        max_width: usize,
342        style: S,
343    ) -> (u16, u16)
344    where
345        T: AsRef<str>,
346        S: Into<Style>,
347    {
348        let max_width = max_width.try_into().unwrap_or(u16::MAX);
349        let mut remaining_width = self.area.right().saturating_sub(x).min(max_width);
350        let graphemes = UnicodeSegmentation::graphemes(string.as_ref(), true)
351            .filter(|symbol| !symbol.contains(char::is_control))
352            .map(|symbol| (symbol, symbol.cell_width()))
353            .filter(|(_symbol, width)| *width > 0)
354            .map_while(|(symbol, width)| {
355                remaining_width = remaining_width.checked_sub(width)?;
356                Some((symbol, width))
357            });
358        let style = style.into();
359        for (symbol, width) in graphemes {
360            self[(x, y)].set_symbol(symbol).set_style(style);
361            let next_symbol = x + width;
362            x += 1;
363            // Reset following cells if multi-width (they would be hidden by the grapheme),
364            while x < next_symbol {
365                self[(x, y)].reset();
366                x += 1;
367            }
368        }
369        (x, y)
370    }
371
372    /// Print a line, starting at the position (x, y)
373    pub fn set_line(&mut self, x: u16, y: u16, line: &Line<'_>, max_width: u16) -> (u16, u16) {
374        let mut remaining_width = max_width;
375        let mut x = x;
376        for span in line {
377            if remaining_width == 0 {
378                break;
379            }
380            let pos = self.set_stringn(
381                x,
382                y,
383                span.content.as_ref(),
384                remaining_width as usize,
385                line.style.patch(span.style),
386            );
387            let w = pos.0.saturating_sub(x);
388            x = pos.0;
389            remaining_width = remaining_width.saturating_sub(w);
390        }
391        (x, y)
392    }
393
394    /// Print a span, starting at the position (x, y)
395    pub fn set_span(&mut self, x: u16, y: u16, span: &Span<'_>, max_width: u16) -> (u16, u16) {
396        self.set_stringn(x, y, &span.content, max_width as usize, span.style)
397    }
398
399    /// Set the style of all cells in the given area.
400    ///
401    /// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
402    /// your own type that implements [`Into<Style>`]).
403    ///
404    /// [`Color`]: crate::style::Color
405    pub fn set_style<S: Into<Style>>(&mut self, area: Rect, style: S) {
406        let style = style.into();
407        let area = self.area.intersection(area);
408        for y in area.top()..area.bottom() {
409            for x in area.left()..area.right() {
410                self[(x, y)].set_style(style);
411            }
412        }
413    }
414
415    /// Resize the buffer so that the mapped area matches the given area and that the buffer
416    /// length is equal to area.width * area.height
417    pub fn resize(&mut self, area: Rect) {
418        let length = area.area() as usize;
419        if self.content.len() > length {
420            self.content.truncate(length);
421        } else {
422            self.content.resize(length, Cell::EMPTY);
423        }
424        self.area = area;
425    }
426
427    /// Reset all cells in the buffer
428    pub fn reset(&mut self) {
429        for cell in &mut self.content {
430            cell.reset();
431        }
432    }
433
434    /// Merge an other buffer into this one
435    pub fn merge(&mut self, other: &Self) {
436        let area = self.area.union(other.area);
437        self.content.resize(area.area() as usize, Cell::EMPTY);
438
439        // Move original content to the appropriate space
440        let size = self.area.area() as usize;
441        for i in (0..size).rev() {
442            let (x, y) = self.pos_of(i);
443            // New index in content
444            let k = ((y - area.y) * area.width + x - area.x) as usize;
445            if i != k {
446                self.content[k] = self.content[i].clone();
447                self.content[i].reset();
448            }
449        }
450
451        // Push content of the other buffer into this one (may erase previous
452        // data)
453        let size = other.area.area() as usize;
454        for i in 0..size {
455            let (x, y) = other.pos_of(i);
456            // New index in content
457            let k = ((y - area.y) * area.width + x - area.x) as usize;
458            self.content[k] = other.content[i].clone();
459        }
460        self.area = area;
461    }
462
463    /// Collects the diff between `self` and `other` into a `Vec`.
464    ///
465    /// This is a convenience wrapper around [`diff_iter`](Self::diff_iter) that collects the
466    /// results. Prefer `diff_iter` to avoid the intermediate allocation.
467    ///
468    /// # Panics
469    ///
470    /// Panics if the two buffers have different `x`, `y`, or `width` values.
471    pub fn diff<'a>(&self, other: &'a Self) -> Vec<(u16, u16, &'a Cell)> {
472        self.diff_iter(other).collect()
473    }
474
475    /// Builds a minimal sequence of coordinates and Cells necessary to update the UI from
476    /// self to other.
477    ///
478    /// We're assuming that buffers are well-formed, that is no double-width cell is followed by
479    /// a non-blank cell.
480    ///
481    /// # Multi-width characters handling:
482    ///
483    /// ```text
484    /// (Index:) `01`
485    /// Prev:    `コ`
486    /// Next:    `aa`
487    /// Updates: `0: a, 1: a'
488    /// ```
489    ///
490    /// ```text
491    /// (Index:) `01`
492    /// Prev:    `a `
493    /// Next:    `コ`
494    /// Updates: `0: コ` (double width symbol at index 0 - skip index 1)
495    /// ```
496    ///
497    /// ```text
498    /// (Index:) `012`
499    /// Prev:    `aaa`
500    /// Next:    `aコ`
501    /// Updates: `0: a, 1: コ` (double width symbol at index 1 - skip index 2)
502    /// ```
503    /// # Panics
504    ///
505    /// Panics if the two buffers have different `x`, `y`, or `width` values.
506    pub fn diff_iter<'prev, 'next>(&'prev self, other: &'next Self) -> BufferDiff<'prev, 'next> {
507        BufferDiff::new(self, other)
508    }
509}
510
511impl<P: Into<Position>> Index<P> for Buffer {
512    type Output = Cell;
513
514    /// Returns a reference to the [`Cell`] at the given position.
515    ///
516    /// This method accepts any value that can be converted to [`Position`] (e.g. `(x, y)` or
517    /// `Position::new(x, y)`).
518    ///
519    /// # Panics
520    ///
521    /// May panic if the given position is outside the buffer's area. For a method that returns
522    /// `None` instead of panicking, use [`Buffer::cell`](Self::cell).
523    ///
524    /// # Examples
525    ///
526    /// ```
527    /// use ratatui_core::buffer::{Buffer, Cell};
528    /// use ratatui_core::layout::{Position, Rect};
529    ///
530    /// let buf = Buffer::empty(Rect::new(0, 0, 10, 10));
531    /// let cell = &buf[(0, 0)];
532    /// let cell = &buf[Position::new(0, 0)];
533    /// ```
534    fn index(&self, position: P) -> &Self::Output {
535        let position = position.into();
536        let index = self.index_of(position.x, position.y);
537        &self.content[index]
538    }
539}
540
541impl<P: Into<Position>> IndexMut<P> for Buffer {
542    /// Returns a mutable reference to the [`Cell`] at the given position.
543    ///
544    /// This method accepts any value that can be converted to [`Position`] (e.g. `(x, y)` or
545    /// `Position::new(x, y)`).
546    ///
547    /// # Panics
548    ///
549    /// May panic if the given position is outside the buffer's area. For a method that returns
550    /// `None` instead of panicking, use [`Buffer::cell_mut`](Self::cell_mut).
551    ///
552    /// # Examples
553    ///
554    /// ```
555    /// use ratatui_core::buffer::{Buffer, Cell};
556    /// use ratatui_core::layout::{Position, Rect};
557    ///
558    /// let mut buf = Buffer::empty(Rect::new(0, 0, 10, 10));
559    /// buf[(0, 0)].set_symbol("A");
560    /// buf[Position::new(0, 0)].set_symbol("B");
561    /// ```
562    fn index_mut(&mut self, position: P) -> &mut Self::Output {
563        let position = position.into();
564        let index = self.index_of(position.x, position.y);
565        &mut self.content[index]
566    }
567}
568
569impl fmt::Debug for Buffer {
570    /// Writes a debug representation of the buffer to the given formatter.
571    ///
572    /// The format is like a pretty printed struct, with the following fields:
573    /// * `area`: displayed as `Rect { x: 1, y: 2, width: 3, height: 4 }`
574    /// * `content`: displayed as a list of strings representing the content of the buffer
575    /// * `styles`: displayed as a list of: `{ x: 1, y: 2, fg: Color::Red, bg: Color::Blue,
576    ///   modifier: Modifier::BOLD }` only showing a value when there is a change in style.
577    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
578        f.write_fmt(format_args!("Buffer {{\n    area: {:?}", self.area))?;
579
580        if self.area.is_empty() {
581            return f.write_str("\n}");
582        }
583
584        f.write_str(",\n    content: [\n")?;
585        let mut last_style = None;
586        let mut styles = vec![];
587        for (y, line) in self.content.chunks(self.area.width as usize).enumerate() {
588            let mut overwritten = vec![];
589            let mut skip: u16 = 0;
590            f.write_str("        \"")?;
591            for (x, c) in line.iter().enumerate() {
592                let sym = c.symbol();
593                if skip == 0 {
594                    f.write_str(sym)?;
595                } else {
596                    overwritten.push((x, sym));
597                }
598                skip = cmp::max(skip, c.cell_width()).saturating_sub(1);
599                #[cfg(feature = "underline-color")]
600                {
601                    let style = (c.fg, c.bg, c.underline_color, c.modifier);
602                    if last_style != Some(style) {
603                        last_style = Some(style);
604                        styles.push((x, y, c.fg, c.bg, c.underline_color, c.modifier));
605                    }
606                }
607                #[cfg(not(feature = "underline-color"))]
608                {
609                    let style = (c.fg, c.bg, c.modifier);
610                    if last_style != Some(style) {
611                        last_style = Some(style);
612                        styles.push((x, y, c.fg, c.bg, c.modifier));
613                    }
614                }
615            }
616            f.write_str("\",")?;
617            if !overwritten.is_empty() {
618                f.write_fmt(format_args!(
619                    " // hidden by multi-width symbols: {overwritten:?}"
620                ))?;
621            }
622            f.write_str("\n")?;
623        }
624        f.write_str("    ],\n    styles: [\n")?;
625        for s in styles {
626            #[cfg(feature = "underline-color")]
627            f.write_fmt(format_args!(
628                "        x: {}, y: {}, fg: {:?}, bg: {:?}, underline: {:?}, modifier: {:?},\n",
629                s.0, s.1, s.2, s.3, s.4, s.5
630            ))?;
631            #[cfg(not(feature = "underline-color"))]
632            f.write_fmt(format_args!(
633                "        x: {}, y: {}, fg: {:?}, bg: {:?}, modifier: {:?},\n",
634                s.0, s.1, s.2, s.3, s.4
635            ))?;
636        }
637        f.write_str("    ]\n}")?;
638        Ok(())
639    }
640}
641
642#[cfg(test)]
643mod tests {
644    #[cfg(not(feature = "std"))]
645    extern crate std;
646
647    use alloc::format;
648    use alloc::string::{String, ToString};
649    use core::iter;
650    use core::num::NonZeroU16;
651    use std::{dbg, println};
652
653    use itertools::Itertools;
654    use rstest::{fixture, rstest};
655
656    use super::*;
657    use crate::buffer::CellDiffOption;
658    use crate::style::{Color, Modifier, Stylize};
659
660    #[test]
661    fn debug_empty_buffer() {
662        let buffer = Buffer::empty(Rect::ZERO);
663        let result = format!("{buffer:?}");
664        println!("{result}");
665        let expected = "Buffer {\n    area: Rect { x: 0, y: 0, width: 0, height: 0 }\n}";
666        assert_eq!(result, expected);
667    }
668
669    #[cfg(feature = "underline-color")]
670    #[test]
671    fn debug_grapheme_override() {
672        let buffer = Buffer::with_lines(["a🦀b"]);
673        let result = format!("{buffer:?}");
674        println!("{result}");
675        let expected = indoc::indoc!(
676            r#"
677            Buffer {
678                area: Rect { x: 0, y: 0, width: 4, height: 1 },
679                content: [
680                    "a🦀b", // hidden by multi-width symbols: [(2, " ")]
681                ],
682                styles: [
683                    x: 0, y: 0, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
684                ]
685            }"#
686        );
687        assert_eq!(result, expected);
688    }
689
690    #[test]
691    fn debug_some_example() {
692        let mut buffer = Buffer::empty(Rect::new(0, 0, 12, 2));
693        buffer.set_string(0, 0, "Hello World!", Style::default());
694        buffer.set_string(
695            0,
696            1,
697            "G'day World!",
698            Style::default()
699                .fg(Color::Green)
700                .bg(Color::Yellow)
701                .add_modifier(Modifier::BOLD),
702        );
703        let result = format!("{buffer:?}");
704        println!("{result}");
705        #[cfg(feature = "underline-color")]
706        let expected = indoc::indoc!(
707            r#"
708            Buffer {
709                area: Rect { x: 0, y: 0, width: 12, height: 2 },
710                content: [
711                    "Hello World!",
712                    "G'day World!",
713                ],
714                styles: [
715                    x: 0, y: 0, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
716                    x: 0, y: 1, fg: Green, bg: Yellow, underline: Reset, modifier: BOLD,
717                ]
718            }"#
719        );
720        #[cfg(not(feature = "underline-color"))]
721        let expected = indoc::indoc!(
722            r#"
723            Buffer {
724                area: Rect { x: 0, y: 0, width: 12, height: 2 },
725                content: [
726                    "Hello World!",
727                    "G'day World!",
728                ],
729                styles: [
730                    x: 0, y: 0, fg: Reset, bg: Reset, modifier: NONE,
731                    x: 0, y: 1, fg: Green, bg: Yellow, modifier: BOLD,
732                ]
733            }"#
734        );
735
736        assert_eq!(result, expected);
737    }
738
739    #[test]
740    fn it_translates_to_and_from_coordinates() {
741        let rect = Rect::new(200, 100, 50, 80);
742        let buf = Buffer::empty(rect);
743
744        // First cell is at the upper left corner.
745        assert_eq!(buf.pos_of(0), (200, 100));
746        assert_eq!(buf.index_of(200, 100), 0);
747
748        // Last cell is in the lower right.
749        assert_eq!(buf.pos_of(buf.content.len() - 1), (249, 179));
750        assert_eq!(buf.index_of(249, 179), buf.content.len() - 1);
751    }
752
753    #[test]
754    #[should_panic(expected = "outside the buffer")]
755    fn pos_of_panics_on_out_of_bounds() {
756        let rect = Rect::new(0, 0, 10, 10);
757        let buf = Buffer::empty(rect);
758
759        // There are a total of 100 cells; zero-indexed means that 100 would be the 101st cell.
760        let _ = buf.pos_of(100);
761    }
762
763    #[rstest]
764    #[case::left(9, 10)]
765    #[case::top(10, 9)]
766    #[case::right(20, 10)]
767    #[case::bottom(10, 20)]
768    #[should_panic(
769        expected = "index outside of buffer: the area is Rect { x: 10, y: 10, width: 10, height: 10 } but index is"
770    )]
771    fn index_of_panics_on_out_of_bounds(#[case] x: u16, #[case] y: u16) {
772        let _ = Buffer::empty(Rect::new(10, 10, 10, 10)).index_of(x, y);
773    }
774
775    #[test]
776    fn test_cell() {
777        let buf = Buffer::with_lines(["Hello", "World"]);
778
779        let mut expected = Cell::default();
780        expected.set_symbol("H");
781
782        assert_eq!(buf.cell((0, 0)), Some(&expected));
783        assert_eq!(buf.cell((10, 10)), None);
784        assert_eq!(buf.cell(Position::new(0, 0)), Some(&expected));
785        assert_eq!(buf.cell(Position::new(10, 10)), None);
786    }
787
788    #[test]
789    fn test_cell_mut() {
790        let mut buf = Buffer::with_lines(["Hello", "World"]);
791
792        let mut expected = Cell::default();
793        expected.set_symbol("H");
794
795        assert_eq!(buf.cell_mut((0, 0)), Some(&mut expected));
796        assert_eq!(buf.cell_mut((10, 10)), None);
797        assert_eq!(buf.cell_mut(Position::new(0, 0)), Some(&mut expected));
798        assert_eq!(buf.cell_mut(Position::new(10, 10)), None);
799    }
800
801    #[test]
802    fn index() {
803        let buf = Buffer::with_lines(["Hello", "World"]);
804
805        let mut expected = Cell::default();
806        expected.set_symbol("H");
807
808        assert_eq!(buf[(0, 0)], expected);
809    }
810
811    #[rstest]
812    #[case::left(9, 10)]
813    #[case::top(10, 9)]
814    #[case::right(20, 10)]
815    #[case::bottom(10, 20)]
816    #[should_panic(
817        expected = "index outside of buffer: the area is Rect { x: 10, y: 10, width: 10, height: 10 } but index is"
818    )]
819    fn index_out_of_bounds_panics(#[case] x: u16, #[case] y: u16) {
820        let rect = Rect::new(10, 10, 10, 10);
821        let buf = Buffer::empty(rect);
822        let _ = buf[(x, y)];
823    }
824
825    #[test]
826    fn index_mut() {
827        let mut buf = Buffer::with_lines(["Cat", "Dog"]);
828        buf[(0, 0)].set_symbol("B");
829        buf[Position::new(0, 1)].set_symbol("L");
830        assert_eq!(buf, Buffer::with_lines(["Bat", "Log"]));
831    }
832
833    #[rstest]
834    #[case::left(9, 10)]
835    #[case::top(10, 9)]
836    #[case::right(20, 10)]
837    #[case::bottom(10, 20)]
838    #[should_panic(
839        expected = "index outside of buffer: the area is Rect { x: 10, y: 10, width: 10, height: 10 } but index is"
840    )]
841    fn index_mut_out_of_bounds_panics(#[case] x: u16, #[case] y: u16) {
842        let mut buf = Buffer::empty(Rect::new(10, 10, 10, 10));
843        buf[(x, y)].set_symbol("A");
844    }
845
846    #[test]
847    fn set_string() {
848        let area = Rect::new(0, 0, 5, 1);
849        let mut buffer = Buffer::empty(area);
850
851        // Zero-width
852        buffer.set_stringn(0, 0, "aaa", 0, Style::default());
853        assert_eq!(buffer, Buffer::with_lines(["     "]));
854
855        buffer.set_string(0, 0, "aaa", Style::default());
856        assert_eq!(buffer, Buffer::with_lines(["aaa  "]));
857
858        // Width limit:
859        buffer.set_stringn(0, 0, "bbbbbbbbbbbbbb", 4, Style::default());
860        assert_eq!(buffer, Buffer::with_lines(["bbbb "]));
861
862        buffer.set_string(0, 0, "12345", Style::default());
863        assert_eq!(buffer, Buffer::with_lines(["12345"]));
864
865        // Width truncation:
866        buffer.set_string(0, 0, "123456", Style::default());
867        assert_eq!(buffer, Buffer::with_lines(["12345"]));
868
869        // multi-line
870        buffer = Buffer::empty(Rect::new(0, 0, 5, 2));
871        buffer.set_string(0, 0, "12345", Style::default());
872        buffer.set_string(0, 1, "67890", Style::default());
873        assert_eq!(buffer, Buffer::with_lines(["12345", "67890"]));
874    }
875
876    #[test]
877    fn set_string_multi_width_overwrite() {
878        let area = Rect::new(0, 0, 5, 1);
879        let mut buffer = Buffer::empty(area);
880
881        // multi-width overwrite
882        buffer.set_string(0, 0, "aaaaa", Style::default());
883        buffer.set_string(0, 0, "称号", Style::default());
884        assert_eq!(buffer, Buffer::with_lines(["称号a"]));
885    }
886
887    #[test]
888    fn set_string_zero_width() {
889        assert_eq!("\u{200B}".cell_width(), 0);
890
891        let area = Rect::new(0, 0, 1, 1);
892        let mut buffer = Buffer::empty(area);
893
894        // Leading grapheme with zero width
895        let s = "\u{200B}a";
896        buffer.set_stringn(0, 0, s, 1, Style::default());
897        assert_eq!(buffer, Buffer::with_lines(["a"]));
898
899        // Trailing grapheme with zero with
900        let s = "a\u{200B}";
901        buffer.set_stringn(0, 0, s, 1, Style::default());
902        assert_eq!(buffer, Buffer::with_lines(["a"]));
903    }
904
905    #[test]
906    fn set_string_double_width() {
907        let area = Rect::new(0, 0, 5, 1);
908        let mut buffer = Buffer::empty(area);
909        buffer.set_string(0, 0, "コン", Style::default());
910        assert_eq!(buffer, Buffer::with_lines(["コン "]));
911
912        // Only 1 space left.
913        buffer.set_string(0, 0, "コンピ", Style::default());
914        assert_eq!(buffer, Buffer::with_lines(["コン "]));
915    }
916
917    #[test]
918    fn set_string_halfwidth_katakana_with_dakuten() {
919        let area = Rect::new(0, 0, 5, 1);
920
921        // Fullwidth katakana: 2 cells
922        let mut buffer = Buffer::empty(area);
923        buffer.set_string(0, 0, "ガ", Style::default());
924        let mut expected = Buffer::empty(area);
925        expected.set_string(0, 0, "ガ", Style::default());
926        assert_eq!(buffer, expected);
927
928        // Halfwidth katakana (no dakuten): 1 cell
929        let mut buffer = Buffer::empty(area);
930        buffer.set_string(0, 0, "カ", Style::default());
931        assert_eq!(buffer.content[0].symbol(), "カ");
932        assert_eq!(buffer.content[1].symbol(), " ");
933
934        // Halfwidth katakana + non-combining dakuten (U+FF9E): grapheme cluster takes 2 cells
935        let mut buffer = Buffer::empty(area);
936        buffer.set_string(0, 0, "ガ", Style::default());
937        // The whole grapheme cluster "ガ" is placed in cell[0], and cell[1] is reset (width=2)
938        assert_eq!(buffer.content[0].symbol(), "ガ");
939        assert_eq!(buffer.content[1].symbol(), " "); // reset cell
940        assert_eq!(buffer.content[2].symbol(), " ");
941
942        // Halfwidth katakana + non-combining handakuten (U+FF9F): grapheme cluster takes 2 cells
943        let mut buffer = Buffer::empty(area);
944        buffer.set_string(0, 0, "パ", Style::default());
945        assert_eq!(buffer.content[0].symbol(), "パ");
946        assert_eq!(buffer.content[1].symbol(), " "); // reset cell
947        assert_eq!(buffer.content[2].symbol(), " ");
948
949        // Multiple halfwidth katakana with dakuten: each cluster takes 2 cells (4 total)
950        let mut buffer = Buffer::empty(area);
951        buffer.set_string(0, 0, "ガギ", Style::default());
952        assert_eq!(buffer.content[0].symbol(), "ガ"); // first cluster: width 2
953        assert_eq!(buffer.content[1].symbol(), " "); // reset by first cluster
954        assert_eq!(buffer.content[2].symbol(), "ギ"); // second cluster: width 2
955        assert_eq!(buffer.content[3].symbol(), " "); // reset by second cluster
956        assert_eq!(buffer.content[4].symbol(), " ");
957
958        // Overflow: only first 2 grapheme clusters fit (4 cells out of 5)
959        let mut buffer = Buffer::empty(area);
960        buffer.set_string(0, 0, "ガギグ", Style::default());
961        assert_eq!(buffer.content[0].symbol(), "ガ");
962        assert_eq!(buffer.content[1].symbol(), " ");
963        assert_eq!(buffer.content[2].symbol(), "ギ");
964        assert_eq!(buffer.content[3].symbol(), " ");
965        assert_eq!(buffer.content[4].symbol(), " ");
966    }
967
968    #[test]
969    fn set_string_combining_vs_halfwidth_dakuten() {
970        let area = Rect::new(0, 0, 5, 1);
971
972        // Combining dakuten (U+3099): forms 1-cell grapheme cluster with width 1
973        let mut buffer1 = Buffer::empty(area);
974        let (x1, _) = buffer1.set_stringn(0, 0, "ガ", usize::MAX, Style::default());
975        // The combining dakuten merges with カ into a single cell (width 1)
976        assert_eq!(buffer1.content[0].symbol(), "ガ");
977        assert_eq!(buffer1.content[0].cell_width(), 1);
978        assert_eq!(x1, 1);
979
980        // Non-combining halfwidth dakuten (U+FF9E): grapheme cluster with width 2
981        let mut buffer2 = Buffer::empty(area);
982        let (x2, _) = buffer2.set_stringn(0, 0, "ガ", usize::MAX, Style::default());
983        // The grapheme cluster "ガ" is stored in cell[0], but takes 2 cells width
984        assert_eq!(buffer2.content[0].symbol(), "ガ");
985        assert_eq!(buffer2.content[0].cell_width(), 2);
986        assert_eq!(x2, 2);
987    }
988
989    #[fixture]
990    fn small_one_line_buffer() -> Buffer {
991        Buffer::empty(Rect::new(0, 0, 5, 1))
992    }
993
994    #[rstest]
995    #[case::empty("", "     ")]
996    #[case::one("1", "1    ")]
997    #[case::full("12345", "12345")]
998    #[case::overflow("123456", "12345")]
999    fn set_line_raw(
1000        mut small_one_line_buffer: Buffer,
1001        #[case] content: &str,
1002        #[case] expected: &str,
1003    ) {
1004        let line = Line::raw(content);
1005        small_one_line_buffer.set_line(0, 0, &line, 5);
1006
1007        // note: testing with empty / set_string here instead of with_lines because with_lines calls
1008        // set_line
1009        let mut expected_buffer = Buffer::empty(small_one_line_buffer.area);
1010        expected_buffer.set_string(0, 0, expected, Style::default());
1011        assert_eq!(small_one_line_buffer, expected_buffer);
1012    }
1013
1014    #[rstest]
1015    #[case::empty("", "     ")]
1016    #[case::one("1", "1    ")]
1017    #[case::full("12345", "12345")]
1018    #[case::overflow("123456", "12345")]
1019    fn set_line_styled(
1020        mut small_one_line_buffer: Buffer,
1021        #[case] content: &str,
1022        #[case] expected: &str,
1023    ) {
1024        let color = Color::Blue;
1025        let line = Line::styled(content, color);
1026        small_one_line_buffer.set_line(0, 0, &line, 5);
1027
1028        // note: manually testing the contents here as the Buffer::with_lines calls set_line
1029        let actual_contents = small_one_line_buffer
1030            .content
1031            .iter()
1032            .map(Cell::symbol)
1033            .join("");
1034        let actual_styles = small_one_line_buffer
1035            .content
1036            .iter()
1037            .map(|c| c.fg)
1038            .collect_vec();
1039
1040        // set_line only sets the style for non-empty cells (unlike Line::render which sets the
1041        // style for all cells)
1042        let expected_styles = iter::repeat_n(color, content.len().min(5))
1043            .chain(iter::repeat_n(
1044                Color::default(),
1045                5_usize.saturating_sub(content.len()),
1046            ))
1047            .collect_vec();
1048        assert_eq!(actual_contents, expected);
1049        assert_eq!(actual_styles, expected_styles);
1050    }
1051
1052    #[test]
1053    fn set_style() {
1054        let mut buffer = Buffer::with_lines(["aaaaa", "bbbbb", "ccccc"]);
1055        buffer.set_style(Rect::new(0, 1, 5, 1), Style::new().red());
1056        #[rustfmt::skip]
1057        let expected = Buffer::with_lines([
1058            "aaaaa".into(),
1059            "bbbbb".red(),
1060            "ccccc".into(),
1061        ]);
1062        assert_eq!(buffer, expected);
1063    }
1064
1065    #[test]
1066    fn set_style_does_not_panic_when_out_of_area() {
1067        let mut buffer = Buffer::with_lines(["aaaaa", "bbbbb", "ccccc"]);
1068        buffer.set_style(Rect::new(0, 1, 10, 3), Style::new().red());
1069        #[rustfmt::skip]
1070        let expected = Buffer::with_lines([
1071            "aaaaa".into(),
1072            "bbbbb".red(),
1073            "ccccc".red(),
1074        ]);
1075        assert_eq!(buffer, expected);
1076    }
1077
1078    #[test]
1079    fn with_lines() {
1080        #[rustfmt::skip]
1081        let buffer = Buffer::with_lines([
1082            "┌────────┐",
1083            "│コンピュ│",
1084            "│ーa 上で│",
1085            "└────────┘",
1086        ]);
1087        assert_eq!(buffer.area.x, 0);
1088        assert_eq!(buffer.area.y, 0);
1089        assert_eq!(buffer.area.width, 10);
1090        assert_eq!(buffer.area.height, 4);
1091    }
1092
1093    #[test]
1094    fn diff_empty_empty() {
1095        let area = Rect::new(0, 0, 40, 40);
1096        let prev = Buffer::empty(area);
1097        let next = Buffer::empty(area);
1098        let diff = prev.diff(&next);
1099        assert_eq!(diff, []);
1100    }
1101
1102    #[test]
1103    fn diff_empty_filled() {
1104        let area = Rect::new(0, 0, 40, 40);
1105        let prev = Buffer::empty(area);
1106        let next = Buffer::filled(area, Cell::new("a"));
1107        let diff = prev.diff_iter(&next);
1108        assert_eq!(diff.count(), 40 * 40);
1109    }
1110
1111    #[test]
1112    fn diff_filled_filled() {
1113        let area = Rect::new(0, 0, 40, 40);
1114        let prev = Buffer::filled(area, Cell::new("a"));
1115        let next = Buffer::filled(area, Cell::new("a"));
1116        let diff = prev.diff(&next);
1117        assert_eq!(diff, []);
1118    }
1119
1120    #[test]
1121    fn diff_single_width() {
1122        let prev = Buffer::with_lines([
1123            "          ",
1124            "┌Title─┐  ",
1125            "│      │  ",
1126            "│      │  ",
1127            "└──────┘  ",
1128        ]);
1129        let next = Buffer::with_lines([
1130            "          ",
1131            "┌TITLE─┐  ",
1132            "│      │  ",
1133            "│      │  ",
1134            "└──────┘  ",
1135        ]);
1136        let diff = prev.diff(&next);
1137        assert_eq!(
1138            diff,
1139            [
1140                (2, 1, &Cell::new("I")),
1141                (3, 1, &Cell::new("T")),
1142                (4, 1, &Cell::new("L")),
1143                (5, 1, &Cell::new("E")),
1144            ]
1145        );
1146    }
1147
1148    #[test]
1149    fn diff_multi_width() {
1150        #[rustfmt::skip]
1151        let prev = Buffer::with_lines([
1152            "┌Title─┐  ",
1153            "└──────┘  ",
1154        ]);
1155        #[rustfmt::skip]
1156        let next = Buffer::with_lines([
1157            "┌称号──┐  ",
1158            "└──────┘  ",
1159        ]);
1160        let diff = prev.diff(&next);
1161        assert_eq!(
1162            diff,
1163            [
1164                (1, 0, &Cell::new("称")),
1165                // Skipped "i"
1166                (3, 0, &Cell::new("号")),
1167                // Skipped "l"
1168                (5, 0, &Cell::new("─")),
1169            ]
1170        );
1171    }
1172
1173    #[test]
1174    fn diff_multi_width_offset() {
1175        let prev = Buffer::with_lines(["┌称号──┐"]);
1176        let next = Buffer::with_lines(["┌─称号─┐"]);
1177
1178        let diff = prev.diff(&next);
1179        assert_eq!(
1180            diff,
1181            [
1182                (1, 0, &Cell::new("─")),
1183                (2, 0, &Cell::new("称")),
1184                (4, 0, &Cell::new("号")),
1185            ]
1186        );
1187    }
1188
1189    #[test]
1190    fn merge_diff_idempotent() {
1191        let mut prev = Buffer::with_lines(["123"]);
1192        let next = Buffer::with_lines(["456"]);
1193        prev.merge(&next);
1194
1195        let diff = prev.diff(&next);
1196        assert_eq!(diff, []);
1197    }
1198
1199    #[test]
1200    fn merge_diff_forcedwidth() {
1201        let mut prev = Buffer::with_lines(["123"]);
1202        let mut next = Buffer::empty(Rect::new(0, 0, 3, 1));
1203        next.cell_mut((0, 0))
1204            .unwrap()
1205            .set_symbol("456")
1206            .set_diff_option(CellDiffOption::ForcedWidth(NonZeroU16::new(3).unwrap()));
1207        prev.merge(&next);
1208
1209        let diff = prev.diff(&next);
1210        assert_eq!(diff, []);
1211    }
1212
1213    #[test]
1214    fn merge_diff_link() {
1215        let mut prev = Buffer::with_lines(["_".repeat(4)]);
1216        let mut next = Buffer::empty(Rect::new(0, 0, 4, 1));
1217        // Squeeze everything into the first cell, remaining cells are irrelevant.
1218        next.cell_mut((0, 0))
1219            .unwrap()
1220            .set_symbol("\x1b]8;;http://example.com\x1b\\link\x1b]8;;\x1b\\")
1221            .set_diff_option(CellDiffOption::ForcedWidth(NonZeroU16::new(4).unwrap()));
1222        prev.merge(&next);
1223
1224        let diff = prev.diff(&next);
1225        assert_eq!(diff, []);
1226    }
1227
1228    #[test]
1229    fn merge_diff_split_link() {
1230        let mut prev = Buffer::with_lines(["_".repeat(8)]);
1231        let mut next = Buffer::empty(Rect::new(0, 0, 8, 1));
1232        // Squeeze starting sequence and first character in first cell.
1233        next.cell_mut((0, 0))
1234            .unwrap()
1235            .set_symbol("\x1b]8;;http://example.com\x1b\\🔗")
1236            .set_diff_option(CellDiffOption::ForcedWidth(NonZeroU16::new(2).unwrap()));
1237        // Set inner characters normally.
1238        next.cell_mut((2, 0)).unwrap().set_symbol("l");
1239        next.cell_mut((3, 0)).unwrap().set_symbol("i");
1240        next.cell_mut((4, 0)).unwrap().set_symbol("n");
1241        next.cell_mut((5, 0)).unwrap().set_symbol("k");
1242        // Squeeze closing part into last cell.
1243        next.cell_mut((7, 0))
1244            .unwrap()
1245            .set_symbol("🔗\x1b]8;;\x1b\\")
1246            .set_diff_option(CellDiffOption::ForcedWidth(NonZeroU16::new(2).unwrap()));
1247        prev.merge(&next);
1248
1249        let diff = prev.diff(&next);
1250        assert_eq!(diff, []);
1251    }
1252
1253    #[test]
1254    fn merge_diff_image_sequences() {
1255        let mut prev = Buffer::empty(Rect::new(0, 0, 20, 1));
1256
1257        let mut kitty_image_placeholder_start = String::new();
1258        // The first control sequence is to store the cursors state, including foreground color.
1259        // It is necessary because we need to introduce a foreground color, but restore later to
1260        // the previous foreground color, to which we don't have access in this buffer.
1261        // At this point, the symbol width is already greater than one and would cause the next
1262        // cell to be skipped, if we don't `set_forced_width(Some(1))`.
1263        kitty_image_placeholder_start.push_str("\x1b[s");
1264        // The second control sequence sets the foreground color to some 24 bit value, which is an
1265        // imaginary "kitty protocol image id".
1266        kitty_image_placeholder_start.push_str("\x1b[38;2;0;0;0m");
1267        // The following are some special (reserved) unicode placeholder symbols.
1268        // 10EEEE is the actual placeholder, and the 3 following characters would contain some row,
1269        // column, and extra "image id" data.
1270        kitty_image_placeholder_start.push_str("\u{10EEEE}\u{305}\u{305}\u{305}");
1271
1272        prev.cell_mut((0, 0))
1273            .unwrap()
1274            .set_symbol(&kitty_image_placeholder_start)
1275            .set_diff_option(CellDiffOption::ForcedWidth(NonZeroU16::new(1).unwrap()));
1276
1277        // Add two follow up placeholder symbols that have a natural width of 1.
1278        prev.cell_mut((1, 0)).unwrap().set_char('\u{10EEEE}');
1279        prev.cell_mut((2, 0)).unwrap().set_char('\u{10EEEE}');
1280        // The last symbol also has a control sequence which restores the style, so we need to
1281        // force the width again to 1 to avoid skipping the next cell.
1282        prev.cell_mut((3, 0))
1283            .unwrap()
1284            .set_symbol("\u{10EEEE}\x1b[u")
1285            .set_diff_option(CellDiffOption::ForcedWidth(NonZeroU16::new(1).unwrap()));
1286
1287        let mut buffer = Buffer::filled(Rect::new(0, 0, 20, 1), Cell::new("x"));
1288        buffer.merge(&prev);
1289
1290        let diff = buffer.diff(&prev);
1291        assert_eq!(diff, []);
1292    }
1293
1294    #[test]
1295    fn diff_skip() {
1296        let prev = Buffer::with_lines(["123"]);
1297        let mut next = Buffer::with_lines(["456"]);
1298        for i in 1..3 {
1299            next.content[i].set_diff_option(CellDiffOption::Skip);
1300        }
1301
1302        let diff = prev.diff(&next);
1303        assert_eq!(diff, [(0, 0, &Cell::new("4"))],);
1304    }
1305
1306    #[rstest]
1307    #[case(Rect::new(0, 0, 2, 2), Rect::new(0, 2, 2, 2), ["11", "11", "22", "22"])]
1308    #[case(Rect::new(2, 2, 2, 2), Rect::new(0, 0, 2, 2), ["22  ", "22  ", "  11", "  11"])]
1309    fn merge<'line, Lines>(#[case] one: Rect, #[case] two: Rect, #[case] expected: Lines)
1310    where
1311        Lines: IntoIterator,
1312        Lines::Item: Into<Line<'line>>,
1313    {
1314        let mut one = Buffer::filled(one, Cell::new("1"));
1315        let two = Buffer::filled(two, Cell::new("2"));
1316        one.merge(&two);
1317        assert_eq!(one, Buffer::with_lines(expected));
1318    }
1319
1320    #[test]
1321    fn merge_with_offset() {
1322        let mut one = Buffer::filled(
1323            Rect {
1324                x: 3,
1325                y: 3,
1326                width: 2,
1327                height: 2,
1328            },
1329            Cell::new("1"),
1330        );
1331        let two = Buffer::filled(
1332            Rect {
1333                x: 1,
1334                y: 1,
1335                width: 3,
1336                height: 4,
1337            },
1338            Cell::new("2"),
1339        );
1340        one.merge(&two);
1341        let mut expected = Buffer::with_lines(["222 ", "222 ", "2221", "2221"]);
1342        expected.area = Rect {
1343            x: 1,
1344            y: 1,
1345            width: 4,
1346            height: 4,
1347        };
1348        assert_eq!(one, expected);
1349    }
1350
1351    #[rstest]
1352    #[case(CellDiffOption::None, CellDiffOption::Skip, [CellDiffOption::None, CellDiffOption::None, CellDiffOption::Skip, CellDiffOption::Skip, CellDiffOption::Skip, CellDiffOption::Skip])]
1353    #[case(CellDiffOption::Skip, CellDiffOption::None, [CellDiffOption::Skip, CellDiffOption::Skip, CellDiffOption::None, CellDiffOption::None, CellDiffOption::None, CellDiffOption::None])]
1354    fn merge_skip(
1355        #[case] skip_one: CellDiffOption,
1356        #[case] skip_two: CellDiffOption,
1357        #[case] expected: [CellDiffOption; 6],
1358    ) {
1359        let mut one = {
1360            let area = Rect {
1361                x: 0,
1362                y: 0,
1363                width: 2,
1364                height: 2,
1365            };
1366            let mut cell = Cell::new("1");
1367            cell.diff_option = skip_one;
1368            Buffer::filled(area, cell)
1369        };
1370        let two = {
1371            let area = Rect {
1372                x: 0,
1373                y: 1,
1374                width: 2,
1375                height: 2,
1376            };
1377            let mut cell = Cell::new("2");
1378            cell.diff_option = skip_two;
1379            Buffer::filled(area, cell)
1380        };
1381        one.merge(&two);
1382        let skipped = one
1383            .content()
1384            .iter()
1385            .map(|c| c.diff_option)
1386            .collect::<Vec<_>>();
1387        assert_eq!(skipped, expected);
1388    }
1389
1390    #[test]
1391    fn with_lines_accepts_into_lines() {
1392        use crate::style::Stylize;
1393        let mut buf = Buffer::empty(Rect::new(0, 0, 3, 2));
1394        buf.set_string(0, 0, "foo", Style::new().red());
1395        buf.set_string(0, 1, "bar", Style::new().blue());
1396        assert_eq!(buf, Buffer::with_lines(["foo".red(), "bar".blue()]));
1397    }
1398
1399    #[test]
1400    fn control_sequence_rendered_full() {
1401        let text = "I \x1b[0;36mwas\x1b[0m here!";
1402
1403        let mut buffer = Buffer::filled(Rect::new(0, 0, 25, 3), Cell::new("x"));
1404        buffer.set_string(1, 1, text, Style::new());
1405
1406        let expected = Buffer::with_lines([
1407            "xxxxxxxxxxxxxxxxxxxxxxxxx",
1408            "xI [0;36mwas[0m here!xxxx",
1409            "xxxxxxxxxxxxxxxxxxxxxxxxx",
1410        ]);
1411        assert_eq!(buffer, expected);
1412    }
1413
1414    #[test]
1415    fn control_sequence_rendered_partially() {
1416        let text = "I \x1b[0;36mwas\x1b[0m here!";
1417
1418        let mut buffer = Buffer::filled(Rect::new(0, 0, 11, 3), Cell::new("x"));
1419        buffer.set_string(1, 1, text, Style::new());
1420
1421        #[rustfmt::skip]
1422        let expected = Buffer::with_lines([
1423            "xxxxxxxxxxx",
1424            "xI [0;36mwa",
1425            "xxxxxxxxxxx",
1426        ]);
1427        assert_eq!(buffer, expected);
1428    }
1429
1430    /// Emojis normally contain various characters which should stay part of the Emoji.
1431    /// This should work fine by utilizing `unicode_segmentation` but a testcase is probably helpful
1432    /// due to the nature of never perfect Unicode implementations and all of its quirks.
1433    #[rstest]
1434    // Shrug without gender or skintone. Has a width of 2 like all emojis have.
1435    #[case::shrug("🤷", "🤷xxxxx")]
1436    // Technically this is a (brown) bear, a zero-width joiner and a snowflake
1437    // As it is joined its a single emoji and should therefore have a width of 2.
1438    // Prior to unicode-width 0.2, this was incorrectly detected as width 4 for some reason
1439    #[case::polarbear("🐻‍❄️", "🐻‍❄️xxxxx")]
1440    // Technically this is an eye, a zero-width joiner and a speech bubble
1441    // Both eye and speech bubble include a 'display as emoji' variation selector
1442    // Prior to unicode-width 0.2, this was incorrectly detected as width 4 for some reason
1443    #[case::eye_speechbubble("👁️‍🗨️", "👁️‍🗨️xxxxx")]
1444    // Keyboard keycap emoji: base symbol + VS16 for emoji presentation
1445    // This should render as a single grapheme with width 2.
1446    #[case::keyboard_emoji("⌨️", "⌨️xxxxx")]
1447    fn renders_emoji(#[case] input: &str, #[case] expected: &str) {
1448        use unicode_width::UnicodeWidthChar;
1449
1450        dbg!(input);
1451        dbg!(input.len());
1452        dbg!(
1453            input
1454                .graphemes(true)
1455                .map(|symbol| (
1456                    symbol,
1457                    symbol.escape_unicode().to_string(),
1458                    symbol.cell_width()
1459                ))
1460                .collect::<Vec<_>>()
1461        );
1462        dbg!(
1463            input
1464                .chars()
1465                .map(|char| (
1466                    char,
1467                    char.escape_unicode().to_string(),
1468                    char.width(),
1469                    char.is_control()
1470                ))
1471                .collect::<Vec<_>>()
1472        );
1473
1474        let mut buffer = Buffer::filled(Rect::new(0, 0, 7, 1), Cell::new("x"));
1475        buffer.set_string(0, 0, input, Style::new());
1476
1477        let expected = Buffer::with_lines([expected]);
1478        assert_eq!(buffer, expected);
1479    }
1480
1481    /// Regression test for <https://github.com/ratatui/ratatui/issues/1441>
1482    ///
1483    /// Previously the `pos_of` function would incorrectly cast the index to a u16 value instead of
1484    /// using the index as is. This caused incorrect rendering of any buffer with an length > 65535.
1485    #[test]
1486    fn index_pos_of_u16_max() {
1487        let buffer = Buffer::empty(Rect::new(0, 0, 256, 256 + 1));
1488        assert_eq!(buffer.index_of(255, 255), 65535);
1489        assert_eq!(buffer.pos_of(65535), (255, 255));
1490
1491        assert_eq!(buffer.index_of(0, 256), 65536);
1492        assert_eq!(buffer.pos_of(65536), (0, 256)); // previously (0, 0)
1493
1494        assert_eq!(buffer.index_of(1, 256), 65537);
1495        assert_eq!(buffer.pos_of(65537), (1, 256)); // previously (1, 0)
1496
1497        assert_eq!(buffer.index_of(255, 256), 65791);
1498        assert_eq!(buffer.pos_of(65791), (255, 256)); // previously (255, 0)
1499    }
1500
1501    #[test]
1502    fn diff_clears_trailing_cell_for_wide_grapheme() {
1503        // Reproduce: write "ab", then overwrite with a wide emoji like "⌨️"
1504        let prev = Buffer::with_lines(["ab"]); // width 2 area inferred
1505        assert_eq!(prev.area.width, 2);
1506
1507        let mut next = Buffer::with_lines(["  "]); // start with blanks
1508        next.set_string(0, 0, "⌨️", Style::new());
1509
1510        // The next buffer contains a wide grapheme occupying cell 0 and implicitly cell 1.
1511        // The debug formatting shows the hidden trailing space.
1512        let expected_next = Buffer::with_lines(["⌨️"]);
1513        assert_eq!(next, expected_next);
1514
1515        // The diff should include an update for (0,0) to draw the emoji. Depending on
1516        // terminal behavior, it may or may not be necessary to explicitly clear (1,0).
1517        // At minimum, ensure the first cell is updated and nothing incorrect is emitted.
1518        let diff = prev.diff(&next);
1519        assert!(
1520            diff.iter()
1521                .any(|(x, y, c)| *x == 0 && *y == 0 && c.symbol() == "⌨️")
1522        );
1523        // And it should explicitly clear the trailing cell (1,0) to avoid leftovers on terminals
1524        // that don't automatically clear the following cell for wide characters.
1525        assert!(
1526            diff.iter()
1527                .any(|(x, y, c)| *x == 1 && *y == 0 && c.symbol() == " ")
1528        );
1529    }
1530
1531    /// Regression test for a bug where trailing cells of wide VS16 emojis would generate
1532    /// unnecessary diff updates when only the style (not the symbol) changed.
1533    ///
1534    /// This caused visual artifacts when:
1535    /// 1. A previous widget applied a foreground color to the entire area
1536    /// 2. A border with VS16 emojis (like ⚠️) was rendered on top
1537    /// 3. The trailing cells had different styles but the same symbol (space)
1538    ///
1539    /// The fix ensures that only symbol changes trigger trailing cell updates,
1540    /// not style-only changes, since the style of hidden trailing cells is not visible.
1541    #[test]
1542    fn diff_ignores_style_only_changes_in_trailing_cells() {
1543        use crate::style::Color;
1544
1545        // Previous buffer: spaces with specific fg color
1546        let mut prev = Buffer::empty(Rect::new(0, 0, 3, 1));
1547        prev.content[0].set_symbol(" ");
1548        prev.content[0].set_fg(Color::LightBlue); // <-- style A
1549        prev.content[1].set_symbol(" ");
1550        prev.content[1].set_fg(Color::LightBlue);
1551        prev.content[2].set_symbol("x");
1552
1553        // Next buffer: emoji but DIFFERENT style on main cell,
1554        // trailing cell has same symbol but different style
1555        let mut next = Buffer::empty(Rect::new(0, 0, 3, 1));
1556        next.content[0].set_symbol("⚠️"); // emoji symbol
1557        next.content[0].set_fg(Color::Reset); // <-- style B (DIFFERENT from prev!)
1558        next.content[1].set_symbol(" "); // same symbol (space), trailing cell (hidden by emoji)
1559        next.content[1].set_fg(Color::Reset);
1560        next.content[2].set_symbol("x");
1561
1562        let diff = prev.diff(&next);
1563
1564        // The first cell (0,0) SHOULD be in the diff because the symbol changed
1565        assert!(
1566            diff.iter().any(|(x, y, _)| *x == 0 && *y == 0),
1567            "Diff should include first cell (0,0) because the symbol changed"
1568        );
1569
1570        // The diff should NOT contain an update for the trailing cell (1, 0)
1571        // because the symbol is the same (space) - only the style changed,
1572        // and the style of a hidden trailing cell is not visible.
1573        assert!(
1574            !diff.iter().any(|(x, y, _)| *x == 1 && *y == 0),
1575            "Diff should not include trailing cell (1,0) when only style changed. \
1576             Found updates: {:?}",
1577            diff.iter()
1578                .map(|(x, y, c)| (x, y, c.symbol(), c.fg))
1579                .collect::<Vec<_>>()
1580        );
1581    }
1582}