ratatui/buffer/
buffer.rs

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