Repository: zhiburt/tabled
Pull request #485 (2025-01-24 01:17:23)

Rework `Span`


Patch:
diff --git a/tabled/Cargo.toml b/tabled/Cargo.toml
index 97f3b06..29446d8 100644
--- a/tabled/Cargo.toml
+++ b/tabled/Cargo.toml
@@ -183,6 +183,16 @@ name = "span"
 path = "examples/span.rs"
 required-features = ["std"]
 
+[[example]]
+name = "span_column"
+path = "examples/span_column.rs"
+required-features = ["std"]
+
+[[example]]
+name = "span_row"
+path = "examples/span_row.rs"
+required-features = ["std"]
+
 [[example]]
 name = "nested_table"
 path = "examples/nested_table.rs"
diff --git a/tabled/examples/span_column.rs b/tabled/examples/span_column.rs
new file mode 100644
index 0000000..8cef690
--- /dev/null
+++ b/tabled/examples/span_column.rs
@@ -0,0 +1,58 @@
+//! [COMMENTED] A list of examples for a span use.
+
+use tabled::{
+    settings::{style::BorderSpanCorrection, Alignment, Span, Style},
+    Table,
+};
+
+fn main() {
+    let data = [
+        (1, 2, 3),
+        (4, 5, 6),
+        (7, 8, 9),
+        (10, 11, 12),
+        (13, 14, 15),
+        (16, 17, 18),
+        (19, 20, 21),
+    ];
+    let mut table = Table::new(data);
+
+    table.with(Style::modern_rounded());
+
+    // Here we show how original table looks.
+    println!("{table}");
+
+    // Then there's a list of Span appliences and it's affects.
+
+    // Spread cell by 1 column to the right.
+    table.modify((0, 1), Span::column(2));
+
+    // Spread cell all the way to the right,
+    // Which essentially covers all row.
+    table.modify((1, 0), Span::column(isize::MAX));
+
+    // Spread cell by 1 column to the left.
+    table.modify((2, 1), Span::column(-1));
+
+    // Spread cell all the way to the left.
+    table.modify((3, 2), Span::column(isize::MIN));
+
+    // Spread cell to cover the whole row.
+    table.modify((4, 0), Span::column(0));
+
+    // Spread cell to cover the whole row.
+    table.modify((5, 1), Span::column(0));
+
+    // Spread cell to cover the whole row.
+    table.modify((6, 2), Span::column(0));
+
+    // Set a default span for a cell,
+    // Essentially removing a span setting for it.
+    table.modify((4, 0), Span::column(1));
+
+    // Correct the style to look good
+    table.with(BorderSpanCorrection);
+    table.with(Alignment::center());
+
+    println!("{table}");
+}
diff --git a/tabled/examples/span_row.rs b/tabled/examples/span_row.rs
new file mode 100644
index 0000000..d8e6698
--- /dev/null
+++ b/tabled/examples/span_row.rs
@@ -0,0 +1,55 @@
+//! [COMMENTED] A list of examples for a span use.
+
+use tabled::{
+    settings::{style::BorderSpanCorrection, Alignment, Span, Style},
+    Table,
+};
+
+fn main() {
+    let data = [
+        (1, 2, 3, 5, (6, 7, 8)),
+        (9, 10, 11, 12, (13, 14, 15)),
+        (16, 17, 18, 19, (20, 21, 22)),
+        (23, 24, 25, 26, (27, 28, 29)),
+    ];
+    let mut table = Table::new(data);
+
+    table.with(Style::modern_rounded());
+
+    // Here we show how original table looks.
+    println!("{table}");
+
+    // Then there's a list of Span appliences and it's affects.
+
+    // Spread cell by 1 row to the right.
+    table.modify((0, 0), Span::row(2));
+
+    // Spread cell all the way to the right,
+    // Which essentially covers all columns.
+    table.modify((0, 1), Span::row(isize::MAX));
+
+    // Spread cell by 1 row to the top.
+    table.modify((1, 2), Span::row(-1));
+
+    // Spread cell all the way to the top.
+    table.modify((2, 3), Span::row(isize::MIN));
+
+    // Spread cell to cover the whole column.
+    table.modify((0, 4), Span::row(0));
+
+    // Spread cell to cover the whole column.
+    table.modify((1, 5), Span::row(0));
+
+    // Spread cell to cover the whole column.
+    table.modify((2, 6), Span::row(0));
+
+    // Set a default span for a cell,
+    // Essentially removing a span setting for it.
+    table.modify((0, 4), Span::row(1));
+
+    // Correct the style to look good
+    table.with(BorderSpanCorrection);
+    table.with(Alignment::center_vertical());
+
+    println!("{table}");
+}
diff --git a/tabled/src/settings/span/column.rs b/tabled/src/settings/span/column.rs
index c658f68..9893538 100644
--- a/tabled/src/settings/span/column.rs
+++ b/tabled/src/settings/span/column.rs
@@ -1,58 +1,113 @@
+use std::cmp::{self, Ordering};
+
 use crate::{
     grid::{
         config::{ColoredConfig, Entity, Position, SpannedConfig},
-        records::{ExactRecords, Records},
+        records::{ExactRecords, PeekableRecords, Records, RecordsMut},
     },
     settings::CellOption,
 };
 
-/// Columns (Vertical) span.
+/// Columns (horizontal) span.
 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
 pub struct ColumnSpan {
-    size: usize,
+    size: isize,
 }
 
 impl ColumnSpan {
-    /// Creates a new column (vertical) span.
-    pub fn new(size: usize) -> Self {
+    /// Creates a new column (horizontal) span.
+    pub fn new(size: isize) -> Self {
         Self { size }
     }
 
-    /// Creates a new column (vertical) span with a maximux value possible.
+    /// Creates a new column (horizontal) span with a maximux value possible.
     pub fn max() -> Self {
-        Self::new(usize::MAX)
+        Self::new(isize::MAX)
+    }
+
+    /// Creates a new column (horizontal) span with a min value possible.
+    pub fn min() -> Self {
+        Self::new(isize::MIN)
+    }
+
+    /// Creates a new column (horizontal) to spread all the columns.
+    pub fn spread() -> Self {
+        Self::new(0)
     }
 }
 
 impl<R> CellOption<R, ColoredConfig> for ColumnSpan
 where
-    R: Records + ExactRecords,
+    R: Records + ExactRecords + PeekableRecords + RecordsMut<String>,
 {
     fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) {
         let count_rows = records.count_rows();
         let count_cols = records.count_columns();
+        let shape = (count_rows, count_cols).into();
+
+        for pos in entity.iter(count_rows, count_cols) {
+            set_col_span(records, cfg, self.size, pos, shape);
+        }
 
-        set_col_spans(cfg, self.size, entity, (count_rows, count_cols));
         remove_false_spans(cfg);
     }
 }
 
-fn set_col_spans(cfg: &mut SpannedConfig, span: usize, entity: Entity, shape: (usize, usize)) {
-    for pos in entity.iter(shape.0, shape.1) {
-        if !pos.is_covered(shape.into()) {
-            continue;
-        }
+fn set_col_span<R>(
+    recs: &mut R,
+    cfg: &mut SpannedConfig,
+    span: isize,
+    pos: Position,
+    shape: Position,
+) where
+    R: Records + ExactRecords + PeekableRecords + RecordsMut<String>,
+{
+    if !pos.is_covered(shape) {
+        return;
+    }
+
+    match span.cmp(&0) {
+        Ordering::Less => {
+            // * got correct value [span, col]
+            // * clean the route from col to pos.col()
+            // * set the (pos.row(), col) to content from pos
+            // * set span
+
+            let span = span.unsigned_abs();
+            let (col, span) = if span > pos.col() {
+                (0, pos.col())
+            } else {
+                (pos.col() - span, span)
+            };
+
+            let content = recs.get_text(pos).to_string();
+
+            for i in col..span + 1 {
+                recs.set(Position::new(pos.row(), i), String::new());
+            }
 
-        let mut span = span;
-        if !is_column_span_valid(pos.col(), span, shape.1) {
-            span = shape.1 - pos.col();
+            recs.set(Position::new(pos.row(), col), content);
+            cfg.set_column_span(Position::new(pos.row(), col), span + 1);
         }
+        Ordering::Equal => {
+            let content = recs.get_text(pos).to_string();
+            let span = recs.count_columns();
 
-        if span_has_intersections(cfg, pos, span) {
-            continue;
+            for i in 0..recs.count_columns() {
+                recs.set(Position::new(pos.row(), i), String::new());
+            }
+
+            recs.set(Position::new(pos.row(), 0), content);
+            cfg.set_column_span(Position::new(pos.row(), 0), span);
         }
+        Ordering::Greater => {
+            let span = cmp::min(span as usize, shape.col() - pos.col());
+            if span_has_intersections(cfg, pos, span) {
+                return;
+            }
 
-        set_span_column(cfg, pos, span);
+            set_span_column(cfg, pos, span);
+        }
     }
 }
 
@@ -85,10 +140,6 @@ fn closest_visible(cfg: &SpannedConfig, mut pos: Position) -> Option<usize> {
     }
 }
 
-fn is_column_span_valid(col: usize, span: usize, count_cols: usize) -> bool {
-    span + col <= count_cols
-}
-
 fn span_has_intersections(cfg: &SpannedConfig, p: Position, span: usize) -> bool {
     for col in p.col()..p.col() + span {
         if !cfg.is_cell_visible((p.row(), col).into()) {
diff --git a/tabled/src/settings/span/mod.rs b/tabled/src/settings/span/mod.rs
index 1139a2b..b88696c 100644
--- a/tabled/src/settings/span/mod.rs
+++ b/tabled/src/settings/span/mod.rs
@@ -3,28 +3,11 @@
 //!
 //! # Example
 //!
-//! ```
-//! use tabled::{settings::{Span, Modify}, Table};
-//!
-//! let data = [[1, 2, 3], [4, 5, 6]];
-//!
-//! let table = Table::new(data)
-//!     .with(Modify::new((2, 0)).with(Span::column(2)))
-//!     .with(Modify::new((0, 1)).with(Span::column(2)))
-//!     .to_string();
-//!
-//! assert_eq!(
-//!     table,
-//!     concat!(
-//!         "+---+---+---+\n",
-//!         "| 0 | 1     |\n",
-//!         "+---+---+---+\n",
-//!         "| 1 | 2 | 3 |\n",
-//!         "+---+---+---+\n",
-//!         "| 4     | 6 |\n",
-//!         "+---+---+---+",
-//!     )
-//! )
+//! ```rust,no_run
+//! # use tabled::{Table, settings::{Style, Span, Modify, object::Columns}};
+//! # let data: Vec<&'static str> = Vec::new();
+//! let table = Table::new(&data)
+//!     .with(Modify::new(Columns::single(0)).with(Span::column(2)));
 //! ```
 
 mod column;
@@ -40,11 +23,28 @@ pub use row::RowSpan;
 ///  - size is bigger then the total number of columns.
 ///  - size is bigger then the total number of rows.
 ///
-/// ```rust,no_run
-/// # use tabled::{Table, settings::{Style, Span, Modify, object::Columns}};
-/// # let data: Vec<&'static str> = Vec::new();
-/// let table = Table::new(&data)
-///     .with(Modify::new(Columns::single(0)).with(Span::column(2)));
+/// ```
+/// use tabled::{settings::{Span, Modify}, Table};
+///
+/// let data = [[1, 2, 3], [4, 5, 6]];
+///
+/// let table = Table::new(data)
+///     .with(Modify::new((2, 0)).with(Span::column(2)))
+///     .with(Modify::new((0, 1)).with(Span::column(2)))
+///     .to_string();
+///
+/// assert_eq!(
+///     table,
+///     concat!(
+///         "+---+---+---+\n",
+///         "| 0 | 1     |\n",
+///         "+---+---+---+\n",
+///         "| 1 | 2 | 3 |\n",
+///         "+---+---+---+\n",
+///         "| 4     | 6 |\n",
+///         "+---+---+---+",
+///     )
+/// )
 /// ```
 ///
 /// [`Table`]: crate::Table
@@ -55,14 +55,14 @@ impl Span {
     /// New constructs a horizontal/column [`Span`].
     ///
     /// If size is bigger then the total number of columns it will be ignored.
-    pub fn column(size: usize) -> ColumnSpan {
+    pub fn column(size: isize) -> ColumnSpan {
         ColumnSpan::new(size)
     }
 
     /// New constructs a vertical/row [`Span`].
     ///
     /// If size is bigger then the total number of rows it will be ignored.
-    pub fn row(size: usize) -> RowSpan {
+    pub fn row(size: isize) -> RowSpan {
         RowSpan::new(size)
     }
 }
diff --git a/tabled/src/settings/span/row.rs b/tabled/src/settings/span/row.rs
index ffacf05..b883af6 100644
--- a/tabled/src/settings/span/row.rs
+++ b/tabled/src/settings/span/row.rs
@@ -1,7 +1,9 @@
+use std::cmp::{self, Ordering};
+
 use crate::{
     grid::{
         config::{ColoredConfig, Entity, Position, SpannedConfig},
-        records::{ExactRecords, Records},
+        records::{ExactRecords, PeekableRecords, Records, RecordsMut},
     },
     settings::CellOption,
 };
@@ -9,69 +11,117 @@ use crate::{
 /// Row (vertical) span.
 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
 pub struct RowSpan {
-    size: usize,
+    size: isize,
 }
 
 impl RowSpan {
     /// Creates a new row (vertical) span.
-    pub const fn new(size: usize) -> Self {
+    pub const fn new(size: isize) -> Self {
         Self { size }
     }
 
     /// Creates a new row (vertical) span with a maximux value possible.
     pub const fn max() -> Self {
-        Self::new(usize::MAX)
+        Self::new(isize::MAX)
+    }
+
+    /// Creates a new row (vertical) span with a min value possible.
+    pub fn min() -> Self {
+        Self::new(isize::MIN)
+    }
+
+    /// Creates a new row (vertical) to spread on all rows.
+    pub fn spread() -> Self {
+        Self::new(0)
     }
 }
 
 impl<R> CellOption<R, ColoredConfig> for RowSpan
 where
-    R: Records + ExactRecords,
+    R: Records + ExactRecords + PeekableRecords + RecordsMut<String>,
 {
     fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) {
         let count_rows = records.count_rows();
         let count_cols = records.count_columns();
+        let shape = (count_rows, count_cols).into();
+
+        for pos in entity.iter(count_rows, count_cols) {
+            set_span(records, cfg, self.size, pos, shape);
+        }
 
-        set_row_spans(cfg, self.size, entity, (count_rows, count_cols));
         remove_false_spans(cfg);
     }
 }
 
-fn set_row_spans(cfg: &mut SpannedConfig, span: usize, entity: Entity, shape: (usize, usize)) {
-    for p in entity.iter(shape.0, shape.1) {
-        if !p.is_covered(shape.into()) {
-            continue;
-        }
+fn set_span<R>(recs: &mut R, cfg: &mut SpannedConfig, span: isize, pos: Position, shape: Position)
+where
+    R: Records + ExactRecords + PeekableRecords + RecordsMut<String>,
+{
+    if !pos.is_covered(shape) {
+        return;
+    }
+
+    match span.cmp(&0) {
+        Ordering::Less => {
+            // * got correct value [span, row]
+            // * clean the route from row to pos.row()
+            // * set the (row, pos.col()) to content from pos
+            // * set span
+
+            let span = span.unsigned_abs();
+            let (row, span) = if span > pos.row() {
+                (0, pos.row())
+            } else {
+                (pos.row() - span, span)
+            };
+
+            let content = recs.get_text(pos).to_string();
+
+            for i in row..span + 1 {
+                recs.set(Position::new(i, pos.col()), String::new());
+            }
 
-        let mut span = span;
-        if !is_row_span_valid(p.row(), span, shape.0) {
-            span = shape.0 - p.row();
+            recs.set(Position::new(row, pos.col()), content);
+            cfg.set_row_span(Position::new(row, pos.col()), span + 1);
         }
+        Ordering::Equal => {
+            let content = recs.get_text(pos).to_string();
+            let span = recs.count_rows();
 
-        if span_has_intersections(cfg, p, span) {
-            continue;
+            for i in 0..recs.count_rows() {
+                recs.set(Position::new(i, pos.col()), String::new());
+            }
+
+            recs.set(Position::new(0, pos.col()), content);
+            cfg.set_row_span(Position::new(0, pos.col()), span);
         }
+        Ordering::Greater => {
+            let span = cmp::min(span as usize, shape.row() - pos.row());
+            if span_has_intersections(cfg, pos, span) {
+                return;
+            }
 
-        set_span_row(cfg, p, span);
+            set_span_row(cfg, pos, span);
+        }
     }
 }
 
 fn set_span_row(cfg: &mut SpannedConfig, p: Position, span: usize) {
     if span == 0 {
-        if p.row() == 0 {
+        if p.col() == 0 {
             return;
         }
 
-        if let Some(closerow) = closest_visible_row(cfg, p - (1, 0)) {
-            let span = p.row() + 1 - closerow;
-            cfg.set_row_span((closerow, p.col()).into(), span);
+        if let Some(nearcol) = closest_visible(cfg, p - (0, 1)) {
+            let span = p.col() + 1 - nearcol;
+            cfg.set_row_span((p.row(), nearcol).into(), span);
         }
     }
 
     cfg.set_row_span(p, span);
 }
 
-fn closest_visible_row(cfg: &SpannedConfig, mut pos: Position) -> Option<usize> {
+fn closest_visible(cfg: &SpannedConfig, mut pos: Position) -> Option<usize> {
     loop {
         if cfg.is_cell_visible(pos) {
             return Some(pos.row());
@@ -86,10 +136,6 @@ fn closest_visible_row(cfg: &SpannedConfig, mut pos: Position) -> Option<usize>
     }
 }
 
-fn is_row_span_valid(row: usize, span: usize, count_rows: usize) -> bool {
-    span + row <= count_rows
-}
-
 fn span_has_intersections(cfg: &SpannedConfig, p: Position, span: usize) -> bool {
     for row in p.row()..p.row() + span {
         if !cfg.is_cell_visible((row, p.col()).into()) {
diff --git a/tabled/src/settings/style/line_text.rs b/tabled/src/settings/style/line_text.rs
index 724abc9..b4535e0 100644
--- a/tabled/src/settings/style/line_text.rs
+++ b/tabled/src/settings/style/line_text.rs
@@ -500,7 +500,10 @@ fn get_horizontal_alignment_offset(
             Offset::Begin(off)
         }
         AlignmentHorizontal::Left => Offset::Begin(0),
-        AlignmentHorizontal::Right => Offset::End(0),
+        AlignmentHorizontal::Right => {
+            let width = get_text_width(text);
+            Offset::End(width)
+        }
     }
 }
 


Test patch:
diff --git a/tabled/tests/core/kv_test.rs b/tabled/tests/core/kv_test.rs
index e76fa95..68935f0 100644
--- a/tabled/tests/core/kv_test.rs
+++ b/tabled/tests/core/kv_test.rs
@@ -841,31 +841,27 @@ fn multiline_table_test2() {
 
     assert_table!(
         ansi_str::AnsiStr::ansi_strip(&table.to_string()),
-        r#"┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐"#
-        r#"│ 0                                                                                                                                                                                                                                                                    │"#
-        r#"├──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤"#
-        r#"│ This is the 0.19 release of Nushell. If you'd like to read more about it, please check out: https://www.nushell.sh/blog/2020/09/01/nushell_0_19.html                                                                                                                 │"#
-        r#"│                                                                                                                                                                                                                                                                      │"#
-        r#"│ For convenience, we are providing full builds for Windows, Linux, and macOS. These are the "all extra features" builds, so be sure you have the requirements to enable all capabilities: https://github.com/nushell/book/blob/master/en/installation.md#dependencies │"#
-        r#"│                                                                                                                                                                                                                                                                      │"#
-        r#"└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘"#
+        r#"┌───┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐"#
+        r#"│ 0 │ This is the 0.19 release of Nushell. If you'd like to read more about it, please check out: https://www.nushell.sh/blog/2020/09/01/nushell_0_19.html                                                                                                                 │"#
+        r#"│   │                                                                                                                                                                                                                                                                      │"#
+        r#"│   │ For convenience, we are providing full builds for Windows, Linux, and macOS. These are the "all extra features" builds, so be sure you have the requirements to enable all capabilities: https://github.com/nushell/book/blob/master/en/installation.md#dependencies │"#
+        r#"│   │                                                                                                                                                                                                                                                                      │"#
+        r#"└───┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘"#
     );
 
     table.with(Width::wrap(100));
 
     assert_table!(
         ansi_str::AnsiStr::ansi_strip(&table.to_string()),
-        "┌──────────────────────────────────────────────────────────────────────────────────────────────────┐"
-        "│ 0                                                                                                │"
-        "├──────────────────────────────────────────────────────────────────────────────────────────────────┤"
-        "│ This is the 0.19 release of Nushell. If you'd like to read more about it, please check out: http │"
-        "│ s://www.nushell.sh/blog/2020/09/01/nushell_0_19.html                                             │"
-        "│                                                                                                  │"
-        "│ For convenience, we are providing full builds for Windows, Linux, and macOS. These are the \"all  │"
-        "│ extra features\" builds, so be sure you have the requirements to enable all capabilities: https:/ │"
-        "│ /github.com/nushell/book/blob/master/en/installation.md#dependencies                             │"
-        "│                                                                                                  │"
-        "└──────────────────────────────────────────────────────────────────────────────────────────────────┘"
+        r#"┌──┬───────────────────────────────────────────────────────────────────────────────────────────────┐"#
+        r#"│  │ This is the 0.19 release of Nushell. If you'd like to read more about it, please check out: h │"#
+        r#"│  │ ttps://www.nushell.sh/blog/2020/09/01/nushell_0_19.html                                       │"#
+        r#"│  │                                                                                               │"#
+        r#"│  │ For convenience, we are providing full builds for Windows, Linux, and macOS. These are the "a │"#
+        r#"│  │ ll extra features" builds, so be sure you have the requirements to enable all capabilities: h │"#
+        r#"│  │ ttps://github.com/nushell/book/blob/master/en/installation.md#dependencies                    │"#
+        r#"│  │                                                                                               │"#
+        r#"└──┴───────────────────────────────────────────────────────────────────────────────────────────────┘"#
     );
 }
 
diff --git a/tabled/tests/qc/tests/span.rs b/tabled/tests/qc/tests/span.rs
index 13566f6..30ecec4 100644
--- a/tabled/tests/qc/tests/span.rs
+++ b/tabled/tests/qc/tests/span.rs
@@ -147,7 +147,7 @@ fn set_span_hspan(table: &mut Table, list: &[u8]) {
             }
 
             let span = list[i];
-            table.with(Modify::new((r, c)).with(Span::column(span as usize)));
+            table.with(Modify::new((r, c)).with(Span::column(span as isize)));
 
             i += 1;
         }
@@ -163,7 +163,7 @@ fn set_span_vspan(table: &mut Table, list: &[u8]) {
             }
 
             let span = list[i];
-            table.with(Modify::new((r, c)).with(Span::row(span as usize)));
+            table.with(Modify::new((r, c)).with(Span::row(span as isize)));
 
             i += 1;
         }
diff --git a/tabled/tests/settings/span_test.rs b/tabled/tests/settings/span_test.rs
index 432751d..f2acb94 100644
--- a/tabled/tests/settings/span_test.rs
+++ b/tabled/tests/settings/span_test.rs
@@ -1222,11 +1222,11 @@ test_table!(
 
         Builder::from_iter(data).build().with(Style::ascii()).with(rspans).with(cspans).to_string()
     },
-    "+------+-----++++++++++++++++++++++++++++------------+------------+------------+------------+------------+-----------+-----------+------------+------------+-----------+"
-    "| 2542785870 | 2879895075               | 513091574                                                                                                                    |"
-    "+            +                          +------------+------------+------------+------------+------------+-----------+-----------+------------+------------+-----------+"
-    "|            |                          | 1611436878 | 1682224972 | 3249055253 | 1562255501 | 1370527728 | 240481955 | 334260406 | 2247343342 | 3000635978 | 395723768 |"
-    "+------+-----++++++++++++++++++++++++++++------------+------------+------------+------------+------------+-----------+-----------+------------+------------+-----------+"
+    "+------+-----++++++++++++++++++++++++++++--+--+--+--+--+--+--+--+--+--+"
+    "| 2542785870 | 2879895075               | 513091574                   |"
+    "+            +                          +--+--+--+--+--+--+--+--+--+--+"
+    "|            |                          |  |  |  |  |  |  |  |  |  |  |"
+    "+------+-----++++++++++++++++++++++++++++--+--+--+--+--+--+--+--+--+--+"
 );
 
 test_table!(
@@ -1255,6 +1255,155 @@ test_table!(
     "└───────────────┴───────────────┴───────────────┴───────────────┴───────────────┘"
 );
 
+test_table!(
+    span_col_example_0,
+    {
+        let mut table = Table::new([
+            (1, 2, 3),
+            (4, 5, 6),
+            (7, 8, 9),
+            (10, 11, 12),
+            (13, 14, 15),
+            (16, 17, 18),
+            (19, 20, 21),
+        ]);
+
+        table.with(Style::modern_rounded());
+        table.modify((0, 1), Span::column(2));
+        table.modify((1, 0), Span::column(isize::MAX));
+        table.modify((2, 1), Span::column(-1));
+        table.modify((3, 2), Span::column(isize::MIN));
+        table.modify((4, 0), Span::column(0));
+        table.modify((5, 1), Span::column(0));
+        table.modify((6, 2), Span::column(0));
+        table.modify((4, 0), Span::column(1));
+        table.with(BorderSpanCorrection);
+        table.with(Alignment::center());
+
+        table.to_string()
+    },
+    "╭─────┬─────────╮"
+    "│ i32 │   i32   │"
+    "├─────┴─────────┤"
+    "│       1       │"
+    "├──────────┬────┤"
+    "│    5     │ 6  │"
+    "├──────────┴────┤"
+    "│       9       │"
+    "├─────┬────┬────┤"
+    "│ 10  │    │    │"
+    "├─────┴────┴────┤"
+    "│      14       │"
+    "├───────────────┤"
+    "│      18       │"
+    "├─────┬────┬────┤"
+    "│ 19  │ 20 │ 21 │"
+    "╰─────┴────┴────╯"
+);
+
+test_table!(
+    span_col_negative_0,
+    Matrix::new(3, 3)
+        .with(Modify::new((1, 2)).with(Span::column(-1)))
+        .with(Modify::new((2, 3)).with(Span::column(-2)))
+        .with(Modify::new((3, 3)).with(Span::column(-100))),
+    "+---+----------+----------+----------+"
+    "| N | column 0 | column 1 | column 2 |"
+    "+---+----------+----------+----------+"
+    "| 0 |         0-1         |   0-2    |"
+    "+---+----------+----------+----------+"
+    "| 1 |              1-2               |"
+    "+---+----------+----------+----------+"
+    "|                2-2                 |"
+    "+---+----------+----------+----------+"
+);
+
+test_table!(
+    span_col_zero_0,
+    Matrix::new(3, 3)
+        .with(Modify::new((1, 2)).with(Span::column(0)))
+        .with(Modify::new((2, 3)).with(Span::column(0)))
+        .with(Modify::new((3, 3)).with(Span::column(-0))),
+    "+---+----------+----------+----------+"
+    "| N | column 0 | column 1 | column 2 |"
+    "+---+----------+----------+----------+"
+    "|                0-1                 |"
+    "+---+----------+----------+----------+"
+    "|                1-2                 |"
+    "+---+----------+----------+----------+"
+    "|                2-2                 |"
+    "+---+----------+----------+----------+"
+);
+
+test_table!(
+    span_row_example_0,
+    {
+        let mut table = Table::new([
+            (1, 2, 3, 5, (6, 7, 8)),
+            (9, 10, 11, 12, (13, 14, 15)),
+            (16, 17, 18, 19, (20, 21, 22)),
+            (23, 24, 25, 26, (27, 28, 29)),
+        ]);
+        table.modify((0, 0), Span::row(2));
+        table.modify((0, 1), Span::row(isize::MAX));
+        table.modify((1, 2), Span::row(-1));
+        table.modify((2, 3), Span::row(isize::MIN));
+        table.modify((0, 4), Span::row(0));
+        table.modify((1, 5), Span::row(0));
+        table.modify((2, 6), Span::row(0));
+        table.modify((0, 4), Span::row(1));
+        table.with(Style::modern_rounded());
+        table.with(BorderSpanCorrection);
+        table.with(Alignment::center_vertical());
+        table.to_string()
+    },
+    "╭─────┬─────┬────┬────┬─────┬───┬────╮"
+    "│     │     │    │    │ i32 │   │    │"
+    "│ i32 │     │ 3  │    ├─────┤   │    │"
+    "│     │     │    │ 12 │     │   │    │"
+    "├─────┤     ├────┤    ├─────┤   │    │"
+    "│ 9   │ i32 │ 11 │    │     │ 7 │ 15 │"
+    "├─────┤     ├────┼────┼─────┤   │    │"
+    "│ 16  │     │ 18 │ 19 │     │   │    │"
+    "├─────┤     ├────┼────┼─────┤   │    │"
+    "│ 23  │     │ 25 │ 26 │     │   │    │"
+    "╰─────┴─────┴────┴────┴─────┴───┴────╯"
+);
+
+test_table!(
+    span_row_negative_0,
+    Matrix::new(3, 3)
+        .with(Modify::new((2, 1)).with(Span::row(-1)))
+        .with(Modify::new((3, 2)).with(Span::row(-2)))
+        .with(Modify::new((3, 3)).with(Span::row(-100))),
+    "+---+----------+----------+-----+"
+    "| N | column 0 | column 1 | 2-2 |"
+    "+---+----------+----------+     +"
+    "| 0 |   1-0    |   2-1    |     |"
+    "+---+          +          +     +"
+    "| 1 |          |          |     |"
+    "+---+----------+          +     +"
+    "| 2 |   2-0    |          |     |"
+    "+---+----------+----------+-----+"
+);
+
+test_table!(
+    span_row_zero_0,
+    Matrix::new(3, 3)
+        .with(Modify::new((2, 1)).with(Span::row(0)))
+        .with(Modify::new((3, 2)).with(Span::row(0)))
+        .with(Modify::new((3, 3)).with(Span::row(-0))),
+    "+---+-----+-----+-----+"
+    "| N | 1-0 | 2-1 | 2-2 |"
+    "+---+     +     +     +"
+    "| 0 |     |     |     |"
+    "+---+     +     +     +"
+    "| 1 |     |     |     |"
+    "+---+     +     +     +"
+    "| 2 |     |     |     |"
+    "+---+-----+-----+-----+"
+);
+
 fn create_span_list(count_rows: usize, count_cols: usize) -> impl Iterator<Item = Position> {
     (0..count_rows).flat_map(move |r| (0..count_cols).map(move |c| (r, c).into()))
 }