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 crate::{table::TrimStrategyModifier, Alignments, TableTheme};
use crate::{string_truncate, string_width, Alignments, TableTheme};
use nu_color_config::StyleComputer;
use nu_protocol::{Config, Span, Value};
use tabled::{
color::Color, formatting::AlignmentStrategy, object::Segment, papergrid::records::Records,
Alignment, Modify, Table,
color::Color,
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.
///
/// It doesn't support alignment and a proper width control.
pub struct NuTable {
inner: tabled::Table,
inner: Option<String>,
}
impl NuTable {
@ -29,30 +34,59 @@ impl NuTable {
load_theme(&mut table, style_computer, theme);
let cfg = table.get_config().clone();
let val = nu_protocol_value_to_json(value, config, with_footer);
let mut table = json_to_table::json_to_table(&val);
table.set_config(cfg);
let mut val = nu_protocol_value_to_json(value, config, with_footer);
if collapse {
table.collapse();
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 },
}
}
}
let mut table: Table<_> = table.into();
table.with(TrimStrategyModifier::new(termwidth, &config.trim_strategy));
Self { inner: table }
Self { inner: Some(table) }
}
pub fn draw(&self) -> Option<String> {
Some(self.inner.to_string())
self.inner.clone()
}
}
fn nu_protocol_value_to_json(
value: Value,
config: &Config,
with_footer: bool,
) -> serde_json::Value {
fn build_table(val: Json, cfg: GridConfig, collapse: bool) -> String {
let mut table = json_to_table::json_to_table(&val);
table.set_config(cfg);
if collapse {
table.collapse();
}
table.to_string()
}
fn nu_protocol_value_to_json(value: Value, config: &Config, with_footer: bool) -> Json {
match value {
Value::Record { cols, vals, .. } => {
let mut map = serde_json::Map::new();
@ -61,7 +95,7 @@ fn nu_protocol_value_to_json(
map.insert(key, val);
}
serde_json::Value::Object(map)
Json::Object(map)
}
Value::List { vals, .. } => {
let mut used_cols: Option<&[String]> = None;
@ -95,29 +129,27 @@ fn nu_protocol_value_to_json(
});
let head = build_map(head, config);
arr.push(serde_json::Value::Object(head.clone()));
arr.push(Json::Object(head.clone()));
for value in &vals {
if let Ok((_, vals)) = value.as_record() {
let vals = build_map(vals.iter().cloned(), config);
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 {
arr.push(serde_json::Value::Object(head));
arr.push(Json::Object(head));
}
return serde_json::Value::Array(arr);
return Json::Array(arr);
} else {
let mut map = vec![];
let head = serde_json::Value::Array(vec![serde_json::Value::String(
cols[0].to_owned(),
)]);
let head = Json::Array(vec![Json::String(cols[0].to_owned())]);
map.push(head.clone());
for value in vals {
@ -136,7 +168,7 @@ fn nu_protocol_value_to_json(
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);
}
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(
values: impl Iterator<Item = Value> + DoubleEndedIterator,
config: &Config,
) -> serde_json::Map<String, serde_json::Value> {
) -> serde_json::Map<String, Json> {
let mut map = serde_json::Map::new();
let mut last_val: Option<Value> = None;
for val in values.rev() {
@ -174,7 +206,7 @@ fn build_map(
let mut new_m = serde_json::Map::new();
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;
}
}
@ -182,13 +214,13 @@ fn build_map(
map
}
fn connect_maps(map: &mut serde_json::Map<String, serde_json::Value>, value: serde_json::Value) {
if let serde_json::Value::Object(m) = value {
fn connect_maps(map: &mut serde_json::Map<String, Json>, value: Json) {
if let Json::Object(m) = value {
for (key, value) in m {
if value.is_object() {
let mut new_m = serde_json::Map::new();
connect_maps(&mut new_m, value);
map.insert(key, serde_json::Value::Object(new_m));
map.insert(key, Json::Object(new_m));
} else {
map.insert(key, value);
}
@ -219,3 +251,75 @@ where
.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])
}
}
}