Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-tui/src/buffer.rs')
| -rw-r--r-- | helix-tui/src/buffer.rs | 944 |
1 files changed, 122 insertions, 822 deletions
diff --git a/helix-tui/src/buffer.rs b/helix-tui/src/buffer.rs index 22956b04..b55c22ad 100644 --- a/helix-tui/src/buffer.rs +++ b/helix-tui/src/buffer.rs @@ -1,282 +1,97 @@ use crate::text::{Span, Spans}; -use helix_core::unicode::width::UnicodeWidthStr; -use std::cmp::min; -use unicode_segmentation::UnicodeSegmentation; use helix_view::graphics::{Color, Modifier, Rect, Style}; -/// A buffer cell -#[derive(Debug, Clone, PartialEq)] -pub struct Cell { - pub symbol: String, - pub fg: Color, - pub bg: Color, - pub modifier: Modifier, -} - -impl Cell { - pub fn set_symbol(&mut self, symbol: &str) -> &mut Cell { - self.symbol.clear(); - self.symbol.push_str(symbol); - self - } - - pub fn set_char(&mut self, ch: char) -> &mut Cell { - self.symbol.clear(); - self.symbol.push(ch); - self - } +pub use termwiz::surface::Surface as Buffer; +use termwiz::{cell::*, surface::*}; - pub fn set_fg(&mut self, color: Color) -> &mut Cell { - self.fg = color; - self - } +pub struct Cell<'a> { + surface: &'a mut Surface, +} - pub fn set_bg(&mut self, color: Color) -> &mut Cell { - self.bg = color; +impl<'a> Cell<'a> { + pub fn set_symbol<'b>(self, symbol: &'b str) -> Cell<'a> { + self.surface.add_change(Change::Text(symbol.into())); self } - - pub fn set_style(&mut self, style: Style) -> &mut Cell { - if let Some(c) = style.fg { - self.fg = c; - } - if let Some(c) = style.bg { - self.bg = c; - } - self.modifier.insert(style.add_modifier); - self.modifier.remove(style.sub_modifier); + pub fn set_style(self, style: Style) -> Cell<'a> { + if let Some(fg) = style.fg { + self.surface + .add_change(Change::Attribute(AttributeChange::Foreground(fg.into()))); + } + if let Some(bg) = style.bg { + self.surface + .add_change(Change::Attribute(AttributeChange::Background(bg.into()))); + } + + self.surface + .add_change(Change::Attribute(AttributeChange::Intensity( + if style.add_modifier.contains(Modifier::BOLD) { + Intensity::Bold + } else if style.add_modifier.contains(Modifier::DIM) { + Intensity::Half + } else { + Intensity::Normal + }, + ))); + + self.surface + .add_change(Change::Attribute(AttributeChange::Italic( + style.add_modifier.contains(Modifier::ITALIC), + ))); + + self.surface + .add_change(Change::Attribute(AttributeChange::Underline( + if style.add_modifier.contains(Modifier::UNDERLINED) { + Underline::Single + } else { + Underline::None + }, + ))); + + self.surface + .add_change(Change::Attribute(AttributeChange::Reverse( + style.add_modifier.contains(Modifier::REVERSED), + ))); + + self.surface + .add_change(Change::Attribute(AttributeChange::Invisible( + style.add_modifier.contains(Modifier::HIDDEN), + ))); + + self.surface + .add_change(Change::Attribute(AttributeChange::StrikeThrough( + style.add_modifier.contains(Modifier::CROSSED_OUT), + ))); + + self.surface + .add_change(Change::Attribute(AttributeChange::Blink( + if style.add_modifier.contains(Modifier::SLOW_BLINK) { + Blink::Slow + } else if style.add_modifier.contains(Modifier::RAPID_BLINK) { + Blink::Rapid + } else { + Blink::None + }, + ))); self } - - pub fn style(&self) -> Style { - Style::default() - .fg(self.fg) - .bg(self.bg) - .add_modifier(self.modifier) - } - - pub fn reset(&mut self) { - self.symbol.clear(); - self.symbol.push(' '); - self.fg = Color::Reset; - self.bg = Color::Reset; - self.modifier = Modifier::empty(); - } -} - -impl Default for Cell { - fn default() -> Cell { - Cell { - symbol: " ".into(), - fg: Color::Reset, - bg: Color::Reset, - modifier: Modifier::empty(), - } - } } -/// A buffer that maps to the desired content of the terminal after the draw call -/// -/// No widget in the library interacts directly with the terminal. Instead each of them is required -/// to draw their state to an intermediate buffer. It is basically a grid where each cell contains -/// a grapheme, a foreground color and a background color. This grid will then be used to output -/// the appropriate escape sequences and characters to draw the UI as the user has defined it. -/// -/// # Examples: -/// -/// ``` -/// use helix_tui::buffer::{Buffer, Cell}; -/// use helix_view::graphics::{Rect, Color, Style, Modifier}; -/// -/// let mut buf = Buffer::empty(Rect{x: 0, y: 0, width: 10, height: 5}); -/// buf[(0, 2)].set_symbol("x"); -/// assert_eq!(buf[(0, 2)].symbol, "x"); -/// buf.set_string(3, 0, "string", Style::default().fg(Color::Red).bg(Color::White)); -/// assert_eq!(buf[(5, 0)], Cell{ -/// symbol: String::from("r"), -/// fg: Color::Red, -/// bg: Color::White, -/// modifier: Modifier::empty() -/// }); -/// buf[(5, 0)].set_char('x'); -/// assert_eq!(buf[(5, 0)].symbol, "x"); -/// ``` -#[derive(Debug, Default, Clone, PartialEq)] -pub struct Buffer { - /// The area represented by this buffer - pub area: Rect, - /// The content of the buffer. The length of this Vec should always be equal to area.width * - /// area.height - pub content: Vec<Cell>, -} +pub trait SurfaceExt { + // + fn set_style(&mut self, area: Rect, style: Style) {} -impl Buffer { - /// Returns a Buffer with all cells set to the default one - pub fn empty(area: Rect) -> Buffer { - let cell: Cell = Default::default(); - Buffer::filled(area, &cell) - } + fn clear_with(&mut self, area: Rect, style: Style) {} - /// Returns a Buffer with all cells initialized with the attributes of the given Cell - pub fn filled(area: Rect, cell: &Cell) -> Buffer { - let size = area.area() as usize; - let mut content = Vec::with_capacity(size); - for _ in 0..size { - content.push(cell.clone()); - } - Buffer { area, content } - } - - /// Returns a Buffer containing the given lines - pub fn with_lines<S>(lines: Vec<S>) -> Buffer - where - S: AsRef<str>, - { - let height = lines.len() as u16; - let width = lines - .iter() - .map(|i| i.as_ref().width() as u16) - .max() - .unwrap_or_default(); - let mut buffer = Buffer::empty(Rect { - x: 0, - y: 0, - width, - height, - }); - for (y, line) in lines.iter().enumerate() { - buffer.set_string(0, y as u16, line, Style::default()); - } - buffer - } - - /// Returns the content of the buffer as a slice - pub fn content(&self) -> &[Cell] { - &self.content - } - - /// Returns the area covered by this buffer - pub fn area(&self) -> &Rect { - &self.area - } - - /// Returns a reference to Cell at the given coordinates - pub fn get(&self, x: u16, y: u16) -> Option<&Cell> { - self.index_of_opt(x, y).map(|i| &self.content[i]) - } - - /// Returns a mutable reference to Cell at the given coordinates - pub fn get_mut(&mut self, x: u16, y: u16) -> Option<&mut Cell> { - self.index_of_opt(x, y).map(|i| &mut self.content[i]) - } - - /// Tells whether the global (x, y) coordinates are inside the Buffer's area. - /// - /// Global coordinates are offset by the Buffer's area offset (`x`/`y`). - /// - /// # Examples - /// - /// ``` - /// # use helix_tui::buffer::Buffer; - /// # use helix_view::graphics::Rect; - /// let rect = Rect::new(200, 100, 10, 10); - /// let buffer = Buffer::empty(rect); - /// // Global coordinates inside the Buffer's area - /// assert!(buffer.in_bounds(209, 100)); - /// // Global coordinates outside the Buffer's area - /// assert!(!buffer.in_bounds(210, 100)); - /// ``` - /// - /// Global coordinates are offset by the Buffer's area offset (`x`/`y`). - pub fn in_bounds(&self, x: u16, y: u16) -> bool { - x >= self.area.left() - && x < self.area.right() - && y >= self.area.top() - && y < self.area.bottom() - } - - /// Returns the index in the Vec<Cell> for the given global (x, y) coordinates. - /// - /// Global coordinates are offset by the Buffer's area offset (`x`/`y`). - /// - /// # Examples - /// - /// ``` - /// # use helix_tui::buffer::Buffer; - /// # use helix_view::graphics::Rect; - /// let rect = Rect::new(200, 100, 10, 10); - /// let buffer = Buffer::empty(rect); - /// // Global coordinates to the top corner of this Buffer's area - /// assert_eq!(buffer.index_of(200, 100), 0); - /// ``` - /// - /// # Panics - /// - /// Panics when given an coordinate that is outside of this Buffer's area. - pub fn index_of(&self, x: u16, y: u16) -> usize { - debug_assert!( - self.in_bounds(x, y), - "Trying to access position outside the buffer: x={}, y={}, area={:?}", - x, - y, - self.area - ); - ((y - self.area.y) * self.area.width + (x - self.area.x)) as usize - } - - /// Returns the index in the Vec<Cell> for the given global (x, y) coordinates, - /// or `None` if the coordinates are outside the buffer's area. - fn index_of_opt(&self, x: u16, y: u16) -> Option<usize> { - if self.in_bounds(x, y) { - Some(self.index_of(x, y)) - } else { - None - } - } - - /// Returns the (global) coordinates of a cell given its index - /// - /// Global coordinates are offset by the Buffer's area offset (`x`/`y`). - /// - /// # Examples - /// - /// ``` - /// # use helix_tui::buffer::Buffer; - /// # use helix_view::graphics::Rect; - /// let rect = Rect::new(200, 100, 10, 10); - /// let buffer = Buffer::empty(rect); - /// assert_eq!(buffer.pos_of(0), (200, 100)); - /// assert_eq!(buffer.pos_of(14), (204, 101)); - /// ``` - /// - /// # Panics - /// - /// Panics when given an index that is outside the Buffer's content. - pub fn pos_of(&self, i: usize) -> (u16, u16) { - debug_assert!( - i < self.content.len(), - "Trying to get the coords of a cell outside the buffer: i={} len={}", - i, - self.content.len() - ); - ( - self.area.x + i as u16 % self.area.width, - self.area.y + i as u16 / self.area.width, - ) - } - - /// Print a string, starting at the position (x, y) - pub fn set_string<S>(&mut self, x: u16, y: u16, string: S, style: Style) + fn set_string<S>(&mut self, x: u16, y: u16, string: S, style: Style) where S: AsRef<str>, { self.set_stringn(x, y, string, usize::MAX, style); } - /// Print at most the first n characters of a string if enough space is available - /// until the end of the line - pub fn set_stringn<S>( + fn set_stringn<S>( &mut self, x: u16, y: u16, @@ -285,137 +100,9 @@ impl Buffer { style: Style, ) -> (u16, u16) where - S: AsRef<str>, - { - self.set_string_truncated_at_end(x, y, string.as_ref(), width, style) - } - - /// Print at most the first `width` characters of a string if enough space is available - /// until the end of the line. If `ellipsis` is true appends a `…` at the end of - /// truncated lines. If `truncate_start` is `true`, truncate the beginning of the string - /// instead of the end. - #[allow(clippy::too_many_arguments)] - pub fn set_string_truncated( - &mut self, - x: u16, - y: u16, - string: &str, - width: usize, - style: impl Fn(usize) -> Style, // Map a grapheme's string offset to a style - ellipsis: bool, - truncate_start: bool, - ) -> (u16, u16) { - // prevent panic if out of range - if !self.in_bounds(x, y) || width == 0 { - return (x, y); - } - - let mut index = self.index_of(x, y); - let mut x_offset = x as usize; - let width = if ellipsis { width - 1 } else { width }; - let graphemes = string.grapheme_indices(true); - let max_offset = min(self.area.right() as usize, width.saturating_add(x as usize)); - if !truncate_start { - for (byte_offset, s) in graphemes { - let width = s.width(); - if width == 0 { - continue; - } - // `x_offset + width > max_offset` could be integer overflow on 32-bit machines if we - // change dimenstions to usize or u32 and someone resizes the terminal to 1x2^32. - if width > max_offset.saturating_sub(x_offset) { - break; - } - - self.content[index].set_symbol(s); - self.content[index].set_style(style(byte_offset)); - // Reset following cells if multi-width (they would be hidden by the grapheme), - for i in index + 1..index + width { - self.content[i].reset(); - } - index += width; - x_offset += width; - } - if ellipsis && x_offset - (x as usize) < string.width() { - self.content[index].set_symbol("…"); - } - } else { - let mut start_index = self.index_of(x, y); - let mut index = self.index_of(max_offset as u16, y); - - let total_width = string.width(); - let truncated = total_width > width; - if ellipsis && truncated { - self.content[start_index].set_symbol("…"); - start_index += 1; - } - if !truncated { - index -= width - total_width; - } - for (byte_offset, s) in graphemes.rev() { - let width = s.width(); - if width == 0 { - continue; - } - let start = index - width; - if start < start_index { - break; - } - self.content[start].set_symbol(s); - self.content[start].set_style(style(byte_offset)); - for i in start + 1..index { - self.content[i].reset(); - } - index -= width; - } - } - (x_offset as u16, y) - } + S: AsRef<str>; - /// Print at most the first `width` characters of a string if enough space is available - /// until the end of the line. - pub fn set_string_truncated_at_end( - &mut self, - x: u16, - y: u16, - string: &str, - width: usize, - style: Style, - ) -> (u16, u16) { - // prevent panic if out of range - if !self.in_bounds(x, y) { - return (x, y); - } - - let mut index = self.index_of(x, y); - let mut x_offset = x as usize; - let max_x_offset = min(self.area.right() as usize, width.saturating_add(x as usize)); - - for s in string.graphemes(true) { - let width = s.width(); - if width == 0 { - continue; - } - // `x_offset + width > max_offset` could be integer overflow on 32-bit machines if we - // change dimensions to usize or u32 and someone resizes the terminal to 1x2^32. - if width > max_x_offset.saturating_sub(x_offset) { - break; - } - - self.content[index].set_symbol(s); - self.content[index].set_style(style); - // Reset following cells if multi-width (they would be hidden by the grapheme), - for i in index + 1..index + width { - self.content[i].reset(); - } - index += width; - x_offset += width; - } - - (x_offset as u16, y) - } - - pub fn set_spans<'a>(&mut self, x: u16, y: u16, spans: &Spans<'a>, width: u16) -> (u16, u16) { + fn set_spans<'a>(&mut self, x: u16, y: u16, spans: &Spans<'a>, width: u16) -> (u16, u16) { let mut remaining_width = width; let mut x = x; for span in &spans.0 { @@ -436,449 +123,62 @@ impl Buffer { (x, y) } - pub fn set_span<'a>(&mut self, x: u16, y: u16, span: &Span<'a>, width: u16) -> (u16, u16) { + fn set_span<'a>(&mut self, x: u16, y: u16, span: &Span<'a>, width: u16) -> (u16, u16) { self.set_stringn(x, y, span.content.as_ref(), width as usize, span.style) } - #[deprecated( - since = "0.10.0", - note = "You should use styling capabilities of `Buffer::set_style`" - )] - pub fn set_background(&mut self, area: Rect, color: Color) { - for y in area.top()..area.bottom() { - for x in area.left()..area.right() { - self[(x, y)].set_bg(color); - } - } - } - - pub fn set_style(&mut self, area: Rect, style: Style) { - for y in area.top()..area.bottom() { - for x in area.left()..area.right() { - self[(x, y)].set_style(style); - } - } - } - - /// Resize the buffer so that the mapped area matches the given area and that the buffer - /// length is equal to area.width * area.height - 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, Default::default()); - } - self.area = area; - } - - /// Reset all cells in the buffer - pub fn reset(&mut self) { - for c in &mut self.content { - c.reset(); - } - } - - /// Clear an area in the buffer - pub fn clear(&mut self, area: Rect) { - for x in area.left()..area.right() { - for y in area.top()..area.bottom() { - self[(x, y)].reset(); - } - } - } - - /// Clear an area in the buffer with a default style. - pub fn clear_with(&mut self, area: Rect, style: Style) { - for x in area.left()..area.right() { - for y in area.top()..area.bottom() { - let cell = &mut self[(x, y)]; - cell.reset(); - cell.set_style(style); - } - } - } - - /// Merge an other buffer into this one - pub fn merge(&mut self, other: &Buffer) { - let area = self.area.union(other.area); - let cell: Cell = Default::default(); - self.content.resize(area.area() as usize, cell.clone()); - - // Move original content to the appropriate space - let size = self.area.area() as usize; - for i in (0..size).rev() { - let (x, y) = self.pos_of(i); - // New index in content - 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] = cell.clone(); - } - } - - // Push content of the other buffer into this one (may erase previous - // data) - let size = other.area.area() as usize; - for i in 0..size { - let (x, y) = other.pos_of(i); - // New index in content - let k = ((y - area.y) * area.width + x - area.x) as usize; - self.content[k] = other.content[i].clone(); - } - self.area = area; - } - - /// Builds a minimal sequence of coordinates and Cells necessary to update the UI from - /// self to other. - /// - /// We're assuming that buffers are well-formed, that is no double-width cell is followed by - /// a non-blank cell. - /// - /// # Multi-width characters handling: - /// - /// ```text - /// (Index:) `01` - /// Prev: `コ` - /// Next: `aa` - /// Updates: `0: a, 1: a' - /// ``` - /// - /// ```text - /// (Index:) `01` - /// Prev: `a ` - /// Next: `コ` - /// Updates: `0: コ` (double width symbol at index 0 - skip index 1) - /// ``` - /// - /// ```text - /// (Index:) `012` - /// Prev: `aaa` - /// Next: `aコ` - /// Updates: `0: a, 1: コ` (double width symbol at index 1 - skip index 2) - /// ``` - pub fn diff<'a>(&self, other: &'a Buffer) -> Vec<(u16, u16, &'a Cell)> { - let previous_buffer = &self.content; - let next_buffer = &other.content; - let width = self.area.width; - - let mut updates: Vec<(u16, u16, &Cell)> = vec![]; - // Cells invalidated by drawing/replacing preceeding multi-width characters: - let mut invalidated: usize = 0; - // Cells from the current buffer to skip due to preceeding multi-width characters taking their - // place (the skipped cells should be blank anyway): - let mut to_skip: usize = 0; - for (i, (current, previous)) in next_buffer.iter().zip(previous_buffer.iter()).enumerate() { - if (current != previous || invalidated > 0) && to_skip == 0 { - let x = i as u16 % width; - let y = i as u16 / width; - updates.push((x, y, &next_buffer[i])); - } - - let current_width = current.symbol.width(); - to_skip = current_width.saturating_sub(1); - - let affected_width = std::cmp::max(current_width, previous.symbol.width()); - invalidated = std::cmp::max(affected_width, invalidated).saturating_sub(1); - } - updates - } -} - -impl std::ops::Index<(u16, u16)> for Buffer { - type Output = Cell; - - fn index(&self, (x, y): (u16, u16)) -> &Self::Output { - let i = self.index_of(x, y); - &self.content[i] + fn set_string_truncated( + &mut self, + x: u16, + y: u16, + string: &str, + width: usize, + style: impl Fn(usize) -> Style, // Map a grapheme's string offset to a style + ellipsis: bool, + truncate_start: bool, + ) { + // TODO } -} -impl std::ops::IndexMut<(u16, u16)> for Buffer { - fn index_mut(&mut self, (x, y): (u16, u16)) -> &mut Self::Output { - let i = self.index_of(x, y); - &mut self.content[i] - } + fn get_mut(&mut self, x: u16, y: u16) -> Cell; } -#[cfg(test)] -mod tests { - use super::*; - - fn cell(s: &str) -> Cell { - let mut cell = Cell::default(); - cell.set_symbol(s); - cell - } - - #[test] - fn it_translates_to_and_from_coordinates() { - let rect = Rect::new(200, 100, 50, 80); - let buf = Buffer::empty(rect); - - // First cell is at the upper left corner. - assert_eq!(buf.pos_of(0), (200, 100)); - assert_eq!(buf.index_of(200, 100), 0); - - // Last cell is in the lower right. - 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")] - #[cfg(debug_assertions)] - fn pos_of_panics_on_out_of_bounds() { - let rect = Rect::new(0, 0, 10, 10); - let buf = Buffer::empty(rect); - - // There are a total of 100 cells; zero-indexed means that 100 would be the 101st cell. - buf.pos_of(100); - } - - #[test] - #[should_panic(expected = "outside the buffer")] - #[cfg(debug_assertions)] - fn index_of_panics_on_out_of_bounds() { - let rect = Rect::new(0, 0, 10, 10); - let buf = Buffer::empty(rect); - - // width is 10; zero-indexed means that 10 would be the 11th cell. - buf.index_of(10, 0); - } - - #[test] - fn buffer_set_string() { - let area = Rect::new(0, 0, 5, 1); - let mut buffer = Buffer::empty(area); - - // Zero-width - buffer.set_stringn(0, 0, "aaa", 0, Style::default()); - assert_eq!(buffer, Buffer::with_lines(vec![" "])); - - buffer.set_string(0, 0, "aaa", Style::default()); - assert_eq!(buffer, Buffer::with_lines(vec!["aaa "])); - - // Width limit: - buffer.set_stringn(0, 0, "bbbbbbbbbbbbbb", 4, Style::default()); - assert_eq!(buffer, Buffer::with_lines(vec!["bbbb "])); - - buffer.set_string(0, 0, "12345", Style::default()); - assert_eq!(buffer, Buffer::with_lines(vec!["12345"])); - - // Width truncation: - buffer.set_string(0, 0, "123456", Style::default()); - assert_eq!(buffer, Buffer::with_lines(vec!["12345"])); - } - - #[test] - fn buffer_set_string_zero_width() { - let area = Rect::new(0, 0, 1, 1); - let mut buffer = Buffer::empty(area); - - // Leading grapheme with zero width - let s = "\u{1}a"; - buffer.set_stringn(0, 0, s, 1, Style::default()); - assert_eq!(buffer, Buffer::with_lines(vec!["a"])); - - // Trailing grapheme with zero with - let s = "a\u{1}"; - buffer.set_stringn(0, 0, s, 1, Style::default()); - assert_eq!(buffer, Buffer::with_lines(vec!["a"])); - } - - #[test] - fn buffer_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(vec!["コン "])); - - // Only 1 space left. - buffer.set_string(0, 0, "コンピ", Style::default()); - assert_eq!(buffer, Buffer::with_lines(vec!["コン "])); - } - - #[test] - fn buffer_with_lines() { - let buffer = - Buffer::with_lines(vec!["┌────────┐", "│コンピュ│", "│ー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 buffer_diffing_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, vec![]); - } - - #[test] - fn buffer_diffing_empty_filled() { - let area = Rect::new(0, 0, 40, 40); - let prev = Buffer::empty(area); - let next = Buffer::filled(area, Cell::default().set_symbol("a")); - let diff = prev.diff(&next); - assert_eq!(diff.len(), 40 * 40); - } - - #[test] - fn buffer_diffing_filled_filled() { - let area = Rect::new(0, 0, 40, 40); - let prev = Buffer::filled(area, Cell::default().set_symbol("a")); - let next = Buffer::filled(area, Cell::default().set_symbol("a")); - let diff = prev.diff(&next); - assert_eq!(diff, vec![]); - } - - #[test] - fn buffer_diffing_single_width() { - let prev = Buffer::with_lines(vec![ - " ", - "┌Title─┐ ", - "│ │ ", - "│ │ ", - "└──────┘ ", - ]); - let next = Buffer::with_lines(vec![ - " ", - "┌TITLE─┐ ", - "│ │ ", - "│ │ ", - "└──────┘ ", - ]); - let diff = prev.diff(&next); - assert_eq!( - diff, - vec![ - (2, 1, &cell("I")), - (3, 1, &cell("T")), - (4, 1, &cell("L")), - (5, 1, &cell("E")), - ] - ); - } - - #[test] - #[rustfmt::skip] - fn buffer_diffing_multi_width() { - let prev = Buffer::with_lines(vec![ - "┌Title─┐ ", - "└──────┘ ", - ]); - let next = Buffer::with_lines(vec![ - "┌称号──┐ ", - "└──────┘ ", - ]); - let diff = prev.diff(&next); - assert_eq!( - diff, - vec![ - (1, 0, &cell("称")), - // Skipped "i" - (3, 0, &cell("号")), - // Skipped "l" - (5, 0, &cell("─")), - ] - ); - } - - #[test] - fn buffer_diffing_multi_width_offset() { - let prev = Buffer::with_lines(vec!["┌称号──┐"]); - let next = Buffer::with_lines(vec!["┌─称号─┐"]); +impl SurfaceExt for termwiz::surface::Surface { + // + //fn set_style(&mut self, area: Rect, style: Style) { + // // + //} - let diff = prev.diff(&next); - assert_eq!( - diff, - vec![(1, 0, &cell("─")), (2, 0, &cell("称")), (4, 0, &cell("号")),] - ); - } - - #[test] - fn buffer_merge() { - let mut one = Buffer::filled( - Rect { - x: 0, - y: 0, - width: 2, - height: 2, - }, - Cell::default().set_symbol("1"), - ); - let two = Buffer::filled( - Rect { - x: 0, - y: 2, - width: 2, - height: 2, - }, - Cell::default().set_symbol("2"), - ); - one.merge(&two); - assert_eq!(one, Buffer::with_lines(vec!["11", "11", "22", "22"])); - } + fn set_stringn<S>( + &mut self, + x: u16, + y: u16, + string: S, + width: usize, + style: Style, + ) -> (u16, u16) + where + S: AsRef<str>, + { + // TODO: style and limit to width + self.add_change(Change::CursorPosition { + x: Position::Absolute(x as usize), + y: Position::Absolute(y as usize), + }); + let fg = style.fg.unwrap_or(Color::Reset); + self.add_change(Change::Attribute(AttributeChange::Foreground(fg.into()))); + let bg = style.bg.unwrap_or(Color::Reset); + self.add_change(Change::Attribute(AttributeChange::Background(bg.into()))); + self.add_change(Change::Text(string.as_ref().to_owned())); - #[test] - fn buffer_merge2() { - let mut one = Buffer::filled( - Rect { - x: 2, - y: 2, - width: 2, - height: 2, - }, - Cell::default().set_symbol("1"), - ); - let two = Buffer::filled( - Rect { - x: 0, - y: 0, - width: 2, - height: 2, - }, - Cell::default().set_symbol("2"), - ); - one.merge(&two); - assert_eq!( - one, - Buffer::with_lines(vec!["22 ", "22 ", " 11", " 11"]) - ); + (0, 0) } - #[test] - fn buffer_merge3() { - let mut one = Buffer::filled( - Rect { - x: 3, - y: 3, - width: 2, - height: 2, - }, - Cell::default().set_symbol("1"), - ); - let two = Buffer::filled( - Rect { - x: 1, - y: 1, - width: 3, - height: 4, - }, - Cell::default().set_symbol("2"), - ); - one.merge(&two); - let mut merged = Buffer::with_lines(vec!["222 ", "222 ", "2221", "2221"]); - merged.area = Rect { - x: 1, - y: 1, - width: 4, - height: 4, - }; - assert_eq!(one, merged); + fn get_mut(&mut self, x: u16, y: u16) -> Cell { + self.add_change(Change::CursorPosition { + x: Position::Absolute(x as usize), + y: Position::Absolute(y as usize), + }); + Cell { surface: self } } } |