diff options
Diffstat (limited to 'vendor/papergrid/src/config')
-rw-r--r-- | vendor/papergrid/src/config/alignment.rs | 21 | ||||
-rw-r--r-- | vendor/papergrid/src/config/border.rs | 142 | ||||
-rw-r--r-- | vendor/papergrid/src/config/borders.rs | 152 | ||||
-rw-r--r-- | vendor/papergrid/src/config/compact/mod.rs | 141 | ||||
-rw-r--r-- | vendor/papergrid/src/config/entity.rs | 120 | ||||
-rw-r--r-- | vendor/papergrid/src/config/indent.rs | 31 | ||||
-rw-r--r-- | vendor/papergrid/src/config/line.rs | 42 | ||||
-rw-r--r-- | vendor/papergrid/src/config/mod.rs | 23 | ||||
-rw-r--r-- | vendor/papergrid/src/config/position.rs | 13 | ||||
-rw-r--r-- | vendor/papergrid/src/config/sides.rs | 37 | ||||
-rw-r--r-- | vendor/papergrid/src/config/spanned/borders_config.rs | 486 | ||||
-rw-r--r-- | vendor/papergrid/src/config/spanned/entity_map.rs | 108 | ||||
-rw-r--r-- | vendor/papergrid/src/config/spanned/formatting.rs | 21 | ||||
-rw-r--r-- | vendor/papergrid/src/config/spanned/mod.rs | 887 | ||||
-rw-r--r-- | vendor/papergrid/src/config/spanned/offset.rs | 8 |
15 files changed, 2232 insertions, 0 deletions
diff --git a/vendor/papergrid/src/config/alignment.rs b/vendor/papergrid/src/config/alignment.rs new file mode 100644 index 000000000..2b3b5e9db --- /dev/null +++ b/vendor/papergrid/src/config/alignment.rs @@ -0,0 +1,21 @@ +/// [`AlignmentHorizontal`] represents an horizontal alignment of a cell content. +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub enum AlignmentHorizontal { + /// Align to the center. + Center, + /// Align on the left. + Left, + /// Align on the right. + Right, +} + +/// [`AlignmentVertical`] represents an vertical alignment of a cell content. +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub enum AlignmentVertical { + /// Align to the center. + Center, + /// Align to the top. + Top, + /// Align to the bottom. + Bottom, +} diff --git a/vendor/papergrid/src/config/border.rs b/vendor/papergrid/src/config/border.rs new file mode 100644 index 000000000..e803900f7 --- /dev/null +++ b/vendor/papergrid/src/config/border.rs @@ -0,0 +1,142 @@ +/// Border is a representation of a cells's borders (left, right, top, bottom, and the corners) +/// +/// +/// ```text +/// top border +/// | +/// V +/// corner top left ------> +_______+ <---- corner top left +/// | | +/// left border ----------> | cell | <---- right border +/// | | +/// corner bottom right --> +_______+ <---- corner bottom right +/// ^ +/// | +/// bottom border +/// ``` +#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, PartialOrd, Ord)] +pub struct Border<T> { + /// A character for a top. + pub top: Option<T>, + /// A character for a bottom. + pub bottom: Option<T>, + /// A character for a left. + pub left: Option<T>, + /// A character for a right. + pub right: Option<T>, + /// A character for a left top corner. + pub left_top_corner: Option<T>, + /// A character for a left bottom corner. + pub left_bottom_corner: Option<T>, + /// A character for a right top corner. + pub right_top_corner: Option<T>, + /// A character for a right bottom corner. + pub right_bottom_corner: Option<T>, +} + +impl<T> Border<T> { + /// This function constructs a cell borders with all sides set. + #[allow(clippy::too_many_arguments)] + pub const fn full( + top: T, + bottom: T, + left: T, + right: T, + top_left: T, + top_right: T, + bottom_left: T, + bottom_right: T, + ) -> Self { + Self { + top: Some(top), + bottom: Some(bottom), + right: Some(right), + right_top_corner: Some(top_right), + right_bottom_corner: Some(bottom_right), + left: Some(left), + left_bottom_corner: Some(bottom_left), + left_top_corner: Some(top_left), + } + } + + /// Checks whether any side is set. + pub const fn is_empty(&self) -> bool { + self.top.is_none() + && self.left_top_corner.is_none() + && self.right_top_corner.is_none() + && self.bottom.is_none() + && self.left_bottom_corner.is_none() + && self.left_top_corner.is_none() + && self.left.is_none() + && self.right.is_none() + } + + /// Verifies whether anything is set on the top. + pub const fn has_top(&self) -> bool { + self.top.is_some() || self.left_top_corner.is_some() || self.right_top_corner.is_some() + } + + /// Verifies whether anything is set on the bottom. + pub const fn has_bottom(&self) -> bool { + self.bottom.is_some() + || self.left_bottom_corner.is_some() + || self.right_bottom_corner.is_some() + } + + /// Verifies whether anything is set on the left. + pub const fn has_left(&self) -> bool { + self.left.is_some() || self.left_top_corner.is_some() || self.left_bottom_corner.is_some() + } + + /// Verifies whether anything is set on the right. + pub const fn has_right(&self) -> bool { + self.right.is_some() + || self.right_top_corner.is_some() + || self.right_bottom_corner.is_some() + } +} + +impl<T: Copy> Border<T> { + /// This function constructs a cell borders with all sides's char set to a given character. + /// + /// It behaves like [`Border::full`] with the same character set to each side. + pub fn filled(c: T) -> Self { + Self::full(c, c, c, c, c, c, c, c) + } +} + +impl<T: Copy> Border<&T> { + /// This function constructs a cell borders with all sides's char set to a given character. + /// + /// It behaves like [`Border::full`] with the same character set to each side. + pub fn copied(&self) -> Border<T> { + Border { + top: self.top.copied(), + bottom: self.bottom.copied(), + left: self.left.copied(), + right: self.right.copied(), + left_bottom_corner: self.left_bottom_corner.copied(), + left_top_corner: self.left_top_corner.copied(), + right_bottom_corner: self.right_bottom_corner.copied(), + right_top_corner: self.right_top_corner.copied(), + } + } +} + +impl<T: Clone> Border<&T> { + /// This function constructs a cell borders with all sides's char set to a given character. + /// + /// It behaves like [`Border::full`] with the same character set to each side. + pub fn cloned(&self) -> Border<T> { + Border { + top: self.top.cloned(), + bottom: self.bottom.cloned(), + left: self.left.cloned(), + right: self.right.cloned(), + left_bottom_corner: self.left_bottom_corner.cloned(), + left_top_corner: self.left_top_corner.cloned(), + right_bottom_corner: self.right_bottom_corner.cloned(), + right_top_corner: self.right_top_corner.cloned(), + } + } +} diff --git a/vendor/papergrid/src/config/borders.rs b/vendor/papergrid/src/config/borders.rs new file mode 100644 index 000000000..d242e1f63 --- /dev/null +++ b/vendor/papergrid/src/config/borders.rs @@ -0,0 +1,152 @@ +/// Borders represents a Table frame with horizontal and vertical split lines. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Borders<T> { + /// A top horizontal on the frame. + pub top: Option<T>, + /// A top left on the frame. + pub top_left: Option<T>, + /// A top right on the frame. + pub top_right: Option<T>, + /// A top horizontal intersection on the frame. + pub top_intersection: Option<T>, + + /// A bottom horizontal on the frame. + pub bottom: Option<T>, + /// A bottom left on the frame. + pub bottom_left: Option<T>, + /// A bottom right on the frame. + pub bottom_right: Option<T>, + /// A bottom horizontal intersection on the frame. + pub bottom_intersection: Option<T>, + + /// A horizontal split. + pub horizontal: Option<T>, + /// A vertical split. + pub vertical: Option<T>, + /// A top left character on the frame. + pub intersection: Option<T>, + + /// A vertical split on the left frame line. + pub left: Option<T>, + /// A horizontal split on the left frame line. + pub left_intersection: Option<T>, + + /// A vertical split on the right frame line. + pub right: Option<T>, + /// A horizontal split on the right frame line. + pub right_intersection: Option<T>, +} + +impl<T> Borders<T> { + /// Returns empty borders. + pub const fn empty() -> Self { + Self { + top: None, + top_left: None, + top_right: None, + top_intersection: None, + bottom: None, + bottom_left: None, + bottom_right: None, + bottom_intersection: None, + horizontal: None, + left: None, + right: None, + vertical: None, + left_intersection: None, + right_intersection: None, + intersection: None, + } + } + + /// Returns Borders filled in with a supplied value. + pub const fn filled(val: T) -> Self + where + T: Copy, + { + Self { + top: Some(val), + top_left: Some(val), + top_right: Some(val), + top_intersection: Some(val), + bottom: Some(val), + bottom_left: Some(val), + bottom_right: Some(val), + bottom_intersection: Some(val), + horizontal: Some(val), + left: Some(val), + right: Some(val), + vertical: Some(val), + left_intersection: Some(val), + right_intersection: Some(val), + intersection: Some(val), + } + } + + /// A verification whether any border was set. + pub const fn is_empty(&self) -> bool { + !(self.top.is_some() + || self.top_left.is_some() + || self.top_right.is_some() + || self.top_intersection.is_some() + || self.bottom.is_some() + || self.bottom_left.is_some() + || self.bottom_right.is_some() + || self.bottom_intersection.is_some() + || self.horizontal.is_some() + || self.left.is_some() + || self.right.is_some() + || self.vertical.is_some() + || self.left_intersection.is_some() + || self.right_intersection.is_some() + || self.intersection.is_some()) + } + + /// Verifies if borders has left line set on the frame. + pub const fn has_left(&self) -> bool { + self.left.is_some() + || self.left_intersection.is_some() + || self.top_left.is_some() + || self.bottom_left.is_some() + } + + /// Verifies if borders has right line set on the frame. + pub const fn has_right(&self) -> bool { + self.right.is_some() + || self.right_intersection.is_some() + || self.top_right.is_some() + || self.bottom_right.is_some() + } + + /// Verifies if borders has top line set on the frame. + pub const fn has_top(&self) -> bool { + self.top.is_some() + || self.top_intersection.is_some() + || self.top_left.is_some() + || self.top_right.is_some() + } + + /// Verifies if borders has bottom line set on the frame. + pub const fn has_bottom(&self) -> bool { + self.bottom.is_some() + || self.bottom_intersection.is_some() + || self.bottom_left.is_some() + || self.bottom_right.is_some() + } + + /// Verifies if borders has horizontal lines set. + pub const fn has_horizontal(&self) -> bool { + self.horizontal.is_some() + || self.left_intersection.is_some() + || self.right_intersection.is_some() + || self.intersection.is_some() + } + + /// Verifies if borders has vertical lines set. + pub const fn has_vertical(&self) -> bool { + self.intersection.is_some() + || self.vertical.is_some() + || self.top_intersection.is_some() + || self.bottom_intersection.is_some() + } +} diff --git a/vendor/papergrid/src/config/compact/mod.rs b/vendor/papergrid/src/config/compact/mod.rs new file mode 100644 index 000000000..099fa44d9 --- /dev/null +++ b/vendor/papergrid/src/config/compact/mod.rs @@ -0,0 +1,141 @@ +//! A module which contains configuration of a [`CompactGrid`] which is responsible for grid configuration. +//! +//! [`CompactGrid`]: crate::grid::compact::CompactGrid + +use crate::color::StaticColor; + +use crate::config::{AlignmentHorizontal, Borders, Indent, Line, Sides}; + +/// This structure represents a settings of a grid. +/// +/// grid: crate::Grid. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct CompactConfig { + borders: Borders<char>, + horizontal_line1: Option<Line<char>>, + border_colors: Borders<StaticColor>, + margin: Sides<Indent>, + margin_color: Sides<StaticColor>, + padding: Sides<Indent>, + padding_color: Sides<StaticColor>, + halignment: AlignmentHorizontal, +} + +impl Default for CompactConfig { + fn default() -> Self { + Self::empty() + } +} + +impl CompactConfig { + /// Returns an standard config. + pub const fn empty() -> Self { + Self { + halignment: AlignmentHorizontal::Left, + horizontal_line1: None, + borders: Borders::empty(), + border_colors: Borders::empty(), + margin: Sides::filled(Indent::zero()), + margin_color: Sides::filled(StaticColor::new("", "")), + padding: Sides::new( + Indent::spaced(1), + Indent::spaced(1), + Indent::zero(), + Indent::zero(), + ), + padding_color: Sides::filled(StaticColor::new("", "")), + } + } + + /// Set grid margin. + pub const fn set_margin(mut self, margin: Sides<Indent>) -> Self { + self.margin = margin; + self + } + + /// Returns a grid margin. + pub const fn get_margin(&self) -> &Sides<Indent> { + &self.margin + } + + /// Set the [`Borders`] value as correct one. + pub const fn set_borders(mut self, borders: Borders<char>) -> Self { + self.borders = borders; + self + } + + /// Set the first horizontal line. + /// + /// It ignores the [`Borders`] horizontal value if set for 1st row. + pub const fn set_first_horizontal_line(mut self, line: Line<char>) -> Self { + self.horizontal_line1 = Some(line); + self + } + + /// Set the first horizontal line. + /// + /// It ignores the [`Borders`] horizontal value if set for 1st row. + pub const fn get_first_horizontal_line(&self) -> Option<Line<char>> { + self.horizontal_line1 + } + + /// Returns a current [`Borders`] structure. + pub const fn get_borders(&self) -> &Borders<char> { + &self.borders + } + + /// Returns a current [`Borders`] structure. + pub const fn get_borders_color(&self) -> &Borders<StaticColor> { + &self.border_colors + } + + /// Set a padding to a given cells. + pub const fn set_padding(mut self, padding: Sides<Indent>) -> Self { + self.padding = padding; + self + } + + /// Get a padding for a given. + pub const fn get_padding(&self) -> &Sides<Indent> { + &self.padding + } + + /// Set a horizontal alignment. + pub const fn set_alignment_horizontal(mut self, alignment: AlignmentHorizontal) -> Self { + self.halignment = alignment; + self + } + + /// Get a alignment horizontal. + pub const fn get_alignment_horizontal(&self) -> AlignmentHorizontal { + self.halignment + } + + /// Sets colors of border carcass on the grid. + pub const fn set_borders_color(mut self, borders: Borders<StaticColor>) -> Self { + self.border_colors = borders; + self + } + + /// Set colors for a margin. + pub const fn set_margin_color(mut self, color: Sides<StaticColor>) -> Self { + self.margin_color = color; + self + } + + /// Returns a margin color. + pub const fn get_margin_color(&self) -> Sides<StaticColor> { + self.margin_color + } + + /// Set a padding to a given cells. + pub const fn set_padding_color(mut self, color: Sides<StaticColor>) -> Self { + self.padding_color = color; + self + } + + /// Set a padding to a given cells. + pub const fn get_padding_color(&self) -> Sides<StaticColor> { + self.padding_color + } +} diff --git a/vendor/papergrid/src/config/entity.rs b/vendor/papergrid/src/config/entity.rs new file mode 100644 index 000000000..0a2d3ba9b --- /dev/null +++ b/vendor/papergrid/src/config/entity.rs @@ -0,0 +1,120 @@ +use super::Position; + +/// Entity a structure which represent a set of cells. +/// +/// For example such table: +/// +/// ```text +/// ┌───┬───┐ +/// │ 0 │ 1 │ +/// ├───┼───┤ +/// │ 1 │ 2 │ +/// └───┴───┘ +/// ``` +/// +/// - has 4 cells. +/// Which indexes are (0, 0), (0, 1), (1, 0), (1, 1). +/// +/// - has 2 rows. +/// Which indexes are 0, 1. +/// +/// - has 2 column. +/// Which indexes are 0, 1. +/// +/// In [`Entity`] terms, all cells on the grid we call `Global`. +#[derive(PartialEq, Eq, Debug, Hash, Clone, Copy)] +pub enum Entity { + /// All cells on the grid. + Global, + /// All cells in a column on the grid. + Column(usize), + /// All cells in a row on the grid. + Row(usize), + /// A particular cell (row, column) on the grid. + Cell(usize, usize), +} + +impl Entity { + /// Iterate over cells which are covered via the [`Entity`]. + pub fn iter(&self, count_rows: usize, count_cols: usize) -> EntityIterator { + EntityIterator { + entity: *self, + count_rows, + count_cols, + i: 0, + j: 0, + } + } +} + +impl From<Position> for Entity { + fn from((row, col): Position) -> Self { + Self::Cell(row, col) + } +} + +/// An iterator over cells. +/// +/// Produced from [`Entity::iter`]. +#[derive(Debug, Clone)] +pub struct EntityIterator { + entity: Entity, + count_rows: usize, + count_cols: usize, + i: usize, + j: usize, +} + +impl Iterator for EntityIterator { + type Item = Position; + + fn next(&mut self) -> Option<Self::Item> { + if self.count_rows == 0 || self.count_cols == 0 { + return None; + } + + match self.entity { + Entity::Cell(row, col) => { + self.count_cols = 0; + self.count_rows = 0; + + Some((row, col)) + } + Entity::Column(col) => { + if self.i >= self.count_rows { + return None; + } + + let i = self.i; + self.i += 1; + + Some((i, col)) + } + Entity::Row(row) => { + if self.j >= self.count_cols { + return None; + } + + let j = self.j; + self.j += 1; + + Some((row, j)) + } + Entity::Global => { + if self.j >= self.count_cols { + self.j = 0; + self.i += 1; + + if self.i >= self.count_rows { + return None; + } + } + + let j = self.j; + self.j += 1; + + Some((self.i, j)) + } + } + } +} diff --git a/vendor/papergrid/src/config/indent.rs b/vendor/papergrid/src/config/indent.rs new file mode 100644 index 000000000..4d987724c --- /dev/null +++ b/vendor/papergrid/src/config/indent.rs @@ -0,0 +1,31 @@ +/// Indent represent a filled space. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Indent { + /// A fill character. + pub fill: char, + /// A number of repeats of a fill character. + pub size: usize, +} + +impl Indent { + /// Creates a new Indent structure. + pub const fn new(size: usize, fill: char) -> Self { + Self { fill, size } + } + + /// Creates a new Indent structure with space (`' '`) as a fill character. + pub const fn spaced(size: usize) -> Self { + Self { size, fill: ' ' } + } + + /// Creates a new Indent structure with space (`' '`) as a fill character. + pub const fn zero() -> Self { + Self::new(0, ' ') + } +} + +impl Default for Indent { + fn default() -> Self { + Self { size: 0, fill: ' ' } + } +} diff --git a/vendor/papergrid/src/config/line.rs b/vendor/papergrid/src/config/line.rs new file mode 100644 index 000000000..c3089e1c4 --- /dev/null +++ b/vendor/papergrid/src/config/line.rs @@ -0,0 +1,42 @@ +/// A line data structure. +#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord)] +pub struct Line<T> { + /// A horizontal/vertical character. + pub main: T, + /// A horizontal/vertical intersection. + pub intersection: Option<T>, + /// A horizontal left / vertical top intersection. + pub connect1: Option<T>, + /// A horizontal right / vertical bottom intersection. + pub connect2: Option<T>, +} + +impl<T> Line<T> { + /// Creates a new line. + pub const fn new( + main: T, + intersection: Option<T>, + connect1: Option<T>, + connect2: Option<T>, + ) -> Self { + Self { + main, + intersection, + connect1, + connect2, + } + } + + /// Creates a new line. + pub const fn filled(val: T) -> Self + where + T: Copy, + { + Self { + main: val, + intersection: Some(val), + connect1: Some(val), + connect2: Some(val), + } + } +} diff --git a/vendor/papergrid/src/config/mod.rs b/vendor/papergrid/src/config/mod.rs new file mode 100644 index 000000000..bba0b458e --- /dev/null +++ b/vendor/papergrid/src/config/mod.rs @@ -0,0 +1,23 @@ +//! A module which contains a general settings which might be used in other grid implementations. + +mod alignment; +mod border; +mod borders; +mod entity; +mod indent; +mod line; +mod position; +mod sides; + +pub mod compact; +#[cfg(feature = "std")] +pub mod spanned; + +pub use alignment::{AlignmentHorizontal, AlignmentVertical}; +pub use border::Border; +pub use borders::Borders; +pub use entity::{Entity, EntityIterator}; +pub use indent::Indent; +pub use line::Line; +pub use position::Position; +pub use sides::Sides; diff --git a/vendor/papergrid/src/config/position.rs b/vendor/papergrid/src/config/position.rs new file mode 100644 index 000000000..3996453cc --- /dev/null +++ b/vendor/papergrid/src/config/position.rs @@ -0,0 +1,13 @@ +/// Position is a (row, col) position on a Grid. +/// +/// For example such table has 4 cells. +/// Which indexes are (0, 0), (0, 1), (1, 0), (1, 1). +/// +/// ```text +/// ┌───┬───┐ +/// │ 0 │ 1 │ +/// ├───┼───┤ +/// │ 1 │ 2 │ +/// └───┴───┘ +/// ``` +pub type Position = (usize, usize); diff --git a/vendor/papergrid/src/config/sides.rs b/vendor/papergrid/src/config/sides.rs new file mode 100644 index 000000000..7aa0fb498 --- /dev/null +++ b/vendor/papergrid/src/config/sides.rs @@ -0,0 +1,37 @@ +/// A structure which represents 4 box sides. +#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Sides<T> { + /// Top side. + pub top: T, + /// Bottom side. + pub bottom: T, + /// Left side. + pub left: T, + /// Right side. + pub right: T, +} + +impl<T> Sides<T> { + /// Creates a new object. + pub const fn new(left: T, right: T, top: T, bottom: T) -> Self { + Self { + top, + bottom, + left, + right, + } + } + + /// Creates a new object. + pub const fn filled(value: T) -> Self + where + T: Copy, + { + Self { + top: value, + bottom: value, + left: value, + right: value, + } + } +} diff --git a/vendor/papergrid/src/config/spanned/borders_config.rs b/vendor/papergrid/src/config/spanned/borders_config.rs new file mode 100644 index 000000000..fe7729806 --- /dev/null +++ b/vendor/papergrid/src/config/spanned/borders_config.rs @@ -0,0 +1,486 @@ +use std::collections::{HashMap, HashSet}; + +use crate::config::{Border, Borders, Position}; + +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub(crate) struct BordersConfig<T> { + global: Option<T>, + borders: Borders<T>, + cells: BordersMap<T>, + horizontals: HashMap<usize, HorizontalLine<T>>, + verticals: HashMap<usize, VerticalLine<T>>, + layout: BordersLayout, +} + +impl<T: std::fmt::Debug> BordersConfig<T> { + pub(crate) fn insert_border(&mut self, pos: Position, border: Border<T>) { + if let Some(c) = border.top { + self.cells.horizontal.insert(pos, c); + self.layout.horizontals.insert(pos.0); + } + + if let Some(c) = border.bottom { + self.cells.horizontal.insert((pos.0 + 1, pos.1), c); + self.layout.horizontals.insert(pos.0 + 1); + } + + if let Some(c) = border.left { + self.cells.vertical.insert(pos, c); + self.layout.verticals.insert(pos.1); + } + + if let Some(c) = border.right { + self.cells.vertical.insert((pos.0, pos.1 + 1), c); + self.layout.verticals.insert(pos.1 + 1); + } + + if let Some(c) = border.left_top_corner { + self.cells.intersection.insert((pos.0, pos.1), c); + self.layout.horizontals.insert(pos.0); + self.layout.verticals.insert(pos.1); + } + + if let Some(c) = border.right_top_corner { + self.cells.intersection.insert((pos.0, pos.1 + 1), c); + self.layout.horizontals.insert(pos.0); + self.layout.verticals.insert(pos.1 + 1); + } + + if let Some(c) = border.left_bottom_corner { + self.cells.intersection.insert((pos.0 + 1, pos.1), c); + self.layout.horizontals.insert(pos.0 + 1); + self.layout.verticals.insert(pos.1); + } + + if let Some(c) = border.right_bottom_corner { + self.cells.intersection.insert((pos.0 + 1, pos.1 + 1), c); + self.layout.horizontals.insert(pos.0 + 1); + self.layout.verticals.insert(pos.1 + 1); + } + } + + pub(crate) fn remove_border(&mut self, pos: Position, shape: (usize, usize)) { + let (count_rows, count_cols) = shape; + + self.cells.horizontal.remove(&pos); + self.cells.horizontal.remove(&(pos.0 + 1, pos.1)); + self.cells.vertical.remove(&pos); + self.cells.vertical.remove(&(pos.0, pos.1 + 1)); + self.cells.intersection.remove(&pos); + self.cells.intersection.remove(&(pos.0 + 1, pos.1)); + self.cells.intersection.remove(&(pos.0, pos.1 + 1)); + self.cells.intersection.remove(&(pos.0 + 1, pos.1 + 1)); + + // clean up the layout. + + if !self.check_is_horizontal_set(pos.0, count_rows) { + self.layout.horizontals.remove(&pos.0); + } + + if !self.check_is_horizontal_set(pos.0 + 1, count_rows) { + self.layout.horizontals.remove(&(pos.0 + 1)); + } + + if !self.check_is_vertical_set(pos.1, count_cols) { + self.layout.verticals.remove(&pos.1); + } + + if !self.check_is_vertical_set(pos.1 + 1, count_cols) { + self.layout.verticals.remove(&(pos.1 + 1)); + } + } + + pub(crate) fn get_border(&self, pos: Position, shape: (usize, usize)) -> Border<&T> { + Border { + top: self.get_horizontal(pos, shape.0), + bottom: self.get_horizontal((pos.0 + 1, pos.1), shape.0), + left: self.get_vertical(pos, shape.1), + left_top_corner: self.get_intersection(pos, shape), + left_bottom_corner: self.get_intersection((pos.0 + 1, pos.1), shape), + right: self.get_vertical((pos.0, pos.1 + 1), shape.1), + right_top_corner: self.get_intersection((pos.0, pos.1 + 1), shape), + right_bottom_corner: self.get_intersection((pos.0 + 1, pos.1 + 1), shape), + } + } + + pub(crate) fn insert_horizontal_line(&mut self, row: usize, line: HorizontalLine<T>) { + if line.left.is_some() { + self.layout.left = true; + } + + // todo: when we delete lines these are still left set; so has_horizontal/vertical return true in some cases; + // it shall be fixed, but maybe we can improve the logic as it got a bit complicated. + if line.right.is_some() { + self.layout.right = true; + } + + if line.intersection.is_some() { + self.layout.inner_verticals = true; + } + + self.horizontals.insert(row, line); + self.layout.horizontals.insert(row); + } + + pub(crate) fn get_horizontal_line(&self, row: usize) -> Option<&HorizontalLine<T>> { + self.horizontals.get(&row) + } + + pub(crate) fn remove_horizontal_line(&mut self, row: usize, count_rows: usize) { + self.horizontals.remove(&row); + self.layout.horizontals.remove(&row); + + if self.has_horizontal(row, count_rows) { + self.layout.horizontals.insert(row); + } + } + + pub(crate) fn insert_vertical_line(&mut self, row: usize, line: VerticalLine<T>) { + if line.top.is_some() { + self.layout.top = true; + } + + if line.bottom.is_some() { + self.layout.bottom = true; + } + + self.verticals.insert(row, line); + self.layout.verticals.insert(row); + } + + pub(crate) fn get_vertical_line(&self, row: usize) -> Option<&VerticalLine<T>> { + self.verticals.get(&row) + } + + pub(crate) fn remove_vertical_line(&mut self, col: usize, count_columns: usize) { + self.verticals.remove(&col); + self.layout.verticals.remove(&col); + + if self.has_vertical(col, count_columns) { + self.layout.verticals.insert(col); + } + } + + pub(crate) fn set_borders(&mut self, borders: Borders<T>) { + self.borders = borders; + } + + pub(crate) fn get_borders(&self) -> &Borders<T> { + &self.borders + } + + pub(crate) fn get_global(&self) -> Option<&T> { + self.global.as_ref() + } + + pub(crate) fn set_global(&mut self, value: T) { + self.global = Some(value); + } + + pub(crate) fn get_vertical(&self, pos: Position, count_cols: usize) -> Option<&T> { + self.cells + .vertical + .get(&pos) + .or_else(|| self.verticals.get(&pos.1).and_then(|l| l.main.as_ref())) + .or({ + if pos.1 == count_cols { + self.borders.right.as_ref() + } else if pos.1 == 0 { + self.borders.left.as_ref() + } else { + self.borders.vertical.as_ref() + } + }) + .or(self.global.as_ref()) + } + + pub(crate) fn get_horizontal(&self, pos: Position, count_rows: usize) -> Option<&T> { + self.cells + .horizontal + .get(&pos) + .or_else(|| self.horizontals.get(&pos.0).and_then(|l| l.main.as_ref())) + .or({ + if pos.0 == 0 { + self.borders.top.as_ref() + } else if pos.0 == count_rows { + self.borders.bottom.as_ref() + } else { + self.borders.horizontal.as_ref() + } + }) + .or(self.global.as_ref()) + } + + pub(crate) fn get_intersection( + &self, + pos: Position, + (count_rows, count_cols): (usize, usize), + ) -> Option<&T> { + let use_top = pos.0 == 0; + let use_bottom = pos.0 == count_rows; + let use_left = pos.1 == 0; + let use_right = pos.1 == count_cols; + + if let Some(c) = self.cells.intersection.get(&pos) { + return Some(c); + } + + let hl_c = self.horizontals.get(&pos.0).and_then(|l| { + if use_left && l.left.is_some() { + l.left.as_ref() + } else if use_right && l.right.is_some() { + l.right.as_ref() + } else if !use_right && !use_left && l.intersection.is_some() { + l.intersection.as_ref() + } else { + None + } + }); + + if let Some(c) = hl_c { + return Some(c); + } + + let vl_c = self.verticals.get(&pos.1).and_then(|l| { + if use_top && l.top.is_some() { + l.top.as_ref() + } else if use_bottom && l.bottom.is_some() { + l.bottom.as_ref() + } else if !use_top && !use_bottom && l.intersection.is_some() { + l.intersection.as_ref() + } else { + None + } + }); + + if let Some(c) = vl_c { + return Some(c); + } + + let borders_c = { + if use_top && use_left { + self.borders.top_left.as_ref() + } else if use_top && use_right { + self.borders.top_right.as_ref() + } else if use_bottom && use_left { + self.borders.bottom_left.as_ref() + } else if use_bottom && use_right { + self.borders.bottom_right.as_ref() + } else if use_top { + self.borders.top_intersection.as_ref() + } else if use_bottom { + self.borders.bottom_intersection.as_ref() + } else if use_left { + self.borders.left_intersection.as_ref() + } else if use_right { + self.borders.right_intersection.as_ref() + } else { + self.borders.intersection.as_ref() + } + }; + + if let Some(c) = borders_c { + return Some(c); + } + + self.global.as_ref() + } + + pub(crate) fn has_horizontal(&self, row: usize, count_rows: usize) -> bool { + self.global.is_some() + || (row == 0 && self.borders.has_top()) + || (row == count_rows && self.borders.has_bottom()) + || (row > 0 && row < count_rows && self.borders.has_horizontal()) + || self.is_horizontal_set(row, count_rows) + } + + pub(crate) fn has_vertical(&self, col: usize, count_cols: usize) -> bool { + self.global.is_some() + || (col == 0 && self.borders.has_left()) + || (col == count_cols && self.borders.has_right()) + || (col > 0 && col < count_cols && self.borders.has_vertical()) + || self.is_vertical_set(col, count_cols) + } + + fn is_horizontal_set(&self, row: usize, count_rows: usize) -> bool { + (row == 0 && self.layout.top) + || (row == count_rows && self.layout.bottom) + || (row > 0 && row < count_rows && self.layout.inner_horizontals) + || self.layout.horizontals.contains(&row) + } + + fn is_vertical_set(&self, col: usize, count_cols: usize) -> bool { + (col == 0 && self.layout.left) + || (col == count_cols && self.layout.right) + || (col > 0 && col < count_cols && self.layout.inner_verticals) + || self.layout.verticals.contains(&col) + } + + fn check_is_horizontal_set(&self, row: usize, count_rows: usize) -> bool { + (row == 0 && self.layout.top) + || (row == count_rows && self.layout.bottom) + || (row > 0 && row < count_rows && self.layout.inner_horizontals) + || self.cells.horizontal.keys().any(|&p| p.0 == row) + || self.cells.intersection.keys().any(|&p| p.0 == row) + } + + fn check_is_vertical_set(&self, col: usize, count_cols: usize) -> bool { + (col == 0 && self.layout.left) + || (col == count_cols && self.layout.right) + || (col > 0 && col < count_cols && self.layout.inner_verticals) + || self.cells.vertical.keys().any(|&p| p.1 == col) + || self.cells.intersection.keys().any(|&p| p.1 == col) + } +} + +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub(crate) struct BordersMap<T> { + vertical: HashMap<Position, T>, + horizontal: HashMap<Position, T>, + intersection: HashMap<Position, T>, +} + +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub(crate) struct BordersLayout { + left: bool, + right: bool, + top: bool, + bottom: bool, + inner_verticals: bool, + inner_horizontals: bool, + horizontals: HashSet<usize>, + verticals: HashSet<usize>, +} + +/// A structure for a custom horizontal line. +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)] +pub struct HorizontalLine<T> { + /// Line character. + pub main: Option<T>, + /// Line intersection character. + pub intersection: Option<T>, + /// Left intersection character. + pub left: Option<T>, + /// Right intersection character. + pub right: Option<T>, +} + +impl<T> HorizontalLine<T> { + /// Verifies if the line has any setting set. + pub const fn is_empty(&self) -> bool { + self.main.is_none() + && self.intersection.is_none() + && self.left.is_none() + && self.right.is_none() + } +} + +/// A structure for a vertical line. +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)] +pub struct VerticalLine<T> { + /// Line character. + pub main: Option<T>, + /// Line intersection character. + pub intersection: Option<T>, + /// Left intersection character. + pub top: Option<T>, + /// Right intersection character. + pub bottom: Option<T>, +} + +impl<T> VerticalLine<T> { + /// Verifies if the line has any setting set. + pub const fn is_empty(&self) -> bool { + self.main.is_none() + && self.intersection.is_none() + && self.top.is_none() + && self.bottom.is_none() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_insert_border() { + let mut borders = BordersConfig::<char>::default(); + borders.insert_border((0, 0), Border::filled('x')); + + assert_eq!(borders.get_border((0, 0), (10, 10)), Border::filled(&'x')); + assert_eq!(borders.get_border((0, 0), (0, 0)), Border::filled(&'x')); + + assert!(borders.is_horizontal_set(0, 10)); + assert!(borders.is_horizontal_set(1, 10)); + assert!(!borders.is_horizontal_set(2, 10)); + assert!(borders.is_vertical_set(0, 10)); + assert!(borders.is_vertical_set(1, 10)); + assert!(!borders.is_vertical_set(2, 10)); + + assert!(borders.is_horizontal_set(0, 0)); + assert!(borders.is_horizontal_set(1, 0)); + assert!(!borders.is_horizontal_set(2, 0)); + assert!(borders.is_vertical_set(0, 0)); + assert!(borders.is_vertical_set(1, 0)); + assert!(!borders.is_vertical_set(2, 0)); + } + + #[test] + fn test_insert_border_override() { + let mut borders = BordersConfig::<char>::default(); + borders.insert_border((0, 0), Border::filled('x')); + borders.insert_border((1, 0), Border::filled('y')); + borders.insert_border((0, 1), Border::filled('w')); + borders.insert_border((1, 1), Border::filled('q')); + + assert_eq!( + borders.get_border((0, 0), (10, 10)).copied(), + Border::full('x', 'y', 'x', 'w', 'x', 'w', 'y', 'q') + ); + assert_eq!( + borders.get_border((0, 1), (10, 10)).copied(), + Border::full('w', 'q', 'w', 'w', 'w', 'w', 'q', 'q') + ); + assert_eq!( + borders.get_border((1, 0), (10, 10)).copied(), + Border::full('y', 'y', 'y', 'q', 'y', 'q', 'y', 'q') + ); + assert_eq!( + borders.get_border((1, 1), (10, 10)).copied(), + Border::filled('q') + ); + + assert!(borders.is_horizontal_set(0, 10)); + assert!(borders.is_horizontal_set(1, 10)); + assert!(borders.is_horizontal_set(2, 10)); + assert!(!borders.is_horizontal_set(3, 10)); + assert!(borders.is_vertical_set(0, 10)); + assert!(borders.is_vertical_set(1, 10)); + assert!(borders.is_vertical_set(2, 10)); + assert!(!borders.is_vertical_set(3, 10)); + } + + #[test] + fn test_set_global() { + let mut borders = BordersConfig::<char>::default(); + borders.insert_border((0, 0), Border::filled('x')); + borders.set_global('l'); + + assert_eq!(borders.get_border((0, 0), (10, 10)), Border::filled(&'x')); + assert_eq!(borders.get_border((2, 0), (10, 10)), Border::filled(&'l')); + + assert!(borders.is_horizontal_set(0, 10)); + assert!(borders.is_horizontal_set(1, 10)); + assert!(!borders.is_horizontal_set(2, 10)); + assert!(borders.is_vertical_set(0, 10)); + assert!(borders.is_vertical_set(1, 10)); + assert!(!borders.is_vertical_set(2, 10)); + + assert!(borders.is_horizontal_set(0, 0)); + assert!(borders.is_horizontal_set(1, 0)); + assert!(!borders.is_horizontal_set(2, 0)); + assert!(borders.is_vertical_set(0, 0)); + assert!(borders.is_vertical_set(1, 0)); + assert!(!borders.is_vertical_set(2, 0)); + } +} diff --git a/vendor/papergrid/src/config/spanned/entity_map.rs b/vendor/papergrid/src/config/spanned/entity_map.rs new file mode 100644 index 000000000..472ea5fb2 --- /dev/null +++ b/vendor/papergrid/src/config/spanned/entity_map.rs @@ -0,0 +1,108 @@ +use fnv::FnvHashMap; + +use crate::config::{Entity, Position}; + +/// A structure to keep information for [`Entity`] as a key. +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub struct EntityMap<T> { + // we have a global type to allocate in on stack. + // because most of the time no changes are made to the [`EntityMap`]. + global: T, + columns: FnvHashMap<usize, T>, + rows: FnvHashMap<usize, T>, + cells: FnvHashMap<Position, T>, +} + +impl<T> EntityMap<T> { + /// Creates an empty [`EntityMap`]. + pub fn new(global: T) -> Self { + Self { + global, + rows: FnvHashMap::default(), + columns: FnvHashMap::default(), + cells: FnvHashMap::default(), + } + } + + /// Get a value for an [`Entity`]. + pub fn get(&self, entity: Entity) -> &T { + if self.rows.is_empty() && self.columns.is_empty() && self.cells.is_empty() { + return &self.global; + } + + match entity { + Entity::Column(col) => self.columns.get(&col).unwrap_or(&self.global), + Entity::Row(row) => self.rows.get(&row).unwrap_or(&self.global), + Entity::Cell(row, col) => { + // todo: optimize; + // + // Cause we can change rows/columns/cells separately we need to check them separately. + // But we often doing this checks in `Grid::fmt` and I believe if we could optimize it it could be beneficial. + // + // Haven't found a solution for that yet. + // + // I was wondering if there is a hash function like. + // Apparently it doesn't make sense cause we will reset columns/rows on cell insert which is not what we want. + // + // ``` + // hash(column, row) == hash(column) == hash(row) + // ``` + // + // ref: https://opendsa-server.cs.vt.edu/ODSA/Books/Everything/html/Sparse.html + // ref: https://users.rust-lang.org/t/make-hash-return-same-value-whather-the-order-of-element-of-a-tuple/69932/13 + + self.cells + .get(&(row, col)) + .or_else(|| self.columns.get(&col)) + .or_else(|| self.rows.get(&row)) + .unwrap_or(&self.global) + } + Entity::Global => &self.global, + } + } + + /// Removes a value for an [`Entity`]. + pub fn remove(&mut self, entity: Entity) { + match entity { + Entity::Global => { + self.cells.clear(); + self.rows.clear(); + self.columns.clear(); + } + Entity::Column(col) => self.cells.retain(|&(_, c), _| c != col), + Entity::Row(row) => self.cells.retain(|&(r, _), _| r != row), + Entity::Cell(row, col) => { + self.cells.remove(&(row, col)); + } + } + } +} + +impl<T: Clone> EntityMap<T> { + /// Set a value for an [`Entity`]. + pub fn insert(&mut self, entity: Entity, value: T) { + match entity { + Entity::Column(col) => { + for &row in self.rows.keys() { + self.cells.insert((row, col), value.clone()); + } + + self.columns.insert(col, value); + } + Entity::Row(row) => { + for &col in self.columns.keys() { + self.cells.insert((row, col), value.clone()); + } + + self.rows.insert(row, value); + } + Entity::Cell(row, col) => { + self.cells.insert((row, col), value); + } + Entity::Global => { + self.remove(Entity::Global); + self.global = value + } + } + } +} diff --git a/vendor/papergrid/src/config/spanned/formatting.rs b/vendor/papergrid/src/config/spanned/formatting.rs new file mode 100644 index 000000000..0865f6c5b --- /dev/null +++ b/vendor/papergrid/src/config/spanned/formatting.rs @@ -0,0 +1,21 @@ +/// Formatting represent a logic of formatting of a cell. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Formatting { + /// An setting to allow horizontal trim. + pub horizontal_trim: bool, + /// An setting to allow vertical trim. + pub vertical_trim: bool, + /// An setting to allow alignment per line. + pub allow_lines_alignment: bool, +} + +impl Formatting { + /// Creates a new [`Formatting`] structure. + pub fn new(horizontal_trim: bool, vertical_trim: bool, allow_lines_alignment: bool) -> Self { + Self { + horizontal_trim, + vertical_trim, + allow_lines_alignment, + } + } +} diff --git a/vendor/papergrid/src/config/spanned/mod.rs b/vendor/papergrid/src/config/spanned/mod.rs new file mode 100644 index 000000000..a7f71e11c --- /dev/null +++ b/vendor/papergrid/src/config/spanned/mod.rs @@ -0,0 +1,887 @@ +//! A module which contains configuration options for a [`Grid`]. +//! +//! [`Grid`]: crate::grid::iterable::Grid + +mod borders_config; +mod entity_map; +mod formatting; +mod offset; + +use std::collections::HashMap; + +use crate::color::{AnsiColor, StaticColor}; +use crate::config::compact::CompactConfig; +use crate::config::{ + AlignmentHorizontal, AlignmentVertical, Border, Borders, Entity, Indent, Position, Sides, +}; +use borders_config::BordersConfig; + +pub use self::{entity_map::EntityMap, formatting::Formatting, offset::Offset}; + +/// This structure represents a settings of a grid. +/// +/// grid: crate::Grid. +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct SpannedConfig { + margin: Sides<ColoredMarginIndent>, + padding: EntityMap<Sides<ColoredIndent>>, + alignment_h: EntityMap<AlignmentHorizontal>, + alignment_v: EntityMap<AlignmentVertical>, + formatting: EntityMap<Formatting>, + span_columns: HashMap<Position, usize>, + span_rows: HashMap<Position, usize>, + borders: BordersConfig<char>, + borders_colors: BordersConfig<AnsiColor<'static>>, + borders_missing_char: char, + horizontal_chars: HashMap<Position, HashMap<Offset, char>>, + horizontal_colors: HashMap<Position, HashMap<Offset, AnsiColor<'static>>>, + vertical_chars: HashMap<Position, HashMap<Offset, char>>, + vertical_colors: HashMap<Position, HashMap<Offset, AnsiColor<'static>>>, + justification: EntityMap<char>, + justification_color: EntityMap<Option<AnsiColor<'static>>>, +} + +impl Default for SpannedConfig { + fn default() -> Self { + Self { + margin: Sides::default(), + padding: EntityMap::default(), + formatting: EntityMap::default(), + alignment_h: EntityMap::new(AlignmentHorizontal::Left), + alignment_v: EntityMap::new(AlignmentVertical::Top), + span_columns: HashMap::default(), + span_rows: HashMap::default(), + borders: BordersConfig::default(), + borders_colors: BordersConfig::default(), + borders_missing_char: ' ', + horizontal_chars: HashMap::default(), + horizontal_colors: HashMap::default(), + vertical_chars: HashMap::default(), + vertical_colors: HashMap::default(), + justification: EntityMap::new(' '), + justification_color: EntityMap::default(), + } + } +} + +impl SpannedConfig { + /// Set a margin of a grid. + pub fn set_margin(&mut self, margin: Sides<Indent>) { + self.margin.left.indent = margin.left; + self.margin.right.indent = margin.right; + self.margin.top.indent = margin.top; + self.margin.bottom.indent = margin.bottom; + } + + /// Set a color of margin of a grid. + pub fn set_margin_color(&mut self, margin: Sides<Option<AnsiColor<'static>>>) { + self.margin.left.color = margin.left; + self.margin.right.color = margin.right; + self.margin.top.color = margin.top; + self.margin.bottom.color = margin.bottom; + } + + /// Set an offset of margin of a grid. + pub fn set_margin_offset(&mut self, margin: Sides<Offset>) { + self.margin.left.offset = margin.left; + self.margin.right.offset = margin.right; + self.margin.top.offset = margin.top; + self.margin.bottom.offset = margin.bottom; + } + + /// Returns a margin value currently set. + pub fn get_margin(&self) -> Sides<Indent> { + Sides::new( + self.margin.left.indent, + self.margin.right.indent, + self.margin.top.indent, + self.margin.bottom.indent, + ) + } + + /// Returns a margin color value currently set. + pub fn get_margin_color(&self) -> Sides<Option<AnsiColor<'static>>> { + Sides::new( + self.margin.left.color.clone(), + self.margin.right.color.clone(), + self.margin.top.color.clone(), + self.margin.bottom.color.clone(), + ) + } + + /// Returns a margin offset value currently set. + pub fn get_margin_offset(&self) -> Sides<Offset> { + Sides::new( + self.margin.left.offset, + self.margin.right.offset, + self.margin.top.offset, + self.margin.bottom.offset, + ) + } + + /// Clears all theme changes. + /// And sets it to default. + pub fn clear_theme(&mut self) { + self.borders = BordersConfig::default(); + self.horizontal_chars.clear(); + self.vertical_chars.clear(); + self.horizontal_colors.clear(); + self.vertical_colors.clear(); + } + + /// Set the [`Borders`] value as correct one. + pub fn set_borders(&mut self, borders: Borders<char>) { + self.borders.set_borders(borders); + } + + /// Gets a global border value if set. + pub fn get_global_border(&self) -> Option<&char> { + self.borders.get_global() + } + + /// Set the all [`Borders`] values to a char. + pub fn set_global_border(&mut self, c: char) { + self.borders.set_global(c); + } + + /// Returns a current [`Borders`] structure. + pub fn get_borders(&self) -> &Borders<char> { + self.borders.get_borders() + } + + /// Set the border line by row index. + /// + /// Row `0` means the top row. + /// Row `grid.count_rows()` means the bottom row. + pub fn insert_horizontal_line(&mut self, line: usize, val: HorizontalLine) { + self.borders.insert_horizontal_line(line, val); + } + + /// Sets off the border line by row index if any were set + /// + /// Row `0` means the top row. + /// Row `grid.count_rows()` means the bottom row. + pub fn remove_horizontal_line(&mut self, line: usize, count_rows: usize) { + self.borders.remove_horizontal_line(line, count_rows); + } + + /// Gets a overridden vertical line. + /// + /// Row `0` means the left row. + /// Row `grid.count_columns()` means the right most row. + pub fn get_vertical_line(&self, line: usize) -> Option<&VerticalLine> { + self.borders.get_vertical_line(line) + } + + /// Set the border line by column index. + /// + /// Row `0` means the left row. + /// Row `grid.count_columns()` means the right most row. + pub fn insert_vertical_line(&mut self, line: usize, val: VerticalLine) { + self.borders.insert_vertical_line(line, val); + } + + /// Sets off the border line by column index if any were set + /// + /// Row `0` means the left row. + /// Row `grid.count_columns()` means the right most row. + pub fn remove_vertical_line(&mut self, line: usize, count_columns: usize) { + self.borders.remove_vertical_line(line, count_columns); + } + + /// Gets a overridden line. + /// + /// Row `0` means the top row. + /// Row `grid.count_rows()` means the bottom row. + pub fn get_horizontal_line(&self, line: usize) -> Option<&HorizontalLine> { + self.borders.get_horizontal_line(line) + } + + /// Override a character on a horizontal line. + /// + /// If borders are not set the char won't be used. + /// + /// It takes not cell position but line as row and column of a cell; + /// So its range is line <= count_rows && col < count_columns. + pub fn set_horizontal_char(&mut self, pos: Position, c: char, offset: Offset) { + let chars = self + .horizontal_chars + .entry(pos) + .or_insert_with(|| HashMap::with_capacity(1)); + + chars.insert(offset, c); + } + + /// Get a list of overridden chars in a horizontal border. + /// + /// It takes not cell position but line as row and column of a cell; + /// So its range is line <= count_rows && col < count_columns. + pub fn lookup_horizontal_char(&self, pos: Position, offset: usize, end: usize) -> Option<char> { + self.horizontal_chars + .get(&pos) + .and_then(|chars| { + chars.get(&Offset::Begin(offset)).or_else(|| { + if end > offset { + if end == 0 { + chars.get(&Offset::End(0)) + } else { + chars.get(&Offset::End(end - offset - 1)) + } + } else { + None + } + }) + }) + .copied() + } + + /// Checks if there any char in a horizontal border being overridden. + /// + /// It takes not cell position but line as row and column of a cell; + /// So its range is line <= count_rows && col < count_columns. + pub fn is_overridden_horizontal(&self, pos: Position) -> bool { + self.horizontal_chars.get(&pos).is_some() + } + + /// Removes a list of overridden chars in a horizontal border. + /// + /// It takes not cell position but line as row and column of a cell; + /// So its range is line <= count_rows && col < count_columns. + pub fn remove_overridden_horizontal(&mut self, pos: Position) { + self.horizontal_chars.remove(&pos); + } + + /// Override a vertical split line. + /// + /// If borders are not set the char won't be used. + /// + /// It takes not cell position but cell row and column of a line; + /// So its range is row < count_rows && col <= count_columns. + pub fn set_vertical_char(&mut self, pos: Position, c: char, offset: Offset) { + let chars = self + .vertical_chars + .entry(pos) + .or_insert_with(|| HashMap::with_capacity(1)); + + chars.insert(offset, c); + } + + /// Get a list of overridden chars in a horizontal border. + /// + /// It takes not cell position but cell row and column of a line; + /// So its range is row < count_rows && col <= count_columns. + pub fn lookup_vertical_char(&self, pos: Position, offset: usize, end: usize) -> Option<char> { + self.vertical_chars + .get(&pos) + .and_then(|chars| { + chars.get(&Offset::Begin(offset)).or_else(|| { + if end > offset { + if end == 0 { + chars.get(&Offset::End(0)) + } else { + chars.get(&Offset::End(end - offset - 1)) + } + } else { + None + } + }) + }) + .copied() + } + + /// Checks if there any char in a horizontal border being overridden. + /// + /// It takes not cell position but cell row and column of a line; + /// So its range is row < count_rows && col <= count_columns. + pub fn is_overridden_vertical(&self, pos: Position) -> bool { + self.vertical_chars.get(&pos).is_some() + } + + /// Removes a list of overridden chars in a horizontal border. + /// + /// It takes not cell position but cell row and column of a line; + /// So its range is row < count_rows && col <= count_columns. + pub fn remove_overridden_vertical(&mut self, pos: Position) { + self.vertical_chars.remove(&pos); + } + + /// Override a character color on a horizontal line. + pub fn set_horizontal_color(&mut self, pos: Position, c: AnsiColor<'static>, offset: Offset) { + let chars = self + .horizontal_colors + .entry(pos) + .or_insert_with(|| HashMap::with_capacity(1)); + + chars.insert(offset, c); + } + + /// Get a overridden color in a horizontal border. + pub fn lookup_horizontal_color( + &self, + pos: Position, + offset: usize, + end: usize, + ) -> Option<&AnsiColor<'static>> { + self.horizontal_colors.get(&pos).and_then(|chars| { + chars.get(&Offset::Begin(offset)).or_else(|| { + if end > offset { + if end == 0 { + chars.get(&Offset::End(0)) + } else { + chars.get(&Offset::End(end - offset - 1)) + } + } else { + None + } + }) + }) + } + + /// Override a character color on a vertical line. + pub fn set_vertical_color(&mut self, pos: Position, c: AnsiColor<'static>, offset: Offset) { + let chars = self + .vertical_colors + .entry(pos) + .or_insert_with(|| HashMap::with_capacity(1)); + + chars.insert(offset, c); + } + + /// Get a overridden color in a vertical border. + pub fn lookup_vertical_color( + &self, + pos: Position, + offset: usize, + end: usize, + ) -> Option<&AnsiColor<'static>> { + self.vertical_colors.get(&pos).and_then(|chars| { + chars.get(&Offset::Begin(offset)).or_else(|| { + if end > offset { + if end == 0 { + chars.get(&Offset::End(0)) + } else { + chars.get(&Offset::End(end - offset - 1)) + } + } else { + None + } + }) + }) + } + + /// Set a padding to a given cells. + pub fn set_padding(&mut self, entity: Entity, padding: Sides<Indent>) { + let mut pad = self.padding.get(entity).clone(); + pad.left.indent = padding.left; + pad.right.indent = padding.right; + pad.top.indent = padding.top; + pad.bottom.indent = padding.bottom; + + self.padding.insert(entity, pad); + } + + /// Set a padding to a given cells. + pub fn set_padding_color( + &mut self, + entity: Entity, + padding: Sides<Option<AnsiColor<'static>>>, + ) { + let mut pad = self.padding.get(entity).clone(); + pad.left.color = padding.left; + pad.right.color = padding.right; + pad.top.color = padding.top; + pad.bottom.color = padding.bottom; + + self.padding.insert(entity, pad); + } + + /// Get a padding for a given [Entity]. + pub fn get_padding(&self, entity: Entity) -> Sides<Indent> { + let pad = self.padding.get(entity); + Sides::new( + pad.left.indent, + pad.right.indent, + pad.top.indent, + pad.bottom.indent, + ) + } + + /// Get a padding color for a given [Entity]. + pub fn get_padding_color(&self, entity: Entity) -> Sides<Option<AnsiColor<'static>>> { + let pad = self.padding.get(entity); + Sides::new( + pad.left.color.clone(), + pad.right.color.clone(), + pad.top.color.clone(), + pad.bottom.color.clone(), + ) + } + + /// Set a formatting to a given cells. + pub fn set_formatting(&mut self, entity: Entity, formatting: Formatting) { + self.formatting.insert(entity, formatting); + } + + /// Get a formatting settings for a given [Entity]. + pub fn get_formatting(&self, entity: Entity) -> &Formatting { + self.formatting.get(entity) + } + + /// Set a vertical alignment to a given cells. + pub fn set_alignment_vertical(&mut self, entity: Entity, alignment: AlignmentVertical) { + self.alignment_v.insert(entity, alignment); + } + + /// Get a vertical alignment for a given [Entity]. + pub fn get_alignment_vertical(&self, entity: Entity) -> &AlignmentVertical { + self.alignment_v.get(entity) + } + + /// Set a horizontal alignment to a given cells. + pub fn set_alignment_horizontal(&mut self, entity: Entity, alignment: AlignmentHorizontal) { + self.alignment_h.insert(entity, alignment); + } + + /// Get a horizontal alignment for a given [Entity]. + pub fn get_alignment_horizontal(&self, entity: Entity) -> &AlignmentHorizontal { + self.alignment_h.get(entity) + } + + /// Set border set a border value to all cells in [`Entity`]. + pub fn set_border(&mut self, pos: Position, border: Border<char>) { + self.borders.insert_border(pos, border); + } + + /// Returns a border of a cell. + pub fn get_border(&self, pos: Position, shape: (usize, usize)) -> Border<char> { + self.borders.get_border(pos, shape).copied() + } + + /// Returns a border color of a cell. + pub fn get_border_color( + &self, + pos: Position, + shape: (usize, usize), + ) -> Border<&AnsiColor<'static>> { + self.borders_colors.get_border(pos, shape) + } + + /// Set a character which will be used in case any misconfiguration of borders. + /// + /// It will be usde for example when you set a left char for border frame and top but didn't set a top left corner. + pub fn set_borders_missing(&mut self, c: char) { + self.borders_missing_char = c; + } + + /// Get a character which will be used in case any misconfiguration of borders. + pub fn get_borders_missing(&self) -> char { + self.borders_missing_char + } + + /// Gets a color of all borders on the grid. + pub fn get_border_color_global(&self) -> Option<&AnsiColor<'static>> { + self.borders_colors.get_global() + } + + /// Sets a color of all borders on the grid. + pub fn set_border_color_global(&mut self, clr: AnsiColor<'static>) { + self.borders_colors = BordersConfig::default(); + self.borders_colors.set_global(clr); + } + + /// Gets colors of a borders carcass on the grid. + pub fn get_color_borders(&self) -> &Borders<AnsiColor<'static>> { + self.borders_colors.get_borders() + } + + /// Sets colors of border carcass on the grid. + pub fn set_borders_color(&mut self, clrs: Borders<AnsiColor<'static>>) { + self.borders_colors.set_borders(clrs); + } + + /// Sets a color of border of a cell on the grid. + pub fn set_border_color(&mut self, pos: Position, border: Border<AnsiColor<'static>>) { + self.borders_colors.insert_border(pos, border) + } + + /// Sets off all borders possible on the [`Entity`]. + /// + /// It doesn't changes globally set borders through [`SpannedConfig::set_borders`]. + // + // todo: would be great to remove a shape + pub fn remove_border(&mut self, pos: Position, shape: (usize, usize)) { + self.borders.remove_border(pos, shape); + } + + /// Gets a color of border of a cell on the grid. + // + // todo: would be great to remove a shape + pub fn remove_border_color(&mut self, pos: Position, shape: (usize, usize)) { + self.borders_colors.remove_border(pos, shape); + } + + /// Get a justification which will be used while expanding cells width/height. + pub fn get_justification(&self, entity: Entity) -> char { + *self.justification.get(entity) + } + + /// Get a justification color which will be used while expanding cells width/height. + /// + /// `None` means no color. + pub fn get_justification_color(&self, entity: Entity) -> Option<&AnsiColor<'static>> { + self.justification_color.get(entity).as_ref() + } + + /// Set a justification which will be used while expanding cells width/height. + pub fn set_justification(&mut self, entity: Entity, c: char) { + self.justification.insert(entity, c); + } + + /// Set a justification color which will be used while expanding cells width/height. + /// + /// `None` removes it. + pub fn set_justification_color(&mut self, entity: Entity, color: Option<AnsiColor<'static>>) { + self.justification_color.insert(entity, color); + } + + /// Get a span value of the cell, if any is set. + pub fn get_column_spans(&self) -> HashMap<Position, usize> { + self.span_columns.clone() + } + + /// Get a span value of the cell, if any is set. + pub fn get_row_spans(&self) -> HashMap<Position, usize> { + self.span_rows.clone() + } + + /// Get a span value of the cell, if any is set. + pub fn get_column_span(&self, pos: Position) -> Option<usize> { + self.span_columns.get(&pos).copied() + } + + /// Get a span value of the cell, if any is set. + pub fn get_row_span(&self, pos: Position) -> Option<usize> { + self.span_rows.get(&pos).copied() + } + + /// Removes column spans. + pub fn remove_column_spans(&mut self) { + self.span_columns.clear() + } + + /// Removes row spans. + pub fn remove_row_spans(&mut self) { + self.span_rows.clear() + } + + /// Set a column span to a given cells. + /// + /// BEWARE + /// + /// IT'S CALLER RESPONSIBILITY TO MAKE SURE + /// THAT THERE NO INTERSECTIONS IN PLACE AND THE SPAN VALUE IS CORRECT + pub fn set_column_span(&mut self, pos: Position, span: usize) { + set_cell_column_span(self, pos, span); + } + + /// Verifies if there's any spans set. + pub fn has_column_spans(&self) -> bool { + !self.span_columns.is_empty() + } + + /// Set a column span to a given cells. + /// + /// BEWARE + /// + /// IT'S CALLER RESPONSIBILITY TO MAKE SURE + /// THAT THERE NO INTERSECTIONS IN PLACE AND THE SPAN VALUE IS CORRECT + pub fn set_row_span(&mut self, pos: Position, span: usize) { + set_cell_row_span(self, pos, span); + } + + /// Verifies if there's any spans set. + pub fn has_row_spans(&self) -> bool { + !self.span_rows.is_empty() + } + + /// Gets an intersection character which would be rendered on the grid. + /// + /// grid: crate::Grid + pub fn get_intersection(&self, pos: Position, shape: (usize, usize)) -> Option<char> { + let c = self.borders.get_intersection(pos, shape); + if let Some(c) = c { + return Some(*c); + } + + if self.has_horizontal(pos.0, shape.0) && self.has_vertical(pos.1, shape.1) { + return Some(self.get_borders_missing()); + } + + None + } + + /// Gets a horizontal character which would be rendered on the grid. + /// + /// grid: crate::Grid + pub fn get_horizontal(&self, pos: Position, count_rows: usize) -> Option<char> { + let c = self.borders.get_horizontal(pos, count_rows); + if let Some(c) = c { + return Some(*c); + } + + if self.has_horizontal(pos.0, count_rows) { + return Some(self.get_borders_missing()); + } + + None + } + + /// Gets a vertical character which would be rendered on the grid. + /// + /// grid: crate::Grid + pub fn get_vertical(&self, pos: Position, count_columns: usize) -> Option<char> { + if let Some(c) = self.borders.get_vertical(pos, count_columns) { + return Some(*c); + } + + if self.has_vertical(pos.1, count_columns) { + return Some(self.get_borders_missing()); + } + + None + } + + /// Gets a color of a cell horizontal. + pub fn get_horizontal_color( + &self, + pos: Position, + count_rows: usize, + ) -> Option<&AnsiColor<'static>> { + self.borders_colors.get_horizontal(pos, count_rows) + } + + /// Gets a color of a cell vertical. + pub fn get_vertical_color( + &self, + pos: Position, + count_columns: usize, + ) -> Option<&AnsiColor<'static>> { + self.borders_colors.get_vertical(pos, count_columns) + } + + /// Gets a color of a cell vertical. + pub fn get_intersection_color( + &self, + pos: Position, + shape: (usize, usize), + ) -> Option<&AnsiColor<'static>> { + self.borders_colors.get_intersection(pos, shape) + } + + /// Checks if grid would have a horizontal border with the current configuration. + /// + /// grid: crate::Grid + pub fn has_horizontal(&self, row: usize, count_rows: usize) -> bool { + self.borders.has_horizontal(row, count_rows) + } + + /// Checks if grid would have a vertical border with the current configuration. + /// + /// grid: crate::Grid + pub fn has_vertical(&self, col: usize, count_columns: usize) -> bool { + self.borders.has_vertical(col, count_columns) + } + + /// Calculates an amount of horizontal lines would present on the grid. + /// + /// grid: crate::Grid + pub fn count_horizontal(&self, count_rows: usize) -> usize { + (0..=count_rows) + .filter(|&row| self.has_horizontal(row, count_rows)) + .count() + } + + /// Calculates an amount of vertical lines would present on the grid. + /// + /// grid: crate::Grid + pub fn count_vertical(&self, count_columns: usize) -> usize { + (0..=count_columns) + .filter(|&col| self.has_vertical(col, count_columns)) + .count() + } + + /// The function returns whether the cells will be rendered or it will be hidden because of a span. + pub fn is_cell_visible(&self, pos: Position) -> bool { + !(self.is_cell_covered_by_column_span(pos) + || self.is_cell_covered_by_row_span(pos) + || self.is_cell_covered_by_both_spans(pos)) + } + + /// The function checks if a cell is hidden because of a row span. + pub fn is_cell_covered_by_row_span(&self, pos: Position) -> bool { + is_cell_covered_by_row_span(self, pos) + } + + /// The function checks if a cell is hidden because of a column span. + pub fn is_cell_covered_by_column_span(&self, pos: Position) -> bool { + is_cell_covered_by_column_span(self, pos) + } + + /// The function checks if a cell is hidden indirectly because of a row and column span combination. + pub fn is_cell_covered_by_both_spans(&self, pos: Position) -> bool { + is_cell_covered_by_both_spans(self, pos) + } +} + +impl From<CompactConfig> for SpannedConfig { + fn from(compact: CompactConfig) -> Self { + use Entity::Global; + + let mut cfg = Self::default(); + + cfg.set_padding(Global, *compact.get_padding()); + cfg.set_padding_color(Global, to_ansi_color(compact.get_padding_color())); + cfg.set_margin(*compact.get_margin()); + cfg.set_margin_color(to_ansi_color(compact.get_margin_color())); + cfg.set_alignment_horizontal(Global, compact.get_alignment_horizontal()); + cfg.set_borders(*compact.get_borders()); + cfg.set_borders_color(borders_static_color_to_ansi_color( + *compact.get_borders_color(), + )); + + if let Some(line) = compact.get_first_horizontal_line() { + cfg.insert_horizontal_line( + 1, + HorizontalLine { + intersection: line.intersection, + left: line.connect1, + right: line.connect2, + main: Some(line.main), + }, + ); + } + + cfg + } +} + +fn to_ansi_color(b: Sides<StaticColor>) -> Sides<Option<AnsiColor<'static>>> { + Sides::new( + Some(b.left.into()), + Some(b.right.into()), + Some(b.top.into()), + Some(b.bottom.into()), + ) +} + +fn borders_static_color_to_ansi_color(b: Borders<StaticColor>) -> Borders<AnsiColor<'static>> { + Borders { + left: b.left.map(|c| c.into()), + right: b.right.map(|c| c.into()), + top: b.top.map(|c| c.into()), + bottom: b.bottom.map(|c| c.into()), + bottom_intersection: b.bottom_intersection.map(|c| c.into()), + bottom_left: b.bottom_left.map(|c| c.into()), + bottom_right: b.bottom_right.map(|c| c.into()), + horizontal: b.horizontal.map(|c| c.into()), + intersection: b.intersection.map(|c| c.into()), + left_intersection: b.left_intersection.map(|c| c.into()), + right_intersection: b.right_intersection.map(|c| c.into()), + top_intersection: b.top_intersection.map(|c| c.into()), + top_left: b.top_left.map(|c| c.into()), + top_right: b.top_right.map(|c| c.into()), + vertical: b.vertical.map(|c| c.into()), + } +} + +fn set_cell_row_span(cfg: &mut SpannedConfig, pos: Position, span: usize) { + // such spans aren't supported + if span == 0 { + return; + } + + // It's a default span so we can do nothing. + // but we check if it's an override of a span. + if span == 1 { + cfg.span_rows.remove(&pos); + return; + } + + cfg.span_rows.insert(pos, span); +} + +fn set_cell_column_span(cfg: &mut SpannedConfig, pos: Position, span: usize) { + // such spans aren't supported + if span == 0 { + return; + } + + // It's a default span so we can do nothing. + // but we check if it's an override of a span. + if span == 1 { + cfg.span_columns.remove(&pos); + return; + } + + cfg.span_columns.insert(pos, span); +} + +fn is_cell_covered_by_column_span(cfg: &SpannedConfig, pos: Position) -> bool { + cfg.span_columns + .iter() + .any(|(&(row, col), span)| pos.1 > col && pos.1 < col + span && row == pos.0) +} + +fn is_cell_covered_by_row_span(cfg: &SpannedConfig, pos: Position) -> bool { + cfg.span_rows + .iter() + .any(|(&(row, col), span)| pos.0 > row && pos.0 < row + span && col == pos.1) +} + +fn is_cell_covered_by_both_spans(cfg: &SpannedConfig, pos: Position) -> bool { + if !cfg.has_column_spans() || !cfg.has_row_spans() { + return false; + } + + cfg.span_rows.iter().any(|(p1, row_span)| { + cfg.span_columns + .iter() + .filter(|(p2, _)| &p1 == p2) + .any(|(_, col_span)| { + pos.0 > p1.0 && pos.0 < p1.0 + row_span && pos.1 > p1.1 && pos.1 < p1.1 + col_span + }) + }) +} + +#[derive(Default, Debug, Clone, PartialEq, Eq)] +struct ColoredIndent { + indent: Indent, + color: Option<AnsiColor<'static>>, +} + +/// A colorefull margin indent. +#[derive(Debug, Clone, PartialEq, Eq)] +struct ColoredMarginIndent { + /// An indent value. + indent: Indent, + /// An offset value. + offset: Offset, + /// An color value. + color: Option<AnsiColor<'static>>, +} + +impl Default for ColoredMarginIndent { + fn default() -> Self { + Self { + indent: Indent::default(), + offset: Offset::Begin(0), + color: None, + } + } +} + +/// HorizontalLine represents a horizontal border line. +pub type HorizontalLine = borders_config::HorizontalLine<char>; + +/// HorizontalLine represents a vertical border line. +pub type VerticalLine = borders_config::VerticalLine<char>; diff --git a/vendor/papergrid/src/config/spanned/offset.rs b/vendor/papergrid/src/config/spanned/offset.rs new file mode 100644 index 000000000..03055ca34 --- /dev/null +++ b/vendor/papergrid/src/config/spanned/offset.rs @@ -0,0 +1,8 @@ +/// The structure represents an offset in a text. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum Offset { + /// An offset from the start. + Begin(usize), + /// An offset from the end. + End(usize), +} |