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