diff --git a/examples/config/default.nuon b/examples/config/default.nuon index 2895c62..8da13c4 100644 --- a/examples/config/default.nuon +++ b/examples/config/default.nuon @@ -6,6 +6,7 @@ margin: 10, # the number of lines to keep between the cursor and the top / bottom number: false, # show line numbers relativenumber: false, # show line numbers, relative to the current one (overrides number) + strict_tables: false, # only render tables when all rows have the same keys with the same types # "reset" is used instead of "black" in a dark terminal because, when the terminal is actually # black, "black" is not really black which is ugly, whereas "reset" is really black. diff --git a/src/config/mod.rs b/src/config/mod.rs index 24a7482..9e38357 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -150,6 +150,7 @@ pub struct Config { pub number: bool, pub relativenumber: bool, pub show_hints: bool, + pub strict_tables: bool, } impl Default for Config { @@ -164,6 +165,7 @@ impl Default for Config { number: false, relativenumber: false, show_hints: true, + strict_tables: false, colors: ColorConfig { normal: TableRowColorConfig { name: BgFgColorConfig { @@ -304,6 +306,11 @@ impl Config { config.show_hints = val } } + "strict_tables" => { + if let Some(val) = try_bool(value, &["strict_tables"])? { + config.strict_tables = val + } + } "colors" => { let cell = follow_cell_path(value, &["colors"]).unwrap(); let columns = match &cell { diff --git a/src/nu/value.rs b/src/nu/value.rs index e1cbdcb..7e0b150 100644 --- a/src/nu/value.rs +++ b/src/nu/value.rs @@ -125,7 +125,7 @@ pub(crate) fn mutate_value_cell( Some(res) } -pub(crate) fn is_table(value: &Value) -> Table { +pub(crate) fn is_table(value: &Value, loose: bool) -> Table { match value { Value::List { vals, .. } => { if vals.is_empty() { @@ -164,6 +164,9 @@ pub(crate) fn is_table(value: &Value) -> Table { Some(v) => match ty { Type::Nothing => ty = v, _ => { + if loose { + continue; + } if !matches!(v, Type::Nothing) { if v.is_numeric() && ty.is_numeric() { } else if (!v.is_numeric() && ty.is_numeric()) @@ -227,7 +230,7 @@ pub(crate) fn is_table(value: &Value) -> Table { /// ``` // WARNING: some _unwraps_ haven't been proven to be safe in this function pub(crate) fn transpose(value: &Value) -> Value { - if matches!(is_table(value), Table::IsValid) { + if matches!(is_table(value, false), Table::IsValid) { let value_rows = match value { Value::List { vals, .. } => vals, _ => return value.clone(), @@ -540,11 +543,18 @@ mod tests { table_with_number_colum, ] { assert_eq!( - is_table(&table), + is_table(&table, false), Table::IsValid, "{} should be a table", default_value_repr(&table) ); + + assert_eq!( + is_table(&table, true), + Table::IsValid, + "{} should be a loose table", + default_value_repr(&table) + ); } let not_a_table_missing_field = ( @@ -607,15 +617,35 @@ mod tests { not_a_table_row_invalid_key, ] { assert_eq!( - is_table(¬_a_table), + is_table(¬_a_table, false), expected, "{} should not be a table", default_value_repr(¬_a_table) ); } - assert_eq!(is_table(&Value::test_int(0)), Table::NotAList); - assert_eq!(is_table(&Value::test_list(vec![])), Table::Empty); + let loosy_table_incompatible_types = Value::test_list(vec![ + Value::test_record(record! { + "a" => Value::test_string("a"), + "b" => Value::test_int(1), + }), + Value::test_record(record! { + "a" => Value::test_string("a"), + "b" => Value::test_list(vec![Value::test_int(1)]), + }), + ]); + + for loosy_table in [loosy_table_incompatible_types] { + assert_eq!( + is_table(&loosy_table, true), + Table::IsValid, + "{} should be a loose table", + default_value_repr(&loosy_table) + ); + } + + assert_eq!(is_table(&Value::test_int(0), false), Table::NotAList); + assert_eq!(is_table(&Value::test_list(vec![]), false), Table::Empty); } #[test] diff --git a/src/ui.rs b/src/ui.rs index 11516ab..20cc2d4 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -201,12 +201,16 @@ fn repr_table(table: &[Record]) -> (Vec, Vec, Vec>) let val = row.get(col).unwrap(); let cell_type = val.get_type(); - if !matches!(cell_type, Type::Nothing) { - if shapes[j].is_numeric() && cell_type.is_numeric() && (shapes[j] != cell_type) { - shapes[j] = Type::Number; - } else { - shapes[j] = cell_type; - } + + if shapes[j].is_numeric() && cell_type.is_numeric() && (shapes[j] != cell_type) { + shapes[j] = Type::Number; + } else if shapes[j] == Type::Nothing && cell_type != Type::Nothing { + shapes[j] = cell_type; + } else if shapes[j] != Type::Nothing && cell_type == Type::Nothing { + } else if shapes[j] != cell_type { + shapes[j] = Type::Any; + } else { + shapes[j] = cell_type; } rows[i].push(repr_value(val).data); @@ -238,7 +242,7 @@ fn render_data(frame: &mut Frame, app: &mut App) { let value = app.value_under_cursor(Some(CellPath { members: data_path })); - let table_type = is_table(&value); + let table_type = is_table(&value, !config.strict_tables); let is_a_table = matches!(table_type, crate::nu::value::Table::IsValid); let mut data_frame_height = if config.show_cell_path { @@ -938,4 +942,26 @@ mod tests { assert_eq!(repr_table(&table), expected); } + + #[test] + fn repr_loose_table_with_mixed_types() { + let table = vec![ + record! { + "a" => Value::test_string("x"), + "b" => Value::test_int(1), + }, + record! { + "a" => Value::test_string("y"), + "b" => Value::test_string("z"), + }, + ]; + + let expected = ( + vec!["a".into(), "b".into()], + vec!["string".into(), "any".into()], + vec![vec!["x".into(), "1".into()], vec!["y".into(), "z".into()]], + ); + + assert_eq!(repr_table(&table), expected); + } }