mirror of
https://github.com/nushell/nushell.git
synced 2025-08-09 05:04:40 +02:00
nu-table: optimize table creation and width functions (#15900)
> Further tests are welcomed. It was already implemented that we precalculate widths, but nothing stops to do heights as well. Because before all the calculus were wasted (literally). It affects `table` and `table --expand`. The only case when it does not work (even makes things slightly less optimal in case of `table` when `truncation` is used) Sadly my tests are not showing the clear benefit. I have no idea why I was expecting something 😞 But it must be there :) Running `scope commands` + `$env.CMD_DURATION_MS`: ```log # patch (release) 2355 2462 2210 2356 2303 # main (release) 2375 2240 2202 2297 2385 ``` PS: as once mentioned all this stuff ought to be moved out `nu-table` --------- Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>
This commit is contained in:
@ -21,7 +21,7 @@ use tabled::{
|
||||
},
|
||||
dimension::{CompleteDimension, PeekableGridDimension},
|
||||
records::{
|
||||
IterRecords,
|
||||
IterRecords, PeekableRecords,
|
||||
vec_records::{Cell, Text, VecRecords},
|
||||
},
|
||||
},
|
||||
@ -47,6 +47,7 @@ pub type NuRecordsValue = Text<String>;
|
||||
pub struct NuTable {
|
||||
data: Vec<Vec<NuRecordsValue>>,
|
||||
widths: Vec<usize>,
|
||||
heights: Vec<usize>,
|
||||
count_rows: usize,
|
||||
count_cols: usize,
|
||||
styles: Styles,
|
||||
@ -59,6 +60,7 @@ impl NuTable {
|
||||
Self {
|
||||
data: vec![vec![Text::default(); count_cols]; count_rows],
|
||||
widths: vec![2; count_cols],
|
||||
heights: vec![0; count_rows],
|
||||
count_rows,
|
||||
count_cols,
|
||||
styles: Styles {
|
||||
@ -98,14 +100,19 @@ impl NuTable {
|
||||
|
||||
pub fn insert_value(&mut self, pos: (usize, usize), value: NuRecordsValue) {
|
||||
let width = value.width() + indent_sum(self.config.indent);
|
||||
let height = value.count_lines();
|
||||
self.widths[pos.1] = max(self.widths[pos.1], width);
|
||||
self.heights[pos.0] = max(self.heights[pos.0], height);
|
||||
self.data[pos.0][pos.1] = value;
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, pos: (usize, usize), text: String) {
|
||||
let text = Text::new(text);
|
||||
let width = text.width() + indent_sum(self.config.indent);
|
||||
let pad = indent_sum(self.config.indent);
|
||||
let width = text.width() + pad;
|
||||
let height = text.count_lines();
|
||||
self.widths[pos.1] = max(self.widths[pos.1], width);
|
||||
self.heights[pos.0] = max(self.heights[pos.0], height);
|
||||
self.data[pos.0][pos.1] = text;
|
||||
}
|
||||
|
||||
@ -113,10 +120,12 @@ impl NuTable {
|
||||
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),
|
||||
);
|
||||
let pad = indent_sum(self.config.indent);
|
||||
let width = text.width() + pad;
|
||||
let height = text.count_lines();
|
||||
|
||||
self.widths[i] = max(self.widths[i], width);
|
||||
self.heights[index] = max(self.heights[index], height);
|
||||
}
|
||||
|
||||
self.data[index] = row;
|
||||
@ -125,8 +134,23 @@ impl NuTable {
|
||||
pub fn pop_column(&mut self, count: usize) {
|
||||
self.count_cols -= count;
|
||||
self.widths.truncate(self.count_cols);
|
||||
for row in &mut self.data[..] {
|
||||
|
||||
for (row, height) in self.data.iter_mut().zip(self.heights.iter_mut()) {
|
||||
row.truncate(self.count_cols);
|
||||
|
||||
let row_height = *height;
|
||||
let mut new_height = 0;
|
||||
for cell in row.iter() {
|
||||
let height = cell.count_lines();
|
||||
if height == row_height {
|
||||
new_height = height;
|
||||
break;
|
||||
}
|
||||
|
||||
new_height = max(new_height, height);
|
||||
}
|
||||
|
||||
*height = new_height;
|
||||
}
|
||||
|
||||
// set to default styles of the popped columns
|
||||
@ -146,8 +170,14 @@ impl NuTable {
|
||||
pub fn push_column(&mut self, text: String) {
|
||||
let value = Text::new(text);
|
||||
|
||||
self.widths
|
||||
.push(value.width() + indent_sum(self.config.indent));
|
||||
let pad = indent_sum(self.config.indent);
|
||||
let width = value.width() + pad;
|
||||
let height = value.count_lines();
|
||||
self.widths.push(width);
|
||||
|
||||
for row in 0..self.count_rows {
|
||||
self.heights[row] = max(self.heights[row], height);
|
||||
}
|
||||
|
||||
for row in &mut self.data[..] {
|
||||
row.push(value.clone());
|
||||
@ -274,13 +304,19 @@ impl NuTable {
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: Must never be called from nu-table - made only for tests
|
||||
// FIXME: remove it?
|
||||
// #[cfg(test)]
|
||||
impl From<Vec<Vec<Text<String>>>> for NuTable {
|
||||
fn from(value: Vec<Vec<Text<String>>>) -> Self {
|
||||
let count_rows = value.len();
|
||||
let count_cols = if value.is_empty() { 0 } else { value[0].len() };
|
||||
|
||||
let mut t = Self::new(count_rows, count_cols);
|
||||
t.data = value;
|
||||
for (i, row) in value.into_iter().enumerate() {
|
||||
t.set_row(i, row);
|
||||
}
|
||||
|
||||
table_recalculate_widths(&mut t);
|
||||
|
||||
t
|
||||
@ -398,8 +434,15 @@ fn is_header_on_border(t: &NuTable) -> bool {
|
||||
}
|
||||
|
||||
fn table_insert_footer_if(t: &mut NuTable) {
|
||||
if t.config.structure.with_header && t.config.structure.with_footer {
|
||||
let with_footer = t.config.structure.with_header && t.config.structure.with_footer;
|
||||
if !with_footer {
|
||||
return;
|
||||
}
|
||||
|
||||
duplicate_row(&mut t.data, 0);
|
||||
|
||||
if !t.heights.is_empty() {
|
||||
t.heights.push(t.heights[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -453,8 +496,12 @@ fn remove_header(t: &mut NuTable) -> HeadInfo {
|
||||
.map(|s| s.to_string())
|
||||
.collect();
|
||||
|
||||
// drop height row
|
||||
t.heights.remove(0);
|
||||
|
||||
// WE NEED TO RELCULATE WIDTH.
|
||||
// TODO: cause we have configuration beforehand we can just not calculate it in?
|
||||
// Why we do it exactly??
|
||||
table_recalculate_widths(t);
|
||||
|
||||
let alignment = t.styles.alignments.header;
|
||||
@ -482,7 +529,7 @@ fn draw_table(
|
||||
set_styles(&mut table, t.styles, &structure);
|
||||
set_indent(&mut table, t.config.indent);
|
||||
load_theme(&mut table, &t.config.theme, &structure, sep_color);
|
||||
truncate_table(&mut table, &t.config, width, termwidth);
|
||||
truncate_table(&mut table, &t.config, width, termwidth, t.heights);
|
||||
table_set_border_header(&mut table, head, &t.config);
|
||||
|
||||
let string = table.to_string();
|
||||
@ -527,10 +574,16 @@ fn table_set_border_header(table: &mut Table, head: Option<HeadInfo>, cfg: &Tabl
|
||||
table.with(SetLineHeaders::new(head, 0, pad));
|
||||
}
|
||||
|
||||
fn truncate_table(table: &mut Table, cfg: &TableConfig, width: WidthEstimation, termwidth: usize) {
|
||||
fn truncate_table(
|
||||
table: &mut Table,
|
||||
cfg: &TableConfig,
|
||||
width: WidthEstimation,
|
||||
termwidth: usize,
|
||||
heights: Vec<usize>,
|
||||
) {
|
||||
let trim = cfg.trim.clone();
|
||||
let pad = cfg.indent.left + cfg.indent.right;
|
||||
let ctrl = WidthCtrl::new(termwidth, width, trim, cfg.expand, pad);
|
||||
let pad = indent_sum(cfg.indent);
|
||||
let ctrl = DimensionCtrl::new(termwidth, width, trim, cfg.expand, pad, heights);
|
||||
table.with(ctrl);
|
||||
}
|
||||
|
||||
@ -542,21 +595,23 @@ fn set_indent(table: &mut Table, indent: TableIndent) {
|
||||
table.with(Padding::new(indent.left, indent.right, 0, 0));
|
||||
}
|
||||
|
||||
struct WidthCtrl {
|
||||
struct DimensionCtrl {
|
||||
width: WidthEstimation,
|
||||
trim_strategy: TrimStrategy,
|
||||
max_width: usize,
|
||||
expand: bool,
|
||||
pad: usize,
|
||||
heights: Vec<usize>,
|
||||
}
|
||||
|
||||
impl WidthCtrl {
|
||||
impl DimensionCtrl {
|
||||
fn new(
|
||||
max_width: usize,
|
||||
width: WidthEstimation,
|
||||
trim_strategy: TrimStrategy,
|
||||
expand: bool,
|
||||
pad: usize,
|
||||
heights: Vec<usize>,
|
||||
) -> Self {
|
||||
Self {
|
||||
width,
|
||||
@ -564,6 +619,7 @@ impl WidthCtrl {
|
||||
max_width,
|
||||
expand,
|
||||
pad,
|
||||
heights,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -596,7 +652,7 @@ impl WidthEstimation {
|
||||
}
|
||||
}
|
||||
|
||||
impl TableOption<NuRecords, ColoredConfig, CompleteDimension> for WidthCtrl {
|
||||
impl TableOption<NuRecords, ColoredConfig, CompleteDimension> for DimensionCtrl {
|
||||
fn change(self, recs: &mut NuRecords, cfg: &mut ColoredConfig, dims: &mut CompleteDimension) {
|
||||
if self.width.truncate {
|
||||
width_ctrl_truncate(self, recs, cfg, dims);
|
||||
@ -609,30 +665,43 @@ impl TableOption<NuRecords, ColoredConfig, CompleteDimension> for WidthCtrl {
|
||||
}
|
||||
|
||||
// NOTE: just an optimization; to not recalculate it internally
|
||||
dims.set_heights(self.heights);
|
||||
dims.set_widths(self.width.needed);
|
||||
}
|
||||
|
||||
fn hint_change(&self) -> Option<Entity> {
|
||||
// NOTE:
|
||||
// Because we are assuming that:
|
||||
// len(lines(wrapped(string))) >= len(lines(string))
|
||||
//
|
||||
// Only truncation case must be relaclucated in term of height.
|
||||
if self.width.truncate && matches!(self.trim_strategy, TrimStrategy::Truncate { .. }) {
|
||||
Some(Entity::Row(0))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn width_ctrl_expand(
|
||||
ctrl: WidthCtrl,
|
||||
ctrl: DimensionCtrl,
|
||||
recs: &mut NuRecords,
|
||||
cfg: &mut ColoredConfig,
|
||||
dims: &mut CompleteDimension,
|
||||
) {
|
||||
dims.set_heights(ctrl.heights);
|
||||
let opt = Width::increase(ctrl.max_width);
|
||||
TableOption::<NuRecords, _, _>::change(opt, recs, cfg, dims);
|
||||
}
|
||||
|
||||
fn width_ctrl_truncate(
|
||||
ctrl: WidthCtrl,
|
||||
ctrl: DimensionCtrl,
|
||||
recs: &mut NuRecords,
|
||||
cfg: &mut ColoredConfig,
|
||||
dims: &mut CompleteDimension,
|
||||
) {
|
||||
let mut heights = ctrl.heights;
|
||||
|
||||
// todo: maybe general for loop better
|
||||
for (col, (&width, width_original)) in ctrl
|
||||
.width
|
||||
@ -652,6 +721,13 @@ fn width_ctrl_truncate(
|
||||
let wrap = Width::wrap(width).keep_words(*try_to_keep_words);
|
||||
|
||||
CellOption::<NuRecords, _>::change(wrap, recs, cfg, Entity::Column(col));
|
||||
|
||||
// NOTE: An optimization to have proper heights without going over all the data again.
|
||||
// We are going only for all rows in changed columns
|
||||
for (row, row_height) in heights.iter_mut().enumerate() {
|
||||
let height = recs.count_lines(Position::new(row, col));
|
||||
*row_height = max(*row_height, height);
|
||||
}
|
||||
}
|
||||
TrimStrategy::Truncate { suffix } => {
|
||||
let mut truncate = Width::truncate(width);
|
||||
@ -664,6 +740,7 @@ fn width_ctrl_truncate(
|
||||
}
|
||||
}
|
||||
|
||||
dims.set_heights(heights);
|
||||
dims.set_widths(ctrl.width.needed);
|
||||
}
|
||||
|
||||
@ -675,22 +752,12 @@ fn align_table(
|
||||
table.with(AlignmentStrategy::PerLine);
|
||||
|
||||
if structure.with_header {
|
||||
table.modify(
|
||||
Rows::first(),
|
||||
(
|
||||
AlignmentStrategy::PerCell,
|
||||
Alignment::from(alignments.header),
|
||||
),
|
||||
);
|
||||
table.modify(Rows::first(), AlignmentStrategy::PerCell);
|
||||
table.modify(Rows::first(), Alignment::from(alignments.header));
|
||||
|
||||
if structure.with_footer {
|
||||
table.modify(
|
||||
Rows::last(),
|
||||
(
|
||||
AlignmentStrategy::PerCell,
|
||||
Alignment::from(alignments.header),
|
||||
),
|
||||
);
|
||||
table.modify(Rows::last(), AlignmentStrategy::PerCell);
|
||||
table.modify(Rows::last(), Alignment::from(alignments.header));
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user