From 97360e173b87ccaaf6a24bf1b900ad323b047b27 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 30 Mar 2025 11:05:07 +0000 Subject: [PATCH 01/24] chore(deps): update codecov/codecov-action action to v5 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index df08809..e52a04a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -138,7 +138,7 @@ jobs: grcov . --binary-path="${COVERAGE_REPORT_DIR}" --output-type lcov --output-path "${COVERAGE_REPORT_FILE}" --branch --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()" echo "name=report::${COVERAGE_REPORT_FILE}" >> $GITHUB_OUTPUT - name: Upload coverage results (to Codecov.io) - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_TOKEN }} file: ${{ steps.coverage.outputs.report }} From ab7e5d4ecae33a341b5ed0535820761e141af8b1 Mon Sep 17 00:00:00 2001 From: Leonid Emar-Kar Date: Sun, 30 Mar 2025 11:36:26 +0100 Subject: [PATCH 02/24] add new Tabs filling and rework fmt process --- src/lib.rs | 64 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 56 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 9253b81..59b2fe3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,12 @@ use ansi_width::ansi_width; use std::fmt; +/// Number of spaces in one \t. +pub const SPACES_IN_TAB: usize = 8; + +// Default size for tab separator. +const DEFAULT_SEPARATOR_SIZE: usize = 2; + /// Direction cells should be written in: either across or downwards. #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub enum Direction { @@ -37,6 +43,9 @@ pub enum Filling { /// /// `"|"` is a common choice. Text(String), + + /// Size of spaces to replace with \t + Tabs(usize), } impl Filling { @@ -44,6 +53,9 @@ impl Filling { match self { Filling::Spaces(w) => *w, Filling::Text(t) => ansi_width(t), + // Need to return default separator size to + // calculate width of the grid correctly. + Filling::Tabs(_) => DEFAULT_SEPARATOR_SIZE, } } } @@ -257,9 +269,15 @@ impl> Grid { impl> fmt::Display for Grid { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - let separator = match &self.options.filling { - Filling::Spaces(n) => " ".repeat(*n), - Filling::Text(s) => s.clone(), + // If cells are empty then, nothing to print, skip. + if self.cells.is_empty() { + return Ok(()); + } + + let (tab_size, separator) = match &self.options.filling { + Filling::Spaces(n) => (0, " ".repeat(*n)), + Filling::Text(s) => (0, s.clone()), + Filling::Tabs(n) => (*n, " ".repeat(DEFAULT_SEPARATOR_SIZE)), }; // Initialize a buffer of spaces. The idea here is that any cell @@ -270,9 +288,14 @@ impl> fmt::Display for Grid { // We overestimate how many spaces we need, but this is not // part of the loop and it's therefore not super important to // get exactly right. - let padding = " ".repeat(self.widest_cell_width); + let mut padding = " ".repeat(self.widest_cell_width); + // Push separator as the last element to be able to add padding and + // separator in one write_str call. + padding.push_str(&separator); for y in 0..self.dimensions.num_lines { + // Current position on the line. + let mut cursor: usize = 0; for x in 0..self.dimensions.widths.len() { let num = match self.options.direction { Direction::LeftToRight => y * self.dimensions.widths.len() + x, @@ -287,7 +310,6 @@ impl> fmt::Display for Grid { let contents = &self.cells[num]; let width = self.widths[num]; let last_in_row = x == self.dimensions.widths.len() - 1; - let col_width = self.dimensions.widths[x]; let padding_size = col_width - width; @@ -306,10 +328,36 @@ impl> fmt::Display for Grid { // another optimization. f.write_str(contents.as_ref())?; if !last_in_row { - if padding_size > 0 { - f.write_str(&padding[0..padding_size])?; + // Special case if tab size was not set. Fill with spaces and separator. + if tab_size == 0 { + f.write_str(&padding[padding.len() - padding_size - separator.len()..])?; + } else { + // Move cursor to the end of the current contents. + cursor += width; + // Calculate position of the next column start. + let to: usize = cursor + padding_size + separator.len(); + // The size of \t can be inconsistent in terminal. + // Tab stops are relative to the cursor position e.g., + // * cursor = 0, \t moves to column 8; + // * cursor = 5, \t moves to column 8 (3 spaces); + // * cursor = 9, \t moves to column 16 (7 spaces). + // Instead of adding tabs here, calculate the required + // number to call write_str once. + let mut tabs: usize = 0; + while cursor + 1 < to && (cursor / tab_size) != (to / tab_size) { + tabs += 1; + cursor += tab_size - (cursor % tab_size); + } + + if tabs != 0 { + f.write_str(&"\t".repeat(tabs))?; + } + + if cursor != to { + f.write_str(&padding[..(to - cursor)])?; + cursor = to; + } } - f.write_str(&separator)?; } } f.write_str("\n")?; From 30594c97bc7314fe95330c810dda1000caec862b Mon Sep 17 00:00:00 2001 From: Leonid Emar-Kar Date: Sun, 30 Mar 2025 11:36:41 +0100 Subject: [PATCH 03/24] add filling_with_tabs test --- tests/test.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/test.rs b/tests/test.rs index b761d5d..9e14286 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -238,6 +238,25 @@ fn eza_many_folders() { assert_eq!(grid.row_count(), 20); } +#[test] +fn filling_with_tabs() { + let grid = Grid::new( + vec![ + "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", + "eleven", "twelve", + ], + GridOptions { + filling: Filling::Tabs(2), + direction: Direction::LeftToRight, + width: 24, + }, + ); + + let bits = "one\t\t two\t\t three\nfour\t five\t\t six\nseven\t eight\t nine\nten\t\t eleven\t twelve\n"; + assert_eq!(grid.to_string(), bits); + assert_eq!(grid.row_count(), 4); +} + // These test are based on the tests in uutils ls, to ensure we won't break // it while editing this library. mod uutils_ls { From a836447aa9da376ac0965ebb8261eea2a91a8fdd Mon Sep 17 00:00:00 2001 From: Leonid Emar-Kar Date: Sun, 30 Mar 2025 11:36:47 +0100 Subject: [PATCH 04/24] add tabs example --- examples/tabs.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 examples/tabs.rs diff --git a/examples/tabs.rs b/examples/tabs.rs new file mode 100644 index 0000000..f50d135 --- /dev/null +++ b/examples/tabs.rs @@ -0,0 +1,29 @@ +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +use term_grid::{Direction, Filling, Grid, GridOptions}; + +// This produces: +// +// 1···128↹··16384↹···2097152····268435456↹···34359738368├┤··4398046511104␊ +// 2···256↹··32768↹···4194304····536870912↹···68719476736├┤··8796093022208␊ +// 4···512↹··65536↹···8388608····1073741824···137438953472↹··17592186044416␊ +// 8···1024··131072···16777216···2147483648···274877906944↹··35184372088832␊ +// 16··2048··262144···33554432···4294967296···549755813888↹··70368744177664␊ +// 32··4096··524288···67108864···8589934592···1099511627776··140737488355328␊ +// 64··8192··1048576··134217728··17179869184··2199023255552··␊ + +fn main() { + let cells: Vec<_> = (0..48).map(|i| 2_isize.pow(i).to_string()).collect(); + + let grid = Grid::new( + cells, + GridOptions { + direction: Direction::TopToBottom, + filling: Filling::Tabs(8), + width: 80, + }, + ); + + println!("{}", grid); +} From 5004ad8b6bb479db22a542ad07f3f85634365503 Mon Sep 17 00:00:00 2001 From: Leonid Emar-Kar Date: Sun, 30 Mar 2025 11:36:54 +0100 Subject: [PATCH 05/24] update readme --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e0c0124..4deda06 100644 --- a/README.md +++ b/README.md @@ -36,8 +36,10 @@ and a set of options. There are three options that must be specified in the [`GridOptions`] value that dictate how the grid is formatted: -- [`filling`][filling]: what to put in between two columns — either a number of - spaces, or a text string; +- [`filling`][filling]: how to feel empty space between columns: + - [`Filling::Spaces`][Spaces] number of spaces between columns; + - [`Filling::Text`][Text] text string separator between columns; + - [`Filling::Tabs`][Tabs] special option which adds default 2 spaces as separator, but converts every number of spaces into `\t` character. - [`direction`][direction]: specifies whether the cells should go along rows, or columns: - [`Direction::LeftToRight`][LeftToRight] starts them in the top left and @@ -94,6 +96,9 @@ nine ten eleven twelve [width]: https://docs.rs/uutils_term_grid/latest/term_grid/struct.GridOptions.html#structfield.width [LeftToRight]: https://docs.rs/uutils_term_grid/latest/term_grid/enum.Direction.html#variant.LeftToRight [TopToBottom]: https://docs.rs/uutils_term_grid/latest/term_grid/enum.Direction.html#variant.TopToBottom +[Spaces]: https://docs.rs/uutils_term_grid/latest/term_grid/enum.Filling.html#variant.Spaces +[Text]: https://docs.rs/uutils_term_grid/latest/term_grid/enum.Filling.html#variant.Text +[Tabs]:https://docs.rs/uutils_term_grid/latest/term_grid/enum.Filling.html#variant.Tabs ## Width of grid cells From 01ba3493ea0f97ffa3620359103092de56a1ee13 Mon Sep 17 00:00:00 2001 From: Leonid Emar-Kar Date: Sun, 30 Mar 2025 12:05:04 +0100 Subject: [PATCH 06/24] fix misspelling --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4deda06..9308b0e 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ and a set of options. There are three options that must be specified in the [`GridOptions`] value that dictate how the grid is formatted: -- [`filling`][filling]: how to feel empty space between columns: +- [`filling`][filling]: how to fill empty space between columns: - [`Filling::Spaces`][Spaces] number of spaces between columns; - [`Filling::Text`][Text] text string separator between columns; - [`Filling::Tabs`][Tabs] special option which adds default 2 spaces as separator, but converts every number of spaces into `\t` character. From d4989f39dd3f6136126b76a4c0de485bc11100d8 Mon Sep 17 00:00:00 2001 From: Leonid Emar-Kar Date: Sun, 30 Mar 2025 12:11:33 +0100 Subject: [PATCH 07/24] fix fmt in tests --- tests/test.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test.rs b/tests/test.rs index 9e14286..961d5bc 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -251,7 +251,7 @@ fn filling_with_tabs() { width: 24, }, ); - + let bits = "one\t\t two\t\t three\nfour\t five\t\t six\nseven\t eight\t nine\nten\t\t eleven\t twelve\n"; assert_eq!(grid.to_string(), bits); assert_eq!(grid.row_count(), 4); From 6962463df81c6b4d05300a9274cd4f3a8237c503 Mon Sep 17 00:00:00 2001 From: Leonid Emar-Kar Date: Sun, 30 Mar 2025 12:58:00 +0100 Subject: [PATCH 08/24] optimize padding size calc --- src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 59b2fe3..f1018af 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -311,7 +311,7 @@ impl> fmt::Display for Grid { let width = self.widths[num]; let last_in_row = x == self.dimensions.widths.len() - 1; let col_width = self.dimensions.widths[x]; - let padding_size = col_width - width; + let padding_size = col_width - width + separator.len(); // The final column doesn’t need to have trailing spaces, // as long as it’s left-aligned. @@ -330,12 +330,12 @@ impl> fmt::Display for Grid { if !last_in_row { // Special case if tab size was not set. Fill with spaces and separator. if tab_size == 0 { - f.write_str(&padding[padding.len() - padding_size - separator.len()..])?; + f.write_str(&padding[padding.len() - padding_size..])?; } else { // Move cursor to the end of the current contents. cursor += width; // Calculate position of the next column start. - let to: usize = cursor + padding_size + separator.len(); + let to: usize = cursor + padding_size; // The size of \t can be inconsistent in terminal. // Tab stops are relative to the cursor position e.g., // * cursor = 0, \t moves to column 8; From cfec022e9880e75fea177f0353548ebfcb629611 Mon Sep 17 00:00:00 2001 From: Leonid Emar-Kar Date: Sun, 30 Mar 2025 13:34:39 +0100 Subject: [PATCH 09/24] remove separator duplication to reduce memory usage --- src/lib.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f1018af..e50fca4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -288,10 +288,7 @@ impl> fmt::Display for Grid { // We overestimate how many spaces we need, but this is not // part of the loop and it's therefore not super important to // get exactly right. - let mut padding = " ".repeat(self.widest_cell_width); - // Push separator as the last element to be able to add padding and - // separator in one write_str call. - padding.push_str(&separator); + let padding = " ".repeat(self.widest_cell_width); for y in 0..self.dimensions.num_lines { // Current position on the line. @@ -311,7 +308,7 @@ impl> fmt::Display for Grid { let width = self.widths[num]; let last_in_row = x == self.dimensions.widths.len() - 1; let col_width = self.dimensions.widths[x]; - let padding_size = col_width - width + separator.len(); + let padding_size = col_width - width; // The final column doesn’t need to have trailing spaces, // as long as it’s left-aligned. @@ -331,11 +328,12 @@ impl> fmt::Display for Grid { // Special case if tab size was not set. Fill with spaces and separator. if tab_size == 0 { f.write_str(&padding[padding.len() - padding_size..])?; + f.write_str(&separator)?; } else { // Move cursor to the end of the current contents. cursor += width; // Calculate position of the next column start. - let to: usize = cursor + padding_size; + let to: usize = cursor + padding_size + DEFAULT_SEPARATOR_SIZE; // The size of \t can be inconsistent in terminal. // Tab stops are relative to the cursor position e.g., // * cursor = 0, \t moves to column 8; From 39b377ec8992bbbddaef169c2b68999c645ac1ef Mon Sep 17 00:00:00 2001 From: Leonid Emar-Kar Date: Sun, 30 Mar 2025 15:14:01 +0100 Subject: [PATCH 10/24] make DEFAULT_SEPARATOR_SIZE pub --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e50fca4..034afd7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,8 +16,8 @@ use std::fmt; /// Number of spaces in one \t. pub const SPACES_IN_TAB: usize = 8; -// Default size for tab separator. -const DEFAULT_SEPARATOR_SIZE: usize = 2; +/// Default size for separator in spaces. +pub const DEFAULT_SEPARATOR_SIZE: usize = 2; /// Direction cells should be written in: either across or downwards. #[derive(PartialEq, Eq, Debug, Copy, Clone)] From 23bf389ae361df1c43316fc38067735d5937c0ad Mon Sep 17 00:00:00 2001 From: Leonid Emar-Kar Date: Sun, 30 Mar 2025 15:14:12 +0100 Subject: [PATCH 11/24] fix padding calculation --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 034afd7..169be3c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -327,7 +327,7 @@ impl> fmt::Display for Grid { if !last_in_row { // Special case if tab size was not set. Fill with spaces and separator. if tab_size == 0 { - f.write_str(&padding[padding.len() - padding_size..])?; + f.write_str(&padding[..padding_size])?; f.write_str(&separator)?; } else { // Move cursor to the end of the current contents. From ef8ffc4eb16e55b16dbd68c02d3659d3e6cde544 Mon Sep 17 00:00:00 2001 From: Leonid Emar-Kar Date: Thu, 3 Apr 2025 14:30:28 +0100 Subject: [PATCH 12/24] fix required padding is bigger than widest cell --- src/lib.rs | 2 +- tests/test.rs | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 169be3c..af89a41 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -288,7 +288,7 @@ impl> fmt::Display for Grid { // We overestimate how many spaces we need, but this is not // part of the loop and it's therefore not super important to // get exactly right. - let padding = " ".repeat(self.widest_cell_width); + let padding = " ".repeat(self.widest_cell_width + self.options.filling.width()); for y in 0..self.dimensions.num_lines { // Current position on the line. diff --git a/tests/test.rs b/tests/test.rs index 961d5bc..0e11c21 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -3,7 +3,7 @@ // spell-checker:ignore underflowed -use term_grid::{Direction, Filling, Grid, GridOptions}; +use term_grid::{Direction, Filling, Grid, GridOptions, SPACES_IN_TAB}; #[test] fn no_items() { @@ -246,8 +246,8 @@ fn filling_with_tabs() { "eleven", "twelve", ], GridOptions { - filling: Filling::Tabs(2), direction: Direction::LeftToRight, + filling: Filling::Tabs(2), width: 24, }, ); @@ -257,6 +257,21 @@ fn filling_with_tabs() { assert_eq!(grid.row_count(), 4); } +#[test] +fn padding_bigger_than_widest() { + let grid = Grid::new( + vec!["1", "2", "3"], + GridOptions { + direction: Direction::LeftToRight, + filling: Filling::Tabs(SPACES_IN_TAB), + width: 20, + }, + ); + + let bits = "1 2 3\n"; + assert_eq!(grid.to_string(), bits); +} + // These test are based on the tests in uutils ls, to ensure we won't break // it while editing this library. mod uutils_ls { From e705bdf09ca18bad6d569bac75b8f8eb883283f5 Mon Sep 17 00:00:00 2001 From: Leonid Emar-Kar Date: Fri, 4 Apr 2025 18:34:40 +0100 Subject: [PATCH 13/24] add next entry check before printing the separator --- examples/basic.rs | 2 +- examples/tabs.rs | 2 +- src/lib.rs | 20 ++++++++++++++------ tests/test.rs | 26 ++++++++++++++++++++++++++ 4 files changed, 42 insertions(+), 8 deletions(-) diff --git a/examples/basic.rs b/examples/basic.rs index 876d131..eeb98a5 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -11,7 +11,7 @@ use term_grid::{Direction, Filling, Grid, GridOptions}; // 8 | 1024 | 131072 | 16777216 | 2147483648 | 274877906944 | 35184372088832 // 16 | 2048 | 262144 | 33554432 | 4294967296 | 549755813888 | 70368744177664 // 32 | 4096 | 524288 | 67108864 | 8589934592 | 1099511627776 | 140737488355328 -// 64 | 8192 | 1048576 | 134217728 | 17179869184 | 2199023255552 | +// 64 | 8192 | 1048576 | 134217728 | 17179869184 | 2199023255552 fn main() { let cells: Vec<_> = (0..48).map(|i| 2_isize.pow(i).to_string()).collect(); diff --git a/examples/tabs.rs b/examples/tabs.rs index f50d135..86a1084 100644 --- a/examples/tabs.rs +++ b/examples/tabs.rs @@ -11,7 +11,7 @@ use term_grid::{Direction, Filling, Grid, GridOptions}; // 8···1024··131072···16777216···2147483648···274877906944↹··35184372088832␊ // 16··2048··262144···33554432···4294967296···549755813888↹··70368744177664␊ // 32··4096··524288···67108864···8589934592···1099511627776··140737488355328␊ -// 64··8192··1048576··134217728··17179869184··2199023255552··␊ +// 64··8192··1048576··134217728··17179869184··2199023255552␊ fn main() { let cells: Vec<_> = (0..48).map(|i| 2_isize.pow(i).to_string()).collect(); diff --git a/src/lib.rs b/src/lib.rs index af89a41..8595305 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -294,19 +294,25 @@ impl> fmt::Display for Grid { // Current position on the line. let mut cursor: usize = 0; for x in 0..self.dimensions.widths.len() { - let num = match self.options.direction { - Direction::LeftToRight => y * self.dimensions.widths.len() + x, - Direction::TopToBottom => y + self.dimensions.num_lines * x, + let (num, next) = match self.options.direction { + Direction::LeftToRight => (y * self.dimensions.widths.len() + x, 1), + Direction::TopToBottom => { + (y + self.dimensions.num_lines * x, self.dimensions.num_lines) + } }; // Abandon a line mid-way through if that’s where the cells end if num >= self.cells.len() { - continue; + break; } + // Last in row checks only the predifined grid width. + // It does not check if there will be more entries. + // For this purpose we define next value as well. + // This prevents printing separator after the actual last value in a row. + let last_in_row = x == self.dimensions.widths.len() - 1; let contents = &self.cells[num]; let width = self.widths[num]; - let last_in_row = x == self.dimensions.widths.len() - 1; let col_width = self.dimensions.widths[x]; let padding_size = col_width - width; @@ -324,7 +330,9 @@ impl> fmt::Display for Grid { // We also only call `write_str` when we actually need padding as // another optimization. f.write_str(contents.as_ref())?; - if !last_in_row { + // In case if this entry was the last on the current line, + // there is no need to check for separator or padding. + if !last_in_row && num + next < self.cells.len() { // Special case if tab size was not set. Fill with spaces and separator. if tab_size == 0 { f.write_str(&padding[..padding_size])?; diff --git a/tests/test.rs b/tests/test.rs index 0e11c21..ae69495 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -272,6 +272,32 @@ fn padding_bigger_than_widest() { assert_eq!(grid.to_string(), bits); } +#[test] +fn odd_number_of_entries() { + let cells = vec!["one", "two", "three", "four", "five"]; + let grid = Grid::new( + cells.clone(), + GridOptions { + direction: Direction::LeftToRight, + filling: Filling::Spaces(2), + width: 15, + }, + ); + + assert_eq!(grid.to_string(), "one two\nthree four\nfive\n"); + + let grid = Grid::new( + cells.clone(), + GridOptions { + direction: Direction::TopToBottom, + filling: Filling::Spaces(2), + width: 15, + }, + ); + + assert_eq!(grid.to_string(), "one four\ntwo five\nthree\n"); +} + // These test are based on the tests in uutils ls, to ensure we won't break // it while editing this library. mod uutils_ls { From 89f0d98ab5585fd7aa58478d3f86f70fd185a5d1 Mon Sep 17 00:00:00 2001 From: Leonid Emar-Kar Date: Sun, 6 Apr 2025 11:41:53 +0100 Subject: [PATCH 14/24] update docs for tabs --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 8595305..d86b0b2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,7 +44,7 @@ pub enum Filling { /// `"|"` is a common choice. Text(String), - /// Size of spaces to replace with \t + /// Size of \t in spaces Tabs(usize), } From c98708454e01d4fe3d14e05b3b72fe5f9e8519de Mon Sep 17 00:00:00 2001 From: Leonid Emar-Kar Date: Sun, 6 Apr 2025 14:09:55 +0100 Subject: [PATCH 15/24] update comments --- src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d86b0b2..c2a2066 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -306,7 +306,7 @@ impl> fmt::Display for Grid { break; } - // Last in row checks only the predifined grid width. + // Last in row checks only the predefined grid width. // It does not check if there will be more entries. // For this purpose we define next value as well. // This prevents printing separator after the actual last value in a row. @@ -330,8 +330,8 @@ impl> fmt::Display for Grid { // We also only call `write_str` when we actually need padding as // another optimization. f.write_str(contents.as_ref())?; - // In case if this entry was the last on the current line, - // there is no need to check for separator or padding. + // In case this entry was the last on the current line, + // there is no need to print the separator and padding. if !last_in_row && num + next < self.cells.len() { // Special case if tab size was not set. Fill with spaces and separator. if tab_size == 0 { From 7b88c50a52cbde654bcced6babb2f51eb9587e1f Mon Sep 17 00:00:00 2001 From: Leonid Emar-Kar Date: Sun, 6 Apr 2025 17:21:29 +0100 Subject: [PATCH 16/24] add diff size separator with Tabs --- examples/tabs.rs | 7 +++++-- src/lib.rs | 13 +++++++++---- tests/test.rs | 33 ++++++++++++++++++++++++++++++--- 3 files changed, 44 insertions(+), 9 deletions(-) diff --git a/examples/tabs.rs b/examples/tabs.rs index 86a1084..b32ef0f 100644 --- a/examples/tabs.rs +++ b/examples/tabs.rs @@ -1,7 +1,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -use term_grid::{Direction, Filling, Grid, GridOptions}; +use term_grid::{Direction, Filling, Grid, GridOptions, DEFAULT_SEPARATOR_SIZE}; // This produces: // @@ -20,7 +20,10 @@ fn main() { cells, GridOptions { direction: Direction::TopToBottom, - filling: Filling::Tabs(8), + filling: Filling::Tabs { + spaces: DEFAULT_SEPARATOR_SIZE, + tab_size: 8, + }, width: 80, }, ); diff --git a/src/lib.rs b/src/lib.rs index c2a2066..f05fe22 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,8 +44,13 @@ pub enum Filling { /// `"|"` is a common choice. Text(String), - /// Size of \t in spaces - Tabs(usize), + /// Fill spaces with \t + Tabs { + /// A number of spaces + spaces: usize, + /// Size of \t in spaces + tab_size: usize, + }, } impl Filling { @@ -55,7 +60,7 @@ impl Filling { Filling::Text(t) => ansi_width(t), // Need to return default separator size to // calculate width of the grid correctly. - Filling::Tabs(_) => DEFAULT_SEPARATOR_SIZE, + Filling::Tabs { spaces, .. } => *spaces, } } } @@ -277,7 +282,7 @@ impl> fmt::Display for Grid { let (tab_size, separator) = match &self.options.filling { Filling::Spaces(n) => (0, " ".repeat(*n)), Filling::Text(s) => (0, s.clone()), - Filling::Tabs(n) => (*n, " ".repeat(DEFAULT_SEPARATOR_SIZE)), + Filling::Tabs { spaces, tab_size } => (*tab_size, " ".repeat(*spaces)), }; // Initialize a buffer of spaces. The idea here is that any cell diff --git a/tests/test.rs b/tests/test.rs index ae69495..b1a0db4 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -3,7 +3,7 @@ // spell-checker:ignore underflowed -use term_grid::{Direction, Filling, Grid, GridOptions, SPACES_IN_TAB}; +use term_grid::{Direction, Filling, Grid, GridOptions, DEFAULT_SEPARATOR_SIZE, SPACES_IN_TAB}; #[test] fn no_items() { @@ -247,7 +247,10 @@ fn filling_with_tabs() { ], GridOptions { direction: Direction::LeftToRight, - filling: Filling::Tabs(2), + filling: Filling::Tabs { + spaces: DEFAULT_SEPARATOR_SIZE, + tab_size: 2, + }, width: 24, }, ); @@ -263,7 +266,10 @@ fn padding_bigger_than_widest() { vec!["1", "2", "3"], GridOptions { direction: Direction::LeftToRight, - filling: Filling::Tabs(SPACES_IN_TAB), + filling: Filling::Tabs { + spaces: DEFAULT_SEPARATOR_SIZE, + tab_size: SPACES_IN_TAB, + }, width: 20, }, ); @@ -298,6 +304,27 @@ fn odd_number_of_entries() { assert_eq!(grid.to_string(), "one four\ntwo five\nthree\n"); } +#[test] +fn different_size_separator_with_tabs() { + let grid = Grid::new( + vec![ + "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", + "eleven", "twelve", + ], + GridOptions { + direction: Direction::LeftToRight, + filling: Filling::Tabs { + spaces: 4, + tab_size: 2, + }, + width: 40, + }, + ); + + let bits = "one\t\t\ttwo\t\t three\t\t four\nfive\t\tsix\t\t seven\t\t eight\nnine\t\tten\t\t eleven\t\t twelve\n"; + assert_eq!(grid.to_string(), bits); +} + // These test are based on the tests in uutils ls, to ensure we won't break // it while editing this library. mod uutils_ls { From b3382b58c43b8e6f925bb77292b468ddecac0eef Mon Sep 17 00:00:00 2001 From: Leonid Emar-Kar Date: Sun, 6 Apr 2025 17:24:26 +0100 Subject: [PATCH 17/24] simplify the tabs number calculation --- src/lib.rs | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f05fe22..85c156b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -345,29 +345,26 @@ impl> fmt::Display for Grid { } else { // Move cursor to the end of the current contents. cursor += width; - // Calculate position of the next column start. - let to: usize = cursor + padding_size + DEFAULT_SEPARATOR_SIZE; + let total_spaces = padding_size + self.options.filling.width(); // The size of \t can be inconsistent in terminal. // Tab stops are relative to the cursor position e.g., // * cursor = 0, \t moves to column 8; // * cursor = 5, \t moves to column 8 (3 spaces); // * cursor = 9, \t moves to column 16 (7 spaces). - // Instead of adding tabs here, calculate the required - // number to call write_str once. - let mut tabs: usize = 0; - while cursor + 1 < to && (cursor / tab_size) != (to / tab_size) { - tabs += 1; - cursor += tab_size - (cursor % tab_size); - } - - if tabs != 0 { + // Calculate first \t size. + let first_tab = tab_size - (cursor % tab_size); + + if first_tab > total_spaces { + f.write_str(&padding[..total_spaces])?; + } else { + let rest_spaces = total_spaces - first_tab; + let tabs = 1 + (rest_spaces / tab_size); + let spaces = rest_spaces % tab_size; f.write_str(&"\t".repeat(tabs))?; + f.write_str(&padding[..spaces])?; } - if cursor != to { - f.write_str(&padding[..(to - cursor)])?; - cursor = to; - } + cursor += total_spaces; } } } From 1e61c40ab87e7dd2b333a749e2b7a65ec524ef13 Mon Sep 17 00:00:00 2001 From: Leonid Emar-Kar Date: Sun, 6 Apr 2025 17:28:41 +0100 Subject: [PATCH 18/24] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9308b0e..0e05745 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ dictate how the grid is formatted: - [`filling`][filling]: how to fill empty space between columns: - [`Filling::Spaces`][Spaces] number of spaces between columns; - [`Filling::Text`][Text] text string separator between columns; - - [`Filling::Tabs`][Tabs] special option which adds default 2 spaces as separator, but converts every number of spaces into `\t` character. + - [`Filling::Tabs`][Tabs] special option which allows to set number of spaces between columns and sets the size of `\t` character. - [`direction`][direction]: specifies whether the cells should go along rows, or columns: - [`Direction::LeftToRight`][LeftToRight] starts them in the top left and From a1379793738481a6a078520cfbd1f30593cd02ce Mon Sep 17 00:00:00 2001 From: Leonid Emar-Kar Date: Sun, 6 Apr 2025 17:29:49 +0100 Subject: [PATCH 19/24] remove comments --- src/lib.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 85c156b..0375d42 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -58,8 +58,6 @@ impl Filling { match self { Filling::Spaces(w) => *w, Filling::Text(t) => ansi_width(t), - // Need to return default separator size to - // calculate width of the grid correctly. Filling::Tabs { spaces, .. } => *spaces, } } From 95a9aa82665d57f346595ddd71e96ae575330edf Mon Sep 17 00:00:00 2001 From: Leonid Emar-Kar Date: Sun, 6 Apr 2025 17:34:44 +0100 Subject: [PATCH 20/24] change last in row check to break --- src/lib.rs | 59 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0375d42..3b07302 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -304,7 +304,7 @@ impl> fmt::Display for Grid { } }; - // Abandon a line mid-way through if that’s where the cells end + // Abandon a line mid-way through if that’s where the cells end. if num >= self.cells.len() { break; } @@ -333,37 +333,40 @@ impl> fmt::Display for Grid { // We also only call `write_str` when we actually need padding as // another optimization. f.write_str(contents.as_ref())?; + // In case this entry was the last on the current line, // there is no need to print the separator and padding. - if !last_in_row && num + next < self.cells.len() { - // Special case if tab size was not set. Fill with spaces and separator. - if tab_size == 0 { - f.write_str(&padding[..padding_size])?; - f.write_str(&separator)?; + if last_in_row || num + next >= self.cells.len() { + break; + } + + // Special case if tab size was not set. Fill with spaces and separator. + if tab_size == 0 { + f.write_str(&padding[..padding_size])?; + f.write_str(&separator)?; + } else { + // Move cursor to the end of the current contents. + cursor += width; + let total_spaces = padding_size + self.options.filling.width(); + // The size of \t can be inconsistent in terminal. + // Tab stops are relative to the cursor position e.g., + // * cursor = 0, \t moves to column 8; + // * cursor = 5, \t moves to column 8 (3 spaces); + // * cursor = 9, \t moves to column 16 (7 spaces). + // Calculate first \t size. + let first_tab = tab_size - (cursor % tab_size); + + if first_tab > total_spaces { + f.write_str(&padding[..total_spaces])?; } else { - // Move cursor to the end of the current contents. - cursor += width; - let total_spaces = padding_size + self.options.filling.width(); - // The size of \t can be inconsistent in terminal. - // Tab stops are relative to the cursor position e.g., - // * cursor = 0, \t moves to column 8; - // * cursor = 5, \t moves to column 8 (3 spaces); - // * cursor = 9, \t moves to column 16 (7 spaces). - // Calculate first \t size. - let first_tab = tab_size - (cursor % tab_size); - - if first_tab > total_spaces { - f.write_str(&padding[..total_spaces])?; - } else { - let rest_spaces = total_spaces - first_tab; - let tabs = 1 + (rest_spaces / tab_size); - let spaces = rest_spaces % tab_size; - f.write_str(&"\t".repeat(tabs))?; - f.write_str(&padding[..spaces])?; - } - - cursor += total_spaces; + let rest_spaces = total_spaces - first_tab; + let tabs = 1 + (rest_spaces / tab_size); + let spaces = rest_spaces % tab_size; + f.write_str(&"\t".repeat(tabs))?; + f.write_str(&padding[..spaces])?; } + + cursor += total_spaces; } } f.write_str("\n")?; From afd508acba31f6b7a6a3819129951c709004a47a Mon Sep 17 00:00:00 2001 From: Leonid Emar-Kar Date: Sun, 6 Apr 2025 17:48:29 +0100 Subject: [PATCH 21/24] fix some grammar in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0e05745..d3572fd 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ dictate how the grid is formatted: - [`filling`][filling]: how to fill empty space between columns: - [`Filling::Spaces`][Spaces] number of spaces between columns; - [`Filling::Text`][Text] text string separator between columns; - - [`Filling::Tabs`][Tabs] special option which allows to set number of spaces between columns and sets the size of `\t` character. + - [`Filling::Tabs`][Tabs] special option which allows to set number of spaces between columns and set the size of `\t` character. - [`direction`][direction]: specifies whether the cells should go along rows, or columns: - [`Direction::LeftToRight`][LeftToRight] starts them in the top left and From 16e2a0d40abdc0cee350206bc65fc89ed0d82820 Mon Sep 17 00:00:00 2001 From: Leonid Emar-Kar Date: Tue, 8 Apr 2025 21:22:00 +0100 Subject: [PATCH 22/24] update docs with quotes for \t --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 3b07302..ed7cf44 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,11 +44,11 @@ pub enum Filling { /// `"|"` is a common choice. Text(String), - /// Fill spaces with \t + /// Fill spaces with `\t` Tabs { /// A number of spaces spaces: usize, - /// Size of \t in spaces + /// Size of `\t` in spaces tab_size: usize, }, } From 988bcc2f72981ec6858d278d2006b38b9881b5cb Mon Sep 17 00:00:00 2001 From: Leonid Emar-Kar Date: Tue, 8 Apr 2025 21:24:11 +0100 Subject: [PATCH 23/24] rename postion vars and add comment --- src/lib.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ed7cf44..52945b4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -297,7 +297,9 @@ impl> fmt::Display for Grid { // Current position on the line. let mut cursor: usize = 0; for x in 0..self.dimensions.widths.len() { - let (num, next) = match self.options.direction { + // Calculate position of the current element of the grid + // in cells and widths vectors and the offset to the next value. + let (current, offset) = match self.options.direction { Direction::LeftToRight => (y * self.dimensions.widths.len() + x, 1), Direction::TopToBottom => { (y + self.dimensions.num_lines * x, self.dimensions.num_lines) @@ -305,7 +307,7 @@ impl> fmt::Display for Grid { }; // Abandon a line mid-way through if that’s where the cells end. - if num >= self.cells.len() { + if current >= self.cells.len() { break; } @@ -314,8 +316,8 @@ impl> fmt::Display for Grid { // For this purpose we define next value as well. // This prevents printing separator after the actual last value in a row. let last_in_row = x == self.dimensions.widths.len() - 1; - let contents = &self.cells[num]; - let width = self.widths[num]; + let contents = &self.cells[current]; + let width = self.widths[current]; let col_width = self.dimensions.widths[x]; let padding_size = col_width - width; @@ -336,7 +338,7 @@ impl> fmt::Display for Grid { // In case this entry was the last on the current line, // there is no need to print the separator and padding. - if last_in_row || num + next >= self.cells.len() { + if last_in_row || current + offset >= self.cells.len() { break; } From 12105f9ec946336954efede4090056fb3a987eaa Mon Sep 17 00:00:00 2001 From: Leonid Emar-Kar Date: Tue, 8 Apr 2025 21:24:32 +0100 Subject: [PATCH 24/24] rename neares tab var and update comment --- src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 52945b4..b6a0d68 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -355,13 +355,13 @@ impl> fmt::Display for Grid { // * cursor = 0, \t moves to column 8; // * cursor = 5, \t moves to column 8 (3 spaces); // * cursor = 9, \t moves to column 16 (7 spaces). - // Calculate first \t size. - let first_tab = tab_size - (cursor % tab_size); + // Calculate the nearest \t position in relation to cursor. + let closest_tab = tab_size - (cursor % tab_size); - if first_tab > total_spaces { + if closest_tab > total_spaces { f.write_str(&padding[..total_spaces])?; } else { - let rest_spaces = total_spaces - first_tab; + let rest_spaces = total_spaces - closest_tab; let tabs = 1 + (rest_spaces / tab_size); let spaces = rest_spaces % tab_size; f.write_str(&"\t".repeat(tabs))?;