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