use std::{
convert::From,
fmt,
ops::{Deref, DerefMut, Index, IndexMut},
};
use termion::color::{AnsiValue, Rgb as TermionRgb};
use super::position::*;
use crate::text_processing::wcwidth;
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct ScrollRegion {
pub top: usize,
pub bottom: usize,
pub left: usize,
pub right: usize,
}
#[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;
}
pub fn new(cols: usize, rows: usize, cell: Cell) -> CellBuffer {
CellBuffer {
cols,
rows,
buf: vec![cell; cols * rows],
}
}
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;
}
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
}
}
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,
}
}
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)]
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)]
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;
}
}
}
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),
),
}
}
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 {
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(())
}
}
#[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 {
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,
}
}
pub fn with_char(ch: char) -> Cell {
Cell::new(ch, Color::Default, Color::Default, Attr::Default)
}
pub fn with_style(fg: Color, bg: Color, attr: Attr) -> Cell {
Cell::new(' ', fg, bg, attr)
}
pub fn ch(&self) -> char {
self.ch
}
pub fn set_ch(&mut self, newch: char) -> &mut Cell {
self.ch = newch;
self.keep_fg = false;
self.keep_bg = false;
self
}
pub fn fg(&self) -> Color {
self.fg
}
pub fn set_fg(&mut self, newfg: Color) -> &mut Cell {
if !self.keep_fg {
self.fg = newfg;
}
self
}
pub fn bg(&self) -> Color {
self.bg
}
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
}
pub fn empty(&self) -> bool {
self.empty
}
pub fn set_empty(&mut self, new_val: bool) -> &mut Cell {
self.empty = new_val;
self
}
pub fn set_keep_fg(&mut self, new_val: bool) -> &mut Cell {
self.keep_fg = new_val;
self
}
pub fn set_keep_bg(&mut self, new_val: bool) -> &mut Cell {
self.keep_bg = new_val;
self
}
}
impl Default for Cell {
fn default() -> Cell {
Cell::new(' ', Color::Default, Color::Default, Attr::Default)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
pub enum Color {
Black,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
White,
Byte(u8),
Rgb(u8, u8, u8),
#[default]
Default,
}
impl 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),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
pub enum Attr {
#[default]
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 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)) {
ret.1 = y;
break 'y_;
}
break 'x_;
}
}
}
ret
}
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);
}
};
}
pub fn write_string_to_grid(
s: &str,
grid: &mut CellBuffer,
fg_color: Color,
bg_color: Color,
attrs: Attr,
area: Area,
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 => {
grid[(x, y)].empty = true;
}
Some(2) => {
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)
}
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();
}
}
}
pub struct RowIterator {
row: usize,
col: std::ops::Range<usize>,
}
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
}
}
}