nu-table/ 1 refactoring + a few optimizations + small fix (#15653)

- A few days back I've got this idea regarding recalculus of width.
Now it calculates step by step.
So 1 loop over all data was removed.
All though there's full recalculation in case of `header_on_border`
😞 (can be fixed..... but I decided to be short)

In perfect world it also shall be refactored ......

- Also have done small refactoring to switch build table from
`Vec<Vec<_>>>` to table itself. To hide internals (kind of still there's
things which I don't like).
It touched the `--expand` algorithm lightly you can see the tests
changes.

- And when doing that noticed one more opportunity, to remove HashMap
usage and directly use `tabled::ColoredConfig`. Which reduces copy
operations and allocations.

- And fixed a small issue where trailing column being using deleted
column styles.


![image](https://github.com/user-attachments/assets/19b09dba-c688-4e91-960a-e11ed11fd275)

To conclude optimizations;
I did small testing and it's not slower.
But I didn't get the faster results either.
But I believe it must be faster well in all cases, I think maybe bigger
tables must be tested.
Maybe someone could have a few runs to compare performance.

cc: @fdncred
This commit is contained in:
Maxim Zhiburt 2025-05-01 17:43:30 +03:00 committed by GitHub
parent 60e9f469af
commit deca337a56
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 821 additions and 692 deletions

View File

@ -17,8 +17,8 @@ use nu_protocol::{
Signals, TableMode, ValueIterator,
};
use nu_table::{
common::configure_table, CollapsedTable, ExpandedTable, JustTable, NuRecordsValue, NuTable,
StringResult, TableOpts, TableOutput,
common::configure_table, CollapsedTable, ExpandedTable, JustTable, NuTable, StringResult,
TableOpts, TableOutput,
};
use nu_utils::{get_ls_colors, terminal_size};
@ -609,7 +609,7 @@ fn build_table_kv(
span: Span,
) -> StringResult {
match table_view {
TableView::General => JustTable::kv_table(&record, opts),
TableView::General => JustTable::kv_table(record, opts),
TableView::Expanded {
limit,
flatten,
@ -645,7 +645,7 @@ fn build_table_batch(
}
match view {
TableView::General => JustTable::table(&vals, opts),
TableView::General => JustTable::table(vals, opts),
TableView::Expanded {
limit,
flatten,
@ -1090,9 +1090,9 @@ fn create_empty_placeholder(
return String::new();
}
let cell = NuRecordsValue::new(format!("empty {}", value_type_name));
let data = vec![vec![cell]];
let mut table = NuTable::from(data);
let cell = format!("empty {}", value_type_name);
let mut table = NuTable::new(1, 1);
table.insert((0, 0), cell);
table.set_data_style(TextStyle::default().dimmed());
let mut out = TableOutput::from_table(table, false, false);

View File

@ -1333,15 +1333,17 @@ fn test_expand_big_0() {
"│ target │ {record 3 fields} │",
"│ dev-dependencies │ {record 9 fields} │",
"│ features │ {record 8 fields} │",
"│ │ ╭───┬─────┬─────╮ │",
"│ bin │ │ # │ nam │ pat │ │",
"│ │ │ │ e │ h │ │",
"│ │ ├───┼─────┼─────┤ │",
"│ │ │ 0 │ nu │ src │ │",
"│ │ │ │ │ /ma │ │",
"│ │ │ │ │ in. │ │",
"│ │ │ │ │ rs │ │",
"│ │ ╰───┴─────┴─────╯ │",
"│ │ ╭───┬──────┬────╮ │",
"│ bin │ │ # │ name │ pa │ │",
"│ │ │ │ │ th │ │",
"│ │ ├───┼──────┼────┤ │",
"│ │ │ 0 │ nu │ sr │ │",
"│ │ │ │ │ c/ │ │",
"│ │ │ │ │ ma │ │",
"│ │ │ │ │ in │ │",
"│ │ │ │ │ .r │ │",
"│ │ │ │ │ s │ │",
"│ │ ╰───┴──────┴────╯ │",
"│ │ ╭───────────┬───╮ │",
"│ patch │ │ crates-io │ { │ │",
"│ │ │ │ r │ │",
@ -1360,16 +1362,16 @@ fn test_expand_big_0() {
"│ │ │ │ d │ │",
"│ │ │ │ } │ │",
"│ │ ╰───────────┴───╯ │",
"│ │ ╭───┬──────────╮ │",
"│ bench │ │ # │ nam │ har │ │",
"│ │ │ │ e │ nes │ │",
"│ │ │ │ │ s │ │",
"│ │ ├───┼─────┼─────┤",
"│ │ │ 0 │ ben │ fal │",
"│ │ │ │ chm │ se │ │",
"│ │ │ │ ark │ │ │",
"│ │ │ │ s │ │ │",
"│ │ ╰───┴──────────╯ │",
"│ │ ╭───┬─────────╮ │",
"│ bench │ │ # │ name │ ha │ │",
"│ │ │ │ │ rn │ │",
"│ │ │ │ es │ │",
"│ │ │ │ │ s │",
"│ │ ├───┼──────┼────┤",
"│ │ │ 0 │ benc │ fa │ │",
"│ │ │ │ hmar │ ls │ │",
"│ │ │ │ ks │ e │ │",
"│ │ ╰───┴─────────╯ │",
"╰──────────────────┴───────────────────╯",
]);
@ -1551,193 +1553,114 @@ fn table_expande_with_no_header_internally_0() {
"│ │ │ │ │ │ ╰─────┴──────────╯ │ │ │",
"│ │ │ │ │ display_output │ │ │ │",
"│ │ │ │ ╰────────────────┴────────────────────╯ │ │",
"│ │ │ │ ╭───┬───────────────────────────┬────────────────────────┬────────┬───┬─────╮ │ │",
"│ │ │ menus │ │ # │ name │ only_buffer_difference │ marker │ t │ ... │ │ │",
"│ │ │ │ │ │ │ │ │ y │ │ │ │",
"│ │ │ │ │ │ │ │ │ p │ │ │ │",
"│ │ │ │ │ │ │ │ │ e │ │ │ │",
"│ │ │ │ ├───┼───────────────────────────┼────────────────────────┼────────┼───┼─────┤ │ │",
"│ │ │ │ │ 0 │ completion_menu │ false │ | │ { │ ... │ │ │",
"│ │ │ │ │ │ │ │ │ r │ │ │ │",
"│ │ │ │ │ │ │ │ │ e │ │ │ │",
"│ │ │ │ │ │ │ │ │ c │ │ │ │",
"│ │ │ │ │ │ │ │ │ o │ │ │ │",
"│ │ │ │ │ │ │ │ │ r │ │ │ │",
"│ │ │ │ │ │ │ │ │ d │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ 4 │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ f │ │ │ │",
"│ │ │ │ │ │ │ │ │ i │ │ │ │",
"│ │ │ │ │ │ │ │ │ e │ │ │ │",
"│ │ │ │ │ │ │ │ │ l │ │ │ │",
"│ │ │ │ │ │ │ │ │ d │ │ │ │",
"│ │ │ │ │ │ │ │ │ s │ │ │ │",
"│ │ │ │ │ │ │ │ │ } │ │ │ │",
"│ │ │ │ │ 1 │ history_menu │ true │ ? │ { │ ... │ │ │",
"│ │ │ │ │ │ │ │ │ r │ │ │ │",
"│ │ │ │ │ │ │ │ │ e │ │ │ │",
"│ │ │ │ │ │ │ │ │ c │ │ │ │",
"│ │ │ │ │ │ │ │ │ o │ │ │ │",
"│ │ │ │ │ │ │ │ │ r │ │ │ │",
"│ │ │ │ │ │ │ │ │ d │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ 2 │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ f │ │ │ │",
"│ │ │ │ │ │ │ │ │ i │ │ │ │",
"│ │ │ │ │ │ │ │ │ e │ │ │ │",
"│ │ │ │ │ │ │ │ │ l │ │ │ │",
"│ │ │ │ │ │ │ │ │ d │ │ │ │",
"│ │ │ │ │ │ │ │ │ s │ │ │ │",
"│ │ │ │ │ │ │ │ │ } │ │ │ │",
"│ │ │ │ │ 2 │ help_menu │ true │ ? │ { │ ... │ │ │",
"│ │ │ │ │ │ │ │ │ r │ │ │ │",
"│ │ │ │ │ │ │ │ │ e │ │ │ │",
"│ │ │ │ │ │ │ │ │ c │ │ │ │",
"│ │ │ │ │ │ │ │ │ o │ │ │ │",
"│ │ │ │ │ │ │ │ │ r │ │ │ │",
"│ │ │ │ │ │ │ │ │ d │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ 6 │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ f │ │ │ │",
"│ │ │ │ │ │ │ │ │ i │ │ │ │",
"│ │ │ │ │ │ │ │ │ e │ │ │ │",
"│ │ │ │ │ │ │ │ │ l │ │ │ │",
"│ │ │ │ │ │ │ │ │ d │ │ │ │",
"│ │ │ │ │ │ │ │ │ s │ │ │ │",
"│ │ │ │ │ │ │ │ │ } │ │ │ │",
"│ │ │ │ │ 3 │ commands_menu │ false │ # │ { │ ... │ │ │",
"│ │ │ │ │ │ │ │ │ r │ │ │ │",
"│ │ │ │ │ │ │ │ │ e │ │ │ │",
"│ │ │ │ │ │ │ │ │ c │ │ │ │",
"│ │ │ │ │ │ │ │ │ o │ │ │ │",
"│ │ │ │ │ │ │ │ │ r │ │ │ │",
"│ │ │ │ │ │ │ │ │ d │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ 4 │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ f │ │ │ │",
"│ │ │ │ │ │ │ │ │ i │ │ │ │",
"│ │ │ │ │ │ │ │ │ e │ │ │ │",
"│ │ │ │ │ │ │ │ │ l │ │ │ │",
"│ │ │ │ │ │ │ │ │ d │ │ │ │",
"│ │ │ │ │ │ │ │ │ s │ │ │ │",
"│ │ │ │ │ │ │ │ │ } │ │ │ │",
"│ │ │ │ │ 4 │ vars_menu │ true │ # │ { │ ... │ │ │",
"│ │ │ │ │ │ │ │ │ r │ │ │ │",
"│ │ │ │ │ │ │ │ │ e │ │ │ │",
"│ │ │ │ │ │ │ │ │ c │ │ │ │",
"│ │ │ │ │ │ │ │ │ o │ │ │ │",
"│ │ │ │ │ │ │ │ │ r │ │ │ │",
"│ │ │ │ │ │ │ │ │ d │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ 2 │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ f │ │ │ │",
"│ │ │ │ │ │ │ │ │ i │ │ │ │",
"│ │ │ │ │ │ │ │ │ e │ │ │ │",
"│ │ │ │ │ │ │ │ │ l │ │ │ │",
"│ │ │ │ │ │ │ │ │ d │ │ │ │",
"│ │ │ │ │ │ │ │ │ s │ │ │ │",
"│ │ │ │ │ │ │ │ │ } │ │ │ │",
"│ │ │ │ │ 5 │ commands_with_description │ true │ # │ { │ ... │ │ │",
"│ │ │ │ │ │ │ │ │ r │ │ │ │",
"│ │ │ │ │ │ │ │ │ e │ │ │ │",
"│ │ │ │ │ │ │ │ │ c │ │ │ │",
"│ │ │ │ │ │ │ │ │ o │ │ │ │",
"│ │ │ │ │ │ │ │ │ r │ │ │ │",
"│ │ │ │ │ │ │ │ │ d │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ 6 │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ f │ │ │ │",
"│ │ │ │ │ │ │ │ │ i │ │ │ │",
"│ │ │ │ │ │ │ │ │ e │ │ │ │",
"│ │ │ │ │ │ │ │ │ l │ │ │ │",
"│ │ │ │ │ │ │ │ │ d │ │ │ │",
"│ │ │ │ │ │ │ │ │ s │ │ │ │",
"│ │ │ │ │ │ │ │ │ } │ │ │ │",
"│ │ │ │ ╰───┴───────────────────────────┴────────────────────────┴────────┴───┴─────╯ │ │",
"│ │ │ │ ╭────┬───────────────────────────┬──────────┬─────────┬───────────────┬─────╮ │ │",
"│ │ │ keybindings │ │ # │ name │ modifier │ keycode │ mode │ eve │ │ │",
"│ │ │ │ │ │ │ │ │ │ nt │ │ │",
"│ │ │ │ ├────┼───────────────────────────┼──────────┼─────────┼───────────────┼─────┤ │ │",
"│ │ │ │ │ 0 │ completion_menu │ none │ tab │ ╭───┬───────╮ │ {re │ │ │",
"│ │ │ │ │ │ │ │ │ │ 0 │ emacs │ │ cor │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ vi_no │ │ d 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ rmal │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ 2 │ vi_in │ │ eld │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ sert │ │ } │ │ │",
"│ │ │ │ │ │ │ │ │ ╰───┴───────╯ │ │ │ │",
"│ │ │ │ │ 1 │ completion_previous │ shift │ backtab │ ╭───┬───────╮ │ {re │ │ │",
"│ │ │ │ │ │ │ │ │ │ 0 │ emacs │ │ cor │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ vi_no │ │ d 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ rmal │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ 2 │ vi_in │ │ eld │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ sert │ │ } │ │ │",
"│ │ │ │ │ │ │ │ │ ╰───┴───────╯ │ │ │ │",
"│ │ │ │ │ 2 │ history_menu │ control │ char_r │ emacs │ {re │ │ │",
"│ │ │ │ │ │ │ │ │ │ cor │ │ │",
"│ │ │ │ │ │ │ │ │ │ d 2 │ │ │",
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ eld │ │ │",
"│ │ │ │ │ │ │ │ │ │ s} │ │ │",
"│ │ │ │ │ 3 │ next_page │ control │ char_x │ emacs │ {re │ │ │",
"│ │ │ │ │ │ │ │ │ │ cor │ │ │",
"│ │ │ │ │ │ │ │ │ │ d 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ eld │ │ │",
"│ │ │ │ │ │ │ │ │ │ } │ │ │",
"│ │ │ │ │ 4 │ undo_or_previous_page │ control │ char_z │ emacs │ {re │ │ │",
"│ │ │ │ │ │ │ │ │ │ cor │ │ │",
"│ │ │ │ │ │ │ │ │ │ d 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ eld │ │ │",
"│ │ │ │ │ │ │ │ │ │ } │ │ │",
"│ │ │ │ │ 5 │ yank │ control │ char_y │ emacs │ {re │ │ │",
"│ │ │ │ │ │ │ │ │ │ cor │ │ │",
"│ │ │ │ │ │ │ │ │ │ d 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ eld │ │ │",
"│ │ │ │ │ │ │ │ │ │ } │ │ │",
"│ │ │ │ │ 6 │ unix-line-discard │ control │ char_u │ ╭───┬───────╮ │ {re │ │ │",
"│ │ │ │ │ │ │ │ │ │ 0 │ emacs │ │ cor │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ vi_no │ │ d 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ rmal │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ 2 │ vi_in │ │ eld │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ sert │ │ } │ │ │",
"│ │ │ │ │ │ │ │ │ ╰───┴───────╯ │ │ │ │",
"│ │ │ │ │ 7 │ kill-line │ control │ char_k │ ╭───┬───────╮ │ {re │ │ │",
"│ │ │ │ │ │ │ │ │ │ 0 │ emacs │ │ cor │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ vi_no │ │ d 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ rmal │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ 2 │ vi_in │ │ eld │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ sert │ │ } │ │ │",
"│ │ │ │ │ │ │ │ │ ╰───┴───────╯ │ │ │ │",
"│ │ │ │ │ 8 │ commands_menu │ control │ char_t │ ╭───┬───────╮ │ {re │ │ │",
"│ │ │ │ │ │ │ │ │ │ 0 │ emacs │ │ cor │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ vi_no │ │ d 2 │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ rmal │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ 2 │ vi_in │ │ eld │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ sert │ │ s} │ │ │",
"│ │ │ │ │ │ │ │ │ ╰───┴───────╯ │ │ │ │",
"│ │ │ │ │ 9 │ vars_menu │ alt │ char_o │ ╭───┬───────╮ │ {re │ │ │",
"│ │ │ │ │ │ │ │ │ │ 0 │ emacs │ │ cor │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ vi_no │ │ d 2 │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ rmal │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ 2 │ vi_in │ │ eld │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ sert │ │ s} │ │ │",
"│ │ │ │ │ │ │ │ │ ╰───┴───────╯ │ │ │ │",
"│ │ │ │ │ 10 │ commands_with_description │ control │ char_s │ ╭───┬───────╮ │ {re │ │ │",
"│ │ │ │ │ │ │ │ │ │ 0 │ emacs │ │ cor │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ vi_no │ │ d 2 │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ rmal │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ 2 │ vi_in │ │ eld │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ sert │ │ s} │ │ │",
"│ │ │ │ │ │ │ │ │ ╰───┴───────╯ │ │ │ │",
"│ │ │ │ ╰────┴───────────────────────────┴──────────┴─────────┴───────────────┴─────╯ │ │",
"│ │ │ │ ╭───┬───────────────────────────┬────────────────────────┬────────┬─────╮ │ │",
"│ │ │ menus │ │ # │ name │ only_buffer_difference │ marker │ ... │ │ │",
"│ │ │ │ ├───┼───────────────────────────┼────────────────────────┼────────┼─────┤ │ │",
"│ │ │ │ │ 0 │ completion_menu │ false │ | │ ... │ │ │",
"│ │ │ │ │ 1 │ history_menu │ true │ ? │ ... │ │ │",
"│ │ │ │ │ 2 │ help_menu │ true │ ? │ ... │ │ │",
"│ │ │ │ │ 3 │ commands_menu │ false │ # │ ... │ │ │",
"│ │ │ │ │ 4 │ vars_menu │ true │ # │ ... │ │ │",
"│ │ │ │ │ 5 │ commands_with_description │ true │ # │ ... │ │ │",
"│ │ │ │ ╰───┴───────────────────────────┴────────────────────────┴────────┴─────╯ │ │",
"│ │ │ │ ╭────┬───────────────────────────┬──────────┬─────────┬────────────────┬────╮ │ │",
"│ │ │ keybindings │ │ # │ name │ modifier │ keycode │ mode │ ev │ │ │",
"│ │ │ │ │ │ │ │ │ │ en │ │ │",
"│ │ │ │ │ │ │ │ │ │ t │ │ │",
"│ │ │ │ ├────┼───────────────────────────┼──────────┼─────────┼────────────────┼────┤ │ │",
"│ │ │ │ │ 0 │ completion_menu │ none │ tab │ ╭───┬────────╮ │ {r │ │ │",
"│ │ │ │ │ │ │ │ │ │ 0 │ emacs │ │ ec │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ vi_nor │ │ or │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ mal │ │ d │ │ │",
"│ │ │ │ │ │ │ │ │ │ 2 │ vi_ins │ │ 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ ert │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ ╰───┴────────╯ │ el │ │ │",
"│ │ │ │ │ │ │ │ │ │ d} │ │ │",
"│ │ │ │ │ 1 │ completion_previous │ shift │ backtab │ ╭───┬────────╮ │ {r │ │ │",
"│ │ │ │ │ │ │ │ │ │ 0 │ emacs │ │ ec │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ vi_nor │ │ or │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ mal │ │ d │ │ │",
"│ │ │ │ │ │ │ │ │ │ 2 │ vi_ins │ │ 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ ert │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ ╰───┴────────╯ │ el │ │ │",
"│ │ │ │ │ │ │ │ │ │ d} │ │ │",
"│ │ │ │ │ 2 │ history_menu │ control │ char_r │ emacs │ {r │ │ │",
"│ │ │ │ │ │ │ │ │ │ ec │ │ │",
"│ │ │ │ │ │ │ │ │ │ or │ │ │",
"│ │ │ │ │ │ │ │ │ │ d │ │ │",
"│ │ │ │ │ │ │ │ │ │ 2 │ │ │",
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ el │ │ │",
"│ │ │ │ │ │ │ │ │ │ ds │ │ │",
"│ │ │ │ │ │ │ │ │ │ } │ │ │",
"│ │ │ │ │ 3 │ next_page │ control │ char_x │ emacs │ {r │ │ │",
"│ │ │ │ │ │ │ │ │ │ ec │ │ │",
"│ │ │ │ │ │ │ │ │ │ or │ │ │",
"│ │ │ │ │ │ │ │ │ │ d │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ el │ │ │",
"│ │ │ │ │ │ │ │ │ │ d} │ │ │",
"│ │ │ │ │ 4 │ undo_or_previous_page │ control │ char_z │ emacs │ {r │ │ │",
"│ │ │ │ │ │ │ │ │ │ ec │ │ │",
"│ │ │ │ │ │ │ │ │ │ or │ │ │",
"│ │ │ │ │ │ │ │ │ │ d │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ el │ │ │",
"│ │ │ │ │ │ │ │ │ │ d} │ │ │",
"│ │ │ │ │ 5 │ yank │ control │ char_y │ emacs │ {r │ │ │",
"│ │ │ │ │ │ │ │ │ │ ec │ │ │",
"│ │ │ │ │ │ │ │ │ │ or │ │ │",
"│ │ │ │ │ │ │ │ │ │ d │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ el │ │ │",
"│ │ │ │ │ │ │ │ │ │ d} │ │ │",
"│ │ │ │ │ 6 │ unix-line-discard │ control │ char_u │ ╭───┬────────╮ │ {r │ │ │",
"│ │ │ │ │ │ │ │ │ │ 0 │ emacs │ │ ec │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ vi_nor │ │ or │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ mal │ │ d │ │ │",
"│ │ │ │ │ │ │ │ │ │ 2 │ vi_ins │ │ 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ ert │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ ╰───┴────────╯ │ el │ │ │",
"│ │ │ │ │ │ │ │ │ │ d} │ │ │",
"│ │ │ │ │ 7 │ kill-line │ control │ char_k │ ╭───┬────────╮ │ {r │ │ │",
"│ │ │ │ │ │ │ │ │ │ 0 │ emacs │ │ ec │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ vi_nor │ │ or │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ mal │ │ d │ │ │",
"│ │ │ │ │ │ │ │ │ │ 2 │ vi_ins │ │ 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ ert │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ ╰───┴────────╯ │ el │ │ │",
"│ │ │ │ │ │ │ │ │ │ d} │ │ │",
"│ │ │ │ │ 8 │ commands_menu │ control │ char_t │ ╭───┬────────╮ │ {r │ │ │",
"│ │ │ │ │ │ │ │ │ │ 0 │ emacs │ │ ec │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ vi_nor │ │ or │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ mal │ │ d │ │ │",
"│ │ │ │ │ │ │ │ │ │ 2 │ vi_ins │ │ 2 │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ ert │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ ╰───┴────────╯ │ el │ │ │",
"│ │ │ │ │ │ │ │ │ │ ds │ │ │",
"│ │ │ │ │ │ │ │ │ │ } │ │ │",
"│ │ │ │ │ 9 │ vars_menu │ alt │ char_o │ ╭───┬────────╮ │ {r │ │ │",
"│ │ │ │ │ │ │ │ │ │ 0 │ emacs │ │ ec │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ vi_nor │ │ or │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ mal │ │ d │ │ │",
"│ │ │ │ │ │ │ │ │ │ 2 │ vi_ins │ │ 2 │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ ert │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ ╰───┴────────╯ │ el │ │ │",
"│ │ │ │ │ │ │ │ │ │ ds │ │ │",
"│ │ │ │ │ │ │ │ │ │ } │ │ │",
"│ │ │ │ │ 10 │ commands_with_description │ control │ char_s │ ╭───┬────────╮ │ {r │ │ │",
"│ │ │ │ │ │ │ │ │ │ 0 │ emacs │ │ ec │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ vi_nor │ │ or │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ mal │ │ d │ │ │",
"│ │ │ │ │ │ │ │ │ │ 2 │ vi_ins │ │ 2 │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ ert │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ ╰───┴────────╯ │ el │ │ │",
"│ │ │ │ │ │ │ │ │ │ ds │ │ │",
"│ │ │ │ │ │ │ │ │ │ } │ │ │",
"│ │ │ │ ╰────┴───────────────────────────┴──────────┴─────────┴────────────────┴────╯ │ │",
"│ │ ╰──────────────────────────────────┴───────────────────────────────────────────────────────────────────────────────╯ │",
"╰────────────────────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯",
])
@ -1929,77 +1852,220 @@ fn table_expande_with_no_header_internally_1() {
"│ │ │ │ │ 4 │ vars_menu │ true │ # │ ... │ │ │",
"│ │ │ │ │ 5 │ commands_with_description │ true │ # │ ... │ │ │",
"│ │ │ │ ╰───┴───────────────────────────┴────────────────────────┴───────┴─────╯ │ │",
"│ │ │ │ ╭────┬───────────────────────────┬──────────┬─────────┬──────────┬─────╮ │ │",
"│ │ │ keybindings │ │ # │ name │ modifier │ keycode │ mode │ eve │ │ │",
"│ │ │ │ │ │ │ │ │ │ nt │ │ │",
"│ │ │ │ ├────┼───────────────────────────┼──────────┼─────────┼──────────┼─────┤ │ │",
"│ │ │ │ │ 0 │ completion_menu │ none │ tab │ [list 3 │ {re │ │ │",
"│ │ │ │ │ │ │ │ │ items] │ cor │ │ │",
"│ │ │ │ │ │ │ │ │ │ d 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ eld │ │ │",
"│ │ │ │ │ │ │ │ │ │ } │ │ │",
"│ │ │ │ │ 1 │ completion_previous │ shift │ backtab │ [list 3 │ {re │ │ │",
"│ │ │ │ │ │ │ │ │ items] │ cor │ │ │",
"│ │ │ │ │ │ │ │ │ │ d 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ eld │ │ │",
"│ │ │ │ │ │ │ │ │ │ } │ │ │",
"│ │ │ │ │ 2 │ history_menu │ control │ char_r │ emacs │ {re │ │ │",
"│ │ │ │ │ │ │ │ │ │ cor │ │ │",
"│ │ │ │ │ │ │ │ │ │ d 2 │ │ │",
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ eld │ │ │",
"│ │ │ │ │ │ │ │ │ │ s} │ │ │",
"│ │ │ │ │ 3 │ next_page │ control │ char_x │ emacs │ {re │ │ │",
"│ │ │ │ │ │ │ │ │ │ cor │ │ │",
"│ │ │ │ │ │ │ │ │ │ d 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ eld │ │ │",
"│ │ │ │ │ │ │ │ │ │ } │ │ │",
"│ │ │ │ │ 4 │ undo_or_previous_page │ control │ char_z │ emacs │ {re │ │ │",
"│ │ │ │ │ │ │ │ │ │ cor │ │ │",
"│ │ │ │ │ │ │ │ │ │ d 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ eld │ │ │",
"│ │ │ │ │ │ │ │ │ │ } │ │ │",
"│ │ │ │ │ 5 │ yank │ control │ char_y │ emacs │ {re │ │ │",
"│ │ │ │ │ │ │ │ │ │ cor │ │ │",
"│ │ │ │ │ │ │ │ │ │ d 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ eld │ │ │",
"│ │ │ │ │ │ │ │ │ │ } │ │ │",
"│ │ │ │ │ 6 │ unix-line-discard │ control │ char_u │ [list 3 │ {re │ │ │",
"│ │ │ │ │ │ │ │ │ items] │ cor │ │ │",
"│ │ │ │ │ │ │ │ │ │ d 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ eld │ │ │",
"│ │ │ │ │ │ │ │ │ │ } │ │ │",
"│ │ │ │ │ 7 │ kill-line │ control │ char_k │ [list 3 │ {re │ │ │",
"│ │ │ │ │ │ │ │ │ items] │ cor │ │ │",
"│ │ │ │ │ │ │ │ │ │ d 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ eld │ │ │",
"│ │ │ │ │ │ │ │ │ │ } │ │ │",
"│ │ │ │ │ 8 │ commands_menu │ control │ char_t │ [list 3 │ {re │ │ │",
"│ │ │ │ │ │ │ │ │ items] │ cor │ │ │",
"│ │ │ │ │ │ │ │ │ │ d 2 │ │ │",
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ eld │ │ │",
"│ │ │ │ │ │ │ │ │ │ s} │ │ │",
"│ │ │ │ │ 9 │ vars_menu │ alt │ char_o │ [list 3 │ {re │ │ │",
"│ │ │ │ │ │ │ │ │ items] │ cor │ │ │",
"│ │ │ │ │ │ │ │ │ │ d 2 │ │ │",
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ eld │ │ │",
"│ │ │ │ │ │ │ │ │ │ s} │ │ │",
"│ │ │ │ │ 10 │ commands_with_description │ control │ char_s │ [list 3 │ {re │ │ │",
"│ │ │ │ │ │ │ │ │ items] │ cor │ │ │",
"│ │ │ │ │ │ │ │ │ │ d 2 │ │ │",
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ eld │ │ │",
"│ │ │ │ │ │ │ │ │ │ s} │ │ │",
"│ │ │ │ ╰────┴───────────────────────────┴──────────┴─────────┴──────────┴─────╯ │ │",
"│ │ │ │ ╭────┬───────────────────────────┬──────────┬─────────┬───────────┬────╮ │ │",
"│ │ │ keybindings │ │ # │ name │ modifier │ keycode │ mode │ ev │ │ │",
"│ │ │ │ │ │ │ │ │ │ en │ │ │",
"│ │ │ │ │ │ │ │ │ │ t │ │ │",
"│ │ │ │ ├────┼───────────────────────────┼──────────┼─────────┼───────────┼────┤ │ │",
"│ │ │ │ │ 0 │ completion_menu │ none │ tab │ ╭───┬───╮ │ {r │ │ │",
"│ │ │ │ │ │ │ │ │ │ 0 │ e │ │ ec │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ m │ │ or │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ a │ │ d │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ c │ │ 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ s │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ v │ │ el │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ i │ │ d} │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ _ │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ n │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ o │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ r │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ m │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ a │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ l │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ 2 │ v │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ i │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ _ │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ i │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ n │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ s │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ e │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ r │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ t │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ ╰───┴───╯ │ │ │ │",
"│ │ │ │ │ 1 │ completion_previous │ shift │ backtab │ ╭───┬───╮ │ {r │ │ │",
"│ │ │ │ │ │ │ │ │ │ 0 │ e │ │ ec │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ m │ │ or │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ a │ │ d │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ c │ │ 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ s │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ v │ │ el │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ i │ │ d} │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ _ │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ n │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ o │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ r │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ m │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ a │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ l │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ 2 │ v │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ i │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ _ │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ i │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ n │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ s │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ e │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ r │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ t │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ ╰───┴───╯ │ │ │ │",
"│ │ │ │ │ 2 │ history_menu │ control │ char_r │ emacs │ {r │ │ │",
"│ │ │ │ │ │ │ │ │ │ ec │ │ │",
"│ │ │ │ │ │ │ │ │ │ or │ │ │",
"│ │ │ │ │ │ │ │ │ │ d │ │ │",
"│ │ │ │ │ │ │ │ │ │ 2 │ │ │",
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ el │ │ │",
"│ │ │ │ │ │ │ │ │ │ ds │ │ │",
"│ │ │ │ │ │ │ │ │ │ } │ │ │",
"│ │ │ │ │ 3 │ next_page │ control │ char_x │ emacs │ {r │ │ │",
"│ │ │ │ │ │ │ │ │ │ ec │ │ │",
"│ │ │ │ │ │ │ │ │ │ or │ │ │",
"│ │ │ │ │ │ │ │ │ │ d │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ el │ │ │",
"│ │ │ │ │ │ │ │ │ │ d} │ │ │",
"│ │ │ │ │ 4 │ undo_or_previous_page │ control │ char_z │ emacs │ {r │ │ │",
"│ │ │ │ │ │ │ │ │ │ ec │ │ │",
"│ │ │ │ │ │ │ │ │ │ or │ │ │",
"│ │ │ │ │ │ │ │ │ │ d │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ el │ │ │",
"│ │ │ │ │ │ │ │ │ │ d} │ │ │",
"│ │ │ │ │ 5 │ yank │ control │ char_y │ emacs │ {r │ │ │",
"│ │ │ │ │ │ │ │ │ │ ec │ │ │",
"│ │ │ │ │ │ │ │ │ │ or │ │ │",
"│ │ │ │ │ │ │ │ │ │ d │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ el │ │ │",
"│ │ │ │ │ │ │ │ │ │ d} │ │ │",
"│ │ │ │ │ 6 │ unix-line-discard │ control │ char_u │ ╭───┬───╮ │ {r │ │ │",
"│ │ │ │ │ │ │ │ │ │ 0 │ e │ │ ec │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ m │ │ or │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ a │ │ d │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ c │ │ 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ s │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ v │ │ el │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ i │ │ d} │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ _ │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ n │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ o │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ r │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ m │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ a │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ l │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ 2 │ v │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ i │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ _ │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ i │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ n │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ s │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ e │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ r │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ t │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ ╰───┴───╯ │ │ │ │",
"│ │ │ │ │ 7 │ kill-line │ control │ char_k │ ╭───┬───╮ │ {r │ │ │",
"│ │ │ │ │ │ │ │ │ │ 0 │ e │ │ ec │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ m │ │ or │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ a │ │ d │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ c │ │ 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ s │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ v │ │ el │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ i │ │ d} │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ _ │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ n │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ o │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ r │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ m │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ a │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ l │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ 2 │ v │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ i │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ _ │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ i │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ n │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ s │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ e │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ r │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ t │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ ╰───┴───╯ │ │ │ │",
"│ │ │ │ │ 8 │ commands_menu │ control │ char_t │ ╭───┬───╮ │ {r │ │ │",
"│ │ │ │ │ │ │ │ │ │ 0 │ e │ │ ec │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ m │ │ or │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ a │ │ d │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ c │ │ 2 │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ s │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ v │ │ el │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ i │ │ ds │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ _ │ │ } │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ n │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ o │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ r │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ m │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ a │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ l │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ 2 │ v │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ i │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ _ │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ i │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ n │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ s │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ e │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ r │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ t │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ ╰───┴───╯ │ │ │ │",
"│ │ │ │ │ 9 │ vars_menu │ alt │ char_o │ ╭───┬───╮ │ {r │ │ │",
"│ │ │ │ │ │ │ │ │ │ 0 │ e │ │ ec │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ m │ │ or │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ a │ │ d │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ c │ │ 2 │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ s │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ v │ │ el │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ i │ │ ds │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ _ │ │ } │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ n │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ o │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ r │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ m │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ a │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ l │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ 2 │ v │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ i │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ _ │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ i │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ n │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ s │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ e │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ r │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ t │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ ╰───┴───╯ │ │ │ │",
"│ │ │ │ │ 10 │ commands_with_description │ control │ char_s │ ╭───┬───╮ │ {r │ │ │",
"│ │ │ │ │ │ │ │ │ │ 0 │ e │ │ ec │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ m │ │ or │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ a │ │ d │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ c │ │ 2 │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ s │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ v │ │ el │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ i │ │ ds │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ _ │ │ } │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ n │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ o │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ r │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ m │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ a │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ l │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ 2 │ v │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ i │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ _ │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ i │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ n │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ s │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ e │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ r │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ t │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ ╰───┴───╯ │ │ │ │",
"│ │ │ │ ╰────┴───────────────────────────┴──────────┴─────────┴───────────┴────╯ │ │",
"│ │ ╰──────────────────────────────────┴──────────────────────────────────────────────────────────────────────────╯ │",
"╰────────────────────┴─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯",
])

View File

@ -21,10 +21,12 @@ fn main() {
let headers = to_cell_info_vec(&table_headers);
let rows = to_cell_info_vec(&row_data);
let mut rows = vec![rows; 3];
rows.insert(0, headers);
let mut table = NuTable::new(4, 3);
table.set_row(0, headers);
let mut table = NuTable::from(rows);
for i in 0..3 {
table.set_row(i + 1, rows.clone());
}
table.set_data_style(TextStyle::basic_left());
table.set_header_style(TextStyle::basic_center().style(Style::new().on(Color::Blue)));

View File

@ -3,7 +3,7 @@
// NOTE: TODO the above we could expose something like [`WidthCtrl`] in which case we could also laverage the width list build right away.
// currently it seems like we do recacalculate it for `table -e`?
use std::{cmp::min, collections::HashMap};
use std::cmp::{max, min};
use nu_ansi_term::Style;
use nu_color_config::TextStyle;
@ -13,13 +13,14 @@ use tabled::{
builder::Builder,
grid::{
ansi::ANSIBuf,
colors::Colors,
config::{
AlignmentHorizontal, ColoredConfig, Entity, Indent, Position, Sides, SpannedConfig,
},
dimension::{CompleteDimensionVecRecords, SpannedGridDimension},
records::{
vec_records::{Text, VecRecords},
ExactRecords, Records,
vec_records::{Cell, Text, VecRecords},
IntoRecords, IterRecords, Records,
},
},
settings::{
@ -43,24 +44,30 @@ pub type NuRecordsValue = Text<String>;
/// NuTable is a table rendering implementation.
#[derive(Debug, Clone)]
pub struct NuTable {
data: NuRecords,
data: Vec<Vec<NuRecordsValue>>,
widths: Vec<usize>,
count_rows: usize,
count_cols: usize,
styles: Styles,
alignments: Alignments,
config: TableConfig,
}
impl NuTable {
/// Creates an empty [`NuTable`] instance.
pub fn new(count_rows: usize, count_columns: usize) -> Self {
pub fn new(count_rows: usize, count_cols: usize) -> Self {
Self {
data: VecRecords::new(vec![vec![Text::default(); count_columns]; count_rows]),
styles: Styles::default(),
alignments: Alignments {
data: AlignmentHorizontal::Left,
index: AlignmentHorizontal::Right,
header: AlignmentHorizontal::Center,
columns: HashMap::default(),
cells: HashMap::default(),
data: vec![vec![Text::default(); count_cols]; count_rows],
widths: vec![2; count_cols],
count_rows,
count_cols,
styles: Styles {
cfg: ColoredConfig::default(),
alignments: CellConfiguration {
data: AlignmentHorizontal::Left,
index: AlignmentHorizontal::Right,
header: AlignmentHorizontal::Center,
},
colors: CellConfiguration::default(),
},
config: TableConfig {
theme: TableTheme::basic(),
@ -76,84 +83,125 @@ impl NuTable {
/// Return amount of rows.
pub fn count_rows(&self) -> usize {
self.data.count_rows()
self.count_rows
}
/// Return amount of columns.
pub fn count_columns(&self) -> usize {
self.data.count_columns()
self.count_cols
}
pub fn insert(&mut self, pos: Position, text: String) {
self.data[pos.0][pos.1] = Text::new(text);
}
pub fn insert_row(&mut self, index: usize, row: Vec<String>) {
let data = &mut self.data[index];
for (col, text) in row.into_iter().enumerate() {
data[col] = Text::new(text);
}
let text = Text::new(text);
self.widths[pos.1] = max(
self.widths[pos.1],
text.width() + indent_sum(self.config.indent),
);
self.data[pos.0][pos.1] = text;
}
pub fn set_row(&mut self, index: usize, row: Vec<NuRecordsValue>) {
assert_eq!(self.data[index].len(), row.len());
for (i, text) in row.iter().enumerate() {
self.widths[i] = max(
self.widths[i],
text.width() + indent_sum(self.config.indent),
);
}
self.data[index] = row;
}
pub fn set_column_style(&mut self, column: usize, style: TextStyle) {
if let Some(style) = style.color_style {
let style = convert_style(style);
self.styles.columns.insert(column, style);
pub fn pop_column(&mut self, count: usize) {
self.count_cols -= count;
self.widths.truncate(self.count_cols);
for row in &mut self.data[..] {
row.truncate(self.count_cols);
}
let alignment = convert_alignment(style.alignment);
if alignment != self.alignments.data {
self.alignments.columns.insert(column, alignment);
// set to default styles of the popped columns
for i in 0..count {
let col = self.count_cols + i;
for row in 0..self.count_rows {
self.styles
.cfg
.set_alignment_horizontal(Entity::Cell(row, col), self.styles.alignments.data);
self.styles
.cfg
.set_color(Entity::Cell(row, col), ANSIBuf::default());
}
}
}
pub fn push_column(&mut self, text: String) {
let value = Text::new(text);
self.widths
.push(value.width() + indent_sum(self.config.indent));
for row in &mut self.data[..] {
row.push(value.clone());
}
self.count_cols += 1;
}
pub fn insert_style(&mut self, pos: Position, style: TextStyle) {
if let Some(style) = style.color_style {
let style = convert_style(style);
self.styles.cells.insert(pos, style);
self.styles.cfg.set_color(pos.into(), style.into());
}
let alignment = convert_alignment(style.alignment);
if alignment != self.alignments.data {
self.alignments.cells.insert(pos, alignment);
if alignment != self.styles.alignments.data {
self.styles
.cfg
.set_alignment_horizontal(pos.into(), alignment);
}
}
pub fn set_header_style(&mut self, style: TextStyle) {
if let Some(style) = style.color_style {
let style = convert_style(style);
self.styles.header = style;
self.styles.colors.header = style;
}
self.alignments.header = convert_alignment(style.alignment);
self.styles.alignments.header = convert_alignment(style.alignment);
}
pub fn set_index_style(&mut self, style: TextStyle) {
if let Some(style) = style.color_style {
let style = convert_style(style);
self.styles.index = style;
self.styles.colors.index = style;
}
self.alignments.index = convert_alignment(style.alignment);
self.styles.alignments.index = convert_alignment(style.alignment);
}
pub fn set_data_style(&mut self, style: TextStyle) {
if let Some(style) = style.color_style {
let style = convert_style(style);
self.styles.data = style;
if !style.is_plain() {
let style = convert_style(style);
self.styles.cfg.set_color(Entity::Global, style.into());
}
}
self.alignments.data = convert_alignment(style.alignment);
let alignment = convert_alignment(style.alignment);
self.styles
.cfg
.set_alignment_horizontal(Entity::Global, alignment);
self.styles.alignments.data = alignment;
}
// NOTE: Crusial to be called before data changes (todo fix interface)
pub fn set_indent(&mut self, indent: TableIndent) {
self.config.indent = indent;
let pad = indent_sum(indent);
for w in &mut self.widths {
*w = pad;
}
}
pub fn set_theme(&mut self, theme: TableTheme) {
@ -180,7 +228,9 @@ impl NuTable {
self.config.border_color = (!color.is_plain()).then_some(color);
}
pub fn get_records_mut(&mut self) -> &mut NuRecords {
// NOTE: BE CAREFUL TO KEEP WIDTH UNCHANGED
// TODO: fix interface
pub fn get_records_mut(&mut self) -> &mut [Vec<NuRecordsValue>] {
&mut self.data
}
@ -194,32 +244,42 @@ impl NuTable {
/// Return a total table width.
pub fn total_width(&self) -> usize {
let config = create_config(&self.config.theme, false, None);
let pad = indent_sum(self.config.indent);
let widths = build_width(&self.data, pad);
get_total_width2(&widths, &config)
get_total_width2(&self.widths, &config)
}
}
impl From<Vec<Vec<Text<String>>>> for NuTable {
fn from(value: Vec<Vec<Text<String>>>) -> Self {
let mut nutable = Self::new(0, 0);
nutable.data = VecRecords::new(value);
let count_rows = value.len();
let count_cols = if value.is_empty() { 0 } else { value[0].len() };
nutable
let mut t = Self::new(count_rows, count_cols);
t.data = value;
table_recalculate_widths(&mut t);
t
}
}
type Alignments = CellConfiguration<AlignmentHorizontal>;
fn table_recalculate_widths(t: &mut NuTable) {
let pad = indent_sum(t.config.indent);
let records = IterRecords::new(&t.data, t.count_cols, Some(t.count_rows));
let widths = build_width(records, pad);
t.widths = widths;
}
type Styles = CellConfiguration<Color>;
#[derive(Debug, Default, Clone)]
#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Copy, Hash)]
struct CellConfiguration<Value> {
data: Value,
index: Value,
header: Value,
columns: HashMap<usize, Value>,
cells: HashMap<Position, Value>,
data: Value,
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct Styles {
cfg: ColoredConfig,
colors: CellConfiguration<Color>,
alignments: CellConfiguration<AlignmentHorizontal>,
}
#[derive(Debug, Clone)]
@ -304,49 +364,67 @@ fn table_insert_footer_if(t: &mut NuTable) {
}
fn table_truncate(t: &mut NuTable, termwidth: usize) -> Option<WidthEstimation> {
let widths = maybe_truncate_columns(&mut t.data, &t.config, termwidth);
let widths = maybe_truncate_columns(&mut t.data, t.widths.clone(), &t.config, termwidth);
if widths.needed.is_empty() {
return None;
}
// reset style for last column which is a trail one
if widths.trail {
let col = widths.needed.len() - 1;
for row in 0..t.count_rows {
t.styles
.cfg
.set_alignment_horizontal(Entity::Cell(row, col), t.styles.alignments.data);
t.styles
.cfg
.set_color(Entity::Cell(row, col), ANSIBuf::default());
}
}
Some(widths)
}
fn remove_header(t: &mut NuTable) -> HeadInfo {
let head: Vec<String> = t
// move settings by one row down
for row in 1..t.data.len() {
for col in 0..t.count_cols {
let alignment = *t
.styles
.cfg
.get_alignment_horizontal(Entity::Cell(row, col));
if alignment != t.styles.alignments.data {
t.styles
.cfg
.set_alignment_horizontal(Entity::Cell(row - 1, col), alignment);
}
// TODO: use get_color from upstream (when released)
let color = t.styles.cfg.get_colors().get_color((row, col)).cloned();
if let Some(color) = color {
t.styles.cfg.set_color(Entity::Cell(row - 1, col), color);
}
}
}
let head = t
.data
.remove(0)
.into_iter()
.map(|s| s.to_string())
.collect();
let align = t.alignments.header;
let color = if is_color_empty(&t.styles.header) {
None
} else {
Some(t.styles.header.clone())
};
// move settings by one row down
t.alignments.cells = t
.alignments
.cells
.drain()
.filter(|(k, _)| k.0 != 0)
.map(|(k, v)| ((k.0 - 1, k.1), v))
.collect();
t.alignments.header = AlignmentHorizontal::Center;
// WE NEED TO RELCULATE WIDTH.
// TODO: cause we have configuration beforehand we can just not calculate it in?
table_recalculate_widths(t);
// move settings by one row down
t.styles.cells = t
.styles
.cells
.drain()
.filter(|(k, _)| k.0 != 0)
.map(|(k, v)| ((k.0 - 1, k.1), v))
.collect();
t.styles.header = Color::empty();
let alignment = t.styles.alignments.header;
let color = get_color_if_exists(&t.styles.colors.header);
HeadInfo::new(head, align, color)
t.styles.alignments.header = AlignmentHorizontal::Center;
t.styles.colors.header = Color::empty();
HeadInfo::new(head, alignment, color)
}
fn draw_table(
@ -359,19 +437,24 @@ fn draw_table(
structure.with_footer = structure.with_footer && head.is_none();
let sep_color = t.config.border_color;
let data: Vec<Vec<_>> = t.data.into();
let data = t.data;
let mut table = Builder::from_vec(data).build();
set_styles(&mut table, t.styles, &structure);
set_indent(&mut table, t.config.indent);
load_theme(&mut table, &t.config.theme, &structure, sep_color);
align_table(&mut table, t.alignments, &structure);
colorize_table(&mut table, t.styles, &structure);
truncate_table(&mut table, &t.config, width, termwidth);
table_set_border_header(&mut table, head, &t.config);
table_to_string(table, termwidth)
}
fn set_styles(table: &mut Table, styles: Styles, structure: &TableStructure) {
table.with(styles.cfg);
align_table(table, styles.alignments, structure);
colorize_table(table, styles.colors, structure);
}
fn table_set_border_header(table: &mut Table, head: Option<HeadInfo>, cfg: &TableConfig) {
let head = match head {
Some(head) => head,
@ -420,7 +503,6 @@ fn set_indent(table: &mut Table, indent: TableIndent) {
fn table_to_string(table: Table, termwidth: usize) -> Option<String> {
let total_width = table.total_width();
if total_width > termwidth {
None
} else {
@ -462,15 +544,23 @@ struct WidthEstimation {
#[allow(dead_code)]
total: usize,
truncate: bool,
trail: bool,
}
impl WidthEstimation {
fn new(original: Vec<usize>, needed: Vec<usize>, total: usize, truncate: bool) -> Self {
fn new(
original: Vec<usize>,
needed: Vec<usize>,
total: usize,
truncate: bool,
trail: bool,
) -> Self {
Self {
original,
needed,
total,
truncate,
trail,
}
}
}
@ -546,17 +636,12 @@ fn width_ctrl_truncate(
dims.set_widths(ctrl.width.needed);
}
fn align_table(table: &mut Table, alignments: Alignments, structure: &TableStructure) {
fn align_table(
table: &mut Table,
alignments: CellConfiguration<AlignmentHorizontal>,
structure: &TableStructure,
) {
table.with(AlignmentStrategy::PerLine);
table.with(Alignment::from(alignments.data));
for (column, alignment) in alignments.columns {
table.modify(Columns::single(column), Alignment::from(alignment));
}
for (pos, alignment) in alignments.cells {
table.modify(pos, Alignment::from(alignment));
}
if structure.with_header {
table.modify(Rows::first(), Alignment::from(alignments.header));
@ -571,23 +656,7 @@ fn align_table(table: &mut Table, alignments: Alignments, structure: &TableStruc
}
}
fn colorize_table(table: &mut Table, styles: Styles, structure: &TableStructure) {
if !is_color_empty(&styles.data) {
table.with(styles.data);
}
for (column, color) in styles.columns {
if !is_color_empty(&color) {
table.modify(Columns::single(column), color);
}
}
for (pos, color) in styles.cells {
if !is_color_empty(&color) {
table.modify(pos, color);
}
}
fn colorize_table(table: &mut Table, styles: CellConfiguration<Color>, structure: &TableStructure) {
if structure.with_index && !is_color_empty(&styles.index) {
table.modify(Columns::first(), styles.index);
}
@ -629,7 +698,8 @@ fn load_theme(
}
fn maybe_truncate_columns(
data: &mut NuRecords,
data: &mut Vec<Vec<NuRecordsValue>>,
widths: Vec<usize>,
cfg: &TableConfig,
termwidth: usize,
) -> WidthEstimation {
@ -639,15 +709,16 @@ fn maybe_truncate_columns(
let preserve_content = termwidth > TERMWIDTH_THRESHOLD;
if preserve_content {
truncate_columns_by_columns(data, &cfg.theme, pad, termwidth)
truncate_columns_by_columns(data, widths, &cfg.theme, pad, termwidth)
} else {
truncate_columns_by_content(data, &cfg.theme, pad, termwidth)
truncate_columns_by_content(data, widths, &cfg.theme, pad, termwidth)
}
}
// VERSION where we are showing AS LITTLE COLUMNS AS POSSIBLE but WITH AS MUCH CONTENT AS POSSIBLE.
fn truncate_columns_by_content(
data: &mut NuRecords,
data: &mut Vec<Vec<NuRecordsValue>>,
widths: Vec<usize>,
theme: &TableTheme,
pad: usize,
termwidth: usize,
@ -658,13 +729,14 @@ fn truncate_columns_by_content(
let trailing_column_width = TRAILING_COLUMN_WIDTH + pad;
let min_column_width = MIN_ACCEPTABLE_WIDTH + pad;
let count_columns = data[0].len();
let config = create_config(theme, false, None);
let widths_original = build_width(data, pad);
let widths_original = widths;
let mut widths = vec![];
let borders = config.get_borders();
let vertical = borders.has_vertical() as usize;
let count_columns = data.count_columns();
let mut width = borders.has_left() as usize + borders.has_right() as usize;
let mut truncate_pos = 0;
@ -685,7 +757,7 @@ fn truncate_columns_by_content(
}
if truncate_pos == count_columns {
return WidthEstimation::new(widths_original, widths, width, false);
return WidthEstimation::new(widths_original, widths, width, false, false);
}
if truncate_pos == 0 {
@ -702,11 +774,11 @@ fn truncate_columns_by_content(
widths.push(trailing_column_width);
width += trailing_column_width + vertical;
return WidthEstimation::new(widths_original, widths, width, true);
return WidthEstimation::new(widths_original, widths, width, true, true);
}
}
return WidthEstimation::new(widths_original, widths, width, false);
return WidthEstimation::new(widths_original, widths, width, false, false);
}
let available = termwidth - width;
@ -718,7 +790,7 @@ fn truncate_columns_by_content(
widths.push(w);
width += w + vertical;
return WidthEstimation::new(widths_original, widths, width, true);
return WidthEstimation::new(widths_original, widths, width, true, false);
}
// special case where the last column is smaller then a trailing column
@ -736,7 +808,7 @@ fn truncate_columns_by_content(
widths.push(next_column_width);
width += next_column_width + vertical;
return WidthEstimation::new(widths_original, widths, width, true);
return WidthEstimation::new(widths_original, widths, width, true, false);
}
}
@ -753,7 +825,7 @@ fn truncate_columns_by_content(
widths.push(trailing_column_width);
width += trailing_column_width + vertical;
return WidthEstimation::new(widths_original, widths, width, true);
return WidthEstimation::new(widths_original, widths, width, true, true);
}
if available >= trailing_column_width + vertical {
@ -763,7 +835,7 @@ fn truncate_columns_by_content(
widths.push(trailing_column_width);
width += trailing_column_width + vertical;
return WidthEstimation::new(widths_original, widths, width, false);
return WidthEstimation::new(widths_original, widths, width, false, true);
}
let last_width = widths.last().cloned().expect("ok");
@ -787,7 +859,7 @@ fn truncate_columns_by_content(
widths.push(trailing_column_width);
width += trailing_column_width + vertical;
return WidthEstimation::new(widths_original, widths, width, true);
return WidthEstimation::new(widths_original, widths, width, true, true);
}
}
@ -801,10 +873,10 @@ fn truncate_columns_by_content(
if widths.len() == 1 {
// nothing to show anyhow
return WidthEstimation::new(widths_original, vec![], width, false);
return WidthEstimation::new(widths_original, vec![], width, false, true);
}
WidthEstimation::new(widths_original, widths, width, false)
WidthEstimation::new(widths_original, widths, width, false, true)
}
// VERSION where we are showing AS MANY COLUMNS AS POSSIBLE but as a side affect they MIGHT CONTAIN AS LITTLE CONTENT AS POSSIBLE
@ -818,7 +890,8 @@ fn truncate_columns_by_content(
// Point being of the column needs more space we do can give it a little more based on it's distance from the start.
// Percentage wise.
fn truncate_columns_by_columns(
data: &mut NuRecords,
data: &mut Vec<Vec<NuRecordsValue>>,
widths: Vec<usize>,
theme: &TableTheme,
pad: usize,
termwidth: usize,
@ -829,13 +902,14 @@ fn truncate_columns_by_columns(
let trailing_column_width = TRAILING_COLUMN_WIDTH + pad;
let min_column_width = MIN_ACCEPTABLE_WIDTH + pad;
let count_columns = data[0].len();
let config = create_config(theme, false, None);
let widths_original = build_width(data, pad);
let widths_original = widths;
let mut widths = vec![];
let borders = config.get_borders();
let vertical = borders.has_vertical() as usize;
let count_columns = data.count_columns();
let mut width = borders.has_left() as usize + borders.has_right() as usize;
let mut truncate_pos = 0;
@ -857,7 +931,7 @@ fn truncate_columns_by_columns(
}
if truncate_pos == 0 {
return WidthEstimation::new(widths_original, widths, width, false);
return WidthEstimation::new(widths_original, widths, width, false, false);
}
let mut available = termwidth - width;
@ -882,7 +956,7 @@ fn truncate_columns_by_columns(
}
if truncate_pos == count_columns {
return WidthEstimation::new(widths_original, widths, width, true);
return WidthEstimation::new(widths_original, widths, width, true, false);
}
if available >= trailing_column_width + vertical {
@ -892,7 +966,7 @@ fn truncate_columns_by_columns(
widths.push(trailing_column_width);
width += trailing_column_width + vertical;
return WidthEstimation::new(widths_original, widths, width, true);
return WidthEstimation::new(widths_original, widths, width, true, true);
}
truncate_rows(data, truncate_pos - 1);
@ -903,7 +977,7 @@ fn truncate_columns_by_columns(
widths.push(trailing_column_width);
width += trailing_column_width;
WidthEstimation::new(widths_original, widths, width, true)
WidthEstimation::new(widths_original, widths, width, true, true)
}
fn get_total_width2(widths: &[usize], cfg: &ColoredConfig) -> usize {
@ -921,37 +995,22 @@ fn create_config(theme: &TableTheme, with_header: bool, color: Option<Style>) ->
table.get_config().clone()
}
fn push_empty_column(data: &mut NuRecords) {
let records = std::mem::take(data);
let mut inner: Vec<Vec<_>> = records.into();
fn push_empty_column(data: &mut Vec<Vec<NuRecordsValue>>) {
let empty_cell = Text::new(String::from(EMPTY_COLUMN_TEXT));
for row in &mut inner {
for row in data {
row.push(empty_cell.clone());
}
*data = VecRecords::new(inner);
}
fn duplicate_row(data: &mut NuRecords, row: usize) {
let records = std::mem::take(data);
let mut inner: Vec<Vec<_>> = records.into();
let duplicate = inner[row].clone();
inner.push(duplicate);
*data = VecRecords::new(inner);
fn duplicate_row(data: &mut Vec<Vec<NuRecordsValue>>, row: usize) {
let duplicate = data[row].clone();
data.push(duplicate);
}
fn truncate_rows(data: &mut NuRecords, count: usize) {
let records = std::mem::take(data);
let mut inner: Vec<Vec<_>> = records.into();
for row in &mut inner {
fn truncate_rows(data: &mut Vec<Vec<NuRecordsValue>>, count: usize) {
for row in data {
row.truncate(count);
}
*data = VecRecords::new(inner);
}
fn convert_alignment(alignment: nu_color_config::Alignment) -> AlignmentHorizontal {
@ -971,7 +1030,11 @@ impl<R> TableOption<R, ColoredConfig, CompleteDimensionVecRecords<'_>> for SetDi
}
}
fn build_width(records: &NuRecords, pad: usize) -> Vec<usize> {
fn build_width<R>(records: R, pad: usize) -> Vec<usize>
where
R: Records,
<R::Iter as IntoRecords>::Cell: AsRef<str>,
{
// TODO: Expose not spaned version (could be optimized).
let mut cfg = SpannedConfig::default();
let padding = Sides {
@ -981,6 +1044,7 @@ fn build_width(records: &NuRecords, pad: usize) -> Vec<usize> {
cfg.set_padding(Entity::Global, padding);
// TODO: Use peekable width
SpannedGridDimension::width(records, &cfg)
}
@ -1059,3 +1123,11 @@ impl<R, C> TableOption<R, C, CompleteDimensionVecRecords<'_>> for &mut GetDims {
None
}
}
pub fn get_color_if_exists(c: &Color) -> Option<Color> {
if !is_color_empty(c) {
Some(c.clone())
} else {
None
}
}

View File

@ -1,11 +1,9 @@
use std::{cmp::max, collections::HashMap};
use std::cmp::max;
use nu_color_config::{Alignment, StyleComputer, TextStyle};
use nu_engine::column::get_columns;
use nu_protocol::{Config, Record, ShellError, Span, Value};
use tabled::grid::config::Position;
use crate::{
common::{
check_value, configure_table, error_sign, get_header_style, get_index_style, load_theme,
@ -14,7 +12,7 @@ use crate::{
},
string_width,
types::has_index,
NuRecordsValue, NuTable, TableOpts, TableOutput,
NuTable, TableOpts, TableOutput,
};
#[derive(Debug, Clone)]
@ -106,7 +104,7 @@ fn expand_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
const PADDING_SPACE: usize = 2;
const SPLIT_LINE_SPACE: usize = 1;
const ADDITIONAL_CELL_SPACE: usize = PADDING_SPACE + SPLIT_LINE_SPACE;
const MIN_CELL_CONTENT_WIDTH: usize = 1;
const MIN_CELL_CONTENT_WIDTH: usize = 3;
const TRUNCATE_CONTENT_WIDTH: usize = 3;
const TRUNCATE_CELL_WIDTH: usize = TRUNCATE_CONTENT_WIDTH + PADDING_SPACE;
@ -124,10 +122,7 @@ fn expand_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
}
let headers = get_columns(input);
let with_index = has_index(&cfg.opts, &headers);
let row_offset = cfg.opts.index_offset;
let mut rows_count = 0usize;
// The header with the INDEX is removed from the table headers since
// it is added to the natural table index
@ -135,162 +130,171 @@ fn expand_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
.into_iter()
.filter(|header| header != INDEX_COLUMN_NAME)
.collect();
let with_header = !headers.is_empty();
let row_offset = cfg.opts.index_offset;
let mut data = vec![vec![]; input.len() + with_header as usize];
let mut data_styles = HashMap::new();
let mut total_rows = 0usize;
if with_index {
if with_header {
data[0].push(NuRecordsValue::exact(String::from("#"), 1, vec![]));
}
for (row, item) in input.iter().enumerate() {
cfg.opts.signals.check(cfg.opts.span)?;
check_value(item)?;
let index = row + row_offset;
let text = item
.as_record()
.ok()
.and_then(|val| val.get(INDEX_COLUMN_NAME))
.map(|value| value.to_expanded_string("", cfg.opts.config))
.unwrap_or_else(|| index.to_string());
let row = row + with_header as usize;
let value = NuRecordsValue::new(text);
data[row].push(value);
}
let column_width = string_width(data[data.len() - 1][0].as_ref());
if column_width + ADDITIONAL_CELL_SPACE > available_width {
available_width = 0;
} else {
available_width -= column_width + ADDITIONAL_CELL_SPACE;
}
}
if !with_header {
if available_width > ADDITIONAL_CELL_SPACE {
available_width -= PADDING_SPACE;
} else {
if !with_index && !with_header {
if available_width <= ADDITIONAL_CELL_SPACE {
// it means we have no space left for actual content;
// which means there's no point in index itself if it was even used.
// so we do not print it.
return Ok(None);
}
available_width -= PADDING_SPACE;
let mut table = NuTable::new(input.len(), 1);
table.set_index_style(get_index_style(&cfg.opts.style_computer));
table.set_header_style(get_header_style(&cfg.opts.style_computer));
table.set_indent(cfg.opts.config.table.padding);
for (row, item) in input.iter().enumerate() {
cfg.opts.signals.check(cfg.opts.span)?;
check_value(item)?;
let inner_cfg = cfg_expand_reset_table(cfg.clone(), available_width);
let mut cell = expand_entry(item, inner_cfg);
let cell = expand_entry(item, inner_cfg);
let value_width = string_width(&cell.text);
if value_width > available_width {
// it must only happen when a string is produced, so we can safely wrap it.
// (it might be string table representation as well) (I guess I mean default { table ...} { list ...})
//
// todo: Maybe convert_to_table2_entry could do for strings to not mess caller code?
table.insert((row, 0), cell.text);
table.insert_style((row, 0), cell.style);
cell.text = wrap_text(&cell.text, available_width, cfg.opts.config);
}
let value = NuRecordsValue::new(cell.text);
data[row].push(value);
data_styles.insert((row, with_index as usize), cell.style);
rows_count = rows_count.saturating_add(cell.size);
total_rows = total_rows.saturating_add(cell.size);
}
let mut table = NuTable::from(data);
table.set_indent(cfg.opts.config.table.padding);
table.set_index_style(get_index_style(&cfg.opts.style_computer));
set_data_styles(&mut table, data_styles);
return Ok(Some(TableOutput::new(table, false, with_index, rows_count)));
return Ok(Some(TableOutput::new(table, false, false, total_rows)));
}
if !headers.is_empty() {
let mut pad_space = PADDING_SPACE;
if headers.len() > 1 {
pad_space += SPLIT_LINE_SPACE;
if !with_header && with_index {
let mut table = NuTable::new(input.len(), 2);
table.set_index_style(get_index_style(&cfg.opts.style_computer));
table.set_header_style(get_header_style(&cfg.opts.style_computer));
table.set_indent(cfg.opts.config.table.padding);
let mut index_column_width = 0;
for (row, item) in input.iter().enumerate() {
cfg.opts.signals.check(cfg.opts.span)?;
check_value(item)?;
let index = row + row_offset;
let index_value = item
.as_record()
.ok()
.and_then(|val| val.get(INDEX_COLUMN_NAME))
.map(|value| value.to_expanded_string("", cfg.opts.config))
.unwrap_or_else(|| index.to_string());
let index_width = string_width(&index_value);
if available_width <= index_width + ADDITIONAL_CELL_SPACE + PADDING_SPACE {
// NOTE: we don't wanna wrap index; so we return
return Ok(None);
}
table.insert((row, 0), index_value);
index_column_width = max(index_column_width, index_width);
}
if available_width < pad_space {
// there's no space for actual data so we don't return index if it's present.
// (also see the comment after the loop)
available_width -= index_column_width + ADDITIONAL_CELL_SPACE + PADDING_SPACE;
for (row, item) in input.iter().enumerate() {
cfg.opts.signals.check(cfg.opts.span)?;
check_value(item)?;
let inner_cfg = cfg_expand_reset_table(cfg.clone(), available_width);
let cell = expand_entry(item, inner_cfg);
table.insert((row, 1), cell.text);
table.insert_style((row, 1), cell.style);
total_rows = total_rows.saturating_add(cell.size);
}
return Ok(Some(TableOutput::new(table, false, true, total_rows)));
}
// NOTE: redefine to not break above logic (fixme)
let mut available_width = cfg.opts.width - SPLIT_LINE_SPACE;
let mut table = NuTable::new(input.len() + 1, headers.len() + with_index as usize);
table.set_index_style(get_index_style(&cfg.opts.style_computer));
table.set_header_style(get_header_style(&cfg.opts.style_computer));
table.set_indent(cfg.opts.config.table.padding);
let mut widths = Vec::new();
if with_index {
table.insert((0, 0), String::from("#"));
let mut index_column_width = 1;
for (row, item) in input.iter().enumerate() {
cfg.opts.signals.check(cfg.opts.span)?;
check_value(item)?;
let index = row + row_offset;
let index_value = item
.as_record()
.ok()
.and_then(|val| val.get(INDEX_COLUMN_NAME))
.map(|value| value.to_expanded_string("", cfg.opts.config))
.unwrap_or_else(|| index.to_string());
let index_width = string_width(&index_value);
table.insert((row + 1, 0), index_value);
index_column_width = max(index_column_width, index_width);
}
if available_width <= index_column_width + ADDITIONAL_CELL_SPACE {
// NOTE: we don't wanna wrap index; so we return
return Ok(None);
}
available_width -= index_column_width + ADDITIONAL_CELL_SPACE;
widths.push(index_column_width);
}
let count_columns = headers.len();
let mut widths = Vec::new();
let mut truncate = false;
let mut rendered_column = 0;
for (col, header) in headers.into_iter().enumerate() {
let column = col + with_index as usize;
let extra_space = PADDING_SPACE + SPLIT_LINE_SPACE;
if available_width <= extra_space {
table.pop_column(table.count_columns() - column);
widths.pop();
truncate = true;
break;
}
let mut available = available_width - extra_space;
// We want to reserver some space for next column
// If we can't fit it in it will be popped anyhow.
let is_last_column = col + 1 == count_columns;
let mut pad_space = PADDING_SPACE;
if !is_last_column {
pad_space += SPLIT_LINE_SPACE;
if !is_last_column && available > TRUNCATE_CELL_WIDTH {
available -= TRUNCATE_CELL_WIDTH;
}
let mut available = available_width - pad_space;
let mut total_column_rows = 0usize;
let mut column_width = 0;
if !is_last_column {
// we need to make sure that we have a space for a next column if we use available width
// so we might need to decrease a bit it.
// we consider a header width be a minimum width
let pad_space = PADDING_SPACE + TRUNCATE_CONTENT_WIDTH;
if available > pad_space {
// In we have no space for a next column,
// We consider showing something better then nothing,
// So we try to decrease the width to show at least a truncution column
available -= pad_space;
} else {
truncate = true;
break;
}
if available < column_width {
truncate = true;
break;
}
}
let mut column_rows = 0usize;
for (row, item) in input.iter().enumerate() {
cfg.opts.signals.check(cfg.opts.span)?;
check_value(item)?;
let inner_cfg = cfg_expand_reset_table(cfg.clone(), available);
let mut cell = expand_entry_with_header(item, &header, inner_cfg);
let mut value_width = string_width(&cell.text);
if value_width > available {
// it must only happen when a string is produced, so we can safely wrap it.
// (it might be string table representation as well)
cell.text = wrap_text(&cell.text, available, cfg.opts.config);
value_width = available;
}
let cell = expand_entry_with_header(item, &header, inner_cfg);
let value_width = string_width(&cell.text); // TODO: optimize cause when we expand we alrready know the width (most of the time or all)
column_width = max(column_width, value_width);
let value = NuRecordsValue::new(cell.text);
data[row + 1].push(value);
data_styles.insert((row + 1, col + with_index as usize), cell.style);
table.insert((row + 1, column), cell.text);
table.insert_style((row + 1, column), cell.style);
column_rows = column_rows.saturating_add(cell.size);
total_column_rows = total_column_rows.saturating_add(cell.size);
}
let mut head_width = string_width(&header);
@ -300,50 +304,37 @@ fn expand_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
head_width = available;
}
let head_cell = NuRecordsValue::new(header);
data[0].push(head_cell);
table.insert((0, column), header);
column_width = max(column_width, head_width);
if column_width > available {
// remove the column we just inserted
for row in &mut data {
row.pop();
}
truncate = true;
break;
}
assert!(column_width <= available);
widths.push(column_width);
available_width -= pad_space + column_width;
available_width -= column_width + extra_space;
rendered_column += 1;
rows_count = std::cmp::max(rows_count, column_rows);
}
if truncate && rendered_column == 0 {
// it means that no actual data was rendered, there might be only index present,
// so there's no point in rendering the table.
//
// It's actually quite important in case it's called recursively,
// cause we will back up to the basic table view as a string e.g. '[table 123 columns]'.
//
// But potentially if its reached as a 1st called function we might would love to see the index.
return Ok(None);
total_rows = std::cmp::max(total_rows, total_column_rows);
}
if truncate {
if rendered_column == 0 {
// it means that no actual data was rendered, there might be only index present,
// so there's no point in rendering the table.
//
// It's actually quite important in case it's called recursively,
// cause we will back up to the basic table view as a string e.g. '[table 123 columns]'.
//
// But potentially if its reached as a 1st called function we might would love to see the index.
return Ok(None);
}
if available_width < TRUNCATE_CELL_WIDTH {
// back up by removing last column.
// it's LIKELY that removing only 1 column will leave us enough space for a shift column.
while let Some(width) = widths.pop() {
for row in &mut data {
row.pop();
}
table.pop_column(1);
available_width += width + PADDING_SPACE;
if !widths.is_empty() {
@ -364,22 +355,12 @@ fn expand_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
let is_last_column = widths.len() == count_columns;
if !is_last_column {
let shift = NuRecordsValue::exact(String::from("..."), 3, vec![]);
for row in &mut data {
row.push(shift.clone());
}
table.push_column(String::from("..."));
widths.push(3);
}
}
let mut table = NuTable::from(data);
table.set_index_style(get_index_style(&cfg.opts.style_computer));
table.set_header_style(get_header_style(&cfg.opts.style_computer));
table.set_indent(cfg.opts.config.table.padding);
set_data_styles(&mut table, data_styles);
Ok(Some(TableOutput::new(table, true, with_index, rows_count)))
Ok(Some(TableOutput::new(table, true, with_index, total_rows)))
}
fn expanded_table_kv(record: &Record, cfg: Cfg<'_>) -> CellResult {
@ -400,10 +381,13 @@ fn expanded_table_kv(record: &Record, cfg: Cfg<'_>) -> CellResult {
let value_width = cfg.opts.width - key_width - count_borders - padding - padding;
let mut count_rows = 0usize;
let mut total_rows = 0usize;
let mut data = Vec::with_capacity(record.len());
for (key, value) in record {
let mut table = NuTable::new(record.len(), 2);
table.set_index_style(get_key_style(&cfg));
table.set_indent(cfg.opts.config.table.padding);
for (i, (key, value)) in record.iter().enumerate() {
cfg.opts.signals.check(cfg.opts.span)?;
let cell = match expand_value(value, value_width, &cfg)? {
@ -411,29 +395,24 @@ fn expanded_table_kv(record: &Record, cfg: Cfg<'_>) -> CellResult {
None => return Ok(None),
};
let value = cell.text;
let mut key = key.to_owned();
// we want to have a key being aligned to 2nd line,
// we could use Padding for it but,
// the easiest way to do so is just push a new_line char before
let mut key = key.to_owned();
let is_key_on_next_line = !key.is_empty() && cell.is_expanded && theme.borders_has_top();
if is_key_on_next_line {
key.insert(0, '\n');
}
let key = NuRecordsValue::new(key);
let val = NuRecordsValue::new(cell.text);
let row = vec![key, val];
table.insert((i, 0), key);
table.insert((i, 1), value);
data.push(row);
count_rows = count_rows.saturating_add(cell.size);
total_rows = total_rows.saturating_add(cell.size);
}
let mut table = NuTable::from(data);
table.set_index_style(get_key_style(&cfg));
table.set_indent(cfg.opts.config.table.padding);
let mut out = TableOutput::new(table, false, true, count_rows);
let mut out = TableOutput::new(table, false, true, total_rows);
configure_table(
&mut out,
@ -443,7 +422,7 @@ fn expanded_table_kv(record: &Record, cfg: Cfg<'_>) -> CellResult {
);
maybe_expand_table(out, cfg.opts.width)
.map(|value| value.map(|value| CellOutput::clean(value, count_rows, false)))
.map(|value| value.map(|value| CellOutput::clean(value, total_rows, false)))
}
// the flag is used as an optimization to not do `value.lines().count()` search.
@ -519,6 +498,7 @@ fn expand_entry_with_header(item: &Value, header: &str, cfg: Cfg<'_>) -> CellOut
fn expand_entry(item: &Value, cfg: Cfg<'_>) -> CellOutput {
if is_limit_reached(&cfg) {
let value = nu_value_to_string_clean(item, cfg.opts.config, &cfg.opts.style_computer);
let value = nutext_wrap(value, &cfg);
return CellOutput::styled(value);
}
@ -527,6 +507,7 @@ fn expand_entry(item: &Value, cfg: Cfg<'_>) -> CellOutput {
Value::Record { val: record, .. } => {
if record.is_empty() {
let value = nu_value_to_string(item, cfg.opts.config, &cfg.opts.style_computer);
let value = nutext_wrap(value, &cfg);
return CellOutput::styled(value);
}
@ -538,6 +519,7 @@ fn expand_entry(item: &Value, cfg: Cfg<'_>) -> CellOutput {
Ok(Some(table)) => table,
_ => {
let value = nu_value_to_string(item, cfg.opts.config, &cfg.opts.style_computer);
let value = nutext_wrap(value, &cfg);
CellOutput::styled(value)
}
}
@ -560,6 +542,7 @@ fn expand_entry(item: &Value, cfg: Cfg<'_>) -> CellOutput {
Ok(Some(out)) => out,
_ => {
let value = nu_value_to_string(item, cfg.opts.config, &cfg.opts.style_computer);
let value = nutext_wrap(value, &cfg);
return CellOutput::styled(value);
}
};
@ -571,17 +554,28 @@ fn expand_entry(item: &Value, cfg: Cfg<'_>) -> CellOutput {
Some(table) => CellOutput::clean(table, out.count_rows, false),
None => {
let value = nu_value_to_string(item, cfg.opts.config, &cfg.opts.style_computer);
let value = nutext_wrap(value, &cfg);
CellOutput::styled(value)
}
}
}
_ => {
let value = nu_value_to_string_clean(item, cfg.opts.config, &cfg.opts.style_computer);
let value = nutext_wrap(value, &cfg);
CellOutput::styled(value)
}
}
}
fn nutext_wrap(mut text: NuText, cfg: &Cfg<'_>) -> NuText {
let width = string_width(&text.0);
if width > cfg.opts.width {
text.0 = wrap_text(&text.0, cfg.opts.width, cfg.opts.config);
}
text
}
fn is_limit_reached(cfg: &Cfg<'_>) -> bool {
matches!(cfg.format.expand_limit, Some(0))
}
@ -626,12 +620,6 @@ fn maybe_expand_table(mut out: TableOutput, term_width: usize) -> StringResult {
Ok(table)
}
fn set_data_styles(table: &mut NuTable, styles: HashMap<Position, TextStyle>) {
for (pos, style) in styles {
table.insert_style(pos, style);
}
}
fn table_apply_config(out: &mut TableOutput, cfg: &Cfg<'_>) {
configure_table(
out,

View File

@ -15,23 +15,23 @@ use crate::{
pub struct JustTable;
impl JustTable {
pub fn table(input: &[Value], opts: TableOpts<'_>) -> StringResult {
pub fn table(input: Vec<Value>, opts: TableOpts<'_>) -> StringResult {
list_table(input, opts)
}
pub fn kv_table(record: &Record, opts: TableOpts<'_>) -> StringResult {
pub fn kv_table(record: Record, opts: TableOpts<'_>) -> StringResult {
kv_table(record, opts)
}
}
fn list_table(input: &[Value], opts: TableOpts<'_>) -> Result<Option<String>, ShellError> {
let mut out = match create_table(input, &opts)? {
fn list_table(input: Vec<Value>, opts: TableOpts<'_>) -> Result<Option<String>, ShellError> {
let output = create_table(input, &opts)?;
let mut out = match output {
Some(out) => out,
None => return Ok(None),
};
out.table.set_indent(opts.config.table.padding);
// TODO: It would be WAY more effitient to do right away instead of second pass over the data.
colorize_space(out.table.get_records_mut(), &opts.style_computer);
configure_table(&mut out, opts.config, &opts.style_computer, opts.mode);
@ -40,25 +40,20 @@ fn list_table(input: &[Value], opts: TableOpts<'_>) -> Result<Option<String>, Sh
Ok(table)
}
fn kv_table(record: &Record, opts: TableOpts<'_>) -> StringResult {
let mut data = vec![Vec::with_capacity(2); record.len()];
for ((column, value), row) in record.iter().zip(data.iter_mut()) {
opts.signals.check(opts.span)?;
let key = NuRecordsValue::new(column.to_string());
let value = nu_value_to_string_colored(value, opts.config, &opts.style_computer);
let value = NuRecordsValue::new(value);
row.push(key);
row.push(value);
}
let mut table = NuTable::from(data);
fn kv_table(record: Record, opts: TableOpts<'_>) -> StringResult {
let mut table = NuTable::new(record.len(), 2);
table.set_index_style(TextStyle::default_field());
table.set_indent(opts.config.table.padding);
for (i, (key, value)) in record.into_iter().enumerate() {
opts.signals.check(opts.span)?;
let value = nu_value_to_string_colored(&value, opts.config, &opts.style_computer);
table.insert((i, 0), key);
table.insert((i, 1), value);
}
let mut out = TableOutput::from_table(table, false, true);
configure_table(&mut out, opts.config, &opts.style_computer, opts.mode);
let table = out.table.draw(opts.width);
@ -66,12 +61,12 @@ fn kv_table(record: &Record, opts: TableOpts<'_>) -> StringResult {
Ok(table)
}
fn create_table(input: &[Value], opts: &TableOpts<'_>) -> TableResult {
fn create_table(input: Vec<Value>, opts: &TableOpts<'_>) -> TableResult {
if input.is_empty() {
return Ok(None);
}
let headers = get_columns(input);
let headers = get_columns(&input);
let with_index = has_index(opts, &headers);
let with_header = !headers.is_empty();
let row_offset = opts.index_offset;
@ -89,27 +84,23 @@ fn create_table(input: &[Value], opts: &TableOpts<'_>) -> TableResult {
}
fn create_table_with_header(
input: &[Value],
input: Vec<Value>,
headers: Vec<String>,
opts: &TableOpts<'_>,
) -> Result<Option<NuTable>, ShellError> {
let headers = collect_headers(headers, false);
let count_rows = input.len() + 1;
let count_columns = headers.len();
let mut table = NuTable::new(count_rows, count_columns);
table.set_header_style(get_header_style(&opts.style_computer));
table.set_index_style(get_index_style(&opts.style_computer));
table.set_indent(opts.config.table.padding);
table.set_row(0, headers.clone());
for (row, item) in input.iter().enumerate() {
for (row, item) in input.into_iter().enumerate() {
opts.signals.check(opts.span)?;
check_value(item)?;
check_value(&item)?;
for (col, header) in headers.iter().enumerate() {
let (text, style) = get_string_value_with_header(item, header.as_ref(), opts);
let (text, style) = get_string_value_with_header(&item, header, opts);
let pos = (row + 1, col);
table.insert(pos, text);
@ -117,35 +108,39 @@ fn create_table_with_header(
}
}
let headers = collect_headers(headers, false);
table.set_row(0, headers);
Ok(Some(table))
}
fn create_table_with_header_and_index(
input: &[Value],
input: Vec<Value>,
headers: Vec<String>,
row_offset: usize,
opts: &TableOpts<'_>,
) -> Result<Option<NuTable>, ShellError> {
let headers = collect_headers(headers, true);
let head = collect_headers(headers, true);
let count_rows = input.len() + 1;
let count_columns = headers.len();
let mut table = NuTable::new(count_rows, count_columns);
let count_columns = head.len();
let mut table = NuTable::new(count_rows, count_columns);
table.set_header_style(get_header_style(&opts.style_computer));
table.set_index_style(get_index_style(&opts.style_computer));
table.set_indent(opts.config.table.padding);
table.set_row(0, headers.clone());
table.set_row(0, head.clone());
for (row, item) in input.iter().enumerate() {
for (row, item) in input.into_iter().enumerate() {
opts.signals.check(opts.span)?;
check_value(item)?;
check_value(&item)?;
let text = get_table_row_index(item, opts.config, row, row_offset);
let text = get_table_row_index(&item, opts.config, row, row_offset);
table.insert((row + 1, 0), text);
for (col, header) in headers.iter().enumerate().skip(1) {
let (text, style) = get_string_value_with_header(item, header.as_ref(), opts);
for (col, head) in head.iter().enumerate().skip(1) {
let (text, style) = get_string_value_with_header(&item, head.as_ref(), opts);
let pos = (row + 1, col);
table.insert(pos, text);
@ -157,46 +152,45 @@ fn create_table_with_header_and_index(
}
fn create_table_with_no_header(
input: &[Value],
input: Vec<Value>,
opts: &TableOpts<'_>,
) -> Result<Option<NuTable>, ShellError> {
let mut table = NuTable::new(input.len(), 1);
table.set_index_style(get_index_style(&opts.style_computer));
table.set_indent(opts.config.table.padding);
for (row, item) in input.iter().enumerate() {
for (row, item) in input.into_iter().enumerate() {
opts.signals.check(opts.span)?;
check_value(item)?;
check_value(&item)?;
let (text, style) = get_string_value(item, opts);
let (text, style) = get_string_value(&item, opts);
let pos = (row, 0);
table.insert(pos, text);
table.insert_style(pos, style);
table.insert((row, 0), text);
table.insert_style((row, 0), style);
}
Ok(Some(table))
}
fn create_table_with_no_header_and_index(
input: &[Value],
input: Vec<Value>,
row_offset: usize,
opts: &TableOpts<'_>,
) -> Result<Option<NuTable>, ShellError> {
let mut table = NuTable::new(input.len(), 1 + 1);
table.set_index_style(get_index_style(&opts.style_computer));
table.set_indent(opts.config.table.padding);
for (row, item) in input.iter().enumerate() {
for (row, item) in input.into_iter().enumerate() {
opts.signals.check(opts.span)?;
check_value(item)?;
check_value(&item)?;
let text = get_table_row_index(item, opts.config, row, row_offset);
table.insert((row, 0), text);
let index = get_table_row_index(&item, opts.config, row, row_offset);
let (value, style) = get_string_value(&item, opts);
let (text, style) = get_string_value(item, opts);
let pos = (row, 1);
table.insert(pos, text);
table.insert_style(pos, style);
table.insert((row, 0), index);
table.insert((row, 1), value);
table.insert_style((row, 1), style);
}
Ok(Some(table))

View File

@ -88,7 +88,14 @@ where
}
pub fn create_table(data: Data, case: TestCase) -> Option<String> {
let mut table = NuTable::from(data);
let count_rows = data.len();
let count_cols = data[0].len();
let mut table = NuTable::new(count_rows, count_cols);
for (i, row) in data.into_iter().enumerate() {
table.set_row(i, row);
}
table.set_theme(case.theme);
table.set_structure(case.with_index, case.with_header, case.with_footer);
table.set_trim(case.strategy);