Repository: zhiburt/tabled
Pull request #90 (2022-03-20 12:41:32)

A list of fixes for `Highlight`
Also it adds a Border modifier


Patch:
diff --git a/examples/highlight.rs b/examples/highlight.rs
index 1588288..b63206d 100644
--- a/examples/highlight.rs
+++ b/examples/highlight.rs
@@ -1,7 +1,10 @@
 //! The example can be run by this command
 //! `cargo run --example highlight`
 
-use tabled::{Border, Highlight, Style, TableIteratorExt};
+use tabled::{
+    style::{Border, Style},
+    Highlight, TableIteratorExt,
+};
 
 fn main() {
     let data = vec![["A", "B", "C"], ["D", "E", "F"], ["G", "H", "I"]];
diff --git a/papergrid/src/lib.rs b/papergrid/src/lib.rs
index f6512c4..60adcdd 100644
--- a/papergrid/src/lib.rs
+++ b/papergrid/src/lib.rs
@@ -134,12 +134,11 @@ impl Grid {
         }
 
         if let Some(border) = settings.border {
-            let frame = self.frame_from_entity(entity);
             if settings.border_split_check {
-                self.add_split_lines_for_border(&frame, &border);
+                self.add_split_lines(entity.clone(), &border);
             }
 
-            self.set_border(&frame, border);
+            self.set_border(entity.clone(), &border);
         }
     }
 
@@ -201,88 +200,71 @@ impl Grid {
         self.override_split_lines.clear();
     }
 
-    fn set_border(&mut self, frame: &EntityFrame, border: Border) {
-        if let Some(top) = border.top {
-            for column in frame.left_column..frame.right_column {
-                self.borders
-                    .set_row_symbol((frame.top_row, column), top)
-                    .unwrap();
-
-                // in case it continues line we change intersection symbol
-                if frame.right_column - frame.left_column > 1 {
-                    self.borders
-                        .set_intersection((frame.top_row, column), top)
-                        .unwrap();
+    fn set_border(&mut self, entity: Entity, border: &Border) {
+        match entity {
+            Entity::Global => {
+                for column in 0..self.count_columns() {
+                    for row in 0..self.count_rows() {
+                        self.set_border_for_cell(row, column, border);
+                    }
                 }
             }
-        }
-
-        if let Some(bottom) = border.bottom {
-            for column in frame.left_column..frame.right_column {
-                self.borders
-                    .set_row_symbol((frame.bottom_row, column), bottom)
-                    .unwrap();
-
-                // in case it continues line we change intersection symbol
-                if frame.right_column - frame.left_column > 1 {
-                    self.borders
-                        .set_intersection((frame.bottom_row, column), bottom)
-                        .unwrap();
+            Entity::Column(column) => {
+                for row in 0..self.count_rows() {
+                    self.set_border_for_cell(row, column, border);
+                }
+            }
+            Entity::Row(row) => {
+                for column in 0..self.count_columns() {
+                    self.set_border_for_cell(row, column, border);
                 }
             }
+            Entity::Cell(row, column) => {
+                self.set_border_for_cell(row, column, border);
+            }
         }
+    }
+
+    fn set_border_for_cell(&mut self, row: usize, column: usize, border: &Border) {
+        let cell = CellBorderIndex::new(row, column);
 
         if let Some(left) = border.left {
-            for row in frame.top_row..frame.bottom_row {
-                self.borders
-                    .set_column_symbol((row, frame.left_column), left)
-                    .unwrap();
-
-                // in case it continues line we change intersection symbol
-                if frame.bottom_row - frame.top_row > 1 {
-                    self.borders
-                        .set_intersection((row, frame.left_column), left)
-                        .unwrap();
-                }
-            }
+            self.borders.set_column_symbol(cell.left(), left).unwrap();
         }
 
         if let Some(right) = border.right {
-            for row in frame.top_row..frame.bottom_row {
-                self.borders
-                    .set_column_symbol((row, frame.right_column), right)
-                    .unwrap();
-
-                // in case it continues line we change intersection symbol
-                if frame.bottom_row - frame.top_row > 1 {
-                    self.borders
-                        .set_intersection((row, frame.right_column), right)
-                        .unwrap();
-                }
-            }
+            self.borders.set_column_symbol(cell.right(), right).unwrap();
+        }
+
+        if let Some(top) = border.top {
+            self.borders.set_row_symbol(cell.top(), top).unwrap();
+        }
+
+        if let Some(bottom) = border.bottom {
+            self.borders.set_row_symbol(cell.bottom(), bottom).unwrap();
         }
 
-        if let Some(top_left_corner) = border.left_top_corner {
+        if let Some(left_top) = border.left_top_corner {
             self.borders
-                .set_intersection(frame.top_left_corner(), top_left_corner)
+                .set_intersection(cell.top_left(), left_top)
                 .unwrap();
         }
 
-        if let Some(top_right_corner) = border.right_top_corner {
+        if let Some(right_top) = border.right_top_corner {
             self.borders
-                .set_intersection(frame.top_right_corner(), top_right_corner)
+                .set_intersection(cell.top_right(), right_top)
                 .unwrap();
         }
 
-        if let Some(bottom_left_corner) = border.left_bottom_corner {
+        if let Some(left_bottom) = border.left_bottom_corner {
             self.borders
-                .set_intersection(frame.bottom_left_corner(), bottom_left_corner)
+                .set_intersection(cell.bottom_left(), left_bottom)
                 .unwrap();
         }
 
-        if let Some(bottom_right_corner) = border.right_bottom_corner {
+        if let Some(right_bottom) = border.right_bottom_corner {
             self.borders
-                .set_intersection(frame.bottom_right_corner(), bottom_right_corner)
+                .set_intersection(cell.bottom_right(), right_bottom)
                 .unwrap();
         }
     }
@@ -404,7 +386,7 @@ impl Grid {
     ///
     /// # Example
     ///
-    /// ```rust,no_run
+    /// ```text,no_run
     /// grid
     /// +---+---+---+
     /// |0-0|0-1|0-2|
@@ -458,21 +440,55 @@ impl Grid {
         self.override_split_lines.insert(row, line.into());
     }
 
-    fn add_split_lines_for_border(&mut self, frame: &EntityFrame, border: &Border) {
-        if border.left.is_some() && !self.is_vertical_present(frame.left_column) {
-            self.add_vertical_split(frame.left_column)
+    fn add_split_lines(&mut self, entity: Entity, border: &Border) {
+        match entity {
+            Entity::Global => {
+                for column in 0..self.count_columns() {
+                    for row in 0..self.count_rows() {
+                        self.add_split_lines_for_cell(row, column, border);
+                    }
+                }
+            }
+            Entity::Column(column) => {
+                for row in 0..self.count_rows() {
+                    self.add_split_lines_for_cell(row, column, border);
+                }
+            }
+            Entity::Row(row) => {
+                for column in 0..self.count_columns() {
+                    self.add_split_lines_for_cell(row, column, border);
+                }
+            }
+            Entity::Cell(row, column) => {
+                self.add_split_lines_for_cell(row, column, border);
+            }
         }
+    }
 
-        if border.right.is_some() && !self.is_vertical_present(frame.right_column) {
-            self.add_vertical_split(frame.right_column)
+    fn add_split_lines_for_cell(&mut self, row: usize, column: usize, border: &Border) {
+        let left_affected = border.left.is_some()
+            || border.left_bottom_corner.is_some()
+            || border.left_top_corner.is_some();
+        if left_affected && !self.is_vertical_present(column) {
+            self.add_vertical_split(column);
         }
-
-        if border.top.is_some() && !self.is_horizontal_present(frame.top_row) {
-            self.add_horizontal_split(frame.top_row)
+        let right_affected = border.right.is_some()
+            || border.right_bottom_corner.is_some()
+            || border.right_top_corner.is_some();
+        if right_affected && !self.is_vertical_present(column + 1) {
+            self.add_vertical_split(column + 1);
         }
-
-        if border.bottom.is_some() && !self.is_horizontal_present(frame.bottom_row) {
-            self.add_horizontal_split(frame.bottom_row)
+        let top_affected = border.top.is_some()
+            || border.right_top_corner.is_some()
+            || border.left_top_corner.is_some();
+        if top_affected && !self.is_horizontal_present(row) {
+            self.add_horizontal_split(row)
+        }
+        let bottom_affected = border.bottom.is_some()
+            || border.right_bottom_corner.is_some()
+            || border.left_bottom_corner.is_some();
+        if bottom_affected && !self.is_horizontal_present(row + 1) {
+            self.add_horizontal_split(row + 1)
         }
     }
 
@@ -508,10 +524,6 @@ impl Grid {
         rows
     }
 
-    fn frame_from_entity(&self, entity: &Entity) -> EntityFrame {
-        entity_frame(entity, self.count_rows(), self.count_columns())
-    }
-
     fn get_split_line(&self, index: usize) -> Vec<BorderLine> {
         self.borders.get_row(index).unwrap()
     }
@@ -583,6 +595,10 @@ impl Border {
         }
     }
 
+    pub fn single(c: char) -> Self {
+        Self::full(c, c, c, c, c, c, c, c)
+    }
+
     pub fn top(mut self, c: char) -> Self {
         self.top = Some(c);
         self
@@ -644,41 +660,6 @@ pub enum Entity {
     Cell(usize, usize),
 }
 
-#[derive(PartialEq, Eq, Debug, Hash, Clone)]
-struct EntityFrame {
-    left_column: usize,
-    right_column: usize,
-    top_row: usize,
-    bottom_row: usize,
-}
-
-impl EntityFrame {
-    fn new(left_column: usize, right_column: usize, top_row: usize, bottom_row: usize) -> Self {
-        Self {
-            left_column,
-            right_column,
-            top_row,
-            bottom_row,
-        }
-    }
-
-    fn top_left_corner(&self) -> GridPosition {
-        (self.top_row, self.left_column)
-    }
-
-    fn top_right_corner(&self) -> GridPosition {
-        (self.top_row, self.right_column)
-    }
-
-    fn bottom_left_corner(&self) -> GridPosition {
-        (self.bottom_row, self.left_column)
-    }
-
-    fn bottom_right_corner(&self) -> GridPosition {
-        (self.bottom_row, self.right_column)
-    }
-}
-
 #[derive(Debug, Clone)]
 pub struct Style {
     pub padding: Padding,
@@ -1599,21 +1580,17 @@ impl Borders {
             return None;
         }
 
-        let frame = entity_frame(
-            &Entity::Cell(row, column),
-            self.count_rows,
-            self.count_columns,
-        );
+        let cell = CellBorderIndex::new(row, column);
 
         let border = Border {
-            top: self.get_horizontal_char(frame.top_row, column),
-            bottom: self.get_horizontal_char(frame.bottom_row, column),
-            left: self.get_vertical_char(row, frame.left_column),
-            right: self.get_vertical_char(row, frame.right_column),
-            left_top_corner: self.get_intersection_char(frame.top_left_corner()),
-            left_bottom_corner: self.get_intersection_char(frame.bottom_left_corner()),
-            right_top_corner: self.get_intersection_char(frame.top_right_corner()),
-            right_bottom_corner: self.get_intersection_char(frame.bottom_right_corner()),
+            top: self.get_horizontal_char(cell.top().0, cell.top().1),
+            bottom: self.get_horizontal_char(cell.bottom().0, cell.bottom().1),
+            left: self.get_vertical_char(cell.left().0, cell.left().1),
+            right: self.get_vertical_char(cell.right().0, cell.right().1),
+            left_top_corner: self.get_intersection_char(cell.top_left()),
+            left_bottom_corner: self.get_intersection_char(cell.bottom_left()),
+            right_top_corner: self.get_intersection_char(cell.top_right()),
+            right_bottom_corner: self.get_intersection_char(cell.bottom_right()),
         };
 
         Some(border)
@@ -1785,12 +1762,47 @@ enum BorderError {
     NotEnoughIntersections,
 }
 
-fn entity_frame(entity: &Entity, count_rows: usize, count_columns: usize) -> EntityFrame {
-    match entity {
-        Entity::Global => EntityFrame::new(0, count_columns, 0, count_rows),
-        &Entity::Column(c) => EntityFrame::new(c, c + 1, 0, count_rows),
-        &Entity::Row(r) => EntityFrame::new(0, count_columns, r, r + 1),
-        &Entity::Cell(r, c) => EntityFrame::new(c, c + 1, r, r + 1),
+#[derive(PartialEq, Eq, Debug, Hash, Clone)]
+struct CellBorderIndex {
+    row: usize,
+    col: usize,
+}
+
+impl CellBorderIndex {
+    fn new(row: usize, col: usize) -> Self {
+        Self { row, col }
+    }
+
+    fn top(&self) -> GridPosition {
+        (self.row, self.col)
+    }
+
+    fn bottom(&self) -> GridPosition {
+        (self.row + 1, self.col)
+    }
+
+    fn left(&self) -> GridPosition {
+        (self.row, self.col)
+    }
+
+    fn right(&self) -> GridPosition {
+        (self.row, self.col + 1)
+    }
+
+    fn top_left(&self) -> GridPosition {
+        (self.row, self.col)
+    }
+
+    fn top_right(&self) -> GridPosition {
+        (self.row, self.col + 1)
+    }
+
+    fn bottom_left(&self) -> GridPosition {
+        (self.row + 1, self.col)
+    }
+
+    fn bottom_right(&self) -> GridPosition {
+        (self.row + 1, self.col + 1)
     }
 }
 
diff --git a/src/concat.rs b/src/concat.rs
index 80acdd8..f178a1b 100644
--- a/src/concat.rs
+++ b/src/concat.rs
@@ -54,88 +54,73 @@ impl Concat {
 }
 
 impl TableOption for Concat {
-    fn change(&mut self, other: &mut Grid) {
+    fn change(&mut self, lhs: &mut Grid) {
+        let rhs = &self.table.grid;
+
         match &mut self.mode {
             ConcatMode::Vertical => {
-                let new_row_size = self.table.grid.count_rows() + other.count_rows();
-                let new_column_size =
-                    cmp::max(self.table.grid.count_columns(), other.count_columns());
+                let new_row_size = lhs.count_rows() + rhs.count_rows();
+                let new_column_size = cmp::max(lhs.count_columns(), rhs.count_columns());
                 let mut new_grid = Grid::new(new_row_size, new_column_size);
 
-                for row in 0..other.count_rows() {
-                    for column in 0..other.count_columns() {
-                        let settings = other.get_settings(row, column);
-                        new_grid.set(
-                            &Entity::Cell(row, column),
-                            settings.border_restriction(false),
-                        );
-                    }
-                }
-
-                for row in 0..self.table.grid.count_rows() {
-                    for column in 0..self.table.grid.count_columns() {
-                        let settings = self.table.grid.get_settings(row, column);
-                        new_grid.set(
-                            &Entity::Cell(other.count_rows() + row, column),
-                            settings.border_restriction(false),
-                        );
-                    }
-                }
-
                 for row in 0..new_grid.count_rows() {
                     for column in 0..new_grid.count_columns() {
-                        if (column >= other.count_columns() && row < other.count_rows())
-                            || (column >= self.table.grid.count_columns()
-                                && row > other.count_rows())
-                        {
-                            let settings =
-                                new_grid.get_settings(row, column).text(&self.default_cell);
-                            new_grid.set(&Entity::Cell(row, column), settings);
-                        }
+                        let is_lhs_side = row < lhs.count_rows();
+                        let is_rhs_side = row >= lhs.count_rows();
+
+                        let is_new_to_lhs = column >= rhs.count_columns() && is_rhs_side;
+                        let is_new_to_rhs = column >= lhs.count_columns() && is_lhs_side;
+                        let is_new_cell = is_new_to_lhs || is_new_to_rhs;
+
+                        let settings = if is_new_cell {
+                            new_grid
+                                .get_settings(row, column)
+                                .text(&self.default_cell)
+                                .border_restriction(false)
+                        } else if is_lhs_side {
+                            lhs.get_settings(row, column).border_restriction(false)
+                        } else {
+                            rhs.get_settings(row - lhs.count_rows(), column)
+                                .border_restriction(false)
+                        };
+
+                        new_grid.set(&Entity::Cell(row, column), settings);
                     }
                 }
 
-                *other = new_grid;
+                *lhs = new_grid;
             }
             ConcatMode::Horizontal => {
-                let new_row_size = cmp::max(self.table.grid.count_rows(), other.count_rows());
-                let new_column_size = self.table.grid.count_columns() + other.count_columns();
+                let new_row_size = cmp::max(lhs.count_rows(), rhs.count_rows());
+                let new_column_size = lhs.count_columns() + rhs.count_columns();
                 let mut new_grid = Grid::new(new_row_size, new_column_size);
 
-                for column in 0..other.count_columns() {
-                    for row in 0..other.count_rows() {
-                        let settings = other.get_settings(row, column);
-                        new_grid.set(
-                            &Entity::Cell(row, column),
-                            settings.border_restriction(false),
-                        );
-                    }
-                }
-
-                for column in 0..self.table.grid.count_columns() {
-                    for row in 0..self.table.grid.count_rows() {
-                        let settings = self.table.grid.get_settings(row, column);
-                        new_grid.set(
-                            &Entity::Cell(row, column + other.count_columns()),
-                            settings.border_restriction(false),
-                        );
-                    }
-                }
-
                 for row in 0..new_grid.count_rows() {
                     for column in 0..new_grid.count_columns() {
-                        if (row >= other.count_rows() && column < other.count_columns())
-                            || (row >= self.table.grid.count_rows()
-                                && column > other.count_columns())
-                        {
-                            let settings =
-                                new_grid.get_settings(row, column).text(&self.default_cell);
-                            new_grid.set(&Entity::Cell(row, column), settings);
-                        }
+                        let is_lhs_side = column < lhs.count_columns();
+                        let is_rhs_side = column >= lhs.count_columns();
+
+                        let is_new_to_lhs = row >= rhs.count_rows() && is_rhs_side;
+                        let is_new_to_rhs = row >= lhs.count_rows() && is_lhs_side;
+                        let is_new_cell = is_new_to_lhs || is_new_to_rhs;
+
+                        let settings = if is_new_cell {
+                            new_grid
+                                .get_settings(row, column)
+                                .text(&self.default_cell)
+                                .border_restriction(false)
+                        } else if is_lhs_side {
+                            lhs.get_settings(row, column).border_restriction(false)
+                        } else {
+                            rhs.get_settings(row, column - lhs.count_columns())
+                                .border_restriction(false)
+                        };
+
+                        new_grid.set(&Entity::Cell(row, column), settings);
                     }
                 }
 
-                *other = new_grid;
+                *lhs = new_grid;
             }
         }
     }
diff --git a/src/extract.rs b/src/extract.rs
index 5a83bf4..8d13ea1 100644
--- a/src/extract.rs
+++ b/src/extract.rs
@@ -4,27 +4,12 @@ use crate::TableOption;
 
 /// Returns a new [Table] that reflects a segment of the referenced [Table]
 ///
-/// The segment is defined by [RangeBounds<usize>] for Rows and Columns
-///
-/// # Range
-///
-/// A [RangeBounds] argument can be less than or equal to the shape of a [Table]
-///
-/// If a [RangeBounds] argument is malformed or too large the thread will panic
-///
-///
-/// ```
-/// // Empty                         Full                      Out of bounds
-///    Extract::segment(0..0, 0..0)  Extract::segment(.., ..)  Extract::segment(0..1, ..4)
-///    [].   .   .                   [O   O   O                [O   O   O  X] //ERROR            
-///      .   .   .                    O   O   O                 .   .   .             
-///      .   .   .                    O   O   O]                .   .   .          
-/// ```
+/// The segment is defined by [RangeBounds<usize>] for Rows and Columns.
 ///
 /// # Example
 ///
 /// ```
-/// use tabled::{Table, Format, Row, Modify};
+/// use tabled::{Table, Format, Row, Modify, Extract};
 ///
 /// let data = vec![
 ///     (0, "Grodno", true),
@@ -38,11 +23,11 @@ use crate::TableOption;
 ///                .with(Extract::segment(1..=2, 1..))
 ///                .to_string();
 ///
-/// assert_eq!(table, "+-------------+-----------+\n\
-///                    | : Grodno :  | : true :  |\n\
-///                    +-------------+-----------+\n\
-///                    |  : Minsk :  | : true :  |\n\
-///                    +-------------+-----------+\n");
+/// assert_eq!(table, "+------------+----------+\n\
+///                    | : Grodno : | : true : |\n\
+///                    +------------+----------+\n\
+///                    | : Minsk :  | : true : |\n\
+///                    +------------+----------+\n");
 /// ```
 ///
 pub struct Extract<R, C> {
@@ -60,6 +45,7 @@ where
     /// The segment is defined by [RangeBounds<usize>] for Rows and Columns
     ///
     /// ```rust,no_run
+    /// # use tabled::Extract;
     /// let rows = 1..3;
     /// let columns = 1..;
     /// Extract::segment(rows, columns);
@@ -71,7 +57,7 @@ where
     ///
     /// If a [RangeBounds] argument is malformed or too large the thread will panic
     ///
-    /// ```
+    /// ```text
     /// // Empty                         Full                      Out of bounds
     ///    Extract::segment(0..0, 0..0)  Extract::segment(.., ..)  Extract::segment(0..1, ..4)
     ///    [].   .   .                   [O   O   O                [O   O   O  X] //ERROR            
@@ -93,6 +79,7 @@ where
     /// The segment is defined by [RangeBounds<usize>] for Rows
     ///
     /// ```rust,no_run
+    /// # use tabled::Extract;
     /// Extract::rows(1..3);
     /// ```
     ///
@@ -102,7 +89,7 @@ where
     ///
     /// If a [RangeBounds] argument is malformed or too large the thread will panic
     ///
-    /// ```
+    /// ```text
     /// // Empty                Full               Out of bounds
     ///    Extract::rows(0..0)  Extract::rows(..)  Extract::rows(0..4)
     ///    [].   .   .          [O   O   O         [O   O   O             
@@ -125,6 +112,7 @@ where
     /// The segment is defined by [RangeBounds<usize>] for Columns
     ///
     /// ```rust,no_run
+    /// # use tabled::Extract;
     /// Extract::columns(1..3);
     /// ```
     ///
@@ -134,7 +122,7 @@ where
     ///
     /// If a [RangeBounds] argument is malformed or too large the thread will panic
     ///
-    /// ```
+    /// ```text
     /// // Empty                   Full                  Out of bounds
     ///    Extract::columns(0..0)  Extract::columns(..)  Extract::columns(0..4)
     ///    [].   .   .             [O   O   O            [O   O   O   X          
diff --git a/src/highlight.rs b/src/highlight.rs
index 5a772e8..68a037b 100644
--- a/src/highlight.rs
+++ b/src/highlight.rs
@@ -1,10 +1,42 @@
 #[allow(unused)]
 use crate::Table;
-use crate::TableOption;
+use crate::{style::Border, TableOption};
 use papergrid::{Entity, Grid, Settings};
 
-pub use papergrid::Border;
-
+/// Highlight modifies a table style by changing a border of a target [Table] segment.
+///
+/// # Example
+///
+/// ```
+/// use tabled::{TableIteratorExt, Highlight, style::{Border, Style}};
+///
+/// let data = [
+///     ("ELF", "Extensible Linking Format", true),
+///     ("DWARF", "", true),
+///     ("PE", "Portable Executable", false),
+/// ];
+///
+/// let table = data.iter()
+///                .enumerate()
+///                .table()
+///                .with(Style::github_markdown())
+///                .with(Highlight::frame(Border::default().top('^').bottom('v')))
+///                .to_string();
+///
+/// assert_eq!(
+///     table,
+///     concat!(
+///         " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ \n",
+///         "| usize | &str  |           &str            | bool  |\n",
+///         "|-------+-------+---------------------------+-------|\n",
+///         "|   0   |  ELF  | Extensible Linking Format | true  |\n",
+///         "|   1   | DWARF |                           | true  |\n",
+///         "|   2   |  PE   |    Portable Executable    | false |\n",
+///         " vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv \n",
+///     ),
+/// );
+/// ```
+///
 pub struct Highlight {
     target: Target,
     border: Border,
@@ -63,71 +95,169 @@ pub enum Target {
 
 impl TableOption for Highlight {
     fn change(&mut self, grid: &mut Grid) {
-        match self.target {
-            Target::Cell { row, column } => {
-                let settings = Settings::default()
-                    .border(self.border.clone())
-                    .border_restriction(false);
-                grid.set(&Entity::Cell(row, column), settings);
-            }
-            Target::Frame => {
-                let settings = Settings::default()
-                    .border(self.border.clone())
-                    .border_restriction(false);
-                grid.set(&Entity::Global, settings);
-            }
-            Target::Row { from, to } => {
-                if to == from + 1 {
-                    let settings = Settings::default()
-                        .border(self.border.clone())
-                        .border_restriction(false);
-                    grid.set(&Entity::Row(from), settings);
-                } else {
-                    for row in from..to {
-                        let mut border = self.border.clone();
-
-                        let is_first_row = row == from;
-                        let is_last_row = row + 1 == to;
-
-                        if !is_first_row {
-                            border.top = None;
-                        }
-
-                        if !is_last_row {
-                            border.bottom = None;
-                        }
-
-                        let settings = Settings::default().border(border).border_restriction(false);
-                        grid.set(&Entity::Row(row), settings);
-                    }
-                }
-            }
-            Target::Column { from, to } => {
-                if to == from + 1 {
-                    let settings = Settings::default()
-                        .border(self.border.clone())
-                        .border_restriction(false);
-                    grid.set(&Entity::Column(from), settings);
-                } else {
-                    for column in from..to {
-                        let mut border = self.border.clone();
-
-                        let is_first_column = column == from;
-                        let is_last_column = column + 1 == to;
-
-                        if !is_first_column {
-                            border.left = None;
-                        }
-
-                        if !is_last_column {
-                            border.right = None;
-                        }
-
-                        let settings = Settings::default().border(border).border_restriction(false);
-                        grid.set(&Entity::Column(column), settings);
-                    }
-                }
-            }
+        let sector = match self.target {
+            Target::Frame => GridSector::new(0, grid.count_columns(), 0, grid.count_rows()),
+            Target::Cell { row, column } => GridSector::new(column, column + 1, row, row + 1),
+            Target::Row { from, to } => GridSector::new(0, grid.count_columns(), from, to),
+            Target::Column { from, to } => GridSector::new(from, to, 0, grid.count_rows()),
+        };
+
+        set_border(grid, sector, self.border.clone());
+    }
+}
+
+#[derive(PartialEq, Eq, Debug, Hash, Clone)]
+struct GridSector {
+    left: usize,
+    right: usize,
+    top: usize,
+    bottom: usize,
+}
+
+impl GridSector {
+    fn new(left: usize, right: usize, top: usize, bottom: usize) -> Self {
+        Self {
+            left,
+            right,
+            top,
+            bottom,
+        }
+    }
+}
+
+fn set_border(grid: &mut Grid, sector: GridSector, border: Border) {
+    let first_row = sector.top;
+    let first_col = sector.left;
+
+    let last_row = if sector.bottom > 0 {
+        sector.bottom - 1
+    } else {
+        sector.bottom
+    };
+
+    let last_col = if sector.right > 0 {
+        sector.right - 1
+    } else {
+        sector.right
+    };
+
+    if let Some(c) = border.top {
+        let border = Border::default().top(c);
+        for column in sector.left..sector.right {
+            grid.set(
+                &Entity::Cell(first_row, column),
+                Settings::default()
+                    .border_restriction(false)
+                    .border(border.clone()),
+            );
+        }
+
+        let border = Border::default().top_left_corner(c);
+        for column in sector.left + 1..sector.right {
+            grid.set(
+                &Entity::Cell(first_row, column),
+                Settings::default()
+                    .border_restriction(false)
+                    .border(border.clone()),
+            );
         }
     }
+
+    if let Some(c) = border.bottom {
+        let border = Border::default().bottom(c);
+        for column in sector.left..sector.right {
+            grid.set(
+                &Entity::Cell(last_row, column),
+                Settings::default()
+                    .border_restriction(false)
+                    .border(border.clone()),
+            );
+        }
+
+        let border = Border::default().bottom_left_corner(c);
+        for column in sector.left + 1..sector.right {
+            grid.set(
+                &Entity::Cell(last_row, column),
+                Settings::default()
+                    .border_restriction(false)
+                    .border(border.clone()),
+            );
+        }
+    }
+
+    if let Some(c) = border.left {
+        let border = Border::default().left(c);
+        for row in sector.top..sector.bottom {
+            grid.set(
+                &Entity::Cell(row, first_col),
+                Settings::default()
+                    .border_restriction(false)
+                    .border(border.clone()),
+            );
+        }
+
+        let border = Border::default().top_left_corner(c);
+        for row in sector.top + 1..sector.bottom {
+            grid.set(
+                &Entity::Cell(row, first_col),
+                Settings::default()
+                    .border_restriction(false)
+                    .border(border.clone()),
+            );
+        }
+    }
+
+    if let Some(c) = border.right {
+        let border = Border::default().right(c);
+        for row in sector.top..sector.bottom {
+            grid.set(
+                &Entity::Cell(row, last_col),
+                Settings::default()
+                    .border_restriction(false)
+                    .border(border.clone()),
+            );
+        }
+
+        let border = Border::default().top_right_corner(c);
+        for row in sector.top + 1..sector.bottom {
+            grid.set(
+                &Entity::Cell(row, last_col),
+                Settings::default()
+                    .border_restriction(false)
+                    .border(border.clone()),
+            );
+        }
+    }
+
+    if let Some(c) = border.left_top_corner {
+        let border = Border::default().top_left_corner(c);
+        grid.set(
+            &Entity::Cell(first_row, first_col),
+            Settings::default().border_restriction(false).border(border),
+        );
+    }
+
+    if let Some(c) = border.left_bottom_corner {
+        let border = Border::default().bottom_left_corner(c);
+        grid.set(
+            &Entity::Cell(last_row, first_col),
+            Settings::default().border_restriction(false).border(border),
+        );
+    }
+
+    if let Some(c) = border.right_top_corner {
+        let border = Border::default().top_right_corner(c);
+        grid.set(
+            &Entity::Cell(first_row, last_col),
+            Settings::default().border_restriction(false).border(border),
+        );
+    }
+
+    if let Some(c) = border.right_bottom_corner {
+        let border = Border::default().bottom_right_corner(c);
+        grid.set(
+            &Entity::Cell(last_row, last_col),
+            Settings::default().border_restriction(false).border(border),
+        );
+    }
 }
diff --git a/src/style.rs b/src/style.rs
index 66e8e6b..dbc315c 100644
--- a/src/style.rs
+++ b/src/style.rs
@@ -4,8 +4,8 @@ use std::{borrow::Cow, marker::PhantomData};
 
 #[allow(unused)]
 use crate::Table;
-use crate::TableOption;
-use papergrid::{Border, Entity, Grid, Settings};
+use crate::{CellOption, Highlight, TableOption};
+use papergrid::{Entity, Grid, Settings};
 
 /// Style is represents a theme of a [Table].
 ///
@@ -1254,3 +1254,20 @@ impl<T, B, L, R, IH, IV, H> TableOption for CustomStyle<T, B, L, R, IH, IV, H> {
         self.inner.change(grid);
     }
 }
+
+/// Border represents a style of a CellBorder.
+///
+/// ```rust,no_run
+///   # use tabled::{style::{Style, Border}, Row, Table, Modify};
+///   # let data: Vec<&'static str> = Vec::new();
+///     let table = Table::new(&data)
+///         .with(Style::ascii())
+///         .with(Modify::new(Row(..1)).with(Border::default().top('x')));
+/// ```
+pub use papergrid::Border;
+
+impl CellOption for Border {
+    fn change_cell(&mut self, grid: &mut Grid, row: usize, column: usize) {
+        Highlight::cell(row, column, self.clone()).change(grid);
+    }
+}


Test patch:
diff --git a/papergrid/tests/extract.rs b/papergrid/tests/extract.rs
index 64fc41d..a763bc6 100644
--- a/papergrid/tests/extract.rs
+++ b/papergrid/tests/extract.rs
@@ -73,8 +73,12 @@ fn extract_bottom_test() {
     assert_eq!(
         grid.to_string(),
         "+---+---+---+\n\
+         |0-0|0-1|0-2|\n\
+         +---+---+---+\n\
+         |1-0|1-1|1-2|\n\
+         +---+---+---+\n\
          |2-0|2-1|2-2|\n\
-         +---+---+---+\n"
+         +---+---+---+\n",
     )
 }
 
diff --git a/papergrid/tests/styling.rs b/papergrid/tests/styling.rs
index d3f7e8e..a7bfc7d 100644
--- a/papergrid/tests/styling.rs
+++ b/papergrid/tests/styling.rs
@@ -12,11 +12,11 @@ fn grid_2x2_custom_frame_test() {
 
     assert_eq!(
         grid.to_string(),
-        "#*******#\n\
+        "#***#***#\n\
          |0-0|0-1|\n\
-         |---+---|\n\
+         #***#***#\n\
          |1-0|1-1|\n\
-         #*******#\n"
+         #***#***#\n"
     )
 }
 
@@ -32,9 +32,9 @@ fn grid_2x2_custom_column_test() {
         grid.to_string(),
         "+---#***#\n\
          |0-0|0-1|\n\
-         +---|---|\n\
+         +---#***#\n\
          |1-0|1-1|\n\
-         +---#***#\n"
+         +---#***#\n",
     );
 
     grid.set_cell_borders(DEFAULT_CELL_STYLE.clone());
@@ -47,9 +47,9 @@ fn grid_2x2_custom_column_test() {
         grid.to_string(),
         "#***#---+\n\
          |0-0|0-1|\n\
-         |---|---+\n\
+         #***#---+\n\
          |1-0|1-1|\n\
-         #***#---+\n"
+         #***#---+\n",
     )
 }
 
@@ -64,11 +64,13 @@ fn grid_2x2_custom_row_test() {
 
     assert_eq!(
         grid.to_string(),
-        "#*******#\n\
-         |0-0|0-1|\n\
-         #*******#\n\
-         |1-0|1-1|\n\
-         +---+---+\n"
+        concat!(
+            "#***#***#\n",
+            "|0-0|0-1|\n",
+            "#***#***#\n",
+            "|1-0|1-1|\n",
+            "+---+---+\n",
+        )
     );
 
     grid.set_cell_borders(DEFAULT_CELL_STYLE.clone());
@@ -82,9 +84,9 @@ fn grid_2x2_custom_row_test() {
         str,
         "+---+---+\n\
          |0-0|0-1|\n\
-         #*******#\n\
+         #***#***#\n\
          |1-0|1-1|\n\
-         #*******#\n"
+         #***#***#\n"
     );
 }
 
diff --git a/tests/concat_test.rs b/tests/concat_test.rs
index 2e8969d..c7d399e 100644
--- a/tests/concat_test.rs
+++ b/tests/concat_test.rs
@@ -5,7 +5,9 @@ mod util;
 
 #[test]
 fn table_join_vertical() {
-    let data1 = create_vector::<2, 3>();
+    let mut data1 = create_vector::<2, 3>();
+    data1[0][0] = "123".to_string();
+
     let data2 = create_vector::<2, 3>();
 
     {
@@ -14,17 +16,17 @@ fn table_join_vertical() {
         let table3 = table1.with(Concat::vertical(table2));
 
         let expected = concat!(
-            "  N | column 0 | column 1 | column 2  \n",
-            " ---+----------+----------+---------- \n",
-            "  0 |   0-0    |   0-1    |   0-2     \n",
-            "  1 |   1-0    |   1-1    |   1-2     \n",
-            "+---+----------+----------+----------+\n",
-            "| N | column 0 | column 1 | column 2 |\n",
-            "+---+----------+----------+----------+\n",
-            "| 0 |   0-0    |   0-1    |   0-2    |\n",
-            "+---+----------+----------+----------+\n",
-            "| 1 |   1-0    |   1-1    |   1-2    |\n",
-            "+---+----------+----------+----------+\n",
+            "   N  | column 0 | column 1 | column 2  \n",
+            " -----+----------+----------+---------- \n",
+            "  123 |   0-0    |   0-1    |   0-2     \n",
+            "   1  |   1-0    |   1-1    |   1-2     \n",
+            "+-----+----------+----------+----------+\n",
+            "|  N  | column 0 | column 1 | column 2 |\n",
+            "+-----+----------+----------+----------+\n",
+            "|  0  |   0-0    |   0-1    |   0-2    |\n",
+            "+-----+----------+----------+----------+\n",
+            "|  1  |   1-0    |   1-1    |   1-2    |\n",
+            "+-----+----------+----------+----------+\n",
         );
 
         assert_eq!(expected, table3.to_string());
@@ -36,17 +38,17 @@ fn table_join_vertical() {
         let table3 = table2.with(Concat::vertical(table1));
 
         let expected = concat!(
-            "+---+----------+----------+----------+\n",
-            "| N | column 0 | column 1 | column 2 |\n",
-            "+---+----------+----------+----------+\n",
-            "| 0 |   0-0    |   0-1    |   0-2    |\n",
-            "+---+----------+----------+----------+\n",
-            "| 1 |   1-0    |   1-1    |   1-2    |\n",
-            "+---+----------+----------+----------+\n",
-            "  N | column 0 | column 1 | column 2  \n",
-            " ---+----------+----------+---------- \n",
-            "  0 |   0-0    |   0-1    |   0-2     \n",
-            "  1 |   1-0    |   1-1    |   1-2     \n",
+            "+-----+----------+----------+----------+\n",
+            "|  N  | column 0 | column 1 | column 2 |\n",
+            "+-----+----------+----------+----------+\n",
+            "|  0  |   0-0    |   0-1    |   0-2    |\n",
+            "+-----+----------+----------+----------+\n",
+            "|  1  |   1-0    |   1-1    |   1-2    |\n",
+            "+-----+----------+----------+----------+\n",
+            "   N  | column 0 | column 1 | column 2  \n",
+            " -----+----------+----------+---------- \n",
+            "  123 |   0-0    |   0-1    |   0-2     \n",
+            "   1  |   1-0    |   1-1    |   1-2     \n",
         );
 
         assert_eq!(table3.to_string(), expected);
diff --git a/tests/highlingt_test.rs b/tests/highlingt_test.rs
index c749427..fd94945 100644
--- a/tests/highlingt_test.rs
+++ b/tests/highlingt_test.rs
@@ -1,5 +1,5 @@
 use crate::util::create_vector;
-use tabled::{Border, Highlight, Style, Table};
+use tabled::{style::Border, Highlight, Style, Table};
 
 mod util;
 
@@ -8,16 +8,8 @@ fn highlingt_cell() {
     let data = create_vector::<3, 3>();
     let table = Table::new(&data)
         .with(Style::modern())
-        .with(Highlight::cell(
-            0,
-            0,
-            Border::full('+', '+', '+', '+', '+', '+', '+', '+'),
-        ))
-        .with(Highlight::cell(
-            1,
-            1,
-            Border::full('*', '*', '*', '*', '*', '*', '*', '*'),
-        ))
+        .with(Highlight::cell(0, 0, Border::single('+')))
+        .with(Highlight::cell(1, 1, Border::single('*')))
         .to_string();
 
     let expected = concat!(
@@ -40,14 +32,8 @@ fn highlingt_row() {
     let data = create_vector::<3, 3>();
     let table = Table::new(&data)
         .with(Style::modern())
-        .with(Highlight::row(
-            0,
-            Border::full('+', '+', '+', '+', '+', '+', '+', '+'),
-        ))
-        .with(Highlight::row(
-            3,
-            Border::full('*', '*', '*', '*', '*', '*', '*', '*'),
-        ))
+        .with(Highlight::row(0, Border::single('+')))
+        .with(Highlight::row(3, Border::single('*')))
         .to_string();
 
     let expected = concat!(
@@ -173,3 +159,27 @@ fn highlingt_frame() {
 
     assert_eq!(table, expected);
 }
+
+#[test]
+fn highlingt_single_column() {
+    let data = create_vector::<3, 0>();
+    let table = Table::new(&data)
+        .with(Style::modern())
+        .with(Highlight::cell(0, 0, Border::default().left('*').top('x')))
+        .with(Highlight::row_range(1, 3, Border::default().left('n')))
+        .to_string();
+
+    let expected = concat!(
+        "┌xxx┐\n",
+        "* N │\n",
+        "├───┤\n",
+        "n 0 │\n",
+        "n───┤\n",
+        "n 1 │\n",
+        "├───┤\n",
+        "│ 2 │\n",
+        "└───┘\n",
+    );
+
+    assert_eq!(table, expected);
+}
diff --git a/tests/panel_test.rs b/tests/panel_test.rs
index 354ee61..7c1cd2f 100644
--- a/tests/panel_test.rs
+++ b/tests/panel_test.rs
@@ -1,7 +1,7 @@
 use crate::util::create_vector;
 use tabled::{
-    Alignment, Border, Cell, Footer, Full, Header, Highlight, Modify, Object, Panel, Row, Style,
-    Table,
+    style::{Border, Style},
+    Alignment, Cell, Footer, Full, Header, Highlight, Modify, Object, Panel, Row, Table,
 };
 
 mod util;
diff --git a/tests/rotate_test.rs b/tests/rotate_test.rs
index 699d92b..6ce1ffd 100644
--- a/tests/rotate_test.rs
+++ b/tests/rotate_test.rs
@@ -1,6 +1,9 @@
 // todo: add method for SPACING between cells.
 //       add MARGIN && PADDING instead of indent?
-use tabled::{Border, Highlight, Rotate, Style, Table};
+use tabled::{
+    style::{Border, Style},
+    Highlight, Rotate, Table,
+};
 
 #[test]
 fn test_rotate() {
@@ -181,6 +184,8 @@ fn rotate_preserve_border_styles_test() {
 
     // it's a correct behaviour because
     // when we sen bottom border of cell(0, 2) we also set top border of cell(1, 2)
+    //
+    // todo: determine if it's correct
     assert_eq!(
         table,
         concat!(