on 376c19e help sign in
helix/helix-tui/src/buffer.rs
902 lines 28 kB history raw
1use crate::text::{Span, Spans};
2use helix_core::unicode::width::UnicodeWidthStr;
3use std::cmp::min;
4use unicode_segmentation::UnicodeSegmentation;
6use helix_view::graphics::{Color, Modifier, Rect, Style, UnderlineStyle};
8/// A buffer cell
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub struct Cell {
11 pub symbol: String,
12 pub fg: Color,
13 pub bg: Color,
14 pub underline_color: Color,
15 pub underline_style: UnderlineStyle,
16 pub modifier: Modifier,
19impl Cell {
20 pub fn set_symbol(&mut self, symbol: &str) -> &mut Cell {
21 self.symbol.clear();
22 self.symbol.push_str(symbol);
23 self
24 }
26 pub fn set_char(&mut self, ch: char) -> &mut Cell {
27 self.symbol.clear();
28 self.symbol.push(ch);
29 self
30 }
32 pub fn set_fg(&mut self, color: Color) -> &mut Cell {
33 self.fg = color;
34 self
35 }
37 pub fn set_bg(&mut self, color: Color) -> &mut Cell {
38 self.bg = color;
39 self
40 }
42 pub fn set_style(&mut self, style: Style) -> &mut Cell {
43 if let Some(c) = style.fg {
44 self.fg = c;
45 }
46 if let Some(c) = style.bg {
47 self.bg = c;
48 }
49 if let Some(c) = style.underline_color {
50 self.underline_color = c;
51 }
52 if let Some(style) = style.underline_style {
53 self.underline_style = style;
54 }
56 self.modifier.insert(style.add_modifier);
57 self.modifier.remove(style.sub_modifier);
58 self
59 }
61 pub fn style(&self) -> Style {
62 Style::default()
63 .fg(self.fg)
64 .bg(self.bg)
65 .underline_color(self.underline_color)
66 .underline_style(self.underline_style)
67 .add_modifier(self.modifier)
68 }
70 pub fn reset(&mut self) {
71 self.symbol.clear();
72 self.symbol.push(' ');
73 self.fg = Color::Reset;
74 self.bg = Color::Reset;
75 self.underline_color = Color::Reset;
76 self.underline_style = UnderlineStyle::Reset;
77 self.modifier = Modifier::empty();
78 }
81impl Default for Cell {
82 fn default() -> Cell {
83 Cell {
84 symbol: " ".into(),
85 fg: Color::Reset,
86 bg: Color::Reset,
87 underline_color: Color::Reset,
88 underline_style: UnderlineStyle::Reset,
89 modifier: Modifier::empty(),
90 }
91 }
94/// A buffer that maps to the desired content of the terminal after the draw call
95///
96/// No widget in the library interacts directly with the terminal. Instead each of them is required
97/// to draw their state to an intermediate buffer. It is basically a grid where each cell contains
98/// a grapheme, a foreground color and a background color. This grid will then be used to output
99/// the appropriate escape sequences and characters to draw the UI as the user has defined it.
100///
101/// # Examples:
102///
103/// ```
104/// use helix_tui::buffer::{Buffer, Cell};
105/// use helix_view::graphics::{Rect, Color, UnderlineStyle, Style, Modifier};
106///
107/// let mut buf = Buffer::empty(Rect{x: 0, y: 0, width: 10, height: 5});
108/// buf[(0, 2)].set_symbol("x");
109/// assert_eq!(buf[(0, 2)].symbol, "x");
110/// buf.set_string(3, 0, "string", Style::default().fg(Color::Red).bg(Color::White));
111/// assert_eq!(buf[(5, 0)], Cell{
112/// symbol: String::from("r"),
113/// fg: Color::Red,
114/// bg: Color::White,
115/// underline_color: Color::Reset,
116/// underline_style: UnderlineStyle::Reset,
117/// modifier: Modifier::empty(),
118/// });
119/// buf[(5, 0)].set_char('x');
120/// assert_eq!(buf[(5, 0)].symbol, "x");
121/// ```
122#[derive(Debug, Default, Clone, PartialEq, Eq)]
123pub struct Buffer {
124 /// The area represented by this buffer
125 pub area: Rect,
126 /// The content of the buffer. The length of this Vec should always be equal to area.width *
127 /// area.height
128 pub content: Vec<Cell>,
131impl Buffer {
132 /// Returns a Buffer with all cells set to the default one
133 pub fn empty(area: Rect) -> Buffer {
134 let cell: Cell = Default::default();
135 Buffer::filled(area, &cell)
136 }
138 /// Returns a Buffer with all cells initialized with the attributes of the given Cell
139 pub fn filled(area: Rect, cell: &Cell) -> Buffer {
140 let size = area.area();
141 let mut content = Vec::with_capacity(size);
142 for _ in 0..size {
143 content.push(cell.clone());
144 }
145 Buffer { area, content }
146 }
148 /// Returns a Buffer containing the given lines
149 pub fn with_lines<S>(lines: Vec<S>) -> Buffer
150 where
151 S: AsRef<str>,
152 {
153 let height = lines.len() as u16;
154 let width = lines
155 .iter()
156 .map(|i| i.as_ref().width() as u16)
157 .max()
158 .unwrap_or_default();
159 let mut buffer = Buffer::empty(Rect {
160 x: 0,
161 y: 0,
162 width,
163 height,
164 });
165 for (y, line) in lines.iter().enumerate() {
166 buffer.set_string(0, y as u16, line, Style::default());
167 }
168 buffer
169 }
171 /// Returns the content of the buffer as a slice
172 pub fn content(&self) -> &[Cell] {
173 &self.content
174 }
176 /// Returns the area covered by this buffer
177 pub fn area(&self) -> &Rect {
178 &self.area
179 }
181 /// Returns a reference to Cell at the given coordinates
182 pub fn get(&self, x: u16, y: u16) -> Option<&Cell> {
183 self.index_of_opt(x, y).map(|i| &self.content[i])
184 }
186 /// Returns a mutable reference to Cell at the given coordinates
187 pub fn get_mut(&mut self, x: u16, y: u16) -> Option<&mut Cell> {
188 self.index_of_opt(x, y).map(|i| &mut self.content[i])
189 }
191 /// Tells whether the global (x, y) coordinates are inside the Buffer's area.
192 ///
193 /// Global coordinates are offset by the Buffer's area offset (`x`/`y`).
194 ///
195 /// # Examples
196 ///
197 /// ```
198 /// # use helix_tui::buffer::Buffer;
199 /// # use helix_view::graphics::Rect;
200 /// let rect = Rect::new(200, 100, 10, 10);
201 /// let buffer = Buffer::empty(rect);
202 /// // Global coordinates inside the Buffer's area
203 /// assert!(buffer.in_bounds(209, 100));
204 /// // Global coordinates outside the Buffer's area
205 /// assert!(!buffer.in_bounds(210, 100));
206 /// ```
207 ///
208 /// Global coordinates are offset by the Buffer's area offset (`x`/`y`).
209 pub fn in_bounds(&self, x: u16, y: u16) -> bool {
210 x >= self.area.left()
211 && x < self.area.right()
212 && y >= self.area.top()
213 && y < self.area.bottom()
214 }
216 /// Returns the index in the Vec<Cell> for the given global (x, y) coordinates.
217 ///
218 /// Global coordinates are offset by the Buffer's area offset (`x`/`y`).
219 ///
220 /// # Examples
221 ///
222 /// ```
223 /// # use helix_tui::buffer::Buffer;
224 /// # use helix_view::graphics::Rect;
225 /// let rect = Rect::new(200, 100, 10, 10);
226 /// let buffer = Buffer::empty(rect);
227 /// // Global coordinates to the top corner of this Buffer's area
228 /// assert_eq!(buffer.index_of(200, 100), 0);
229 /// ```
230 ///
231 /// # Panics
232 ///
233 /// Panics when given an coordinate that is outside of this Buffer's area.
234 pub fn index_of(&self, x: u16, y: u16) -> usize {
235 debug_assert!(
236 self.in_bounds(x, y),
237 "Trying to access position outside the buffer: x={}, y={}, area={:?}",
238 x,
239 y,
240 self.area
241 );
242 ((y - self.area.y) as usize) * (self.area.width as usize) + ((x - self.area.x) as usize)
243 }
245 /// Returns the index in the Vec<Cell> for the given global (x, y) coordinates,
246 /// or `None` if the coordinates are outside the buffer's area.
247 fn index_of_opt(&self, x: u16, y: u16) -> Option<usize> {
248 if self.in_bounds(x, y) {
249 Some(self.index_of(x, y))
250 } else {
251 None
252 }
253 }
255 /// Returns the (global) coordinates of a cell given its index
256 ///
257 /// Global coordinates are offset by the Buffer's area offset (`x`/`y`).
258 ///
259 /// # Examples
260 ///
261 /// ```
262 /// # use helix_tui::buffer::Buffer;
263 /// # use helix_view::graphics::Rect;
264 /// let rect = Rect::new(200, 100, 10, 10);
265 /// let buffer = Buffer::empty(rect);
266 /// assert_eq!(buffer.pos_of(0), (200, 100));
267 /// assert_eq!(buffer.pos_of(14), (204, 101));
268 /// ```
269 ///
270 /// # Panics
271 ///
272 /// Panics when given an index that is outside the Buffer's content.
273 pub fn pos_of(&self, i: usize) -> (u16, u16) {
274 debug_assert!(
275 i < self.content.len(),
276 "Trying to get the coords of a cell outside the buffer: i={} len={}",
277 i,
278 self.content.len()
279 );
280 (
281 (self.area.x as usize + (i % self.area.width as usize)) as u16,
282 (self.area.y as usize + (i / self.area.width as usize)) as u16,
283 )
284 }
286 /// Print a string, starting at the position (x, y)
287 pub fn set_string<S>(&mut self, x: u16, y: u16, string: S, style: Style)
288 where
289 S: AsRef<str>,
290 {
291 self.set_stringn(x, y, string, usize::MAX, style);
292 }
294 /// Print at most the first n characters of a string if enough space is available
295 /// until the end of the line
296 pub fn set_stringn<S>(
297 &mut self,
298 x: u16,
299 y: u16,
300 string: S,
301 width: usize,
302 style: Style,
303 ) -> (u16, u16)
304 where
305 S: AsRef<str>,
306 {
307 self.set_string_truncated_at_end(x, y, string.as_ref(), width, style)
308 }
310 /// Print at most the first `width` characters of a string if enough space is available
311 /// until the end of the line. If `ellipsis` is true appends a `…` at the end of
312 /// truncated lines. If `truncate_start` is `true`, truncate the beginning of the string
313 /// instead of the end.
314 #[allow(clippy::too_many_arguments)]
315 pub fn set_string_truncated(
316 &mut self,
317 x: u16,
318 y: u16,
319 string: &str,
320 width: usize,
321 style: impl Fn(usize) -> Style, // Map a grapheme's string offset to a style
322 ellipsis: bool,
323 truncate_start: bool,
324 ) -> (u16, u16) {
325 // prevent panic if out of range
326 if !self.in_bounds(x, y) || width == 0 {
327 return (x, y);
328 }
330 let mut index = self.index_of(x, y);
331 let mut x_offset = x as usize;
332 let width = if ellipsis { width - 1 } else { width };
333 let graphemes = string.grapheme_indices(true);
334 let max_offset = min(self.area.right() as usize, width.saturating_add(x as usize));
335 if !truncate_start {
336 for (byte_offset, s) in graphemes {
337 let width = s.width();
338 if width == 0 {
339 continue;
340 }
341 // `x_offset + width > max_offset` could be integer overflow on 32-bit machines if we
342 // change dimensions to usize or u32 and someone resizes the terminal to 1x2^32.
343 if width > max_offset.saturating_sub(x_offset) {
344 break;
345 }
347 self.content[index].set_symbol(s);
348 self.content[index].set_style(style(byte_offset));
349 // Reset following cells if multi-width (they would be hidden by the grapheme),
350 for i in index + 1..index + width {
351 self.content[i].reset();
352 }
353 index += width;
354 x_offset += width;
355 }
356 if ellipsis && x_offset - (x as usize) < string.width() {
357 self.content[index].set_symbol("…");
358 }
359 } else {
360 let mut start_index = self.index_of(x, y);
361 let mut index = self.index_of(max_offset as u16, y);
363 let content_width = string.width();
364 let truncated = content_width > width;
365 if ellipsis && truncated {
366 self.content[start_index].set_symbol("…");
367 start_index += 1;
368 }
369 if !truncated {
370 index -= width - content_width;
371 }
372 for (byte_offset, s) in graphemes.rev() {
373 let width = s.width();
374 if width == 0 {
375 continue;
376 }
377 let start = index - width;
378 if start < start_index {
379 break;
380 }
381 self.content[start].set_symbol(s);
382 self.content[start].set_style(style(byte_offset));
383 for i in start + 1..index {
384 self.content[i].reset();
385 }
386 index -= width;
387 x_offset += width;
388 }
389 }
390 (x_offset as u16, y)
391 }
393 /// Print at most the first `width` characters of a string if enough space is available
394 /// until the end of the line.
395 pub fn set_string_truncated_at_end(
396 &mut self,
397 x: u16,
398 y: u16,
399 string: &str,
400 width: usize,
401 style: Style,
402 ) -> (u16, u16) {
403 // prevent panic if out of range
404 if !self.in_bounds(x, y) {
405 return (x, y);
406 }
408 let mut index = self.index_of(x, y);
409 let mut x_offset = x as usize;
410 let max_x_offset = min(self.area.right() as usize, width.saturating_add(x as usize));
412 for s in string.graphemes(true) {
413 let width = s.width();
414 if width == 0 {
415 continue;
416 }
417 // `x_offset + width > max_offset` could be integer overflow on 32-bit machines if we
418 // change dimensions to usize or u32 and someone resizes the terminal to 1x2^32.
419 if width > max_x_offset.saturating_sub(x_offset) {
420 break;
421 }
423 self.content[index].set_symbol(s);
424 self.content[index].set_style(style);
425 // Reset following cells if multi-width (they would be hidden by the grapheme),
426 for i in index + 1..index + width {
427 self.content[i].reset();
428 }
429 index += width;
430 x_offset += width;
431 }
433 (x_offset as u16, y)
434 }
436 pub fn set_spans(&mut self, x: u16, y: u16, spans: &Spans, width: u16) -> (u16, u16) {
437 let mut remaining_width = width;
438 let mut x = x;
439 for span in &spans.0 {
440 if remaining_width == 0 {
441 break;
442 }
443 let pos = self.set_stringn(
444 x,
445 y,
446 span.content.as_ref(),
447 remaining_width as usize,
448 span.style,
449 );
450 let w = pos.0.saturating_sub(x);
451 x = pos.0;
452 remaining_width = remaining_width.saturating_sub(w);
453 }
454 (x, y)
455 }
457 pub fn set_span(&mut self, x: u16, y: u16, span: &Span, width: u16) -> (u16, u16) {
458 self.set_stringn(x, y, span.content.as_ref(), width as usize, span.style)
459 }
461 #[deprecated(
462 since = "0.10.0",
463 note = "You should use styling capabilities of `Buffer::set_style`"
464 )]
465 pub fn set_background(&mut self, area: Rect, color: Color) {
466 for y in area.top()..area.bottom() {
467 for x in area.left()..area.right() {
468 self[(x, y)].set_bg(color);
469 }
470 }
471 }
473 pub fn set_style(&mut self, area: Rect, style: Style) {
474 for y in area.top()..area.bottom() {
475 for x in area.left()..area.right() {
476 self[(x, y)].set_style(style);
477 }
478 }
479 }
481 /// Resize the buffer so that the mapped area matches the given area and that the buffer
482 /// length is equal to area.width * area.height
483 pub fn resize(&mut self, area: Rect) {
484 let length = area.area();
485 if self.content.len() > length {
486 self.content.truncate(length);
487 } else {
488 self.content.resize(length, Default::default());
489 }
490 self.area = area;
491 }
493 /// Reset all cells in the buffer
494 pub fn reset(&mut self) {
495 for c in &mut self.content {
496 c.reset();
497 }
498 }
500 /// Clear an area in the buffer
501 pub fn clear(&mut self, area: Rect) {
502 for x in area.left()..area.right() {
503 for y in area.top()..area.bottom() {
504 self[(x, y)].reset();
505 }
506 }
507 }
509 /// Clear an area in the buffer with a default style.
510 pub fn clear_with(&mut self, area: Rect, style: Style) {
511 for x in area.left()..area.right() {
512 for y in area.top()..area.bottom() {
513 let cell = &mut self[(x, y)];
514 cell.reset();
515 cell.set_style(style);
516 }
517 }
518 }
520 /// Merge an other buffer into this one
521 pub fn merge(&mut self, other: &Buffer) {
522 let area = self.area.union(other.area);
523 let cell: Cell = Default::default();
524 self.content.resize(area.area(), cell.clone());
526 // Move original content to the appropriate space
527 let size = self.area.area();
528 for i in (0..size).rev() {
529 let (x, y) = self.pos_of(i);
530 // New index in content
531 let k = ((y - area.y) * area.width + x - area.x) as usize;
532 if i != k {
533 self.content[k] = self.content[i].clone();
534 self.content[i] = cell.clone();
535 }
536 }
538 // Push content of the other buffer into this one (may erase previous
539 // data)
540 let size = other.area.area();
541 for i in 0..size {
542 let (x, y) = other.pos_of(i);
543 // New index in content
544 let k = ((y - area.y) * area.width + x - area.x) as usize;
545 self.content[k] = other.content[i].clone();
546 }
547 self.area = area;
548 }
550 /// Builds a minimal sequence of coordinates and Cells necessary to update the UI from
551 /// self to other.
552 ///
553 /// We're assuming that buffers are well-formed, that is no double-width cell is followed by
554 /// a non-blank cell.
555 ///
556 /// # Multi-width characters handling:
557 ///
558 /// ```text
559 /// (Index:) `01`
560 /// Prev: `コ`
561 /// Next: `aa`
562 /// Updates: `0: a, 1: a'
563 /// ```
564 ///
565 /// ```text
566 /// (Index:) `01`
567 /// Prev: `a `
568 /// Next: `コ`
569 /// Updates: `0: コ` (double width symbol at index 0 - skip index 1)
570 /// ```
571 ///
572 /// ```text
573 /// (Index:) `012`
574 /// Prev: `aaa`
575 /// Next: `aコ`
576 /// Updates: `0: a, 1: コ` (double width symbol at index 1 - skip index 2)
577 /// ```
578 pub fn diff<'a>(&self, other: &'a Buffer) -> Vec<(u16, u16, &'a Cell)> {
579 let previous_buffer = &self.content;
580 let next_buffer = &other.content;
581 let width = self.area.width;
583 let mut updates: Vec<(u16, u16, &Cell)> = vec![];
584 // Cells invalidated by drawing/replacing preceding multi-width characters:
585 let mut invalidated: usize = 0;
586 // Cells from the current buffer to skip due to preceding multi-width characters taking their
587 // place (the skipped cells should be blank anyway):
588 let mut to_skip: usize = 0;
589 for (i, (current, previous)) in next_buffer.iter().zip(previous_buffer.iter()).enumerate() {
590 if (current != previous || invalidated > 0) && to_skip == 0 {
591 let x = (i % width as usize) as u16;
592 let y = (i / width as usize) as u16;
593 updates.push((x, y, &next_buffer[i]));
594 }
596 let current_width = current.symbol.width();
597 to_skip = current_width.saturating_sub(1);
599 let affected_width = std::cmp::max(current_width, previous.symbol.width());
600 invalidated = std::cmp::max(affected_width, invalidated).saturating_sub(1);
601 }
602 updates
603 }
606impl std::ops::Index<(u16, u16)> for Buffer {
607 type Output = Cell;
609 fn index(&self, (x, y): (u16, u16)) -> &Self::Output {
610 let i = self.index_of(x, y);
611 &self.content[i]
612 }
615impl std::ops::IndexMut<(u16, u16)> for Buffer {
616 fn index_mut(&mut self, (x, y): (u16, u16)) -> &mut Self::Output {
617 let i = self.index_of(x, y);
618 &mut self.content[i]
619 }
622#[cfg(test)]
623mod tests {
624 use super::*;
626 fn cell(s: &str) -> Cell {
627 let mut cell = Cell::default();
628 cell.set_symbol(s);
629 cell
630 }
632 #[test]
633 fn it_translates_to_and_from_coordinates() {
634 let rect = Rect::new(200, 100, 50, 80);
635 let buf = Buffer::empty(rect);
637 // First cell is at the upper left corner.
638 assert_eq!(buf.pos_of(0), (200, 100));
639 assert_eq!(buf.index_of(200, 100), 0);
641 // Last cell is in the lower right.
642 assert_eq!(buf.pos_of(buf.content.len() - 1), (249, 179));
643 assert_eq!(buf.index_of(249, 179), buf.content.len() - 1);
644 }
646 #[test]
647 #[should_panic(expected = "outside the buffer")]
648 #[cfg(debug_assertions)]
649 fn pos_of_panics_on_out_of_bounds() {
650 let rect = Rect::new(0, 0, 10, 10);
651 let buf = Buffer::empty(rect);
653 // There are a total of 100 cells; zero-indexed means that 100 would be the 101st cell.
654 buf.pos_of(100);
655 }
657 #[test]
658 #[should_panic(expected = "outside the buffer")]
659 #[cfg(debug_assertions)]
660 fn index_of_panics_on_out_of_bounds() {
661 let rect = Rect::new(0, 0, 10, 10);
662 let buf = Buffer::empty(rect);
664 // width is 10; zero-indexed means that 10 would be the 11th cell.
665 buf.index_of(10, 0);
666 }
668 #[test]
669 fn buffer_set_string() {
670 let area = Rect::new(0, 0, 5, 1);
671 let mut buffer = Buffer::empty(area);
673 // Zero-width
674 buffer.set_stringn(0, 0, "aaa", 0, Style::default());
675 assert_eq!(buffer, Buffer::with_lines(vec![" "]));
677 buffer.set_string(0, 0, "aaa", Style::default());
678 assert_eq!(buffer, Buffer::with_lines(vec!["aaa "]));
680 // Width limit:
681 buffer.set_stringn(0, 0, "bbbbbbbbbbbbbb", 4, Style::default());
682 assert_eq!(buffer, Buffer::with_lines(vec!["bbbb "]));
684 buffer.set_string(0, 0, "12345", Style::default());
685 assert_eq!(buffer, Buffer::with_lines(vec!["12345"]));
687 // Width truncation:
688 buffer.set_string(0, 0, "123456", Style::default());
689 assert_eq!(buffer, Buffer::with_lines(vec!["12345"]));
690 }
692 #[test]
693 fn buffer_set_string_zero_width() {
694 let area = Rect::new(0, 0, 1, 1);
695 let mut buffer = Buffer::empty(area);
697 // Leading grapheme with zero width
698 let s = "\u{1}a";
699 buffer.set_stringn(0, 0, s, 1, Style::default());
700 assert_eq!(buffer, Buffer::with_lines(vec!["a"]));
702 // Trailing grapheme with zero with
703 let s = "a\u{1}";
704 buffer.set_stringn(0, 0, s, 1, Style::default());
705 assert_eq!(buffer, Buffer::with_lines(vec!["a"]));
706 }
708 #[test]
709 fn buffer_set_string_double_width() {
710 let area = Rect::new(0, 0, 5, 1);
711 let mut buffer = Buffer::empty(area);
712 buffer.set_string(0, 0, "コン", Style::default());
713 assert_eq!(buffer, Buffer::with_lines(vec!["コン "]));
715 // Only 1 space left.
716 buffer.set_string(0, 0, "コンピ", Style::default());
717 assert_eq!(buffer, Buffer::with_lines(vec!["コン "]));
718 }
720 #[test]
721 fn buffer_with_lines() {
722 let buffer =
723 Buffer::with_lines(vec!["┌────────┐", "│コンピュ│", "│ーa 上で│", "└────────┘"]);
724 assert_eq!(buffer.area.x, 0);
725 assert_eq!(buffer.area.y, 0);
726 assert_eq!(buffer.area.width, 10);
727 assert_eq!(buffer.area.height, 4);
728 }
730 #[test]
731 fn buffer_diffing_empty_empty() {
732 let area = Rect::new(0, 0, 40, 40);
733 let prev = Buffer::empty(area);
734 let next = Buffer::empty(area);
735 let diff = prev.diff(&next);
736 assert_eq!(diff, vec![]);
737 }
739 #[test]
740 fn buffer_diffing_empty_filled() {
741 let area = Rect::new(0, 0, 40, 40);
742 let prev = Buffer::empty(area);
743 let next = Buffer::filled(area, Cell::default().set_symbol("a"));
744 let diff = prev.diff(&next);
745 assert_eq!(diff.len(), 40 * 40);
746 }
748 #[test]
749 fn buffer_diffing_filled_filled() {
750 let area = Rect::new(0, 0, 40, 40);
751 let prev = Buffer::filled(area, Cell::default().set_symbol("a"));
752 let next = Buffer::filled(area, Cell::default().set_symbol("a"));
753 let diff = prev.diff(&next);
754 assert_eq!(diff, vec![]);
755 }
757 #[test]
758 fn buffer_diffing_single_width() {
759 let prev = Buffer::with_lines(vec![
760 " ",
761 "┌Title─┐ ",
762 "│ │ ",
763 "│ │ ",
764 "└──────┘ ",
765 ]);
766 let next = Buffer::with_lines(vec![
767 " ",
768 "┌TITLE─┐ ",
769 "│ │ ",
770 "│ │ ",
771 "└──────┘ ",
772 ]);
773 let diff = prev.diff(&next);
774 assert_eq!(
775 diff,
776 vec![
777 (2, 1, &cell("I")),
778 (3, 1, &cell("T")),
779 (4, 1, &cell("L")),
780 (5, 1, &cell("E")),
781 ]
782 );
783 }
785 #[test]
786 #[rustfmt::skip]
787 fn buffer_diffing_multi_width() {
788 let prev = Buffer::with_lines(vec![
789 "┌Title─┐ ",
790 "└──────┘ ",
791 ]);
792 let next = Buffer::with_lines(vec![
793 "┌称号──┐ ",
794 "└──────┘ ",
795 ]);
796 let diff = prev.diff(&next);
797 assert_eq!(
798 diff,
799 vec![
800 (1, 0, &cell("称")),
801 // Skipped "i"
802 (3, 0, &cell("号")),
803 // Skipped "l"
804 (5, 0, &cell("─")),
805 ]
806 );
807 }
809 #[test]
810 fn buffer_diffing_multi_width_offset() {
811 let prev = Buffer::with_lines(vec!["┌称号──┐"]);
812 let next = Buffer::with_lines(vec!["┌─称号─┐"]);
814 let diff = prev.diff(&next);
815 assert_eq!(
816 diff,
817 vec![(1, 0, &cell("─")), (2, 0, &cell("称")), (4, 0, &cell("号")),]
818 );
819 }
821 #[test]
822 fn buffer_merge() {
823 let mut one = Buffer::filled(
824 Rect {
825 x: 0,
826 y: 0,
827 width: 2,
828 height: 2,
829 },
830 Cell::default().set_symbol("1"),
831 );
832 let two = Buffer::filled(
833 Rect {
834 x: 0,
835 y: 2,
836 width: 2,
837 height: 2,
838 },
839 Cell::default().set_symbol("2"),
840 );
841 one.merge(&two);
842 assert_eq!(one, Buffer::with_lines(vec!["11", "11", "22", "22"]));
843 }
845 #[test]
846 fn buffer_merge2() {
847 let mut one = Buffer::filled(
848 Rect {
849 x: 2,
850 y: 2,
851 width: 2,
852 height: 2,
853 },
854 Cell::default().set_symbol("1"),
855 );
856 let two = Buffer::filled(
857 Rect {
858 x: 0,
859 y: 0,
860 width: 2,
861 height: 2,
862 },
863 Cell::default().set_symbol("2"),
864 );
865 one.merge(&two);
866 assert_eq!(
867 one,
868 Buffer::with_lines(vec!["22 ", "22 ", " 11", " 11"])
869 );
870 }
872 #[test]
873 fn buffer_merge3() {
874 let mut one = Buffer::filled(
875 Rect {
876 x: 3,
877 y: 3,
878 width: 2,
879 height: 2,
880 },
881 Cell::default().set_symbol("1"),
882 );
883 let two = Buffer::filled(
884 Rect {
885 x: 1,
886 y: 1,
887 width: 3,
888 height: 4,
889 },
890 Cell::default().set_symbol("2"),
891 );
892 one.merge(&two);
893 let mut merged = Buffer::with_lines(vec!["222 ", "222 ", "2221", "2221"]);
894 merged.area = Rect {
895 x: 1,
896 y: 1,
897 width: 4,
898 height: 4,
899 };
900 assert_eq!(one, merged);
901 }