/* * meli * * Copyright 2017-2018 Manos Pitsidianakis * * This file is part of meli. * * meli 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. * * meli 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 meli. If not, see . */ //! Define a `(x, y)` point in the terminal display as a holder of a character, //! foreground/background colors and attributes. use std::{ collections::HashMap, convert::From, ops::{Deref, DerefMut, Index, IndexMut}, }; use melib::{ log, text_processing::{search::KMP, wcwidth}, }; use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use smallvec::SmallVec; use super::{position::*, Area, Color, ScreenGeneration}; use crate::{state::Context, ThemeAttribute}; /// In a scroll region up and down cursor movements shift the region vertically. /// The new lines are empty. /// /// See `CellBuffer::scroll_up` and `CellBuffer::scroll_down` for an explanation /// of how `xterm` scrolling works. #[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 { pub cols: usize, pub rows: usize, pub buf: Vec, pub default_cell: Cell, /// ASCII-only flag. pub ascii_drawing: bool, /// Use color. pub use_color: bool, /// If printing to this buffer and we run out of space, expand it. growable: bool, tag_table: HashMap, tag_associations: SmallVec<[(u64, (usize, usize)); 128]>, pub(super) area: Area, } impl std::fmt::Debug for CellBuffer { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { f.debug_struct(stringify!(CellBuffer)) .field("cols", &self.cols) .field("rows", &self.rows) .field("buf cells", &self.buf.len()) .field("default_cell", &self.default_cell) .field("ascii_drawing", &self.ascii_drawing) .field("use_color", &self.use_color) .field("growable", &self.growable) .field("tag_table", &self.tag_table) .field("tag_associations", &self.tag_associations) .field("area", &self.area) .field("generation", &self.area.generation()) .finish() } } impl CellBuffer { pub const MAX_SIZE: usize = 1_000_000; pub fn nil(area: Area) -> Self { Self { cols: 0, rows: 0, buf: vec![], default_cell: Cell::new_default(), growable: false, ascii_drawing: false, use_color: false, tag_table: Default::default(), tag_associations: SmallVec::new(), area, } } 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(default_cell: Cell, area: Area) -> Self { let cols = area.width(); let rows = area.height(); Self { cols, rows, buf: vec![default_cell; cols * rows], default_cell, growable: false, ascii_drawing: false, use_color: true, tag_table: Default::default(), tag_associations: SmallVec::new(), area, } } pub fn new_with_context(default_cell: Option, area: Area, context: &Context) -> Self { let default_cell = default_cell.unwrap_or_else(|| { let mut ret = Cell::default(); let theme_default = crate::conf::value(context, "theme_default"); ret.set_fg(theme_default.fg) .set_bg(theme_default.bg) .set_attrs(theme_default.attrs); ret }); Self { ascii_drawing: context.settings.terminal.ascii_drawing, use_color: context.settings.terminal.use_color(), ..Self::new(default_cell, area) } } pub fn set_ascii_drawing(&mut self, new_val: bool) { self.ascii_drawing = new_val; } pub fn set_use_color(&mut self, new_val: bool) { self.use_color = new_val; } pub fn set_growable(&mut self, new_val: bool) { self.growable = new_val; } /// Resizes `CellBuffer` to the given number of rows and columns, using the /// given `Cell` as a blank. #[must_use] pub(super) fn resize_with_context( &mut self, newcols: usize, newrows: usize, context: &Context, ) -> bool { self.default_cell = { let mut ret = Cell::default(); let theme_default = crate::conf::value(context, "theme_default"); ret.set_fg(theme_default.fg) .set_bg(theme_default.bg) .set_attrs(theme_default.attrs); ret }; self.ascii_drawing = context.settings.terminal.ascii_drawing; self.use_color = context.settings.terminal.use_color(); let newlen = newcols * newrows; if (self.cols, self.rows) == (newcols, newrows) || newlen >= Self::MAX_SIZE { return newlen < Self::MAX_SIZE; } self.buf = vec![self.default_cell; newlen]; self.cols = newcols; self.rows = newrows; true } /// Resizes `CellBuffer` to the given number of rows and columns, using the /// given `Cell` as a blank. #[must_use] pub(super) fn resize(&mut self, newcols: usize, newrows: usize, blank: Option) -> bool { let newlen = newcols * newrows; if (self.cols, self.rows) == (newcols, newrows) || newlen >= Self::MAX_SIZE { return newlen < Self::MAX_SIZE; } let blank = blank.unwrap_or(self.default_cell); self.buf = vec![blank; newlen]; self.cols = newcols; self.rows = newrows; true } 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: Option) { let blank = blank.unwrap_or(self.default_cell); for cell in self.cellvec_mut().iter_mut() { *cell = blank; } } pub fn pos_to_index(&self, x: usize, y: usize) -> Option { 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. 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. 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 { &self.buf } pub fn cellvec_mut(&mut self) -> &mut Vec { &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 { 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 { debug_assert_eq!(self.generation(), area.generation()); BoundsIterator { width: area.width(), height: area.height(), rows: std::cmp::min(self.rows.saturating_sub(1), get_y(area.upper_left())) ..(std::cmp::min(self.rows, get_y(area.bottom_right()) + 1)), cols: ( std::cmp::min(self.cols.saturating_sub(1), get_x(area.upper_left())), std::cmp::min(self.cols, get_x(area.bottom_right()) + 1), ), area, } } /// See `RowIterator` documentation. pub fn row_iter( &self, area: Area, bounds: std::ops::Range, relative_row: usize, ) -> RowIterator { debug_assert_eq!(self.generation(), area.generation()); if self.generation() != area.generation() { return RowIterator::empty(self.generation()); } let row = area.offset().1 + relative_row; if row < self.rows { let col = std::cmp::min(self.cols.saturating_sub(1), area.offset().0 + bounds.start) ..(std::cmp::min(self.cols, area.offset().0 + bounds.end)); let area = area .nth_row(relative_row) .skip_cols(bounds.start) .take_cols(bounds.len()); RowIterator { row, col, area } } else { RowIterator::empty(self.generation()) } } pub fn tag_associations(&self) -> SmallVec<[(usize, u64, bool); 128]> { let mut ret: SmallVec<[(usize, u64, bool); 128]> = self.tag_associations.iter().fold( SmallVec::new(), |mut acc, (tag_hash, (start, end))| { acc.push((*start, *tag_hash, true)); acc.push((*end, *tag_hash, false)); acc }, ); ret.sort_by_key(|el| el.0); ret } pub fn tag_table(&self) -> &HashMap { &self.tag_table } pub fn tag_table_mut(&mut self) -> &mut HashMap { &mut self.tag_table } pub fn insert_tag(&mut self, tag: FormatTag) -> u64 { use std::{ collections::hash_map::DefaultHasher, hash::{Hash, Hasher}, }; let mut hasher = DefaultHasher::new(); tag.hash(&mut hasher); let hash = hasher.finish(); self.tag_table.insert(hash, tag); hash } pub fn set_tag(&mut self, tag: u64, start: (usize, usize), end: (usize, usize)) { let start = self .pos_to_index(start.0, start.1) .unwrap_or_else(|| self.buf.len().saturating_sub(1)); let end = self .pos_to_index(end.0, end.1) .unwrap_or_else(|| self.buf.len().saturating_sub(1)); if start != end { self.tag_associations.push((tag, (start, end))); } } #[inline(always)] pub fn generation(&self) -> ScreenGeneration { self.area.generation() } /// Completely clear an `Area` with an empty char and the terminal's default /// colors. pub fn clear_area(&mut self, area: Area, attributes: ThemeAttribute) { for row in self.bounds_iter(area) { for c in row { self[c] = Cell::default(); self[c] .set_fg(attributes.fg) .set_bg(attributes.bg) .set_attrs(attributes.attrs); } } } /// Change foreground and background colors in an `Area` pub fn change_colors(&mut self, area: Area, fg_color: Color, bg_color: Color) { if cfg!(feature = "debug-tracing") { let bounds = self.size(); let upper_left = area.upper_left(); let bottom_right = area.bottom_right(); let (x, y) = upper_left; if y > (get_y(bottom_right)) || x > get_x(bottom_right) || y >= get_y(bounds) || x >= get_x(bounds) { log::debug!("BUG: Invalid area in change_colors:\n area: {:?}", area); return; } } for row in self.bounds_iter(area) { for c in row { self[c].set_fg(fg_color).set_bg(bg_color); } } } /// Change [`ThemeAttribute`] in an `Area` pub fn change_theme(&mut self, area: Area, theme: ThemeAttribute) { if cfg!(feature = "debug-tracing") { let bounds = self.size(); let upper_left = area.upper_left(); let bottom_right = area.bottom_right(); let (x, y) = upper_left; if y > (get_y(bottom_right)) || x > get_x(bottom_right) || y >= get_y(bounds) || x >= get_x(bounds) { log::debug!("BUG: Invalid area in change_theme:\n area: {:?}", area); return; } } for row in self.bounds_iter(area) { for c in row { self[c] .set_fg(theme.fg) .set_bg(theme.bg) .set_attrs(theme.attrs); } } } /// Copy a source `Area` to a destination. pub fn copy_area(&mut self, grid_src: &Self, dest: Area, src: Area) -> Pos { debug_assert_eq!(self.generation(), dest.generation()); debug_assert_eq!(grid_src.generation(), src.generation()); if self.generation() != dest.generation() || grid_src.generation() != src.generation() { log::debug!( "BUG: Invalid areas in copy_area:\n src: {:?}\n dest: {:?}", src, dest ); return dest.upper_left(); } if grid_src.is_empty() || self.is_empty() { return dest.upper_left(); } let mut ret = dest.bottom_right(); let mut src_x = get_x(src.upper_left()); let mut src_y = get_y(src.upper_left()); let (cols, rows) = grid_src.size(); if src_x >= cols || src_y >= rows { log::debug!("BUG: src area outside of grid_src in copy_area",); return dest.upper_left(); } let tag_associations = grid_src.tag_associations(); let start_idx = grid_src.pos_to_index(src_x, src_y).unwrap(); let mut tag_offset: usize = tag_associations .binary_search_by(|probe| probe.0.cmp(&start_idx)) .unwrap_or_else(|i| i); let mut stack: std::collections::BTreeSet<&FormatTag> = std::collections::BTreeSet::default(); for y in get_y(dest.upper_left())..=get_y(dest.bottom_right()) { 'for_x: for x in get_x(dest.upper_left())..=get_x(dest.bottom_right()) { let idx = grid_src.pos_to_index(src_x, src_y).unwrap(); while tag_offset < tag_associations.len() && tag_associations[tag_offset].0 <= idx { if tag_associations[tag_offset].2 { stack.insert(&grid_src.tag_table()[&tag_associations[tag_offset].1]); } else { stack.remove(&grid_src.tag_table()[&tag_associations[tag_offset].1]); } tag_offset += 1; } self[(x, y)] = grid_src[(src_x, src_y)]; for t in &stack { if let Some(fg) = t.fg { self[(x, y)].set_fg(fg).set_keep_fg(true); } if let Some(bg) = t.bg { self[(x, y)].set_bg(bg).set_keep_bg(true); } if let Some(attrs) = t.attrs { self[(x, y)].attrs |= attrs; self[(x, y)].set_keep_attrs(true); } } if src_x >= get_x(src.bottom_right()) { break 'for_x; } src_x += 1; } src_x = get_x(src.upper_left()); src_y += 1; if src_y > get_y(src.bottom_right()) { for row in self.bounds_iter(dest.skip_rows(y + 1 - get_y(dest.upper_left()))) { for c in row { self[c].set_ch(' '); } } ret.1 = y; break; } } ret } /// Write an `&str` to a `CellBuffer` in a specified `Area` with the passed /// colors. pub fn write_string( &mut self, s: &str, fg_color: Color, bg_color: Color, attrs: Attr, area: Area, // The left-most x coordinate. line_break: Option, ) -> Pos { debug_assert_eq!(area.generation(), self.generation()); if area.generation() != self.generation() { // [ref:TODO] log error return (0, 0); } let mut bounds = self.size(); let upper_left = area.upper_left(); let bottom_right = area.bottom_right(); let (mut x, mut y) = upper_left; if y == get_y(bounds) || x == get_x(bounds) { if self.growable { if !self.resize( std::cmp::max(self.cols, x + 2), std::cmp::max(self.rows, y + 2), None, ) { return (x - upper_left.0, y - upper_left.1); } bounds = self.size(); } else { return (x - upper_left.0, y - upper_left.1); } } if y > (get_y(bottom_right)) || x > get_x(bottom_right) || y > get_y(bounds) || x > get_x(bounds) { if self.growable { if !self.resize( std::cmp::max(self.cols, x + 2), std::cmp::max(self.rows, y + 2), None, ) { return (x - upper_left.0, y - upper_left.1); } } else { log::debug!(" Invalid area with string {} and area {:?}", s, area); return (x - upper_left.0, y - upper_left.1); } } for c in s.chars() { if c == '\r' { continue; } if c == '\n' { y += 1; if let Some(_x) = line_break { x = _x + get_x(upper_left); continue; } else { break; } } if y > get_y(bottom_right) || x > get_x(bottom_right) || y > get_y(bounds) || x > get_x(bounds) { if let Some(_x) = line_break { if !(y > get_y(bottom_right) || y > get_y(bounds)) { x = _x + get_x(upper_left); y += 1; continue; } } break; } if c == '\t' { self[(x, y)].set_ch(' '); x += 1; if let Some(c) = self.get_mut(x, y) { c.set_ch(' '); } else if let Some(_x) = line_break { if !(y > get_y(bottom_right) || y > get_y(bounds)) { x = _x + get_x(upper_left); y += 1; continue; } else { break; } } } else { self[(x, y)].set_ch(c); } self[(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 */ self[(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; self[(x, y)] = Cell::default(); self[(x, y)] .set_fg(fg_color) .set_bg(bg_color) .set_attrs(attrs) .set_empty(true); } _ => {} } x += 1; } (x - upper_left.0, y - upper_left.1) } #[inline] pub const fn area(&self) -> Area { self.area } } impl Deref for CellBuffer { type Target = [Cell]; fn deref(&self) -> &Self::Target { &self.buf } } impl DerefMut for CellBuffer { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.buf } } impl Index for CellBuffer { type Output = Cell; fn index(&self, index: Pos) -> &Self::Output { let (x, y) = index; self.get(x, y).expect("index out of bounds") } } impl IndexMut 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 std::fmt::Display for CellBuffer { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::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, /// Set a `Cell` as empty when a previous cell spans multiple columns and it /// would "overflow" to this cell. empty: bool, fg: Color, bg: Color, attrs: Attr, keep_fg: bool, keep_bg: bool, keep_attrs: bool, } impl Cell { /// Creates a new `Cell` with the given `char`, `Color`s and `Attr`. /// /// # Examples /// /// ```no_run /// use meli::{Attr, Cell, Color}; /// /// 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 const fn new(ch: char, fg: Color, bg: Color, attrs: Attr) -> Self { Self { ch, fg, bg, attrs, empty: false, keep_fg: false, keep_bg: false, keep_attrs: false, } } pub const fn new_default() -> Self { Self::new(' ', Color::Default, Color::Default, Attr::DEFAULT) } /// Creates a new `Cell` with the given `char` and default style. /// /// # Examples /// /// ```no_run /// use meli::{Attr, Cell, Color}; /// /// 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) -> Self { Self::new(ch, Color::Default, Color::Default, Attr::DEFAULT) } /// Creates a new `Cell` with the given style and a blank `char`. /// /// # Examples /// /// ```no_run /// use meli::{Attr, Cell, Color}; /// /// 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) -> Self { Self::new(' ', fg, bg, attr) } /// Returns the `Cell`'s character. /// /// # Examples /// /// ```no_run /// use meli::Cell; /// /// 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 /// /// ```no_run /// use meli::Cell; /// /// 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 Self { self.ch = newch; self.keep_fg = false; self.keep_bg = false; self.keep_attrs = false; self } /// Returns the `Cell`'s foreground `Color`. /// /// # Examples /// /// ```no_run /// use meli::{Attr, Cell, Color}; /// /// 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 /// /// ```no_run /// use meli::{Cell, Color}; /// /// 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 Self { if !self.keep_fg { self.fg = newfg; } self } /// Returns the `Cell`'s background `Color`. /// /// # Examples /// /// ```no_run /// use meli::{Attr, Cell, Color}; /// /// 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 /// /// ```no_run /// use meli::{Cell, Color}; /// /// 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 Self { if !self.keep_bg { self.bg = newbg; } self } pub fn attrs(&self) -> Attr { self.attrs } pub fn set_attrs(&mut self, newattrs: Attr) -> &mut Self { if !self.keep_attrs { 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 Self { 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 Self { 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 Self { self.keep_bg = new_val; self } /// Sets `keep_attrs` field. If true, the text attributes will not be /// altered if attempted so until the character content of the cell is /// changed. pub fn set_keep_attrs(&mut self, new_val: bool) -> &mut Self { self.keep_attrs = new_val; self } } impl Default for Cell { /// Constructs a new `Cell` with a blank `char` and default `Color`s. /// /// # Examples /// /// ```no_run /// use meli::{Cell, Color}; /// /// let mut cell = Cell::default(); /// assert_eq!(cell.ch(), ' '); /// assert_eq!(cell.fg(), Color::Default); /// assert_eq!(cell.bg(), Color::Default); /// ``` fn default() -> Self { Self::new(' ', Color::Default, Color::Default, Attr::DEFAULT) } } bitflags::bitflags! { /// The attributes of a `Cell`. /// /// `Attr` enumerates all combinations of attributes a given style may have. /// /// `Attr::DEFAULT` represents no attribute. /// /// # Examples /// /// ```no_run /// use meli::Attr; /// /// // Default attribute. /// let def = Attr::DEFAULT; /// /// // Base attribute. /// let base = Attr::BOLD; /// /// // Combination. /// let comb = Attr::UNDERLINE | Attr::REVERSE; /// ``` #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Attr: u8 { /// Terminal default. const DEFAULT = 0b000_0000; const BOLD = 0b000_0001; const DIM = 0b000_0010; const ITALICS = 0b000_0100; const UNDERLINE = 0b000_1000; const BLINK = 0b001_0000; const REVERSE = 0b010_0000; const HIDDEN = 0b100_0000; } } impl Default for Attr { fn default() -> Self { Self::DEFAULT } } impl std::fmt::Display for Attr { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match *self { Self::DEFAULT => write!(f, "Default"), Self::BOLD => write!(f, "Bold"), Self::DIM => write!(f, "Dim"), Self::ITALICS => write!(f, "Italics"), Self::UNDERLINE => write!(f, "Underline"), Self::BLINK => write!(f, "Blink"), Self::REVERSE => write!(f, "Reverse"), Self::HIDDEN => write!(f, "Hidden"), combination => { let mut ctr = 0; if combination.intersects(Self::BOLD) { ctr += 1; Self::BOLD.fmt(f)?; } if combination.intersects(Self::DIM) { if ctr > 0 { write!(f, "|")?; } ctr += 1; Self::DIM.fmt(f)?; } if combination.intersects(Self::ITALICS) { if ctr > 0 { write!(f, "|")?; } ctr += 1; Self::ITALICS.fmt(f)?; } if combination.intersects(Self::UNDERLINE) { if ctr > 0 { write!(f, "|")?; } ctr += 1; Self::UNDERLINE.fmt(f)?; } if combination.intersects(Self::BLINK) { if ctr > 0 { write!(f, "|")?; } ctr += 1; Self::BLINK.fmt(f)?; } if combination.intersects(Self::REVERSE) { if ctr > 0 { write!(f, "|")?; } ctr += 1; Self::REVERSE.fmt(f)?; } if combination.intersects(Self::HIDDEN) { if ctr > 0 { write!(f, "|")?; } Self::HIDDEN.fmt(f)?; } write!(f, "") } } } } impl<'de> Deserialize<'de> for Attr { fn deserialize(deserializer: D) -> std::result::Result where D: Deserializer<'de>, { if let Ok(s) = ::deserialize(deserializer) { Self::from_string_de::<'de, D, String>(s) } else { Err(de::Error::custom("Attributes value must be a string.")) } } } impl Serialize for Attr { fn serialize(&self, serializer: S) -> std::result::Result where S: Serializer, { serializer.serialize_str(&self.to_string()) } } impl Attr { pub fn from_string_de<'de, D, T: AsRef>(s: T) -> std::result::Result where D: Deserializer<'de>, { match s.as_ref().trim() { "Default" => Ok(Self::DEFAULT), "Dim" => Ok(Self::DIM), "Bold" => Ok(Self::BOLD), "Italics" => Ok(Self::ITALICS), "Underline" => Ok(Self::UNDERLINE), "Blink" => Ok(Self::BLINK), "Reverse" => Ok(Self::REVERSE), "Hidden" => Ok(Self::HIDDEN), combination if combination.contains('|') => { let mut ret = Self::DEFAULT; for c in combination.trim().split('|') { ret |= Self::from_string_de::<'de, D, &str>(c)?; } Ok(ret) } _ => Err(de::Error::custom( r#"Text attribute value must either be a single attribute (eg "Bold") or a combination of attributes separated by "|" (eg "Bold|Underline"). Valid attributes are "Default", "Bold", "Italics", "Underline", "Blink", "Reverse" and "Hidden"."#, )), } } pub fn write(self, prev: Self, stdout: &mut crate::StateStdout) -> std::io::Result<()> { use std::io::Write; match (self.intersects(Self::BOLD), prev.intersects(Self::BOLD)) { (true, true) | (false, false) => Ok(()), (false, true) => write!(stdout, "\x1B[22m"), (true, false) => write!(stdout, "\x1B[1m"), } .and_then( |_| match (self.intersects(Self::DIM), prev.intersects(Self::DIM)) { (true, true) | (false, false) => Ok(()), (false, true) => write!(stdout, "\x1B[22m"), (true, false) => write!(stdout, "\x1B[2m"), }, ) .and_then(|_| { match ( self.intersects(Self::ITALICS), prev.intersects(Self::ITALICS), ) { (true, true) | (false, false) => Ok(()), (false, true) => write!(stdout, "\x1B[23m"), (true, false) => write!(stdout, "\x1B[3m"), } }) .and_then(|_| { match ( self.intersects(Self::UNDERLINE), prev.intersects(Self::UNDERLINE), ) { (true, true) | (false, false) => Ok(()), (false, true) => write!(stdout, "\x1B[24m"), (true, false) => write!(stdout, "\x1B[4m"), } }) .and_then( |_| match (self.intersects(Self::BLINK), prev.intersects(Self::BLINK)) { (true, true) | (false, false) => Ok(()), (false, true) => write!(stdout, "\x1B[25m"), (true, false) => write!(stdout, "\x1B[5m"), }, ) .and_then(|_| { match ( self.intersects(Self::REVERSE), prev.intersects(Self::REVERSE), ) { (true, true) | (false, false) => Ok(()), (false, true) => write!(stdout, "\x1B[27m"), (true, false) => write!(stdout, "\x1B[7m"), } }) .and_then(|_| { match (self.intersects(Self::HIDDEN), prev.intersects(Self::HIDDEN)) { (true, true) | (false, false) => Ok(()), (false, true) => write!(stdout, "\x1B[28m"), (true, false) => write!(stdout, "\x1B[8m"), } }) } } /// 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. /// /// ```rust,no_run /// # use meli::terminal::{Screen, Virtual, Area}; /// # let mut screen = Screen::::new(); /// # assert!(screen.resize(120, 20)); /// # let area = screen.area(); /// for c in screen.grid().row_iter(area, 0..area.width(), 2) { /// screen.grid_mut()[c].set_ch('g'); /// } /// ``` #[derive(Debug)] pub struct RowIterator { row: usize, col: std::ops::Range, area: Area, } /// [`BoundsIterator`] iterates each row returning a [`RowIterator`]. /// /// ```rust,no_run /// # use meli::terminal::{Screen, Virtual, Area}; /// # let mut screen = Screen::::new(); /// # assert!(screen.resize(120, 20)); /// # let area = screen.area(); /// for row in screen.grid().bounds_iter(area) { /// for c in row { /// screen.grid_mut()[c].set_ch('g'); /// } /// } /// ``` #[derive(Clone)] pub struct BoundsIterator { rows: std::ops::Range, cols: (usize, usize), width: usize, height: usize, area: Area, } impl std::fmt::Debug for BoundsIterator { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { f.debug_struct(stringify!(BoundsIterator)) .field("rows", &self.rows) .field("cols", &self.cols) .field("width", &self.width) .field("height", &self.height) .field("is_empty", &self.is_empty()) .field("bounds_area", &self.area) .finish() } } impl BoundsIterator { #[inline] pub fn area(&self) -> Area { self.area } #[inline] pub fn width(&self) -> usize { self.width } #[inline] pub fn height(&self) -> usize { self.height } #[inline] pub fn is_empty(&self) -> bool { self.area.is_empty() || self.width == 0 || self.height == 0 || self.rows.len() == 0 } pub fn add_x(&mut self, x: usize) { if x == 0 { return; } self.width = self.width.saturating_sub(x); self.cols.0 += x; self.cols.0 = self.cols.0.min(self.cols.1); self.area = self.area.skip_cols(x); } } impl Iterator for BoundsIterator { type Item = RowIterator; fn next(&mut self) -> Option { let row = self.rows.next()?; let area = self.area.nth_row(0); self.area = self.area.skip_rows(1); self.height = self.area.height(); Some(RowIterator { row, col: self.cols.0..self.cols.1, area, }) } } impl Iterator for RowIterator { type Item = (usize, usize); fn next(&mut self) -> Option { let x = self.col.next()?; self.area = self.area.skip_cols(1); Some((x, self.row)) } } impl RowIterator { #[inline] pub const fn area(&self) -> Area { self.area } #[inline] pub const fn row_index(&self) -> usize { self.row } pub const fn empty(generation: ScreenGeneration) -> Self { Self { row: 0, col: 0..0, area: Area::new_empty(generation), } } } pub use boundaries::create_box; pub mod boundaries { use super::*; pub const HORZ_BOUNDARY: char = '─'; pub const VERT_BOUNDARY: char = '│'; pub const _TOP_LEFT_CORNER: char = '┌'; pub const _TOP_RIGHT_CORNER: char = '┐'; pub const _BOTTOM_LEFT_CORNER: char = '└'; pub const _BOTTOM_RIGHT_CORNER: char = '┘'; pub const LIGHT_VERTICAL_AND_RIGHT: char = '├'; pub const _LIGHT_VERTICAL_AND_LEFT: char = '┤'; pub const _LIGHT_DOWN_AND_HORIZONTAL: char = '┬'; pub const _LIGHT_UP_AND_HORIZONTAL: char = '┴'; pub const _DOUBLE_DOWN_AND_RIGHT: char = '╔'; pub const _DOUBLE_DOWN_AND_LEFT: char = '╗'; pub const _DOUBLE_UP_AND_LEFT: char = '╝'; pub const _DOUBLE_UP_AND_RIGHT: char = '╚'; fn bin_to_ch(b: u32) -> char { match b { 0b0001 => '╶', 0b0010 => '╵', 0b0011 => '└', 0b0100 => '╴', 0b0101 => '─', 0b0110 => '┘', 0b0111 => '┴', 0b1000 => '╷', 0b1001 => '┌', 0b1010 => '│', 0b1011 => '├', 0b1100 => '┐', 0b1101 => '┬', 0b1110 => '┤', 0b1111 => '┼', _ => unsafe { std::hint::unreachable_unchecked() }, } } fn ch_to_bin(ch: char) -> Option { match ch { '└' => Some(0b0011), '─' => Some(0b0101), '┘' => Some(0b0110), '┴' => Some(0b0111), '┌' => Some(0b1001), '│' => Some(0b1010), '├' => Some(0b1011), '┐' => Some(0b1100), '┬' => Some(0b1101), '┤' => Some(0b1110), '┼' => Some(0b1111), '╷' => Some(0b1000), '╵' => Some(0b0010), '╴' => Some(0b0100), '╶' => Some(0b0001), _ => None, } } #[allow(clippy::never_loop)] fn set_and_join_vert(grid: &mut CellBuffer, idx: Pos) -> u32 { let (x, y) = idx; let mut bin_set = 0b1010; /* Check left side * * 1 * -> 2 │ 0 * 3 */ loop { if x > 0 { if let Some(cell) = grid.get_mut(x - 1, y) { if let Some(adj) = ch_to_bin(cell.ch()) { if (adj & 0b0001) > 0 { bin_set |= 0b0100; break; } else if adj == 0b0100 { cell.set_ch(bin_to_ch(0b0101)); bin_set |= 0b0100; break; } } } } bin_set &= 0b1011; break; } /* Check right side * * 1 * 2 │ 0 <- * 3 */ loop { if let Some(cell) = grid.get_mut(x + 1, y) { if let Some(adj) = ch_to_bin(cell.ch()) { if (adj & 0b0100) > 0 { bin_set |= 0b0001; break; } } } bin_set &= 0b1110; break; } /* Set upper side * * 1 <- * 2 │ 0 * 3 */ loop { if y > 0 { if let Some(cell) = grid.get_mut(x, y - 1) { if let Some(adj) = ch_to_bin(cell.ch()) { cell.set_ch(bin_to_ch(adj | 0b1000)); } else { bin_set &= 0b1101; } } } break; } /* Set bottom side * * 1 * 2 │ 0 * 3 <- */ loop { if let Some(cell) = grid.get_mut(x, y + 1) { if let Some(adj) = ch_to_bin(cell.ch()) { cell.set_ch(bin_to_ch(adj | 0b0010)); } else { bin_set &= 0b0111; } } break; } if bin_set == 0 { bin_set = 0b1010; } bin_set } #[allow(clippy::never_loop)] fn set_and_join_horz(grid: &mut CellBuffer, idx: Pos) -> u32 { let (x, y) = idx; let mut bin_set = 0b0101; /* Check upper side * * 1 <- * 2 ─ 0 * 3 */ loop { if y > 0 { if let Some(cell) = grid.get_mut(x, y - 1) { if let Some(adj) = ch_to_bin(cell.ch()) { if (adj & 0b1000) > 0 { bin_set |= 0b0010; break; } else if adj == 0b0010 { bin_set |= 0b0010; cell.set_ch(bin_to_ch(0b1010)); break; } } } } bin_set &= 0b1101; break; } /* Check bottom side * * 1 * 2 ─ 0 * 3 <- */ loop { if let Some(cell) = grid.get_mut(x, y + 1) { if let Some(adj) = ch_to_bin(cell.ch()) { if (adj & 0b0010) > 0 { bin_set |= 0b1000; break; } else if adj == 0b1000 { bin_set |= 0b1000; cell.set_ch(bin_to_ch(0b1010)); break; } } } bin_set &= 0b0111; break; } /* Set left side * * 1 * -> 2 ─ 0 * 3 */ loop { if x > 0 { if let Some(cell) = grid.get_mut(x - 1, y) { if let Some(adj) = ch_to_bin(cell.ch()) { cell.set_ch(bin_to_ch(adj | 0b0001)); } else { bin_set &= 0b1011; } } } break; } /* Set right side * * 1 * 2 ─ 0 <- * 3 */ loop { if let Some(cell) = grid.get_mut(x + 1, y) { if let Some(adj) = ch_to_bin(cell.ch()) { cell.set_ch(bin_to_ch(adj | 0b0100)); } else { bin_set &= 0b1110; } } break; } if bin_set == 0 { bin_set = 0b0101; } bin_set } pub enum BoxBoundary { Horizontal, Vertical, } pub fn set_and_join_box(grid: &mut CellBuffer, idx: Pos, ch: BoxBoundary) { /* Connected sides: * * 1 * 2 c 0 * 3 * * #3210 * 0b____ */ if grid.ascii_drawing { grid[idx].set_ch(match ch { BoxBoundary::Vertical => '|', BoxBoundary::Horizontal => '-', }); return; } let bin_set = match ch { BoxBoundary::Vertical => set_and_join_vert(grid, idx), BoxBoundary::Horizontal => set_and_join_horz(grid, idx), }; grid[idx].set_ch(bin_to_ch(bin_set)); } /// Puts boundaries in `area`. /// Returns the inner area of the created box. pub fn create_box(grid: &mut CellBuffer, area: Area) -> Area { debug_assert_eq!(grid.generation(), area.generation()); let upper_left = area.upper_left(); let bottom_right = area.bottom_right(); if !grid.ascii_drawing { for x in get_x(upper_left)..get_x(bottom_right) { grid[(x, get_y(upper_left))].set_ch(HORZ_BOUNDARY); grid[(x, get_y(bottom_right))].set_ch(HORZ_BOUNDARY); } for y in get_y(upper_left)..get_y(bottom_right) { grid[(get_x(upper_left), y)].set_ch(VERT_BOUNDARY); grid[(get_x(bottom_right), y)].set_ch(VERT_BOUNDARY); } set_and_join_box(grid, upper_left, BoxBoundary::Horizontal); set_and_join_box( grid, set_x(upper_left, get_x(bottom_right)), BoxBoundary::Horizontal, ); set_and_join_box( grid, set_y(upper_left, get_y(bottom_right)), BoxBoundary::Vertical, ); set_and_join_box(grid, bottom_right, BoxBoundary::Vertical); } area.skip(1, 1).skip_rows_from_end(1).skip_cols_from_end(1) } } impl KMP for CellBuffer { fn kmp_search(&self, pattern: &str) -> smallvec::SmallVec<[usize; 256]> { let (mut w, prev_ind) = pattern .char_indices() .skip(1) .fold((vec![], 0), |(mut acc, prev_ind), (i, _)| { acc.push(&pattern[prev_ind..i]); (acc, i) }); w.push(&pattern[prev_ind..]); let t = Self::kmp_table(&w); let mut j = 0; // (the position of the current character in text) let mut k = 0; // (the position of the current character in pattern) let mut ret = smallvec::SmallVec::new(); while j < self.buf.len() && k < w.len() as i32 { if self.buf[j].ch() == '\n' { j += 1; continue; } if w[k as usize] == self.buf[j].ch().encode_utf8(&mut [0; 4]) { j += 1; k += 1; if k as usize == w.len() { ret.push(j - (k as usize)); k = t[k as usize]; } } else { k = t[k as usize]; if k < 0 { j += 1; k += 1; } } } ret } } #[derive(Debug, Default, Copy, Hash, Clone, PartialEq, Eq)] pub struct FormatTag { pub fg: Option, pub bg: Option, pub attrs: Option, pub priority: u8, } impl std::cmp::Ord for FormatTag { fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.priority.cmp(&other.priority) } } impl std::cmp::PartialOrd for FormatTag { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl From for FormatTag { fn from(val: ThemeAttribute) -> Self { let ThemeAttribute { fg, bg, attrs, .. } = val; Self { fg: Some(fg), bg: Some(bg), attrs: Some(attrs), priority: 0, } } } impl FormatTag { #[inline(always)] pub fn set_priority(mut self, new_val: u8) -> Self { self.priority = new_val; self } } #[derive(Debug, Copy, Hash, Clone, PartialEq, Eq)] pub enum WidgetWidth { Unset, Hold(usize), Set(usize), } #[cfg(test)] mod tests { use crate::terminal::{Screen, Virtual}; //use melib::text_processing::{Reflow, TextProcessing, _ALICE_CHAPTER_1}; #[test] fn test_cellbuffer_search() { //let lines: Vec = // _ALICE_CHAPTER_1.split_lines_reflow(Reflow::All, Some(78)); // let mut buf = CellBuffer::new( // lines.iter().map(String::len).max().unwrap(), // lines.len(), // Cell::with_char(' '), //); //let width = buf.size().0; //for (i, l) in lines.iter().enumerate() { // buf.write_string( // l, // Color::Default, // Color::Default, // Attr::DEFAULT, // ((0, i), (width.saturating_sub(1), i)), // None, // ); //} //for ind in buf.kmp_search("Alice") { // for c in &buf.cellvec()[ind..std::cmp::min(buf.cellvec().len(), // ind + 25)] { print!("{}", c.ch()); // } // println!(); //} } #[test] fn test_bounds_iter() { let mut screen = Screen::::new(); assert!(screen.resize(120, 20)); let area = screen.area(); assert_eq!(area.width(), 120); assert_eq!(area.height(), 20); let mut full_bounds = screen.grid().bounds_iter(area); assert_eq!(full_bounds.area(), area); assert_eq!(full_bounds.width(), area.width()); assert_eq!(full_bounds.height(), area.height()); assert!(!full_bounds.is_empty()); full_bounds.add_x(0); assert_eq!(full_bounds.area(), area); full_bounds.add_x(1); assert_eq!(full_bounds.area().width(), area.width() - 1); full_bounds.add_x(area.width()); assert_eq!(full_bounds.width(), 0); assert_eq!(full_bounds.area().width(), 0); let full_bounds = screen.grid().bounds_iter(area); let row_iters = full_bounds.into_iter().collect::>(); assert_eq!(row_iters.len(), area.height()); for mid in 0..row_iters.len() { assert_eq!(mid, row_iters[mid].row_index()); let (left, right) = row_iters.as_slice().split_at(mid); let mid = &right[0]; assert!(area.contains(mid.area())); for l in left { assert!(area.contains(l.area())); assert!(!mid.area().contains(l.area())); } for r in &right[1..] { assert!(area.contains(r.area())); assert!(!mid.area().contains(r.area())); } } let inner_area = area.place_inside((60, 10), true, true); let bounds = screen.grid().bounds_iter(inner_area); let row_iters = bounds.into_iter().collect::>(); assert_eq!(row_iters.len(), inner_area.height()); for mut row in row_iters { let row_index = row.row_index(); assert_eq!(row.area().width(), 61); assert_eq!(row.next(), Some((2, row_index))); assert_eq!(row.area().width(), 60); assert_eq!( &row.collect::>(), &(3..63) .zip(std::iter::repeat(row_index)) .collect::>() ); } } }