use alloc::vec;
use alloc::vec::Vec;
use core::ops::{Index, IndexMut};
use core::{cmp, fmt};
use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;
use crate::buffer::Cell;
use crate::layout::{Position, Rect};
use crate::style::Style;
use crate::text::{Line, Span};
#[derive(Default, Clone, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Buffer {
pub area: Rect,
pub content: Vec<Cell>,
}
impl Buffer {
#[must_use]
pub fn empty(area: Rect) -> Self {
Self::filled(area, Cell::EMPTY)
}
#[must_use]
pub fn filled(area: Rect, cell: Cell) -> Self {
let size = area.area() as usize;
let content = vec![cell; size];
Self { area, content }
}
#[must_use]
pub fn with_lines<'a, Iter>(lines: Iter) -> Self
where
Iter: IntoIterator,
Iter::Item: Into<Line<'a>>,
{
let lines = lines.into_iter().map(Into::into).collect::<Vec<_>>();
let height = lines.len() as u16;
let width = lines.iter().map(Line::width).max().unwrap_or_default() as u16;
let mut buffer = Self::empty(Rect::new(0, 0, width, height));
for (y, line) in lines.iter().enumerate() {
buffer.set_line(0, y as u16, line, width);
}
buffer
}
pub fn content(&self) -> &[Cell] {
&self.content
}
pub const fn area(&self) -> &Rect {
&self.area
}
#[track_caller]
#[deprecated = "use `Buffer[(x, y)]` instead. To avoid panicking, use `Buffer::cell((x, y))`. Both methods take `impl Into<Position>`."]
#[must_use]
pub fn get(&self, x: u16, y: u16) -> &Cell {
let i = self.index_of(x, y);
&self.content[i]
}
#[track_caller]
#[deprecated = "use `Buffer[(x, y)]` instead. To avoid panicking, use `Buffer::cell_mut((x, y))`. Both methods take `impl Into<Position>`."]
#[must_use]
pub fn get_mut(&mut self, x: u16, y: u16) -> &mut Cell {
let i = self.index_of(x, y);
&mut self.content[i]
}
#[must_use]
pub fn cell<P: Into<Position>>(&self, position: P) -> Option<&Cell> {
let position = position.into();
let index = self.index_of_opt(position)?;
self.content.get(index)
}
#[must_use]
pub fn cell_mut<P: Into<Position>>(&mut self, position: P) -> Option<&mut Cell> {
let position = position.into();
let index = self.index_of_opt(position)?;
self.content.get_mut(index)
}
#[track_caller]
#[must_use]
pub fn index_of(&self, x: u16, y: u16) -> usize {
self.index_of_opt(Position { x, y }).unwrap_or_else(|| {
panic!(
"index outside of buffer: the area is {area:?} but index is ({x}, {y})",
area = self.area,
)
})
}
#[must_use]
const fn index_of_opt(&self, position: Position) -> Option<usize> {
let area = self.area;
if !area.contains(position) {
return None;
}
let y = (position.y - self.area.y) as usize;
let x = (position.x - self.area.x) as usize;
let width = self.area.width as usize;
Some(y * width + x)
}
#[must_use]
pub fn pos_of(&self, index: usize) -> (u16, u16) {
debug_assert!(
index < self.content.len(),
"Trying to get the coords of a cell outside the buffer: i={index} len={}",
self.content.len()
);
let x = index % self.area.width as usize + self.area.x as usize;
let y = index / self.area.width as usize + self.area.y as usize;
(
u16::try_from(x).expect("x overflow. This should never happen as area.width is u16"),
u16::try_from(y).expect("y overflow. This should never happen as area.height is u16"),
)
}
pub fn set_string<T, S>(&mut self, x: u16, y: u16, string: T, style: S)
where
T: AsRef<str>,
S: Into<Style>,
{
self.set_stringn(x, y, string, usize::MAX, style);
}
pub fn set_stringn<T, S>(
&mut self,
mut x: u16,
y: u16,
string: T,
max_width: usize,
style: S,
) -> (u16, u16)
where
T: AsRef<str>,
S: Into<Style>,
{
let max_width = max_width.try_into().unwrap_or(u16::MAX);
let mut remaining_width = self.area.right().saturating_sub(x).min(max_width);
let graphemes = UnicodeSegmentation::graphemes(string.as_ref(), true)
.filter(|symbol| !symbol.contains(char::is_control))
.map(|symbol| (symbol, symbol.width() as u16))
.filter(|(_symbol, width)| *width > 0)
.map_while(|(symbol, width)| {
remaining_width = remaining_width.checked_sub(width)?;
Some((symbol, width))
});
let style = style.into();
for (symbol, width) in graphemes {
self[(x, y)].set_symbol(symbol).set_style(style);
let next_symbol = x + width;
x += 1;
while x < next_symbol {
self[(x, y)].reset();
x += 1;
}
}
(x, y)
}
pub fn set_line(&mut self, x: u16, y: u16, line: &Line<'_>, max_width: u16) -> (u16, u16) {
let mut remaining_width = max_width;
let mut x = x;
for span in line {
if remaining_width == 0 {
break;
}
let pos = self.set_stringn(
x,
y,
span.content.as_ref(),
remaining_width as usize,
line.style.patch(span.style),
);
let w = pos.0.saturating_sub(x);
x = pos.0;
remaining_width = remaining_width.saturating_sub(w);
}
(x, y)
}
pub fn set_span(&mut self, x: u16, y: u16, span: &Span<'_>, max_width: u16) -> (u16, u16) {
self.set_stringn(x, y, &span.content, max_width as usize, span.style)
}
pub fn set_style<S: Into<Style>>(&mut self, area: Rect, style: S) {
let style = style.into();
let area = self.area.intersection(area);
for y in area.top()..area.bottom() {
for x in area.left()..area.right() {
self[(x, y)].set_style(style);
}
}
}
pub fn resize(&mut self, area: Rect) {
let length = area.area() as usize;
if self.content.len() > length {
self.content.truncate(length);
} else {
self.content.resize(length, Cell::EMPTY);
}
self.area = area;
}
pub fn reset(&mut self) {
for cell in &mut self.content {
cell.reset();
}
}
pub fn merge(&mut self, other: &Self) {
let area = self.area.union(other.area);
self.content.resize(area.area() as usize, Cell::EMPTY);
let size = self.area.area() as usize;
for i in (0..size).rev() {
let (x, y) = self.pos_of(i);
let k = ((y - area.y) * area.width + x - area.x) as usize;
if i != k {
self.content[k] = self.content[i].clone();
self.content[i].reset();
}
}
let size = other.area.area() as usize;
for i in 0..size {
let (x, y) = other.pos_of(i);
let k = ((y - area.y) * area.width + x - area.x) as usize;
self.content[k] = other.content[i].clone();
}
self.area = area;
}
pub fn diff<'a>(&self, other: &'a Self) -> Vec<(u16, u16, &'a Cell)> {
let previous_buffer = &self.content;
let next_buffer = &other.content;
let mut updates: Vec<(u16, u16, &Cell)> = vec![];
let mut invalidated: usize = 0;
let mut to_skip: usize = 0;
for (i, (current, previous)) in next_buffer.iter().zip(previous_buffer.iter()).enumerate() {
if !current.skip && (current != previous || invalidated > 0) && to_skip == 0 {
let (x, y) = self.pos_of(i);
updates.push((x, y, &next_buffer[i]));
let symbol = current.symbol();
let cell_width = symbol.width();
let contains_vs16 = symbol.chars().any(|c| c == '\u{FE0F}');
if cell_width > 1 && contains_vs16 {
for k in 1..cell_width {
let j = i + k;
if j >= next_buffer.len() || j >= previous_buffer.len() {
break;
}
let prev_trailing = &previous_buffer[j];
let next_trailing = &next_buffer[j];
if !next_trailing.skip && prev_trailing != next_trailing {
let (tx, ty) = self.pos_of(j);
updates.push((tx, ty, next_trailing));
}
}
}
}
to_skip = current.symbol().width().saturating_sub(1);
let affected_width = cmp::max(current.symbol().width(), previous.symbol().width());
invalidated = cmp::max(affected_width, invalidated).saturating_sub(1);
}
updates
}
}
impl<P: Into<Position>> Index<P> for Buffer {
type Output = Cell;
fn index(&self, position: P) -> &Self::Output {
let position = position.into();
let index = self.index_of(position.x, position.y);
&self.content[index]
}
}
impl<P: Into<Position>> IndexMut<P> for Buffer {
fn index_mut(&mut self, position: P) -> &mut Self::Output {
let position = position.into();
let index = self.index_of(position.x, position.y);
&mut self.content[index]
}
}
impl fmt::Debug for Buffer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_fmt(format_args!("Buffer {{\n area: {:?}", &self.area))?;
if self.area.is_empty() {
return f.write_str("\n}");
}
f.write_str(",\n content: [\n")?;
let mut last_style = None;
let mut styles = vec![];
for (y, line) in self.content.chunks(self.area.width as usize).enumerate() {
let mut overwritten = vec![];
let mut skip: usize = 0;
f.write_str(" \"")?;
for (x, c) in line.iter().enumerate() {
if skip == 0 {
f.write_str(c.symbol())?;
} else {
overwritten.push((x, c.symbol()));
}
skip = cmp::max(skip, c.symbol().width()).saturating_sub(1);
#[cfg(feature = "underline-color")]
{
let style = (c.fg, c.bg, c.underline_color, c.modifier);
if last_style != Some(style) {
last_style = Some(style);
styles.push((x, y, c.fg, c.bg, c.underline_color, c.modifier));
}
}
#[cfg(not(feature = "underline-color"))]
{
let style = (c.fg, c.bg, c.modifier);
if last_style != Some(style) {
last_style = Some(style);
styles.push((x, y, c.fg, c.bg, c.modifier));
}
}
}
f.write_str("\",")?;
if !overwritten.is_empty() {
f.write_fmt(format_args!(
" // hidden by multi-width symbols: {overwritten:?}"
))?;
}
f.write_str("\n")?;
}
f.write_str(" ],\n styles: [\n")?;
for s in styles {
#[cfg(feature = "underline-color")]
f.write_fmt(format_args!(
" x: {}, y: {}, fg: {:?}, bg: {:?}, underline: {:?}, modifier: {:?},\n",
s.0, s.1, s.2, s.3, s.4, s.5
))?;
#[cfg(not(feature = "underline-color"))]
f.write_fmt(format_args!(
" x: {}, y: {}, fg: {:?}, bg: {:?}, modifier: {:?},\n",
s.0, s.1, s.2, s.3, s.4
))?;
}
f.write_str(" ]\n}")?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use alloc::format;
use alloc::string::ToString;
use core::iter;
use std::{dbg, println};
use itertools::Itertools;
use rstest::{fixture, rstest};
use super::*;
use crate::style::{Color, Modifier, Stylize};
#[test]
fn debug_empty_buffer() {
let buffer = Buffer::empty(Rect::ZERO);
let result = format!("{buffer:?}");
println!("{result}");
let expected = "Buffer {\n area: Rect { x: 0, y: 0, width: 0, height: 0 }\n}";
assert_eq!(result, expected);
}
#[cfg(feature = "underline-color")]
#[test]
fn debug_grapheme_override() {
let buffer = Buffer::with_lines(["a🦀b"]);
let result = format!("{buffer:?}");
println!("{result}");
let expected = indoc::indoc!(
r#"
Buffer {
area: Rect { x: 0, y: 0, width: 4, height: 1 },
content: [
"a🦀b", // hidden by multi-width symbols: [(2, " ")]
],
styles: [
x: 0, y: 0, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
]
}"#
);
assert_eq!(result, expected);
}
#[test]
fn debug_some_example() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 12, 2));
buffer.set_string(0, 0, "Hello World!", Style::default());
buffer.set_string(
0,
1,
"G'day World!",
Style::default()
.fg(Color::Green)
.bg(Color::Yellow)
.add_modifier(Modifier::BOLD),
);
let result = format!("{buffer:?}");
println!("{result}");
#[cfg(feature = "underline-color")]
let expected = indoc::indoc!(
r#"
Buffer {
area: Rect { x: 0, y: 0, width: 12, height: 2 },
content: [
"Hello World!",
"G'day World!",
],
styles: [
x: 0, y: 0, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
x: 0, y: 1, fg: Green, bg: Yellow, underline: Reset, modifier: BOLD,
]
}"#
);
#[cfg(not(feature = "underline-color"))]
let expected = indoc::indoc!(
r#"
Buffer {
area: Rect { x: 0, y: 0, width: 12, height: 2 },
content: [
"Hello World!",
"G'day World!",
],
styles: [
x: 0, y: 0, fg: Reset, bg: Reset, modifier: NONE,
x: 0, y: 1, fg: Green, bg: Yellow, modifier: BOLD,
]
}"#
);
assert_eq!(result, expected);
}
#[test]
fn it_translates_to_and_from_coordinates() {
let rect = Rect::new(200, 100, 50, 80);
let buf = Buffer::empty(rect);
assert_eq!(buf.pos_of(0), (200, 100));
assert_eq!(buf.index_of(200, 100), 0);
assert_eq!(buf.pos_of(buf.content.len() - 1), (249, 179));
assert_eq!(buf.index_of(249, 179), buf.content.len() - 1);
}
#[test]
#[should_panic(expected = "outside the buffer")]
fn pos_of_panics_on_out_of_bounds() {
let rect = Rect::new(0, 0, 10, 10);
let buf = Buffer::empty(rect);
let _ = buf.pos_of(100);
}
#[rstest]
#[case::left(9, 10)]
#[case::top(10, 9)]
#[case::right(20, 10)]
#[case::bottom(10, 20)]
#[should_panic(
expected = "index outside of buffer: the area is Rect { x: 10, y: 10, width: 10, height: 10 } but index is"
)]
fn index_of_panics_on_out_of_bounds(#[case] x: u16, #[case] y: u16) {
let _ = Buffer::empty(Rect::new(10, 10, 10, 10)).index_of(x, y);
}
#[test]
fn test_cell() {
let buf = Buffer::with_lines(["Hello", "World"]);
let mut expected = Cell::default();
expected.set_symbol("H");
assert_eq!(buf.cell((0, 0)), Some(&expected));
assert_eq!(buf.cell((10, 10)), None);
assert_eq!(buf.cell(Position::new(0, 0)), Some(&expected));
assert_eq!(buf.cell(Position::new(10, 10)), None);
}
#[test]
fn test_cell_mut() {
let mut buf = Buffer::with_lines(["Hello", "World"]);
let mut expected = Cell::default();
expected.set_symbol("H");
assert_eq!(buf.cell_mut((0, 0)), Some(&mut expected));
assert_eq!(buf.cell_mut((10, 10)), None);
assert_eq!(buf.cell_mut(Position::new(0, 0)), Some(&mut expected));
assert_eq!(buf.cell_mut(Position::new(10, 10)), None);
}
#[test]
fn index() {
let buf = Buffer::with_lines(["Hello", "World"]);
let mut expected = Cell::default();
expected.set_symbol("H");
assert_eq!(buf[(0, 0)], expected);
}
#[rstest]
#[case::left(9, 10)]
#[case::top(10, 9)]
#[case::right(20, 10)]
#[case::bottom(10, 20)]
#[should_panic(
expected = "index outside of buffer: the area is Rect { x: 10, y: 10, width: 10, height: 10 } but index is"
)]
fn index_out_of_bounds_panics(#[case] x: u16, #[case] y: u16) {
let rect = Rect::new(10, 10, 10, 10);
let buf = Buffer::empty(rect);
let _ = buf[(x, y)];
}
#[test]
fn index_mut() {
let mut buf = Buffer::with_lines(["Cat", "Dog"]);
buf[(0, 0)].set_symbol("B");
buf[Position::new(0, 1)].set_symbol("L");
assert_eq!(buf, Buffer::with_lines(["Bat", "Log"]));
}
#[rstest]
#[case::left(9, 10)]
#[case::top(10, 9)]
#[case::right(20, 10)]
#[case::bottom(10, 20)]
#[should_panic(
expected = "index outside of buffer: the area is Rect { x: 10, y: 10, width: 10, height: 10 } but index is"
)]
fn index_mut_out_of_bounds_panics(#[case] x: u16, #[case] y: u16) {
let mut buf = Buffer::empty(Rect::new(10, 10, 10, 10));
buf[(x, y)].set_symbol("A");
}
#[test]
fn set_string() {
let area = Rect::new(0, 0, 5, 1);
let mut buffer = Buffer::empty(area);
buffer.set_stringn(0, 0, "aaa", 0, Style::default());
assert_eq!(buffer, Buffer::with_lines([" "]));
buffer.set_string(0, 0, "aaa", Style::default());
assert_eq!(buffer, Buffer::with_lines(["aaa "]));
buffer.set_stringn(0, 0, "bbbbbbbbbbbbbb", 4, Style::default());
assert_eq!(buffer, Buffer::with_lines(["bbbb "]));
buffer.set_string(0, 0, "12345", Style::default());
assert_eq!(buffer, Buffer::with_lines(["12345"]));
buffer.set_string(0, 0, "123456", Style::default());
assert_eq!(buffer, Buffer::with_lines(["12345"]));
buffer = Buffer::empty(Rect::new(0, 0, 5, 2));
buffer.set_string(0, 0, "12345", Style::default());
buffer.set_string(0, 1, "67890", Style::default());
assert_eq!(buffer, Buffer::with_lines(["12345", "67890"]));
}
#[test]
fn set_string_multi_width_overwrite() {
let area = Rect::new(0, 0, 5, 1);
let mut buffer = Buffer::empty(area);
buffer.set_string(0, 0, "aaaaa", Style::default());
buffer.set_string(0, 0, "称号", Style::default());
assert_eq!(buffer, Buffer::with_lines(["称号a"]));
}
#[test]
fn set_string_zero_width() {
assert_eq!("\u{200B}".width(), 0);
let area = Rect::new(0, 0, 1, 1);
let mut buffer = Buffer::empty(area);
let s = "\u{200B}a";
buffer.set_stringn(0, 0, s, 1, Style::default());
assert_eq!(buffer, Buffer::with_lines(["a"]));
let s = "a\u{200B}";
buffer.set_stringn(0, 0, s, 1, Style::default());
assert_eq!(buffer, Buffer::with_lines(["a"]));
}
#[test]
fn set_string_double_width() {
let area = Rect::new(0, 0, 5, 1);
let mut buffer = Buffer::empty(area);
buffer.set_string(0, 0, "コン", Style::default());
assert_eq!(buffer, Buffer::with_lines(["コン "]));
buffer.set_string(0, 0, "コンピ", Style::default());
assert_eq!(buffer, Buffer::with_lines(["コン "]));
}
#[fixture]
fn small_one_line_buffer() -> Buffer {
Buffer::empty(Rect::new(0, 0, 5, 1))
}
#[rstest]
#[case::empty("", " ")]
#[case::one("1", "1 ")]
#[case::full("12345", "12345")]
#[case::overflow("123456", "12345")]
fn set_line_raw(
mut small_one_line_buffer: Buffer,
#[case] content: &str,
#[case] expected: &str,
) {
let line = Line::raw(content);
small_one_line_buffer.set_line(0, 0, &line, 5);
let mut expected_buffer = Buffer::empty(small_one_line_buffer.area);
expected_buffer.set_string(0, 0, expected, Style::default());
assert_eq!(small_one_line_buffer, expected_buffer);
}
#[rstest]
#[case::empty("", " ")]
#[case::one("1", "1 ")]
#[case::full("12345", "12345")]
#[case::overflow("123456", "12345")]
fn set_line_styled(
mut small_one_line_buffer: Buffer,
#[case] content: &str,
#[case] expected: &str,
) {
let color = Color::Blue;
let line = Line::styled(content, color);
small_one_line_buffer.set_line(0, 0, &line, 5);
let actual_contents = small_one_line_buffer
.content
.iter()
.map(Cell::symbol)
.join("");
let actual_styles = small_one_line_buffer
.content
.iter()
.map(|c| c.fg)
.collect_vec();
let expected_styles = iter::repeat_n(color, content.len().min(5))
.chain(iter::repeat_n(
Color::default(),
5_usize.saturating_sub(content.len()),
))
.collect_vec();
assert_eq!(actual_contents, expected);
assert_eq!(actual_styles, expected_styles);
}
#[test]
fn set_style() {
let mut buffer = Buffer::with_lines(["aaaaa", "bbbbb", "ccccc"]);
buffer.set_style(Rect::new(0, 1, 5, 1), Style::new().red());
#[rustfmt::skip]
let expected = Buffer::with_lines([
"aaaaa".into(),
"bbbbb".red(),
"ccccc".into(),
]);
assert_eq!(buffer, expected);
}
#[test]
fn set_style_does_not_panic_when_out_of_area() {
let mut buffer = Buffer::with_lines(["aaaaa", "bbbbb", "ccccc"]);
buffer.set_style(Rect::new(0, 1, 10, 3), Style::new().red());
#[rustfmt::skip]
let expected = Buffer::with_lines([
"aaaaa".into(),
"bbbbb".red(),
"ccccc".red(),
]);
assert_eq!(buffer, expected);
}
#[test]
fn with_lines() {
#[rustfmt::skip]
let buffer = Buffer::with_lines([
"┌────────┐",
"│コンピュ│",
"│ーa 上で│",
"└────────┘",
]);
assert_eq!(buffer.area.x, 0);
assert_eq!(buffer.area.y, 0);
assert_eq!(buffer.area.width, 10);
assert_eq!(buffer.area.height, 4);
}
#[test]
fn diff_empty_empty() {
let area = Rect::new(0, 0, 40, 40);
let prev = Buffer::empty(area);
let next = Buffer::empty(area);
let diff = prev.diff(&next);
assert_eq!(diff, []);
}
#[test]
fn diff_empty_filled() {
let area = Rect::new(0, 0, 40, 40);
let prev = Buffer::empty(area);
let next = Buffer::filled(area, Cell::new("a"));
let diff = prev.diff(&next);
assert_eq!(diff.len(), 40 * 40);
}
#[test]
fn diff_filled_filled() {
let area = Rect::new(0, 0, 40, 40);
let prev = Buffer::filled(area, Cell::new("a"));
let next = Buffer::filled(area, Cell::new("a"));
let diff = prev.diff(&next);
assert_eq!(diff, []);
}
#[test]
fn diff_single_width() {
let prev = Buffer::with_lines([
" ",
"┌Title─┐ ",
"│ │ ",
"│ │ ",
"└──────┘ ",
]);
let next = Buffer::with_lines([
" ",
"┌TITLE─┐ ",
"│ │ ",
"│ │ ",
"└──────┘ ",
]);
let diff = prev.diff(&next);
assert_eq!(
diff,
[
(2, 1, &Cell::new("I")),
(3, 1, &Cell::new("T")),
(4, 1, &Cell::new("L")),
(5, 1, &Cell::new("E")),
]
);
}
#[test]
fn diff_multi_width() {
#[rustfmt::skip]
let prev = Buffer::with_lines([
"┌Title─┐ ",
"└──────┘ ",
]);
#[rustfmt::skip]
let next = Buffer::with_lines([
"┌称号──┐ ",
"└──────┘ ",
]);
let diff = prev.diff(&next);
assert_eq!(
diff,
[
(1, 0, &Cell::new("称")),
(3, 0, &Cell::new("号")),
(5, 0, &Cell::new("─")),
]
);
}
#[test]
fn diff_multi_width_offset() {
let prev = Buffer::with_lines(["┌称号──┐"]);
let next = Buffer::with_lines(["┌─称号─┐"]);
let diff = prev.diff(&next);
assert_eq!(
diff,
[
(1, 0, &Cell::new("─")),
(2, 0, &Cell::new("称")),
(4, 0, &Cell::new("号")),
]
);
}
#[test]
fn diff_skip() {
let prev = Buffer::with_lines(["123"]);
let mut next = Buffer::with_lines(["456"]);
for i in 1..3 {
next.content[i].set_skip(true);
}
let diff = prev.diff(&next);
assert_eq!(diff, [(0, 0, &Cell::new("4"))],);
}
#[rstest]
#[case(Rect::new(0, 0, 2, 2), Rect::new(0, 2, 2, 2), ["11", "11", "22", "22"])]
#[case(Rect::new(2, 2, 2, 2), Rect::new(0, 0, 2, 2), ["22 ", "22 ", " 11", " 11"])]
fn merge<'line, Lines>(#[case] one: Rect, #[case] two: Rect, #[case] expected: Lines)
where
Lines: IntoIterator,
Lines::Item: Into<Line<'line>>,
{
let mut one = Buffer::filled(one, Cell::new("1"));
let two = Buffer::filled(two, Cell::new("2"));
one.merge(&two);
assert_eq!(one, Buffer::with_lines(expected));
}
#[test]
fn merge_with_offset() {
let mut one = Buffer::filled(
Rect {
x: 3,
y: 3,
width: 2,
height: 2,
},
Cell::new("1"),
);
let two = Buffer::filled(
Rect {
x: 1,
y: 1,
width: 3,
height: 4,
},
Cell::new("2"),
);
one.merge(&two);
let mut expected = Buffer::with_lines(["222 ", "222 ", "2221", "2221"]);
expected.area = Rect {
x: 1,
y: 1,
width: 4,
height: 4,
};
assert_eq!(one, expected);
}
#[rstest]
#[case(false, true, [false, false, true, true, true, true])]
#[case(true, false, [true, true, false, false, false, false])]
fn merge_skip(#[case] skip_one: bool, #[case] skip_two: bool, #[case] expected: [bool; 6]) {
let mut one = {
let area = Rect {
x: 0,
y: 0,
width: 2,
height: 2,
};
let mut cell = Cell::new("1");
cell.skip = skip_one;
Buffer::filled(area, cell)
};
let two = {
let area = Rect {
x: 0,
y: 1,
width: 2,
height: 2,
};
let mut cell = Cell::new("2");
cell.skip = skip_two;
Buffer::filled(area, cell)
};
one.merge(&two);
let skipped = one.content().iter().map(|c| c.skip).collect::<Vec<_>>();
assert_eq!(skipped, expected);
}
#[test]
fn with_lines_accepts_into_lines() {
use crate::style::Stylize;
let mut buf = Buffer::empty(Rect::new(0, 0, 3, 2));
buf.set_string(0, 0, "foo", Style::new().red());
buf.set_string(0, 1, "bar", Style::new().blue());
assert_eq!(buf, Buffer::with_lines(["foo".red(), "bar".blue()]));
}
#[test]
fn control_sequence_rendered_full() {
let text = "I \x1b[0;36mwas\x1b[0m here!";
let mut buffer = Buffer::filled(Rect::new(0, 0, 25, 3), Cell::new("x"));
buffer.set_string(1, 1, text, Style::new());
let expected = Buffer::with_lines([
"xxxxxxxxxxxxxxxxxxxxxxxxx",
"xI [0;36mwas[0m here!xxxx",
"xxxxxxxxxxxxxxxxxxxxxxxxx",
]);
assert_eq!(buffer, expected);
}
#[test]
fn control_sequence_rendered_partially() {
let text = "I \x1b[0;36mwas\x1b[0m here!";
let mut buffer = Buffer::filled(Rect::new(0, 0, 11, 3), Cell::new("x"));
buffer.set_string(1, 1, text, Style::new());
#[rustfmt::skip]
let expected = Buffer::with_lines([
"xxxxxxxxxxx",
"xI [0;36mwa",
"xxxxxxxxxxx",
]);
assert_eq!(buffer, expected);
}
#[rstest]
#[case::shrug("🤷", "🤷xxxxx")]
#[case::polarbear("🐻❄️", "🐻❄️xxxxx")]
#[case::eye_speechbubble("👁️🗨️", "👁️🗨️xxxxx")]
#[case::keyboard_emoji("⌨️", "⌨️xxxxx")]
fn renders_emoji(#[case] input: &str, #[case] expected: &str) {
use unicode_width::UnicodeWidthChar;
dbg!(input);
dbg!(input.len());
dbg!(
input
.graphemes(true)
.map(|symbol| (symbol, symbol.escape_unicode().to_string(), symbol.width()))
.collect::<Vec<_>>()
);
dbg!(
input
.chars()
.map(|char| (
char,
char.escape_unicode().to_string(),
char.width(),
char.is_control()
))
.collect::<Vec<_>>()
);
let mut buffer = Buffer::filled(Rect::new(0, 0, 7, 1), Cell::new("x"));
buffer.set_string(0, 0, input, Style::new());
let expected = Buffer::with_lines([expected]);
assert_eq!(buffer, expected);
}
#[test]
fn index_pos_of_u16_max() {
let buffer = Buffer::empty(Rect::new(0, 0, 256, 256 + 1));
assert_eq!(buffer.index_of(255, 255), 65535);
assert_eq!(buffer.pos_of(65535), (255, 255));
assert_eq!(buffer.index_of(0, 256), 65536);
assert_eq!(buffer.pos_of(65536), (0, 256));
assert_eq!(buffer.index_of(1, 256), 65537);
assert_eq!(buffer.pos_of(65537), (1, 256));
assert_eq!(buffer.index_of(255, 256), 65791);
assert_eq!(buffer.pos_of(65791), (255, 256)); }
#[test]
fn diff_clears_trailing_cell_for_wide_grapheme() {
let prev = Buffer::with_lines(["ab"]); assert_eq!(prev.area.width, 2);
let mut next = Buffer::with_lines([" "]); next.set_string(0, 0, "⌨️", Style::new());
let expected_next = Buffer::with_lines(["⌨️"]);
assert_eq!(next, expected_next);
let diff = prev.diff(&next);
assert!(
diff.iter()
.any(|(x, y, c)| *x == 0 && *y == 0 && c.symbol() == "⌨️")
);
assert!(
diff.iter()
.any(|(x, y, c)| *x == 1 && *y == 0 && c.symbol() == " ")
);
}
}