1use crate::text::{Span, Spans};
2use helix_core::unicode::width::UnicodeWidthStr;
4use unicode_segmentation::UnicodeSegmentation;
6use helix_view::graphics::{Color, Modifier, Rect, Style};
9#[derive(Debug, Clone, PartialEq)]
14 pub modifier: Modifier,
18 pub fn set_symbol(&mut self, symbol: &str) -> &mut Cell {
20 self.symbol.push_str(symbol);
24 pub fn set_char(&mut self, ch: char) -> &mut Cell {
30 pub fn set_fg(&mut self, color: Color) -> &mut Cell {
35 pub fn set_bg(&mut self, color: Color) -> &mut Cell {
40 pub fn set_style(&mut self, style: Style) -> &mut Cell {
41 if let Some(c) = style.fg {
44 if let Some(c) = style.bg {
47 self.modifier.insert(style.add_modifier);
48 self.modifier.remove(style.sub_modifier);
52 pub fn style(&self) -> Style {
56 .add_modifier(self.modifier)
59 pub fn reset(&mut self) {
61 self.symbol.push(' ');
62 self.fg = Color::Reset;
63 self.bg = Color::Reset;
64 self.modifier = Modifier::empty();
68impl Default for Cell {
69 fn default() -> Cell {
74 modifier: Modifier::empty(),
79/// A buffer that maps to the desired content of the terminal after the draw call
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.
89/// use helix_tui::buffer::{Buffer, Cell};
90/// use helix_view::graphics::{Rect, Color, Style, Modifier};
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"),
100/// modifier: Modifier::empty()
102/// buf[(5, 0)].set_char('x');
103/// assert_eq!(buf[(5, 0)].symbol, "x");
105#[derive(Debug, Default, Clone, PartialEq)]
107 /// The area represented by this buffer
109 /// The content of the buffer. The length of this Vec should always be equal to area.width *
111 pub content: Vec<Cell>,
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)
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);
126 content.push(cell.clone());
128 Buffer { area, content }
131 /// Returns a Buffer containing the given lines
132 pub fn with_lines<S>(lines: Vec<S>) -> Buffer
136 let height = lines.len() as u16;
139 .map(|i| i.as_ref().width() as u16)
141 .unwrap_or_default();
142 let mut buffer = Buffer::empty(Rect {
148 for (y, line) in lines.iter().enumerate() {
149 buffer.set_string(0, y as u16, line, Style::default());
154 /// Returns the content of the buffer as a slice
155 pub fn content(&self) -> &[Cell] {
159 /// Returns the area covered by this buffer
160 pub fn area(&self) -> &Rect {
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])
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])
174 /// Tells whether the global (x, y) coordinates are inside the Buffer's area.
176 /// Global coordinates are offset by the Buffer's area offset (`x`/`y`).
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));
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()
199 /// Returns the index in the Vec<Cell> for the given global (x, y) coordinates.
201 /// Global coordinates are offset by the Buffer's area offset (`x`/`y`).
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);
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 {
219 self.in_bounds(x, y),
220 "Trying to access position outside the buffer: x={}, y={}, area={:?}",
225 ((y - self.area.y) * self.area.width + (x - self.area.x)) as usize
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))
238 /// Returns the (global) coordinates of a cell given its index
240 /// Global coordinates are offset by the Buffer's area offset (`x`/`y`).
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));
255 /// Panics when given an index that is outside the Buffer's content.
256 pub fn pos_of(&self, i: usize) -> (u16, u16) {
258 i < self.content.len(),
259 "Trying to get the coords of a cell outside the buffer: i={} len={}",
264 self.area.x + i as u16 % self.area.width,
265 self.area.y + i as u16 / self.area.width,
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)
274 self.set_stringn(x, y, string, usize::MAX, style);
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>(
290 self.set_string_truncated(x, y, string, width, style, false, false)
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<S>(
306 truncate_start: bool,
311 // prevent panic if out of range
312 if !self.in_bounds(x, y) || width == 0 {
316 let mut index = self.index_of(x, y);
317 let mut x_offset = x as usize;
318 let width = if ellipsis { width - 1 } else { width };
319 let graphemes = UnicodeSegmentation::graphemes(string.as_ref(), true);
320 let max_offset = min(self.area.right() as usize, width.saturating_add(x as usize));
323 let width = s.width();
327 // `x_offset + width > max_offset` could be integer overflow on 32-bit machines if we
328 // change dimenstions to usize or u32 and someone resizes the terminal to 1x2^32.
329 if width > max_offset.saturating_sub(x_offset) {
333 self.content[index].set_symbol(s);
334 self.content[index].set_style(style);
335 // Reset following cells if multi-width (they would be hidden by the grapheme),
336 for i in index + 1..index + width {
337 self.content[i].reset();
342 if ellipsis && x_offset - (x as usize) < string.as_ref().width() {
343 self.content[index].set_symbol("…");
346 let mut start_index = self.index_of(x, y);
347 let mut index = self.index_of(max_offset as u16, y);
349 let total_width = string.as_ref().width();
350 let truncated = total_width > width;
351 if ellipsis && truncated {
352 self.content[start_index].set_symbol("…");
356 index -= width - total_width;
358 for s in graphemes.rev() {
359 let width = s.width();
363 let start = index - width;
364 if start < start_index {
367 self.content[start].set_symbol(s);
368 self.content[start].set_style(style);
369 for i in start + 1..index {
370 self.content[i].reset();
378 pub fn set_spans<'a>(&mut self, x: u16, y: u16, spans: &Spans<'a>, width: u16) -> (u16, u16) {
379 let mut remaining_width = width;
381 for span in &spans.0 {
382 if remaining_width == 0 {
385 let pos = self.set_stringn(
388 span.content.as_ref(),
389 remaining_width as usize,
392 let w = pos.0.saturating_sub(x);
394 remaining_width = remaining_width.saturating_sub(w);
399 pub fn set_span<'a>(&mut self, x: u16, y: u16, span: &Span<'a>, width: u16) -> (u16, u16) {
400 self.set_stringn(x, y, span.content.as_ref(), width as usize, span.style)
405 note = "You should use styling capabilities of `Buffer::set_style`"
407 pub fn set_background(&mut self, area: Rect, color: Color) {
408 for y in area.top()..area.bottom() {
409 for x in area.left()..area.right() {
410 self[(x, y)].set_bg(color);
415 pub fn set_style(&mut self, area: Rect, style: Style) {
416 for y in area.top()..area.bottom() {
417 for x in area.left()..area.right() {
418 self[(x, y)].set_style(style);
423 /// Resize the buffer so that the mapped area matches the given area and that the buffer
424 /// length is equal to area.width * area.height
425 pub fn resize(&mut self, area: Rect) {
426 let length = area.area() as usize;
427 if self.content.len() > length {
428 self.content.truncate(length);
430 self.content.resize(length, Default::default());
435 /// Reset all cells in the buffer
436 pub fn reset(&mut self) {
437 for c in &mut self.content {
442 /// Clear an area in the buffer
443 pub fn clear(&mut self, area: Rect) {
444 for x in area.left()..area.right() {
445 for y in area.top()..area.bottom() {
446 self[(x, y)].reset();
451 /// Clear an area in the buffer with a default style.
452 pub fn clear_with(&mut self, area: Rect, style: Style) {
453 for x in area.left()..area.right() {
454 for y in area.top()..area.bottom() {
455 let cell = &mut self[(x, y)];
457 cell.set_style(style);
462 /// Merge an other buffer into this one
463 pub fn merge(&mut self, other: &Buffer) {
464 let area = self.area.union(other.area);
465 let cell: Cell = Default::default();
466 self.content.resize(area.area() as usize, cell.clone());
468 // Move original content to the appropriate space
469 let size = self.area.area() as usize;
470 for i in (0..size).rev() {
471 let (x, y) = self.pos_of(i);
472 // New index in content
473 let k = ((y - area.y) * area.width + x - area.x) as usize;
475 self.content[k] = self.content[i].clone();
476 self.content[i] = cell.clone();
480 // Push content of the other buffer into this one (may erase previous
482 let size = other.area.area() as usize;
484 let (x, y) = other.pos_of(i);
485 // New index in content
486 let k = ((y - area.y) * area.width + x - area.x) as usize;
487 self.content[k] = other.content[i].clone();
492 /// Builds a minimal sequence of coordinates and Cells necessary to update the UI from
495 /// We're assuming that buffers are well-formed, that is no double-width cell is followed by
496 /// a non-blank cell.
498 /// # Multi-width characters handling:
504 /// Updates: `0: a, 1: a'
511 /// Updates: `0: コ` (double width symbol at index 0 - skip index 1)
518 /// Updates: `0: a, 1: コ` (double width symbol at index 1 - skip index 2)
520 pub fn diff<'a>(&self, other: &'a Buffer) -> Vec<(u16, u16, &'a Cell)> {
521 let previous_buffer = &self.content;
522 let next_buffer = &other.content;
523 let width = self.area.width;
525 let mut updates: Vec<(u16, u16, &Cell)> = vec![];
526 // Cells invalidated by drawing/replacing preceeding multi-width characters:
527 let mut invalidated: usize = 0;
528 // Cells from the current buffer to skip due to preceeding multi-width characters taking their
529 // place (the skipped cells should be blank anyway):
530 let mut to_skip: usize = 0;
531 for (i, (current, previous)) in next_buffer.iter().zip(previous_buffer.iter()).enumerate() {
532 if (current != previous || invalidated > 0) && to_skip == 0 {
533 let x = i as u16 % width;
534 let y = i as u16 / width;
535 updates.push((x, y, &next_buffer[i]));
538 let current_width = current.symbol.width();
539 to_skip = current_width.saturating_sub(1);
541 let affected_width = std::cmp::max(current_width, previous.symbol.width());
542 invalidated = std::cmp::max(affected_width, invalidated).saturating_sub(1);
548impl std::ops::Index<(u16, u16)> for Buffer {
551 fn index(&self, (x, y): (u16, u16)) -> &Self::Output {
552 let i = self.index_of(x, y);
557impl std::ops::IndexMut<(u16, u16)> for Buffer {
558 fn index_mut(&mut self, (x, y): (u16, u16)) -> &mut Self::Output {
559 let i = self.index_of(x, y);
568 fn cell(s: &str) -> Cell {
569 let mut cell = Cell::default();
575 fn it_translates_to_and_from_coordinates() {
576 let rect = Rect::new(200, 100, 50, 80);
577 let buf = Buffer::empty(rect);
579 // First cell is at the upper left corner.
580 assert_eq!(buf.pos_of(0), (200, 100));
581 assert_eq!(buf.index_of(200, 100), 0);
583 // Last cell is in the lower right.
584 assert_eq!(buf.pos_of(buf.content.len() - 1), (249, 179));
585 assert_eq!(buf.index_of(249, 179), buf.content.len() - 1);
589 #[should_panic(expected = "outside the buffer")]
590 #[cfg(debug_assertions)]
591 fn pos_of_panics_on_out_of_bounds() {
592 let rect = Rect::new(0, 0, 10, 10);
593 let buf = Buffer::empty(rect);
595 // There are a total of 100 cells; zero-indexed means that 100 would be the 101st cell.
600 #[should_panic(expected = "outside the buffer")]
601 #[cfg(debug_assertions)]
602 fn index_of_panics_on_out_of_bounds() {
603 let rect = Rect::new(0, 0, 10, 10);
604 let buf = Buffer::empty(rect);
606 // width is 10; zero-indexed means that 10 would be the 11th cell.
611 fn buffer_set_string() {
612 let area = Rect::new(0, 0, 5, 1);
613 let mut buffer = Buffer::empty(area);
616 buffer.set_stringn(0, 0, "aaa", 0, Style::default());
617 assert_eq!(buffer, Buffer::with_lines(vec![" "]));
619 buffer.set_string(0, 0, "aaa", Style::default());
620 assert_eq!(buffer, Buffer::with_lines(vec!["aaa "]));
623 buffer.set_stringn(0, 0, "bbbbbbbbbbbbbb", 4, Style::default());
624 assert_eq!(buffer, Buffer::with_lines(vec!["bbbb "]));
626 buffer.set_string(0, 0, "12345", Style::default());
627 assert_eq!(buffer, Buffer::with_lines(vec!["12345"]));
630 buffer.set_string(0, 0, "123456", Style::default());
631 assert_eq!(buffer, Buffer::with_lines(vec!["12345"]));
635 fn buffer_set_string_zero_width() {
636 let area = Rect::new(0, 0, 1, 1);
637 let mut buffer = Buffer::empty(area);
639 // Leading grapheme with zero width
641 buffer.set_stringn(0, 0, s, 1, Style::default());
642 assert_eq!(buffer, Buffer::with_lines(vec!["a"]));
644 // Trailing grapheme with zero with
646 buffer.set_stringn(0, 0, s, 1, Style::default());
647 assert_eq!(buffer, Buffer::with_lines(vec!["a"]));
651 fn buffer_set_string_double_width() {
652 let area = Rect::new(0, 0, 5, 1);
653 let mut buffer = Buffer::empty(area);
654 buffer.set_string(0, 0, "コン", Style::default());
655 assert_eq!(buffer, Buffer::with_lines(vec!["コン "]));
657 // Only 1 space left.
658 buffer.set_string(0, 0, "コンピ", Style::default());
659 assert_eq!(buffer, Buffer::with_lines(vec!["コン "]));
663 fn buffer_with_lines() {
665 Buffer::with_lines(vec!["┌────────┐", "│コンピュ│", "│ーa 上で│", "└────────┘"]);
666 assert_eq!(buffer.area.x, 0);
667 assert_eq!(buffer.area.y, 0);
668 assert_eq!(buffer.area.width, 10);
669 assert_eq!(buffer.area.height, 4);
673 fn buffer_diffing_empty_empty() {
674 let area = Rect::new(0, 0, 40, 40);
675 let prev = Buffer::empty(area);
676 let next = Buffer::empty(area);
677 let diff = prev.diff(&next);
678 assert_eq!(diff, vec![]);
682 fn buffer_diffing_empty_filled() {
683 let area = Rect::new(0, 0, 40, 40);
684 let prev = Buffer::empty(area);
685 let next = Buffer::filled(area, Cell::default().set_symbol("a"));
686 let diff = prev.diff(&next);
687 assert_eq!(diff.len(), 40 * 40);
691 fn buffer_diffing_filled_filled() {
692 let area = Rect::new(0, 0, 40, 40);
693 let prev = Buffer::filled(area, Cell::default().set_symbol("a"));
694 let next = Buffer::filled(area, Cell::default().set_symbol("a"));
695 let diff = prev.diff(&next);
696 assert_eq!(diff, vec![]);
700 fn buffer_diffing_single_width() {
701 let prev = Buffer::with_lines(vec![
708 let next = Buffer::with_lines(vec![
715 let diff = prev.diff(&next);
729 fn buffer_diffing_multi_width() {
730 let prev = Buffer::with_lines(vec![
734 let next = Buffer::with_lines(vec![
738 let diff = prev.diff(&next);
752 fn buffer_diffing_multi_width_offset() {
753 let prev = Buffer::with_lines(vec!["┌称号──┐"]);
754 let next = Buffer::with_lines(vec!["┌─称号─┐"]);
756 let diff = prev.diff(&next);
759 vec![(1, 0, &cell("─")), (2, 0, &cell("称")), (4, 0, &cell("号")),]
765 let mut one = Buffer::filled(
772 Cell::default().set_symbol("1"),
774 let two = Buffer::filled(
781 Cell::default().set_symbol("2"),
784 assert_eq!(one, Buffer::with_lines(vec!["11", "11", "22", "22"]));
789 let mut one = Buffer::filled(
796 Cell::default().set_symbol("1"),
798 let two = Buffer::filled(
805 Cell::default().set_symbol("2"),
810 Buffer::with_lines(vec!["22 ", "22 ", " 11", " 11"])
816 let mut one = Buffer::filled(
823 Cell::default().set_symbol("1"),
825 let two = Buffer::filled(
832 Cell::default().set_symbol("2"),
835 let mut merged = Buffer::with_lines(vec!["222 ", "222 ", "2221", "2221"]);
842 assert_eq!(one, merged);
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list