nu-table/ Fix --collapse wrap/truncate [WIP] (#8042)

close #8034

So it's actually a work around to do "our best".

We basically try 3 attemps to build a table with each time truncating
values bigger then (termwidth/2, termwidth/4, termwidth/8).

PS1: The logic is kind of not ideal need to think about it.

_______________

PS2:
I've just noticed that multi-line key behaves strangely; (not sure
whether it's caused by this wrap (unlikely))
Need to investigate this.

I'd bet it's a multiline key.


![image](https://user-images.githubusercontent.com/20165848/218267237-6b7ee9af-675a-428e-92cd-65808cec50f0.png)

---------

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>
This commit is contained in:
Maxim Zhiburt 2023-02-22 17:10:17 +03:00 committed by GitHub
parent 8608d8d873
commit 0ab6b66d8f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -1,18 +1,23 @@
use std::collections::HashMap; use std::collections::HashMap;
use crate::{table::TrimStrategyModifier, Alignments, TableTheme}; use crate::{string_truncate, string_width, Alignments, TableTheme};
use nu_color_config::StyleComputer; use nu_color_config::StyleComputer;
use nu_protocol::{Config, Span, Value}; use nu_protocol::{Config, Span, Value};
use tabled::{ use tabled::{
color::Color, formatting::AlignmentStrategy, object::Segment, papergrid::records::Records, color::Color,
Alignment, Modify, Table, formatting::AlignmentStrategy,
object::Segment,
papergrid::{records::Records, GridConfig},
Alignment, Modify,
}; };
use serde_json::Value as Json;
/// NuTable has a recursive table representation of nu_protocol::Value. /// NuTable has a recursive table representation of nu_protocol::Value.
/// ///
/// It doesn't support alignment and a proper width control. /// It doesn't support alignment and a proper width control.
pub struct NuTable { pub struct NuTable {
inner: tabled::Table, inner: Option<String>,
} }
impl NuTable { impl NuTable {
@ -29,7 +34,48 @@ impl NuTable {
load_theme(&mut table, style_computer, theme); load_theme(&mut table, style_computer, theme);
let cfg = table.get_config().clone(); let cfg = table.get_config().clone();
let val = nu_protocol_value_to_json(value, config, with_footer); let mut val = nu_protocol_value_to_json(value, config, with_footer);
let table = build_table(val.clone(), cfg.clone(), collapse);
let table_width = string_width(&table);
if table_width > termwidth {
// Doing a soffisticated width control would require some deep rooted changes.
// (Which is might neessery to be done)
//
// Instead we peek biggest cells 1 by 1 and truncating them.
loop {
match get_biggest_value(&mut val) {
Some((value, width)) => {
if width == 0 {
return Self { inner: None };
}
let need_to_cut = width - 1;
__truncate_value(value, need_to_cut);
let table = build_table(val.clone(), cfg.clone(), collapse);
let table_width = string_width(&table);
if table_width <= termwidth {
return Self { inner: Some(table) };
}
}
None => return Self { inner: None },
}
}
}
Self { inner: Some(table) }
}
pub fn draw(&self) -> Option<String> {
self.inner.clone()
}
}
fn build_table(val: Json, cfg: GridConfig, collapse: bool) -> String {
let mut table = json_to_table::json_to_table(&val); let mut table = json_to_table::json_to_table(&val);
table.set_config(cfg); table.set_config(cfg);
@ -37,22 +83,10 @@ impl NuTable {
table.collapse(); table.collapse();
} }
let mut table: Table<_> = table.into(); table.to_string()
table.with(TrimStrategyModifier::new(termwidth, &config.trim_strategy));
Self { inner: table }
}
pub fn draw(&self) -> Option<String> {
Some(self.inner.to_string())
}
} }
fn nu_protocol_value_to_json( fn nu_protocol_value_to_json(value: Value, config: &Config, with_footer: bool) -> Json {
value: Value,
config: &Config,
with_footer: bool,
) -> serde_json::Value {
match value { match value {
Value::Record { cols, vals, .. } => { Value::Record { cols, vals, .. } => {
let mut map = serde_json::Map::new(); let mut map = serde_json::Map::new();
@ -61,7 +95,7 @@ fn nu_protocol_value_to_json(
map.insert(key, val); map.insert(key, val);
} }
serde_json::Value::Object(map) Json::Object(map)
} }
Value::List { vals, .. } => { Value::List { vals, .. } => {
let mut used_cols: Option<&[String]> = None; let mut used_cols: Option<&[String]> = None;
@ -95,29 +129,27 @@ fn nu_protocol_value_to_json(
}); });
let head = build_map(head, config); let head = build_map(head, config);
arr.push(serde_json::Value::Object(head.clone())); arr.push(Json::Object(head.clone()));
for value in &vals { for value in &vals {
if let Ok((_, vals)) = value.as_record() { if let Ok((_, vals)) = value.as_record() {
let vals = build_map(vals.iter().cloned(), config); let vals = build_map(vals.iter().cloned(), config);
let mut map = serde_json::Map::new(); let mut map = serde_json::Map::new();
connect_maps(&mut map, serde_json::Value::Object(vals)); connect_maps(&mut map, Json::Object(vals));
arr.push(serde_json::Value::Object(map)); arr.push(Json::Object(map));
} }
} }
if with_footer { if with_footer {
arr.push(serde_json::Value::Object(head)); arr.push(Json::Object(head));
} }
return serde_json::Value::Array(arr); return Json::Array(arr);
} else { } else {
let mut map = vec![]; let mut map = vec![];
let head = serde_json::Value::Array(vec![serde_json::Value::String( let head = Json::Array(vec![Json::String(cols[0].to_owned())]);
cols[0].to_owned(),
)]);
map.push(head.clone()); map.push(head.clone());
for value in vals { for value in vals {
@ -136,7 +168,7 @@ fn nu_protocol_value_to_json(
map.push(head); map.push(head);
} }
return serde_json::Value::Array(map); return Json::Array(map);
}; };
} }
@ -146,16 +178,16 @@ fn nu_protocol_value_to_json(
map.push(val); map.push(val);
} }
serde_json::Value::Array(map) Json::Array(map)
} }
val => serde_json::Value::String(val.into_abbreviated_string(config)), val => Json::String(val.into_abbreviated_string(config)),
} }
} }
fn build_map( fn build_map(
values: impl Iterator<Item = Value> + DoubleEndedIterator, values: impl Iterator<Item = Value> + DoubleEndedIterator,
config: &Config, config: &Config,
) -> serde_json::Map<String, serde_json::Value> { ) -> serde_json::Map<String, Json> {
let mut map = serde_json::Map::new(); let mut map = serde_json::Map::new();
let mut last_val: Option<Value> = None; let mut last_val: Option<Value> = None;
for val in values.rev() { for val in values.rev() {
@ -174,7 +206,7 @@ fn build_map(
let mut new_m = serde_json::Map::new(); let mut new_m = serde_json::Map::new();
let col = val.into_abbreviated_string(&Config::default()); let col = val.into_abbreviated_string(&Config::default());
new_m.insert(col, serde_json::Value::Object(map)); new_m.insert(col, Json::Object(map));
map = new_m; map = new_m;
} }
} }
@ -182,13 +214,13 @@ fn build_map(
map map
} }
fn connect_maps(map: &mut serde_json::Map<String, serde_json::Value>, value: serde_json::Value) { fn connect_maps(map: &mut serde_json::Map<String, Json>, value: Json) {
if let serde_json::Value::Object(m) = value { if let Json::Object(m) = value {
for (key, value) in m { for (key, value) in m {
if value.is_object() { if value.is_object() {
let mut new_m = serde_json::Map::new(); let mut new_m = serde_json::Map::new();
connect_maps(&mut new_m, value); connect_maps(&mut new_m, value);
map.insert(key, serde_json::Value::Object(new_m)); map.insert(key, Json::Object(new_m));
} else { } else {
map.insert(key, value); map.insert(key, value);
} }
@ -219,3 +251,75 @@ where
.with(AlignmentStrategy::PerLine), .with(AlignmentStrategy::PerLine),
); );
} }
fn __truncate_value(value: &mut Json, width: usize) {
match value {
Json::Null => *value = Json::String(string_truncate("null", width)),
Json::Bool(b) => {
let val = if *b { "true" } else { "false" };
*value = Json::String(string_truncate(val, width));
}
Json::Number(n) => {
let n = n.to_string();
*value = Json::String(string_truncate(&n, width));
}
Json::String(s) => {
*value = Json::String(string_truncate(s, width));
}
Json::Array(_) | Json::Object(_) => {
unreachable!("must never happen")
}
}
}
fn get_biggest_value(value: &mut Json) -> Option<(&mut Json, usize)> {
match value {
Json::Null => Some((value, 4)),
Json::Bool(_) => Some((value, 4)),
Json::Number(n) => {
let width = n.to_string().len();
Some((value, width))
}
Json::String(s) => {
let width = string_width(s);
Some((value, width))
}
Json::Array(arr) => {
if arr.is_empty() {
return None;
}
let mut width = 0;
let mut index = 0;
for (i, value) in arr.iter_mut().enumerate() {
if let Some((_, w)) = get_biggest_value(value) {
if w >= width {
index = i;
width = w;
}
}
}
get_biggest_value(&mut arr[index])
}
Json::Object(map) => {
if map.is_empty() {
return None;
}
let mut width = 0;
let mut index = String::new();
for (key, mut value) in map.clone() {
if let Some((_, w)) = get_biggest_value(&mut value) {
if w >= width {
index = key;
width = w;
}
}
}
get_biggest_value(&mut map[&index])
}
}
}