diff --git a/crates/nu-table/src/nu_protocol_table.rs b/crates/nu-table/src/nu_protocol_table.rs index 9e3af76fd..e5174a195 100644 --- a/crates/nu-table/src/nu_protocol_table.rs +++ b/crates/nu-table/src/nu_protocol_table.rs @@ -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, } 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 { - 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 + DoubleEndedIterator, config: &Config, -) -> serde_json::Map { +) -> serde_json::Map { let mut map = serde_json::Map::new(); let mut last_val: Option = 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, value: serde_json::Value) { - if let serde_json::Value::Object(m) = value { +fn connect_maps(map: &mut serde_json::Map, 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]) + } + } +}