summaryrefslogtreecommitdiffstats
path: root/src/terminal/cells.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/terminal/cells.rs')
-rw-r--r--src/terminal/cells.rs1060
1 files changed, 1060 insertions, 0 deletions
diff --git a/src/terminal/cells.rs b/src/terminal/cells.rs
new file mode 100644
index 0000000..4d40aab
--- /dev/null
+++ b/src/terminal/cells.rs
@@ -0,0 +1,1060 @@
+/*
+ * bb
+ *
+ * Copyright 2019 Manos Pitsidianakis
+ *
+ * This file is part of bb.
+ *
+ * bb is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * bb is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with bb. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*!
+ Define a (x, y) point in the terminal display as a holder of a character, foreground/background
+ colors and attributes.
+*/
+
+use super::position::*;
+use crate::text_processing::wcwidth;
+
+use std::convert::From;
+use std::fmt;
+use std::ops::{Deref, DerefMut, Index, IndexMut};
+use termion::color::{AnsiValue, Rgb as TermionRgb};
+
+/// In a scroll region up and down cursor movements shift the region vertically. The new lines are
+/// empty.
+#[derive(Debug, Clone, PartialEq, Eq, Default)]
+pub struct ScrollRegion {
+ pub top: usize,
+ pub bottom: usize,
+ pub left: usize,
+ pub right: usize,
+}
+
+/// An array of `Cell`s that represents a terminal display.
+///
+/// A `CellBuffer` is a two-dimensional array of `Cell`s, each pair of indices correspond to a
+/// single point on the underlying terminal.
+///
+/// The first index, `Cellbuffer[y]`, corresponds to a row, and thus the y-axis. The second
+/// index, `Cellbuffer[y][x]`, corresponds to a column within a row and thus the x-axis.
+#[derive(Clone, PartialEq, Eq)]
+pub struct CellBuffer {
+ cols: usize,
+ rows: usize,
+ buf: Vec<Cell>,
+}
+
+impl fmt::Debug for CellBuffer {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(
+ f,
+ "CellBuffer {{ cols: {}, rows: {}, buf: {} cells",
+ self.cols,
+ self.rows,
+ self.buf.len()
+ )
+ }
+}
+
+impl CellBuffer {
+ pub fn area(&self) -> Area {
+ (
+ (0, 0),
+ (self.cols.saturating_sub(1), self.rows.saturating_sub(1)),
+ )
+ }
+ pub fn set_cols(&mut self, new_cols: usize) {
+ self.cols = new_cols;
+ }
+
+ /// Constructs a new `CellBuffer` with the given number of columns and rows, using the given
+ /// `cell` as a blank.
+ pub fn new(cols: usize, rows: usize, cell: Cell) -> CellBuffer {
+ CellBuffer {
+ cols,
+ rows,
+ buf: vec![cell; cols * rows],
+ }
+ }
+
+ /// Resizes `CellBuffer` to the given number of rows and columns, using the given `Cell` as
+ /// a blank.
+ pub fn resize(&mut self, newcols: usize, newrows: usize, blank: Cell) {
+ let newlen = newcols * newrows;
+ if self.buf.len() == newlen {
+ self.cols = newcols;
+ self.rows = newrows;
+ return;
+ }
+
+ if newlen >= 200_000 {
+ return;
+ }
+
+ let mut newbuf: Vec<Cell> = Vec::with_capacity(newlen);
+ for y in 0..newrows {
+ for x in 0..newcols {
+ let cell = self.get(x, y).unwrap_or(&blank);
+ newbuf.push(*cell);
+ }
+ }
+ self.buf = newbuf;
+ self.cols = newcols;
+ self.rows = newrows;
+ }
+
+ pub fn is_empty(&self) -> bool {
+ self.buf.is_empty()
+ }
+
+ pub fn empty(&mut self) {
+ self.buf.clear();
+ self.cols = 0;
+ self.rows = 0;
+ }
+
+ /// Clears `self`, using the given `Cell` as a blank.
+ pub fn clear(&mut self, blank: Cell) {
+ for cell in self.cellvec_mut().iter_mut() {
+ *cell = blank;
+ }
+ }
+
+ pub fn pos_to_index(&self, x: usize, y: usize) -> Option<usize> {
+ let (cols, rows) = self.size();
+ if x < cols && y < rows {
+ Some((cols * y) + x)
+ } else {
+ None
+ }
+ }
+
+ /// Returns a reference to the `Cell` at the given coordinates, or `None` if the index is out of
+ /// bounds.
+ ///
+ /// # Examples
+ ///
+ /// ```norun
+ ///
+ /// let mut term = Terminal::new().unwrap();
+ ///
+ /// let a_cell = term.get(5, 5);
+ /// ```
+ pub fn get(&self, x: usize, y: usize) -> Option<&Cell> {
+ match self.pos_to_index(x, y) {
+ Some(i) => self.cellvec().get(i),
+ None => None,
+ }
+ }
+
+ /// Returns a mutable reference to the `Cell` at the given coordinates, or `None` if the index
+ /// is out of bounds.
+ ///
+ /// # Examples
+ ///
+ /// ```norun
+ ///
+ /// let mut term = Terminal::new().unwrap();
+ ///
+ /// let a_mut_cell = term.get_mut(5, 5);
+ /// ```
+ pub fn get_mut(&mut self, x: usize, y: usize) -> Option<&mut Cell> {
+ match self.pos_to_index(x, y) {
+ Some(i) => self.cellvec_mut().get_mut(i),
+ None => None,
+ }
+ }
+
+ pub fn size(&self) -> (usize, usize) {
+ (self.cols, self.rows)
+ }
+
+ pub fn cellvec(&self) -> &Vec<Cell> {
+ &self.buf
+ }
+
+ pub fn cellvec_mut(&mut self) -> &mut Vec<Cell> {
+ &mut self.buf
+ }
+
+ pub fn cols(&self) -> usize {
+ self.size().0
+ }
+
+ pub fn rows(&self) -> usize {
+ self.size().1
+ }
+
+ #[inline(always)]
+ /// Performs the normal scroll up motion:
+ ///
+ /// First clear offset number of lines:
+ ///
+ /// For offset = 1, top = 1:
+ ///
+ /// ```text
+ /// | 111111111111 | | |
+ /// | 222222222222 | | 222222222222 |
+ /// | 333333333333 | | 333333333333 |
+ /// | 444444444444 | --> | 444444444444 |
+ /// | 555555555555 | | 555555555555 |
+ /// | 666666666666 | | 666666666666 |
+ /// ```
+ ///
+ /// In each step, swap the current line with the next by offset:
+ ///
+ /// ```text
+ /// | | | 222222222222 |
+ /// | 222222222222 | | |
+ /// | 333333333333 | | 333333333333 |
+ /// | 444444444444 | --> | 444444444444 |
+ /// | 555555555555 | | 555555555555 |
+ /// | 666666666666 | | 666666666666 |
+ /// ```
+ ///
+ /// Result:
+ /// ```text
+ /// Before After
+ /// | 111111111111 | | 222222222222 |
+ /// | 222222222222 | | 333333333333 |
+ /// | 333333333333 | | 444444444444 |
+ /// | 444444444444 | | 555555555555 |
+ /// | 555555555555 | | 666666666666 |
+ /// | 666666666666 | | |
+ /// ```
+ ///
+ pub fn scroll_up(&mut self, scroll_region: &ScrollRegion, top: usize, offset: usize) {
+ let l = scroll_region.left;
+ let r = if scroll_region.right == 0 {
+ self.size().0
+ } else {
+ scroll_region.right
+ };
+ for y in top..=(top + offset - 1) {
+ for x in l..r {
+ self[(x, y)] = Cell::default();
+ }
+ }
+ for y in top..=(scroll_region.bottom - offset) {
+ for x in l..r {
+ let temp = self[(x, y)];
+ self[(x, y)] = self[(x, y + offset)];
+ self[(x, y + offset)] = temp;
+ }
+ }
+ }
+
+ #[inline(always)]
+ /// Performs the normal scroll down motion:
+ ///
+ /// First clear offset number of lines:
+ ///
+ /// For offset = 1, top = 1:
+ ///
+ /// ```text
+ /// | 111111111111 | | 111111111111 |
+ /// | 222222222222 | | 222222222222 |
+ /// | 333333333333 | | 333333333333 |
+ /// | 444444444444 | --> | 444444444444 |
+ /// | 555555555555 | | 555555555555 |
+ /// | 666666666666 | | |
+ /// ```
+ ///
+ /// In each step, swap the current line with the prev by offset:
+ ///
+ /// ```text
+ /// | 111111111111 | | 111111111111 |
+ /// | 222222222222 | | 222222222222 |
+ /// | 333333333333 | | 333333333333 |
+ /// | 444444444444 | --> | 444444444444 |
+ /// | 555555555555 | | |
+ /// | | | 555555555555 |
+ /// ```
+ ///
+ /// Result:
+ /// ```text
+ /// Before After
+ /// | 111111111111 | | |
+ /// | 222222222222 | | 111111111111 |
+ /// | 333333333333 | | 222222222222 |
+ /// | 444444444444 | | 333333333333 |
+ /// | 555555555555 | | 444444444444 |
+ /// | 666666666666 | | 555555555555 |
+ /// ```
+ ///
+ pub fn scroll_down(&mut self, scroll_region: &ScrollRegion, top: usize, offset: usize) {
+ for y in (scroll_region.bottom - offset + 1)..=scroll_region.bottom {
+ for x in 0..self.size().0 {
+ self[(x, y)] = Cell::default();
+ }
+ }
+
+ for y in ((top + offset)..=scroll_region.bottom).rev() {
+ for x in 0..self.size().0 {
+ let temp = self[(x, y)];
+ self[(x, y)] = self[(x, y - offset)];
+ self[(x, y - offset)] = temp;
+ }
+ }
+ }
+
+ /// See `BoundsIterator` documentation.
+ pub fn bounds_iter(&self, area: Area) -> BoundsIterator {
+ BoundsIterator {
+ rows: std::cmp::min(self.rows.saturating_sub(1), get_y(upper_left!(area)))
+ ..(std::cmp::min(self.rows, get_y(bottom_right!(area)) + 1)),
+ cols: (
+ std::cmp::min(self.cols.saturating_sub(1), get_x(upper_left!(area))),
+ std::cmp::min(self.cols, get_x(bottom_right!(area)) + 1),
+ ),
+ }
+ }
+
+ /// See `RowIterator` documentation.
+ pub fn row_iter(&self, bounds: std::ops::Range<usize>, row: usize) -> RowIterator {
+ if row < self.rows {
+ RowIterator {
+ row,
+ col: std::cmp::min(self.cols.saturating_sub(1), bounds.start)
+ ..(std::cmp::min(self.cols, bounds.end)),
+ }
+ } else {
+ RowIterator { row, col: 0..0 }
+ }
+ }
+}
+
+impl Deref for CellBuffer {
+ type Target = [Cell];
+
+ fn deref(&self) -> &[Cell] {
+ &self.buf
+ }
+}
+
+impl DerefMut for CellBuffer {
+ fn deref_mut(&mut self) -> &mut [Cell] {
+ &mut self.buf
+ }
+}
+
+impl Index<Pos> for CellBuffer {
+ type Output = Cell;
+
+ fn index(&self, index: Pos) -> &Cell {
+ let (x, y) = index;
+ self.get(x, y).expect("index out of bounds")
+ }
+}
+
+impl IndexMut<Pos> for CellBuffer {
+ fn index_mut(&mut self, index: Pos) -> &mut Cell {
+ let (x, y) = index;
+ self.get_mut(x, y).expect("index out of bounds")
+ }
+}
+
+impl Default for CellBuffer {
+ /// Constructs a new `CellBuffer` with a size of `(0, 0)`, using the default `Cell` as a blank.
+ fn default() -> CellBuffer {
+ CellBuffer::new(0, 0, Cell::default())
+ }
+}
+
+impl fmt::Display for CellBuffer {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ '_y: for y in 0..self.rows {
+ for x in 0..self.cols {
+ let c: &char = &self[(x, y)].ch();
+ write!(f, "{}", *c).unwrap();
+ if *c == '\n' {
+ continue '_y;
+ }
+ }
+ }
+ Ok(())
+ }
+}
+
+/// A single point on a terminal display.
+///
+/// A `Cell` contains a character and style.
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub struct Cell {
+ ch: char,
+
+ empty: bool,
+ fg: Color,
+ bg: Color,
+ attrs: Attr,
+ keep_fg: bool,
+ keep_bg: bool,
+}
+
+impl Cell {
+ /// Creates a new `Cell` with the given `char`, `Color`s and `Attr`.
+ ///
+ /// # Examples
+ ///
+ /// ```norun
+ ///
+ /// let cell = Cell::new('x', Color::Default, Color::Green, Attr::Default);
+ /// assert_eq!(cell.ch(), 'x');
+ /// assert_eq!(cell.fg(), Color::Default);
+ /// assert_eq!(cell.bg(), Color::Green);
+ /// assert_eq!(cell.attrs(), Attr::Default);
+ /// ```
+ pub fn new(ch: char, fg: Color, bg: Color, attrs: Attr) -> Cell {
+ Cell {
+ ch,
+ fg,
+ bg,
+ attrs,
+ empty: false,
+ keep_fg: false,
+ keep_bg: false,
+ }
+ }
+
+ /// Creates a new `Cell` with the given `char` and default style.
+ ///
+ /// # Examples
+ ///
+ /// ```norun
+ ///
+ /// let mut cell = Cell::with_char('x');
+ /// assert_eq!(cell.ch(), 'x');
+ /// assert_eq!(cell.fg(), Color::Default);
+ /// assert_eq!(cell.bg(), Color::Default);
+ /// assert_eq!(cell.attrs(), Attr::Default);
+ /// ```
+ pub fn with_char(ch: char) -> Cell {
+ Cell::new(ch, Color::Default, Color::Default, Attr::Default)
+ }
+
+ /// Creates a new `Cell` with the given style and a blank `char`.
+ ///
+ /// # Examples
+ ///
+ /// ```norun
+ ///
+ /// let mut cell = Cell::with_style(Color::Default, Color::Red, Attr::Bold);
+ /// assert_eq!(cell.fg(), Color::Default);
+ /// assert_eq!(cell.bg(), Color::Red);
+ /// assert_eq!(cell.attrs(), Attr::Bold);
+ /// assert_eq!(cell.ch(), ' ');
+ /// ```
+ pub fn with_style(fg: Color, bg: Color, attr: Attr) -> Cell {
+ Cell::new(' ', fg, bg, attr)
+ }
+
+ /// Returns the `Cell`'s character.
+ ///
+ /// # Examples
+ ///
+ /// ```norun
+ ///
+ /// let mut cell = Cell::with_char('x');
+ /// assert_eq!(cell.ch(), 'x');
+ /// ```
+ pub fn ch(&self) -> char {
+ self.ch
+ }
+
+ /// Sets the `Cell`'s character to the given `char`
+ ///
+ /// # Examples
+ ///
+ /// ```norun
+ ///
+ /// let mut cell = Cell::with_char('x');
+ /// assert_eq!(cell.ch(), 'x');
+ ///
+ /// cell.set_ch('y');
+ /// assert_eq!(cell.ch(), 'y');
+ /// ```
+ pub fn set_ch(&mut self, newch: char) -> &mut Cell {
+ self.ch = newch;
+ self.keep_fg = false;
+ self.keep_bg = false;
+ self
+ }
+
+ /// Returns the `Cell`'s foreground `Color`.
+ ///
+ /// # Examples
+ ///
+ /// ```norun
+ ///
+ /// let mut cell = Cell::with_style(Color::Blue, Color::Default, Attr::Default);
+ /// assert_eq!(cell.fg(), Color::Blue);
+ /// ```
+ pub fn fg(&self) -> Color {
+ self.fg
+ }
+
+ /// Sets the `Cell`'s foreground `Color` to the given `Color`.
+ ///
+ /// # Examples
+ ///
+ /// ```norun
+ ///
+ /// let mut cell = Cell::default();
+ /// assert_eq!(cell.fg(), Color::Default);
+ ///
+ /// cell.set_fg(Color::White);
+ /// assert_eq!(cell.fg(), Color::White);
+ /// ```
+ pub fn set_fg(&mut self, newfg: Color) -> &mut Cell {
+ if !self.keep_fg {
+ self.fg = newfg;
+ }
+ self
+ }
+
+ /// Returns the `Cell`'s background `Color`.
+ ///
+ /// # Examples
+ ///
+ /// ```norun
+ /// let mut cell = Cell::with_style(Color::Default, Color::Green, Attr::Default);
+ /// assert_eq!(cell.bg(), Color::Green);
+ /// ```
+ pub fn bg(&self) -> Color {
+ self.bg
+ }
+
+ /// Sets the `Cell`'s background `Color` to the given `Color`.
+ ///
+ /// # Examples
+ ///
+ /// ```norun
+ /// let mut cell = Cell::default();
+ /// assert_eq!(cell.bg(), Color::Default);
+ ///
+ /// cell.set_bg(Color::Black);
+ /// assert_eq!(cell.bg(), Color::Black);
+ /// ```
+ pub fn set_bg(&mut self, newbg: Color) -> &mut Cell {
+ if !self.keep_bg {
+ self.bg = newbg;
+ }
+ self
+ }
+
+ pub fn attrs(&self) -> Attr {
+ self.attrs
+ }
+
+ pub fn set_attrs(&mut self, newattrs: Attr) -> &mut Cell {
+ self.attrs = newattrs;
+ self
+ }
+
+ /// Set a `Cell` as empty when a previous cell spans multiple columns and it would
+ /// "overflow" to this cell.
+ pub fn empty(&self) -> bool {
+ self.empty
+ }
+
+ pub fn set_empty(&mut self, new_val: bool) -> &mut Cell {
+ self.empty = new_val;
+ self
+ }
+
+ /// Sets `keep_fg` field. If true, the foreground color will not be altered if attempted so
+ /// until the character content of the cell is changed.
+ pub fn set_keep_fg(&mut self, new_val: bool) -> &mut Cell {
+ self.keep_fg = new_val;
+ self
+ }
+
+ /// Sets `keep_bg` field. If true, the background color will not be altered if attempted so
+ /// until the character content of the cell is changed.
+ pub fn set_keep_bg(&mut self, new_val: bool) -> &mut Cell {
+ self.keep_bg = new_val;
+ self
+ }
+}
+
+impl Default for Cell {
+ /// Constructs a new `Cell` with a blank `char` and default `Color`s.
+ ///
+ /// # Examples
+ ///
+ /// ```norun
+ ///
+ /// let mut cell = Cell::default();
+ /// assert_eq!(cell.ch(), ' ');
+ /// assert_eq!(cell.fg(), Color::Default);
+ /// assert_eq!(cell.bg(), Color::Default);
+ /// ```
+ fn default() -> Cell {
+ Cell::new(' ', Color::Default, Color::Default, Attr::Default)
+ }
+}
+
+/// The color of a `Cell`.
+///
+/// `Color::Default` represents the default color of the underlying terminal.
+///
+/// The eight basic colors may be used directly and correspond to 0x00..0x07 in the 8-bit (256)
+/// color range; in addition, the eight basic colors coupled with `Attr::Bold` correspond to
+/// 0x08..0x0f in the 8-bit color range.
+///
+/// `Color::Byte(..)` may be used to specify a color in the 8-bit range.
+///
+/// # Examples
+///
+/// ```norun
+///
+/// // The default color.
+/// let default = Color::Default;
+///
+/// // A basic color.
+/// let red = Color::Red;
+///
+/// // An 8-bit color.
+/// let fancy = Color::Byte(0x01);
+///
+/// // Basic colors are also 8-bit colors (but not vice-versa).
+/// assert_eq!(red.as_byte(), fancy.as_byte())
+/// ```
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub enum Color {
+ Black,
+ Red,
+ Green,
+ Yellow,
+ Blue,
+ Magenta,
+ Cyan,
+ White,
+ Byte(u8),
+ Rgb(u8, u8, u8),
+ Default,
+}
+
+impl Color {
+ /// Returns the `u8` representation of the `Color`.
+ pub fn as_byte(self) -> u8 {
+ match self {
+ Color::Black => 0x00,
+ Color::Red => 0x01,
+ Color::Green => 0x02,
+ Color::Yellow => 0x03,
+ Color::Blue => 0x04,
+ Color::Magenta => 0x05,
+ Color::Cyan => 0x06,
+ Color::White => 0x07,
+ Color::Byte(b) => b,
+ Color::Rgb(_, _, _) => unreachable!(),
+ Color::Default => 0x00,
+ }
+ }
+
+ pub fn from_byte(val: u8) -> Self {
+ match val {
+ 0x00 => Color::Black,
+ 0x01 => Color::Red,
+ 0x02 => Color::Green,
+ 0x03 => Color::Yellow,
+ 0x04 => Color::Blue,
+ 0x05 => Color::Magenta,
+ 0x06 => Color::Cyan,
+ 0x07 => Color::White,
+ _ => Color::Default,
+ }
+ }
+
+ pub fn write_fg(self, stdout: &mut crate::state::StateStdout) -> std::io::Result<()> {
+ use std::io::Write;
+ match self {
+ Color::Default => write!(stdout, "{}", termion::color::Fg(termion::color::Reset)),
+ Color::Rgb(r, g, b) => write!(stdout, "{}", termion::color::Fg(TermionRgb(r, g, b))),
+ _ => write!(stdout, "{}", termion::color::Fg(self.as_termion())),
+ }
+ }
+
+ pub fn write_bg(self, stdout: &mut crate::state::StateStdout) -> std::io::Result<()> {
+ use std::io::Write;
+ match self {
+ Color::Default => write!(stdout, "{}", termion::color::Bg(termion::color::Reset)),
+ Color::Rgb(r, g, b) => write!(stdout, "{}", termion::color::Bg(TermionRgb(r, g, b))),
+ _ => write!(stdout, "{}", termion::color::Bg(self.as_termion())),
+ }
+ }
+
+ pub fn as_termion(self) -> AnsiValue {
+ match self {
+ b @ Color::Black
+ | b @ Color::Red
+ | b @ Color::Green
+ | b @ Color::Yellow
+ | b @ Color::Blue
+ | b @ Color::Magenta
+ | b @ Color::Cyan
+ | b @ Color::White
+ | b @ Color::Default => AnsiValue(b.as_byte()),
+ Color::Byte(b) => AnsiValue(b),
+ Color::Rgb(_, _, _) => AnsiValue(0),
+ }
+ }
+}
+
+impl Default for Color {
+ fn default() -> Self {
+ Color::Default
+ }
+}
+
+/// The attributes of a `Cell`.
+///
+/// `Attr` enumerates all combinations of attributes a given style may have.
+///
+/// `Attr::Default` represents no attribute.
+///
+/// # Examples
+///
+/// ```norun
+///
+/// // Default attribute.
+/// let def = Attr::Default;
+///
+/// // Base attribute.
+/// let base = Attr::Bold;
+///
+/// // Combination.
+/// let comb = Attr::UnderlineReverse;
+/// ```
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub enum Attr {
+ Default = 0b000,
+ Bold = 0b001,
+ Underline = 0b100,
+ BoldUnderline = 0b011,
+ Reverse = 0b010,
+ BoldReverse = 0b101,
+ UnderlineReverse = 0b110,
+ BoldReverseUnderline = 0b111,
+}
+
+impl core::ops::BitOr for Attr {
+ type Output = Attr;
+
+ fn bitor(self, rhs: Self) -> Self::Output {
+ match self as u8 | rhs as u8 {
+ 0b000 => Attr::Default,
+ 0b001 => Attr::Bold,
+ 0b100 => Attr::Underline,
+ 0b011 => Attr::BoldUnderline,
+ 0b010 => Attr::Reverse,
+ 0b101 => Attr::BoldReverse,
+ 0b110 => Attr::UnderlineReverse,
+ 0b111 => Attr::BoldReverseUnderline,
+ _ => unsafe { std::hint::unreachable_unchecked() },
+ }
+ }
+}
+
+impl core::ops::BitAnd for Attr {
+ type Output = bool;
+
+ fn bitand(self, rhs: Self) -> Self::Output {
+ self as u8 & rhs as u8 > 0
+ }
+}
+
+impl core::ops::BitOrAssign for Attr {
+ fn bitor_assign(&mut self, rhs: Attr) {
+ use Attr::*;
+ *self = match *self as u8 | rhs as u8 {
+ 0b000 => Default,
+ 0b001 => Bold,
+ 0b100 => Underline,
+ 0b011 => BoldUnderline,
+ 0b010 => Reverse,
+ 0b101 => BoldReverse,
+ 0b110 => UnderlineReverse,
+ 0b111 => BoldReverseUnderline,
+ _ => unsafe { std::hint::unreachable_unchecked() },
+ };
+ }
+}
+
+impl Default for Attr {
+ fn default() -> Self {
+ Attr::Default
+ }
+}
+
+impl Attr {
+ pub fn write(self, prev: Attr, stdout: &mut crate::state::StateStdout) -> std::io::Result<()> {
+ use std::io::Write;
+ match (self & Attr::Bold, prev & Attr::Bold) {
+ (true, true) | (false, false) => Ok(()),
+ (false, true) => write!(stdout, "\x1B[22m"),
+ (true, false) => write!(stdout, "\x1B[1m"),
+ }
+ .and_then(|_| match (self & Attr::Underline, prev & Attr::Underline) {
+ (true, true) | (false, false) => Ok(()),
+ (false, true) => write!(stdout, "\x1B[24m"),
+ (true, false) => write!(stdout, "\x1B[4m"),
+ })
+ .and_then(|_| match (self & Attr::Reverse, prev & Attr::Reverse) {
+ (true, true) | (false, false) => Ok(()),
+ (false, true) => write!(stdout, "\x1B[27m"),
+ (true, false) => write!(stdout, "\x1B[7m"),
+ })
+ }
+}
+
+pub fn copy_area_with_break(
+ grid_dest: &mut CellBuffer,
+ grid_src: &CellBuffer,
+ dest: Area,
+ src: Area,
+) -> Pos {
+ if !is_valid_area!(dest) || !is_valid_area!(src) {
+ eprintln!(
+ "BUG: Invalid areas in copy_area:\n src: {:?}\n dest: {:?}",
+ src, dest
+ );
+ return upper_left!(dest);
+ }
+
+ if grid_src.is_empty() || grid_dest.is_empty() {
+ return upper_left!(dest);
+ }
+
+ let mut ret = bottom_right!(dest);
+ let mut src_x = get_x(upper_left!(src));
+ let mut src_y = get_y(upper_left!(src));
+
+ 'y_: for y in get_y(upper_left!(dest))..=get_y(bottom_right!(dest)) {
+ 'x_: for x in get_x(upper_left!(dest))..=get_x(bottom_right!(dest)) {
+ if grid_src[(src_x, src_y)].ch() == '\n' {
+ src_y += 1;
+ src_x = 0;
+ if src_y >= get_y(bottom_right!(src)) {
+ ret.1 = y;
+ break 'y_;
+ }
+ continue 'y_;
+ }
+
+ grid_dest[(x, y)] = grid_src[(src_x, src_y)];
+ src_x += 1;
+ if src_x >= get_x(bottom_right!(src)) {
+ src_y += 1;
+ src_x = 0;
+ if src_y >= get_y(bottom_right!(src)) {
+ //clear_area(grid_dest, ((get_x(upper_left!(dest)), y), bottom_right!(dest)));
+ ret.1 = y;
+ break 'y_;
+ }
+ break 'x_;
+ }
+ }
+ }
+ ret
+}
+
+/// Change foreground and background colors in an `Area`
+pub fn change_colors(
+ grid: &mut CellBuffer,
+ area: Area,
+ fg_color: Option<Color>,
+ bg_color: Option<Color>,
+) {
+ for row in grid.bounds_iter(area) {
+ for c in row {
+ if let Some(fg_color) = fg_color {
+ grid[c].set_fg(fg_color);
+ }
+ if let Some(bg_color) = bg_color {
+ grid[c].set_bg(bg_color);
+ }
+ }
+ }
+}
+
+macro_rules! inspect_bounds {
+ ($grid:ident, $area:ident, $x: ident, $y: ident, $line_break:ident) => {
+ let bounds = $grid.size();
+ let (upper_left, bottom_right) = $area;
+ if $x > (get_x(bottom_right)) || $x > get_x(bounds) {
+ $x = get_x(upper_left);
+ $y += 1;
+ if $line_break.is_none() {
+ break;
+ } else {
+ $x = $line_break.unwrap();
+ }
+ }
+ if $y > (get_y(bottom_right)) || $y > get_y(bounds) {
+ return ($x, $y - 1);
+ }
+ };
+}
+
+/// Write an `&str` to a `CellBuffer` in a specified `Area` with the passed colors.
+pub fn write_string_to_grid(
+ s: &str,
+ grid: &mut CellBuffer,
+ fg_color: Color,
+ bg_color: Color,
+ attrs: Attr,
+ area: Area,
+ // The left-most x coordinate.
+ line_break: Option<usize>,
+) -> Pos {
+ let bounds = grid.size();
+ let upper_left = upper_left!(area);
+ let bottom_right = bottom_right!(area);
+ let (mut x, mut y) = upper_left;
+ if y == get_y(bounds) || x == get_x(bounds) {
+ return (x, y);
+ }
+
+ if y > (get_y(bottom_right))
+ || x > get_x(bottom_right)
+ || y > get_y(bounds)
+ || x > get_x(bounds)
+ {
+ return (x, y);
+ }
+ for c in s.chars() {
+ if c == '\r' {
+ continue;
+ }
+ if c == '\t' {
+ grid[(x, y)].set_ch(' ');
+ x += 1;
+ inspect_bounds!(grid, area, x, y, line_break);
+ grid[(x, y)].set_ch(' ');
+ } else {
+ grid[(x, y)].set_ch(c);
+ }
+ grid[(x, y)]
+ .set_fg(fg_color)
+ .set_bg(bg_color)
+ .set_attrs(attrs);
+
+ match wcwidth(u32::from(c)) {
+ Some(0) | None => {
+ /* Skip drawing zero width characters */
+ grid[(x, y)].empty = true;
+ }
+ Some(2) => {
+ /* Grapheme takes more than one column, so the next cell will be
+ * drawn over. Set it as empty to skip drawing it. */
+ x += 1;
+ inspect_bounds!(grid, area, x, y, line_break);
+ grid[(x, y)] = Cell::default();
+ grid[(x, y)]
+ .set_fg(fg_color)
+ .set_bg(bg_color)
+ .set_attrs(attrs)
+ .set_empty(true);
+ }
+ _ => {}
+ }
+ x += 1;
+
+ inspect_bounds!(grid, area, x, y, line_break);
+ }
+ (x, y)
+}
+
+/// Completely clear an `Area` with an empty char and the terminal's default colors.
+pub fn clear_area(grid: &mut CellBuffer, area: Area) {
+ if !is_valid_area!(area) {
+ return;
+ }
+ for row in grid.bounds_iter(area) {
+ for c in row {
+ grid[c] = Cell::default();
+ }
+ }
+}
+
+/// Use `RowIterator` to iterate the cells of a row without the need to do any bounds checking;
+/// the iterator will simply return `None` when it reaches the end of the row.
+/// `RowIterator` can be created via the `CellBuffer::row_iter` method and can be returned by
+/// `BoundsIterator` which iterates each row.
+/// ```norun
+/// for c in grid.row_iter(
+/// x..(x + 11),
+/// 0,
+/// ) {
+/// grid[c].set_ch('w');
+/// }
+/// ```
+pub struct RowIterator {
+ row: usize,
+ col: std::ops::Range<usize>,
+}
+
+/// `BoundsIterator` iterates each row returning a `RowIterator`.
+/// ```norun
+/// /* Visit each `Cell` in `area`. */
+/// for c in grid.bounds_iter(area) {
+/// grid[c].set_ch('w');
+/// }
+/// ```
+pub struct BoundsIterator {
+ rows: std::ops::Range<usize>,
+ cols: (usize, usize),
+}
+
+impl Iterator for BoundsIterator {
+ type Item = RowIterator;
+ fn next(&mut self) -> Option<Self::Item> {
+ if let Some(next_row) = self.rows.next() {
+ Some(RowIterator {
+ row: next_row,
+ col: self.cols.0..self.cols.1,
+ })
+ } else {
+ None
+ }
+ }
+}
+
+impl Iterator for RowIterator {
+ type Item = (usize, usize);
+ fn next(&mut self) -> Option<Self::Item> {
+ if let Some(next_col) = self.col.next() {
+ Some((next_col, self.row))
+ } else {
+ None
+ }
+ }
+}
+
+impl RowIterator {
+ pub fn forward_col(mut self, new_val: usize) -> Self {
+ if self.col.start > new_val {
+ self
+ } else if self.col.end <= new_val {
+ self.col.start = self.col.end;
+ self
+ } else {
+ self.col.start = new_val;
+ self
+ }
+ }
+}