on 2158366 help sign in
helix/helix-tui/src/buffer.rs
767 lines 23 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.get_mut(0, 2).set_symbol("x");
94/// assert_eq!(buf.get(0, 2).symbol, "x");
95/// buf.set_string(3, 0, "string", Style::default().fg(Color::Red).bg(Color::White));
96/// assert_eq!(buf.get(5, 0), &Cell{
97/// symbol: String::from("r"),
98/// fg: Color::Red,
99/// bg: Color::White,
100/// modifier: Modifier::empty()
101/// });
102/// buf.get_mut(5, 0).set_char('x');
103/// assert_eq!(buf.get(5, 0).symbol, "x");
104/// ```
105#[derive(Debug, 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 Default for Buffer {
115 fn default() -> Buffer {
116 Buffer {
117 area: Default::default(),
118 content: Vec::new(),
119 }
120 }
123impl Buffer {
124 /// Returns a Buffer with all cells set to the default one
125 pub fn empty(area: Rect) -> Buffer {
126 let cell: Cell = Default::default();
127 Buffer::filled(area, &cell)
128 }
130 /// Returns a Buffer with all cells initialized with the attributes of the given Cell
131 pub fn filled(area: Rect, cell: &Cell) -> Buffer {
132 let size = area.area() as usize;
133 let mut content = Vec::with_capacity(size);
134 for _ in 0..size {
135 content.push(cell.clone());
136 }
137 Buffer { area, content }
138 }
140 /// Returns a Buffer containing the given lines
141 pub fn with_lines<S>(lines: Vec<S>) -> Buffer
142 where
143 S: AsRef<str>,
144 {
145 let height = lines.len() as u16;
146 let width = lines
147 .iter()
148 .map(|i| i.as_ref().width() as u16)
149 .max()
150 .unwrap_or_default();
151 let mut buffer = Buffer::empty(Rect {
152 x: 0,
153 y: 0,
154 width,
155 height,
156 });
157 for (y, line) in lines.iter().enumerate() {
158 buffer.set_string(0, y as u16, line, Style::default());
159 }
160 buffer
161 }
163 /// Returns the content of the buffer as a slice
164 pub fn content(&self) -> &[Cell] {
165 &self.content
166 }
168 /// Returns the area covered by this buffer
169 pub fn area(&self) -> &Rect {
170 &self.area
171 }
173 /// Returns a reference to Cell at the given coordinates
174 pub fn get(&self, x: u16, y: u16) -> &Cell {
175 let i = self.index_of(x, y);
176 &self.content[i]
177 }
179 /// Returns a mutable reference to Cell at the given coordinates
180 pub fn get_mut(&mut self, x: u16, y: u16) -> &mut Cell {
181 let i = self.index_of(x, y);
182 &mut self.content[i]
183 }
185 /// Returns the index in the Vec<Cell> for the given global (x, y) coordinates.
186 ///
187 /// Global coordinates are offset by the Buffer's area offset (`x`/`y`).
188 ///
189 /// # Examples
190 ///
191 /// ```
192 /// # use helix_tui::buffer::Buffer;
193 /// # use helix_view::graphics::Rect;
194 /// let rect = Rect::new(200, 100, 10, 10);
195 /// let buffer = Buffer::empty(rect);
196 /// // Global coordinates to the top corner of this buffer's area
197 /// assert_eq!(buffer.index_of(200, 100), 0);
198 /// ```
199 ///
200 /// # Panics
201 ///
202 /// Panics when given an coordinate that is outside of this Buffer's area.
203 pub fn index_of(&self, x: u16, y: u16) -> usize {
204 debug_assert!(
205 x >= self.area.left()
206 && x < self.area.right()
207 && y >= self.area.top()
208 && y < self.area.bottom(),
209 "Trying to access position outside the buffer: x={}, y={}, area={:?}",
210 x,
211 y,
212 self.area
213 );
214 ((y - self.area.y) * self.area.width + (x - self.area.x)) as usize
215 }
217 /// Returns the (global) coordinates of a cell given its index
218 ///
219 /// Global coordinates are offset by the Buffer's area offset (`x`/`y`).
220 ///
221 /// # Examples
222 ///
223 /// ```
224 /// # use helix_tui::buffer::Buffer;
225 /// # use helix_view::graphics::Rect;
226 /// let rect = Rect::new(200, 100, 10, 10);
227 /// let buffer = Buffer::empty(rect);
228 /// assert_eq!(buffer.pos_of(0), (200, 100));
229 /// assert_eq!(buffer.pos_of(14), (204, 101));
230 /// ```
231 ///
232 /// # Panics
233 ///
234 /// Panics when given an index that is outside the Buffer's content.
235 pub fn pos_of(&self, i: usize) -> (u16, u16) {
236 debug_assert!(
237 i < self.content.len(),
238 "Trying to get the coords of a cell outside the buffer: i={} len={}",
239 i,
240 self.content.len()
241 );
242 (
243 self.area.x + i as u16 % self.area.width,
244 self.area.y + i as u16 / self.area.width,
245 )
246 }
248 /// Print a string, starting at the position (x, y)
249 pub fn set_string<S>(&mut self, x: u16, y: u16, string: S, style: Style)
250 where
251 S: AsRef<str>,
252 {
253 self.set_stringn(x, y, string, usize::MAX, style);
254 }
256 /// Print at most the first n characters of a string if enough space is available
257 /// until the end of the line
258 pub fn set_stringn<S>(
259 &mut self,
260 x: u16,
261 y: u16,
262 string: S,
263 width: usize,
264 style: Style,
265 ) -> (u16, u16)
266 where
267 S: AsRef<str>,
268 {
269 self.set_string_truncated(x, y, string, width, style, false)
270 }
272 /// Print at most the first `width` characters of a string if enough space is available
273 /// until the end of the line. If `markend` is true appends a `…` at the end of
274 /// truncated lines.
275 pub fn set_string_truncated<S>(
276 &mut self,
277 x: u16,
278 y: u16,
279 string: S,
280 width: usize,
281 style: Style,
282 ellipsis: bool,
283 ) -> (u16, u16)
284 where
285 S: AsRef<str>,
286 {
287 let mut index = self.index_of(x, y);
288 let mut x_offset = x as usize;
289 let width = if ellipsis { width - 1 } else { width };
290 let graphemes = UnicodeSegmentation::graphemes(string.as_ref(), true);
291 let max_offset = min(self.area.right() as usize, width.saturating_add(x as usize));
292 for s in graphemes {
293 let width = s.width();
294 if width == 0 {
295 continue;
296 }
297 // `x_offset + width > max_offset` could be integer overflow on 32-bit machines if we
298 // change dimenstions to usize or u32 and someone resizes the terminal to 1x2^32.
299 if width > max_offset.saturating_sub(x_offset) {
300 break;
301 }
303 self.content[index].set_symbol(s);
304 self.content[index].set_style(style);
305 // Reset following cells if multi-width (they would be hidden by the grapheme),
306 for i in index + 1..index + width {
307 self.content[i].reset();
308 }
309 index += width;
310 x_offset += width;
311 }
312 if ellipsis && x_offset - (x as usize) < string.as_ref().width() {
313 self.content[index].set_symbol("…");
314 }
315 (x_offset as u16, y)
316 }
318 pub fn set_spans<'a>(&mut self, x: u16, y: u16, spans: &Spans<'a>, width: u16) -> (u16, u16) {
319 let mut remaining_width = width;
320 let mut x = x;
321 for span in &spans.0 {
322 if remaining_width == 0 {
323 break;
324 }
325 let pos = self.set_stringn(
326 x,
327 y,
328 span.content.as_ref(),
329 remaining_width as usize,
330 span.style,
331 );
332 let w = pos.0.saturating_sub(x);
333 x = pos.0;
334 remaining_width = remaining_width.saturating_sub(w);
335 }
336 (x, y)
337 }
339 pub fn set_span<'a>(&mut self, x: u16, y: u16, span: &Span<'a>, width: u16) -> (u16, u16) {
340 self.set_stringn(x, y, span.content.as_ref(), width as usize, span.style)
341 }
343 #[deprecated(
344 since = "0.10.0",
345 note = "You should use styling capabilities of `Buffer::set_style`"
346 )]
347 pub fn set_background(&mut self, area: Rect, color: Color) {
348 for y in area.top()..area.bottom() {
349 for x in area.left()..area.right() {
350 self.get_mut(x, y).set_bg(color);
351 }
352 }
353 }
355 pub fn set_style(&mut self, area: Rect, style: Style) {
356 for y in area.top()..area.bottom() {
357 for x in area.left()..area.right() {
358 self.get_mut(x, y).set_style(style);
359 }
360 }
361 }
363 /// Resize the buffer so that the mapped area matches the given area and that the buffer
364 /// length is equal to area.width * area.height
365 pub fn resize(&mut self, area: Rect) {
366 let length = area.area() as usize;
367 if self.content.len() > length {
368 self.content.truncate(length);
369 } else {
370 self.content.resize(length, Default::default());
371 }
372 self.area = area;
373 }
375 /// Reset all cells in the buffer
376 pub fn reset(&mut self) {
377 for c in &mut self.content {
378 c.reset();
379 }
380 }
382 /// Clear an area in the buffer
383 pub fn clear(&mut self, area: Rect) {
384 for x in area.left()..area.right() {
385 for y in area.top()..area.bottom() {
386 self.get_mut(x, y).reset();
387 }
388 }
389 }
391 /// Clear an area in the buffer with a default style.
392 pub fn clear_with(&mut self, area: Rect, style: Style) {
393 for x in area.left()..area.right() {
394 for y in area.top()..area.bottom() {
395 let cell = self.get_mut(x, y);
396 cell.reset();
397 cell.set_style(style);
398 }
399 }
400 }
402 /// Merge an other buffer into this one
403 pub fn merge(&mut self, other: &Buffer) {
404 let area = self.area.union(other.area);
405 let cell: Cell = Default::default();
406 self.content.resize(area.area() as usize, cell.clone());
408 // Move original content to the appropriate space
409 let size = self.area.area() as usize;
410 for i in (0..size).rev() {
411 let (x, y) = self.pos_of(i);
412 // New index in content
413 let k = ((y - area.y) * area.width + x - area.x) as usize;
414 if i != k {
415 self.content[k] = self.content[i].clone();
416 self.content[i] = cell.clone();
417 }
418 }
420 // Push content of the other buffer into this one (may erase previous
421 // data)
422 let size = other.area.area() as usize;
423 for i in 0..size {
424 let (x, y) = other.pos_of(i);
425 // New index in content
426 let k = ((y - area.y) * area.width + x - area.x) as usize;
427 self.content[k] = other.content[i].clone();
428 }
429 self.area = area;
430 }
432 /// Builds a minimal sequence of coordinates and Cells necessary to update the UI from
433 /// self to other.
434 ///
435 /// We're assuming that buffers are well-formed, that is no double-width cell is followed by
436 /// a non-blank cell.
437 ///
438 /// # Multi-width characters handling:
439 ///
440 /// ```text
441 /// (Index:) `01`
442 /// Prev: `コ`
443 /// Next: `aa`
444 /// Updates: `0: a, 1: a'
445 /// ```
446 ///
447 /// ```text
448 /// (Index:) `01`
449 /// Prev: `a `
450 /// Next: `コ`
451 /// Updates: `0: コ` (double width symbol at index 0 - skip index 1)
452 /// ```
453 ///
454 /// ```text
455 /// (Index:) `012`
456 /// Prev: `aaa`
457 /// Next: `aコ`
458 /// Updates: `0: a, 1: コ` (double width symbol at index 1 - skip index 2)
459 /// ```
460 pub fn diff<'a>(&self, other: &'a Buffer) -> Vec<(u16, u16, &'a Cell)> {
461 let previous_buffer = &self.content;
462 let next_buffer = &other.content;
463 let width = self.area.width;
465 let mut updates: Vec<(u16, u16, &Cell)> = vec![];
466 // Cells invalidated by drawing/replacing preceeding multi-width characters:
467 let mut invalidated: usize = 0;
468 // Cells from the current buffer to skip due to preceeding multi-width characters taking their
469 // place (the skipped cells should be blank anyway):
470 let mut to_skip: usize = 0;
471 for (i, (current, previous)) in next_buffer.iter().zip(previous_buffer.iter()).enumerate() {
472 if (current != previous || invalidated > 0) && to_skip == 0 {
473 let x = i as u16 % width;
474 let y = i as u16 / width;
475 updates.push((x, y, &next_buffer[i]));
476 }
478 to_skip = current.symbol.width().saturating_sub(1);
480 let affected_width = std::cmp::max(current.symbol.width(), previous.symbol.width());
481 invalidated = std::cmp::max(affected_width, invalidated).saturating_sub(1);
482 }
483 updates
484 }
487#[cfg(test)]
488mod tests {
489 use super::*;
491 fn cell(s: &str) -> Cell {
492 let mut cell = Cell::default();
493 cell.set_symbol(s);
494 cell
495 }
497 #[test]
498 fn it_translates_to_and_from_coordinates() {
499 let rect = Rect::new(200, 100, 50, 80);
500 let buf = Buffer::empty(rect);
502 // First cell is at the upper left corner.
503 assert_eq!(buf.pos_of(0), (200, 100));
504 assert_eq!(buf.index_of(200, 100), 0);
506 // Last cell is in the lower right.
507 assert_eq!(buf.pos_of(buf.content.len() - 1), (249, 179));
508 assert_eq!(buf.index_of(249, 179), buf.content.len() - 1);
509 }
511 #[test]
512 #[should_panic(expected = "outside the buffer")]
513 #[cfg(debug_assertions)]
514 fn pos_of_panics_on_out_of_bounds() {
515 let rect = Rect::new(0, 0, 10, 10);
516 let buf = Buffer::empty(rect);
518 // There are a total of 100 cells; zero-indexed means that 100 would be the 101st cell.
519 buf.pos_of(100);
520 }
522 #[test]
523 #[should_panic(expected = "outside the buffer")]
524 #[cfg(debug_assertions)]
525 fn index_of_panics_on_out_of_bounds() {
526 let rect = Rect::new(0, 0, 10, 10);
527 let buf = Buffer::empty(rect);
529 // width is 10; zero-indexed means that 10 would be the 11th cell.
530 buf.index_of(10, 0);
531 }
533 #[test]
534 fn buffer_set_string() {
535 let area = Rect::new(0, 0, 5, 1);
536 let mut buffer = Buffer::empty(area);
538 // Zero-width
539 buffer.set_stringn(0, 0, "aaa", 0, Style::default());
540 assert_eq!(buffer, Buffer::with_lines(vec![" "]));
542 buffer.set_string(0, 0, "aaa", Style::default());
543 assert_eq!(buffer, Buffer::with_lines(vec!["aaa "]));
545 // Width limit:
546 buffer.set_stringn(0, 0, "bbbbbbbbbbbbbb", 4, Style::default());
547 assert_eq!(buffer, Buffer::with_lines(vec!["bbbb "]));
549 buffer.set_string(0, 0, "12345", Style::default());
550 assert_eq!(buffer, Buffer::with_lines(vec!["12345"]));
552 // Width truncation:
553 buffer.set_string(0, 0, "123456", Style::default());
554 assert_eq!(buffer, Buffer::with_lines(vec!["12345"]));
555 }
557 #[test]
558 fn buffer_set_string_zero_width() {
559 let area = Rect::new(0, 0, 1, 1);
560 let mut buffer = Buffer::empty(area);
562 // Leading grapheme with zero width
563 let s = "\u{1}a";
564 buffer.set_stringn(0, 0, s, 1, Style::default());
565 assert_eq!(buffer, Buffer::with_lines(vec!["a"]));
567 // Trailing grapheme with zero with
568 let s = "a\u{1}";
569 buffer.set_stringn(0, 0, s, 1, Style::default());
570 assert_eq!(buffer, Buffer::with_lines(vec!["a"]));
571 }
573 #[test]
574 fn buffer_set_string_double_width() {
575 let area = Rect::new(0, 0, 5, 1);
576 let mut buffer = Buffer::empty(area);
577 buffer.set_string(0, 0, "コン", Style::default());
578 assert_eq!(buffer, Buffer::with_lines(vec!["コン "]));
580 // Only 1 space left.
581 buffer.set_string(0, 0, "コンピ", Style::default());
582 assert_eq!(buffer, Buffer::with_lines(vec!["コン "]));
583 }
585 #[test]
586 fn buffer_with_lines() {
587 let buffer =
588 Buffer::with_lines(vec!["┌────────┐", "│コンピュ│", "│ーa 上で│", "└────────┘"]);
589 assert_eq!(buffer.area.x, 0);
590 assert_eq!(buffer.area.y, 0);
591 assert_eq!(buffer.area.width, 10);
592 assert_eq!(buffer.area.height, 4);
593 }
595 #[test]
596 fn buffer_diffing_empty_empty() {
597 let area = Rect::new(0, 0, 40, 40);
598 let prev = Buffer::empty(area);
599 let next = Buffer::empty(area);
600 let diff = prev.diff(&next);
601 assert_eq!(diff, vec![]);
602 }
604 #[test]
605 fn buffer_diffing_empty_filled() {
606 let area = Rect::new(0, 0, 40, 40);
607 let prev = Buffer::empty(area);
608 let next = Buffer::filled(area, Cell::default().set_symbol("a"));
609 let diff = prev.diff(&next);
610 assert_eq!(diff.len(), 40 * 40);
611 }
613 #[test]
614 fn buffer_diffing_filled_filled() {
615 let area = Rect::new(0, 0, 40, 40);
616 let prev = Buffer::filled(area, Cell::default().set_symbol("a"));
617 let next = Buffer::filled(area, Cell::default().set_symbol("a"));
618 let diff = prev.diff(&next);
619 assert_eq!(diff, vec![]);
620 }
622 #[test]
623 fn buffer_diffing_single_width() {
624 let prev = Buffer::with_lines(vec![
625 " ",
626 "┌Title─┐ ",
627 "│ │ ",
628 "│ │ ",
629 "└──────┘ ",
630 ]);
631 let next = Buffer::with_lines(vec![
632 " ",
633 "┌TITLE─┐ ",
634 "│ │ ",
635 "│ │ ",
636 "└──────┘ ",
637 ]);
638 let diff = prev.diff(&next);
639 assert_eq!(
640 diff,
641 vec![
642 (2, 1, &cell("I")),
643 (3, 1, &cell("T")),
644 (4, 1, &cell("L")),
645 (5, 1, &cell("E")),
646 ]
647 );
648 }
650 #[test]
651 #[rustfmt::skip]
652 fn buffer_diffing_multi_width() {
653 let prev = Buffer::with_lines(vec![
654 "┌Title─┐ ",
655 "└──────┘ ",
656 ]);
657 let next = Buffer::with_lines(vec![
658 "┌称号──┐ ",
659 "└──────┘ ",
660 ]);
661 let diff = prev.diff(&next);
662 assert_eq!(
663 diff,
664 vec![
665 (1, 0, &cell("称")),
666 // Skipped "i"
667 (3, 0, &cell("号")),
668 // Skipped "l"
669 (5, 0, &cell("─")),
670 ]
671 );
672 }
674 #[test]
675 fn buffer_diffing_multi_width_offset() {
676 let prev = Buffer::with_lines(vec!["┌称号──┐"]);
677 let next = Buffer::with_lines(vec!["┌─称号─┐"]);
679 let diff = prev.diff(&next);
680 assert_eq!(
681 diff,
682 vec![(1, 0, &cell("─")), (2, 0, &cell("称")), (4, 0, &cell("号")),]
683 );
684 }
686 #[test]
687 fn buffer_merge() {
688 let mut one = Buffer::filled(
689 Rect {
690 x: 0,
691 y: 0,
692 width: 2,
693 height: 2,
694 },
695 Cell::default().set_symbol("1"),
696 );
697 let two = Buffer::filled(
698 Rect {
699 x: 0,
700 y: 2,
701 width: 2,
702 height: 2,
703 },
704 Cell::default().set_symbol("2"),
705 );
706 one.merge(&two);
707 assert_eq!(one, Buffer::with_lines(vec!["11", "11", "22", "22"]));
708 }
710 #[test]
711 fn buffer_merge2() {
712 let mut one = Buffer::filled(
713 Rect {
714 x: 2,
715 y: 2,
716 width: 2,
717 height: 2,
718 },
719 Cell::default().set_symbol("1"),
720 );
721 let two = Buffer::filled(
722 Rect {
723 x: 0,
724 y: 0,
725 width: 2,
726 height: 2,
727 },
728 Cell::default().set_symbol("2"),
729 );
730 one.merge(&two);
731 assert_eq!(
732 one,
733 Buffer::with_lines(vec!["22 ", "22 ", " 11", " 11"])
734 );
735 }
737 #[test]
738 fn buffer_merge3() {
739 let mut one = Buffer::filled(
740 Rect {
741 x: 3,
742 y: 3,
743 width: 2,
744 height: 2,
745 },
746 Cell::default().set_symbol("1"),
747 );
748 let two = Buffer::filled(
749 Rect {
750 x: 1,
751 y: 1,
752 width: 3,
753 height: 4,
754 },
755 Cell::default().set_symbol("2"),
756 );
757 one.merge(&two);
758 let mut merged = Buffer::with_lines(vec!["222 ", "222 ", "2221", "2221"]);
759 merged.area = Rect {
760 x: 1,
761 y: 1,
762 width: 4,
763 height: 4,
764 };
765 assert_eq!(one, merged);
766 }