mirror of
https://github.com/nushell/nushell.git
synced 2025-05-05 15:32:56 +00:00
Compare commits
7 Commits
b500ac57c2
...
cb133ed387
Author | SHA1 | Date | |
---|---|---|---|
|
cb133ed387 | ||
|
a7547a54bc | ||
|
d1969a3c9a | ||
|
ce582cdafb | ||
|
55de232a1c | ||
|
deca337a56 | ||
|
60e9f469af |
@ -1,3 +1,5 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use crate::completions::{Completer, CompletionOptions, SemanticSuggestion, SuggestionKind};
|
||||
use nu_engine::{column::get_columns, eval_variable};
|
||||
use nu_protocol::{
|
||||
@ -101,7 +103,9 @@ pub(crate) fn eval_cell_path(
|
||||
} else {
|
||||
eval_constant(working_set, head)
|
||||
}?;
|
||||
head_value.follow_cell_path(path_members, false)
|
||||
head_value
|
||||
.follow_cell_path(path_members, false)
|
||||
.map(Cow::into_owned)
|
||||
}
|
||||
|
||||
fn get_suggestions_by_value(
|
||||
|
@ -253,12 +253,11 @@ fn format_record(
|
||||
optional: false,
|
||||
})
|
||||
.collect();
|
||||
match data_as_value.clone().follow_cell_path(&path_members, false) {
|
||||
Ok(value_at_column) => {
|
||||
output.push_str(value_at_column.to_expanded_string(", ", config).as_str())
|
||||
}
|
||||
Err(se) => return Err(se),
|
||||
}
|
||||
|
||||
let expanded_string = data_as_value
|
||||
.follow_cell_path(&path_members, false)?
|
||||
.to_expanded_string(", ", config);
|
||||
output.push_str(expanded_string.as_str())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,17 +15,8 @@ pub fn empty(
|
||||
if !columns.is_empty() {
|
||||
for val in input {
|
||||
for column in &columns {
|
||||
let val = val.clone();
|
||||
match val.follow_cell_path(&column.members, false) {
|
||||
Ok(Value::Nothing { .. }) => {}
|
||||
Ok(_) => {
|
||||
if negate {
|
||||
return Ok(Value::bool(true, head).into_pipeline_data());
|
||||
} else {
|
||||
return Ok(Value::bool(false, head).into_pipeline_data());
|
||||
}
|
||||
}
|
||||
Err(err) => return Err(err),
|
||||
if !val.follow_cell_path(&column.members, false)?.is_nothing() {
|
||||
return Ok(Value::bool(negate, head).into_pipeline_data());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::{ast::PathMember, Signals};
|
||||
|
||||
@ -188,9 +190,11 @@ fn action(
|
||||
let input = input.into_value(span)?;
|
||||
|
||||
for path in paths {
|
||||
let val = input.clone().follow_cell_path(&path.members, !sensitive);
|
||||
|
||||
output.push(val?);
|
||||
output.push(
|
||||
input
|
||||
.follow_cell_path(&path.members, !sensitive)?
|
||||
.into_owned(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(output.into_iter().into_pipeline_data(span, signals))
|
||||
@ -223,10 +227,10 @@ pub fn follow_cell_path_into_stream(
|
||||
.map(move |value| {
|
||||
let span = value.span();
|
||||
|
||||
match value.follow_cell_path(&cell_path, insensitive) {
|
||||
Ok(v) => v,
|
||||
Err(error) => Value::error(error, span),
|
||||
}
|
||||
value
|
||||
.follow_cell_path(&cell_path, insensitive)
|
||||
.map(Cow::into_owned)
|
||||
.unwrap_or_else(|error| Value::error(error, span))
|
||||
})
|
||||
.into_pipeline_data(head, signals);
|
||||
|
||||
|
@ -322,11 +322,9 @@ fn group_cell_path(
|
||||
let mut groups = IndexMap::<_, Vec<_>>::new();
|
||||
|
||||
for value in values.into_iter() {
|
||||
let key = value
|
||||
.clone()
|
||||
.follow_cell_path(&column_name.members, false)?;
|
||||
let key = value.follow_cell_path(&column_name.members, false)?;
|
||||
|
||||
if matches!(key, Value::Nothing { .. }) {
|
||||
if key.is_nothing() {
|
||||
continue; // likely the result of a failed optional access, ignore this value
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use nu_engine::{command_prelude::*, ClosureEval, ClosureEvalOnce};
|
||||
use nu_protocol::ast::PathMember;
|
||||
|
||||
@ -299,8 +301,8 @@ fn insert_value_by_closure(
|
||||
) -> Result<(), ShellError> {
|
||||
let value_at_path = if first_path_member_int {
|
||||
value
|
||||
.clone()
|
||||
.follow_cell_path(cell_path, false)
|
||||
.map(Cow::into_owned)
|
||||
.unwrap_or(Value::nothing(span))
|
||||
} else {
|
||||
value.clone()
|
||||
@ -319,8 +321,8 @@ fn insert_single_value_by_closure(
|
||||
) -> Result<(), ShellError> {
|
||||
let value_at_path = if first_path_member_int {
|
||||
value
|
||||
.clone()
|
||||
.follow_cell_path(cell_path, false)
|
||||
.map(Cow::into_owned)
|
||||
.unwrap_or(Value::nothing(span))
|
||||
} else {
|
||||
value.clone()
|
||||
|
@ -229,45 +229,37 @@ fn select(
|
||||
match v {
|
||||
Value::List {
|
||||
vals: input_vals, ..
|
||||
} => {
|
||||
Ok(input_vals
|
||||
.into_iter()
|
||||
.map(move |input_val| {
|
||||
if !columns.is_empty() {
|
||||
let mut record = Record::new();
|
||||
for path in &columns {
|
||||
//FIXME: improve implementation to not clone
|
||||
match input_val.clone().follow_cell_path(&path.members, false) {
|
||||
Ok(fetcher) => {
|
||||
record.push(path.to_column_name(), fetcher);
|
||||
}
|
||||
Err(e) => return Value::error(e, call_span),
|
||||
} => Ok(input_vals
|
||||
.into_iter()
|
||||
.map(move |input_val| {
|
||||
if !columns.is_empty() {
|
||||
let mut record = Record::new();
|
||||
for path in &columns {
|
||||
match input_val.follow_cell_path(&path.members, false) {
|
||||
Ok(fetcher) => {
|
||||
record.push(path.to_column_name(), fetcher.into_owned());
|
||||
}
|
||||
Err(e) => return Value::error(e, call_span),
|
||||
}
|
||||
|
||||
Value::record(record, span)
|
||||
} else {
|
||||
input_val.clone()
|
||||
}
|
||||
})
|
||||
.into_pipeline_data_with_metadata(
|
||||
call_span,
|
||||
engine_state.signals().clone(),
|
||||
metadata,
|
||||
))
|
||||
}
|
||||
|
||||
Value::record(record, span)
|
||||
} else {
|
||||
input_val.clone()
|
||||
}
|
||||
})
|
||||
.into_pipeline_data_with_metadata(
|
||||
call_span,
|
||||
engine_state.signals().clone(),
|
||||
metadata,
|
||||
)),
|
||||
_ => {
|
||||
if !columns.is_empty() {
|
||||
let mut record = Record::new();
|
||||
|
||||
for cell_path in columns {
|
||||
// FIXME: remove clone
|
||||
match v.clone().follow_cell_path(&cell_path.members, false) {
|
||||
Ok(result) => {
|
||||
record.push(cell_path.to_column_name(), result);
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
let result = v.follow_cell_path(&cell_path.members, false)?;
|
||||
record.push(cell_path.to_column_name(), result.into_owned());
|
||||
}
|
||||
|
||||
Ok(Value::record(record, call_span)
|
||||
@ -278,31 +270,24 @@ fn select(
|
||||
}
|
||||
}
|
||||
}
|
||||
PipelineData::ListStream(stream, metadata, ..) => {
|
||||
Ok(stream
|
||||
.map(move |x| {
|
||||
if !columns.is_empty() {
|
||||
let mut record = Record::new();
|
||||
for path in &columns {
|
||||
//FIXME: improve implementation to not clone
|
||||
match x.clone().follow_cell_path(&path.members, false) {
|
||||
Ok(value) => {
|
||||
record.push(path.to_column_name(), value);
|
||||
}
|
||||
Err(e) => return Value::error(e, call_span),
|
||||
PipelineData::ListStream(stream, metadata, ..) => Ok(stream
|
||||
.map(move |x| {
|
||||
if !columns.is_empty() {
|
||||
let mut record = Record::new();
|
||||
for path in &columns {
|
||||
match x.follow_cell_path(&path.members, false) {
|
||||
Ok(value) => {
|
||||
record.push(path.to_column_name(), value.into_owned());
|
||||
}
|
||||
Err(e) => return Value::error(e, call_span),
|
||||
}
|
||||
Value::record(record, call_span)
|
||||
} else {
|
||||
x
|
||||
}
|
||||
})
|
||||
.into_pipeline_data_with_metadata(
|
||||
call_span,
|
||||
engine_state.signals().clone(),
|
||||
metadata,
|
||||
))
|
||||
}
|
||||
Value::record(record, call_span)
|
||||
} else {
|
||||
x
|
||||
}
|
||||
})
|
||||
.into_pipeline_data_with_metadata(call_span, engine_state.signals().clone(), metadata)),
|
||||
_ => Ok(PipelineData::empty()),
|
||||
}
|
||||
}
|
||||
|
@ -243,17 +243,17 @@ fn update_value_by_closure(
|
||||
cell_path: &[PathMember],
|
||||
first_path_member_int: bool,
|
||||
) -> Result<(), ShellError> {
|
||||
let value_at_path = value.clone().follow_cell_path(cell_path, false)?;
|
||||
let value_at_path = value.follow_cell_path(cell_path, false)?;
|
||||
|
||||
let arg = if first_path_member_int {
|
||||
&value_at_path
|
||||
value_at_path.as_ref()
|
||||
} else {
|
||||
&*value
|
||||
};
|
||||
|
||||
let new_value = closure
|
||||
.add_arg(arg.clone())
|
||||
.run_with_input(value_at_path.into_pipeline_data())?
|
||||
.run_with_input(value_at_path.into_owned().into_pipeline_data())?
|
||||
.into_value(span)?;
|
||||
|
||||
value.update_data_at_cell_path(cell_path, new_value)
|
||||
@ -266,17 +266,17 @@ fn update_single_value_by_closure(
|
||||
cell_path: &[PathMember],
|
||||
first_path_member_int: bool,
|
||||
) -> Result<(), ShellError> {
|
||||
let value_at_path = value.clone().follow_cell_path(cell_path, false)?;
|
||||
let value_at_path = value.follow_cell_path(cell_path, false)?;
|
||||
|
||||
let arg = if first_path_member_int {
|
||||
&value_at_path
|
||||
value_at_path.as_ref()
|
||||
} else {
|
||||
&*value
|
||||
};
|
||||
|
||||
let new_value = closure
|
||||
.add_arg(arg.clone())
|
||||
.run_with_input(value_at_path.into_pipeline_data())?
|
||||
.run_with_input(value_at_path.into_owned().into_pipeline_data())?
|
||||
.into_value(span)?;
|
||||
|
||||
value.update_data_at_cell_path(cell_path, new_value)
|
||||
|
@ -1,3 +1,5 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use nu_engine::{command_prelude::*, ClosureEval, ClosureEvalOnce};
|
||||
use nu_protocol::ast::PathMember;
|
||||
|
||||
@ -319,15 +321,19 @@ fn upsert_value_by_closure(
|
||||
cell_path: &[PathMember],
|
||||
first_path_member_int: bool,
|
||||
) -> Result<(), ShellError> {
|
||||
let value_at_path = value.clone().follow_cell_path(cell_path, false);
|
||||
let value_at_path = value.follow_cell_path(cell_path, false);
|
||||
|
||||
let arg = if first_path_member_int {
|
||||
value_at_path.clone().unwrap_or(Value::nothing(span))
|
||||
value_at_path
|
||||
.as_deref()
|
||||
.cloned()
|
||||
.unwrap_or(Value::nothing(span))
|
||||
} else {
|
||||
value.clone()
|
||||
};
|
||||
|
||||
let input = value_at_path
|
||||
.map(Cow::into_owned)
|
||||
.map(IntoPipelineData::into_pipeline_data)
|
||||
.unwrap_or(PipelineData::Empty);
|
||||
|
||||
@ -346,15 +352,19 @@ fn upsert_single_value_by_closure(
|
||||
cell_path: &[PathMember],
|
||||
first_path_member_int: bool,
|
||||
) -> Result<(), ShellError> {
|
||||
let value_at_path = value.clone().follow_cell_path(cell_path, false);
|
||||
let value_at_path = value.follow_cell_path(cell_path, false);
|
||||
|
||||
let arg = if first_path_member_int {
|
||||
value_at_path.clone().unwrap_or(Value::nothing(span))
|
||||
value_at_path
|
||||
.as_deref()
|
||||
.cloned()
|
||||
.unwrap_or(Value::nothing(span))
|
||||
} else {
|
||||
value.clone()
|
||||
};
|
||||
|
||||
let input = value_at_path
|
||||
.map(Cow::into_owned)
|
||||
.map(IntoPipelineData::into_pipeline_data)
|
||||
.unwrap_or(PipelineData::Empty);
|
||||
|
||||
|
@ -117,8 +117,13 @@ impl Command for HttpDelete {
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "http delete from example.com, with custom header",
|
||||
example: "http delete --headers [my-header-key my-header-value] https://www.example.com",
|
||||
description: "http delete from example.com, with custom header using a record",
|
||||
example: "http delete --headers {my-header-key: my-header-value} https://www.example.com",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "http delete from example.com, with custom header using a list",
|
||||
example: "http delete --headers [my-header-key-A my-header-value-A my-header-key-B my-header-value-B] https://www.example.com",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
|
@ -115,12 +115,12 @@ impl Command for HttpGet {
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Get content from example.com, with custom header",
|
||||
example: "http get --headers [my-header-key my-header-value] https://www.example.com",
|
||||
description: "Get content from example.com, with custom header using a record",
|
||||
example: "http get --headers {my-header-key: my-header-value} https://www.example.com",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Get content from example.com, with custom headers",
|
||||
description: "Get content from example.com, with custom headers using a list",
|
||||
example: "http get --headers [my-header-key-A my-header-value-A my-header-key-B my-header-value-B] https://www.example.com",
|
||||
result: None,
|
||||
},
|
||||
|
@ -97,9 +97,15 @@ impl Command for HttpHead {
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Get headers from example.com, with custom header",
|
||||
description: "Get headers from example.com, with custom header using a record",
|
||||
example:
|
||||
"http head --headers [my-header-key my-header-value] https://www.example.com",
|
||||
"http head --headers {my-header-key: my-header-value} https://www.example.com",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Get headers from example.com, with custom header using a list",
|
||||
example:
|
||||
"http head --headers [my-header-key-A my-header-value-A my-header-key-B my-header-value-B] https://www.example.com",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
|
@ -96,12 +96,12 @@ impl Command for HttpOptions {
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Get options from example.com, with custom header",
|
||||
example: "http options --headers [my-header-key my-header-value] https://www.example.com",
|
||||
description: "Get options from example.com, with custom header using a record",
|
||||
example: "http options --headers {my-header-key: my-header-value} https://www.example.com",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Get options from example.com, with custom headers",
|
||||
description: "Get options from example.com, with custom headers using a list",
|
||||
example: "http options --headers [my-header-key-A my-header-value-A my-header-key-B my-header-value-B] https://www.example.com",
|
||||
result: None,
|
||||
},
|
||||
|
@ -114,9 +114,15 @@ impl Command for HttpPatch {
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Patch content to example.com, with custom header",
|
||||
description: "Patch content to example.com, with custom header using a record",
|
||||
example:
|
||||
"http patch --headers [my-header-key my-header-value] https://www.example.com",
|
||||
"http patch --headers {my-header-key: my-header-value} https://www.example.com",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Patch content to example.com, with custom header using a list",
|
||||
example:
|
||||
"http patch --headers [my-header-key-A my-header-value-A my-header-key-B my-header-value-B] https://www.example.com",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
|
@ -113,8 +113,13 @@ impl Command for HttpPost {
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Post content to example.com, with custom header",
|
||||
example: "http post --headers [my-header-key my-header-value] https://www.example.com",
|
||||
description: "Post content to example.com, with custom header using a record",
|
||||
example: "http post --headers {my-header-key: my-header-value} https://www.example.com",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Post content to example.com, with custom header using a list",
|
||||
example: "http post --headers [my-header-key-A my-header-value-A my-header-key-B my-header-value-B] https://www.example.com",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
|
@ -113,8 +113,13 @@ impl Command for HttpPut {
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Put content to example.com, with custom header",
|
||||
example: "http put --headers [my-header-key my-header-value] https://www.example.com",
|
||||
description: "Put content to example.com, with custom header using a record",
|
||||
example: "http put --headers {my-header-key: my-header-value} https://www.example.com",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Put content to example.com, with custom header using a list",
|
||||
example: "http put --headers [my-header-key-A my-header-value-A my-header-key-B my-header-value-B] https://www.example.com",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
|
@ -89,8 +89,7 @@ impl Command for InputList {
|
||||
.into_iter()
|
||||
.map(move |val| {
|
||||
let display_value = if let Some(ref cellpath) = display_path {
|
||||
val.clone()
|
||||
.follow_cell_path(&cellpath.members, false)?
|
||||
val.follow_cell_path(&cellpath.members, false)?
|
||||
.to_expanded_string(", ", &config)
|
||||
} else {
|
||||
val.to_expanded_string(", ", &config)
|
||||
|
@ -239,8 +239,8 @@ pub fn compare_cell_path(
|
||||
insensitive: bool,
|
||||
natural: bool,
|
||||
) -> Result<Ordering, ShellError> {
|
||||
let left = left.clone().follow_cell_path(&cell_path.members, false)?;
|
||||
let right = right.clone().follow_cell_path(&cell_path.members, false)?;
|
||||
let left = left.follow_cell_path(&cell_path.members, false)?;
|
||||
let right = right.follow_cell_path(&cell_path.members, false)?;
|
||||
compare_values(&left, &right, insensitive, natural)
|
||||
}
|
||||
|
||||
|
@ -17,8 +17,8 @@ use nu_protocol::{
|
||||
Signals, TableMode, ValueIterator,
|
||||
};
|
||||
use nu_table::{
|
||||
common::configure_table, CollapsedTable, ExpandedTable, JustTable, NuRecordsValue, NuTable,
|
||||
StringResult, TableOpts, TableOutput,
|
||||
common::configure_table, CollapsedTable, ExpandedTable, JustTable, NuTable, StringResult,
|
||||
TableOpts, TableOutput,
|
||||
};
|
||||
use nu_utils::{get_ls_colors, terminal_size};
|
||||
|
||||
@ -609,7 +609,7 @@ fn build_table_kv(
|
||||
span: Span,
|
||||
) -> StringResult {
|
||||
match table_view {
|
||||
TableView::General => JustTable::kv_table(&record, opts),
|
||||
TableView::General => JustTable::kv_table(record, opts),
|
||||
TableView::Expanded {
|
||||
limit,
|
||||
flatten,
|
||||
@ -645,7 +645,7 @@ fn build_table_batch(
|
||||
}
|
||||
|
||||
match view {
|
||||
TableView::General => JustTable::table(&vals, opts),
|
||||
TableView::General => JustTable::table(vals, opts),
|
||||
TableView::Expanded {
|
||||
limit,
|
||||
flatten,
|
||||
@ -1090,9 +1090,9 @@ fn create_empty_placeholder(
|
||||
return String::new();
|
||||
}
|
||||
|
||||
let cell = NuRecordsValue::new(format!("empty {}", value_type_name));
|
||||
let data = vec![vec![cell]];
|
||||
let mut table = NuTable::from(data);
|
||||
let cell = format!("empty {}", value_type_name);
|
||||
let mut table = NuTable::new(1, 1);
|
||||
table.insert((0, 0), cell);
|
||||
table.set_data_style(TextStyle::default().dimmed());
|
||||
let mut out = TableOutput::from_table(table, false, false);
|
||||
|
||||
|
@ -1333,15 +1333,17 @@ fn test_expand_big_0() {
|
||||
"│ target │ {record 3 fields} │",
|
||||
"│ dev-dependencies │ {record 9 fields} │",
|
||||
"│ features │ {record 8 fields} │",
|
||||
"│ │ ╭───┬─────┬─────╮ │",
|
||||
"│ bin │ │ # │ nam │ pat │ │",
|
||||
"│ │ │ │ e │ h │ │",
|
||||
"│ │ ├───┼─────┼─────┤ │",
|
||||
"│ │ │ 0 │ nu │ src │ │",
|
||||
"│ │ │ │ │ /ma │ │",
|
||||
"│ │ │ │ │ in. │ │",
|
||||
"│ │ │ │ │ rs │ │",
|
||||
"│ │ ╰───┴─────┴─────╯ │",
|
||||
"│ │ ╭───┬──────┬────╮ │",
|
||||
"│ bin │ │ # │ name │ pa │ │",
|
||||
"│ │ │ │ │ th │ │",
|
||||
"│ │ ├───┼──────┼────┤ │",
|
||||
"│ │ │ 0 │ nu │ sr │ │",
|
||||
"│ │ │ │ │ c/ │ │",
|
||||
"│ │ │ │ │ ma │ │",
|
||||
"│ │ │ │ │ in │ │",
|
||||
"│ │ │ │ │ .r │ │",
|
||||
"│ │ │ │ │ s │ │",
|
||||
"│ │ ╰───┴──────┴────╯ │",
|
||||
"│ │ ╭───────────┬───╮ │",
|
||||
"│ patch │ │ crates-io │ { │ │",
|
||||
"│ │ │ │ r │ │",
|
||||
@ -1360,16 +1362,16 @@ fn test_expand_big_0() {
|
||||
"│ │ │ │ d │ │",
|
||||
"│ │ │ │ } │ │",
|
||||
"│ │ ╰───────────┴───╯ │",
|
||||
"│ │ ╭───┬─────┬─────╮ │",
|
||||
"│ bench │ │ # │ nam │ har │ │",
|
||||
"│ │ │ │ e │ nes │ │",
|
||||
"│ │ │ │ │ s │ │",
|
||||
"│ │ ├───┼─────┼─────┤ │",
|
||||
"│ │ │ 0 │ ben │ fal │ │",
|
||||
"│ │ │ │ chm │ se │ │",
|
||||
"│ │ │ │ ark │ │ │",
|
||||
"│ │ │ │ s │ │ │",
|
||||
"│ │ ╰───┴─────┴─────╯ │",
|
||||
"│ │ ╭───┬──────┬────╮ │",
|
||||
"│ bench │ │ # │ name │ ha │ │",
|
||||
"│ │ │ │ │ rn │ │",
|
||||
"│ │ │ │ │ es │ │",
|
||||
"│ │ │ │ │ s │ │",
|
||||
"│ │ ├───┼──────┼────┤ │",
|
||||
"│ │ │ 0 │ benc │ fa │ │",
|
||||
"│ │ │ │ hmar │ ls │ │",
|
||||
"│ │ │ │ ks │ e │ │",
|
||||
"│ │ ╰───┴──────┴────╯ │",
|
||||
"╰──────────────────┴───────────────────╯",
|
||||
]);
|
||||
|
||||
@ -1551,193 +1553,114 @@ fn table_expande_with_no_header_internally_0() {
|
||||
"│ │ │ │ │ │ ╰─────┴──────────╯ │ │ │",
|
||||
"│ │ │ │ │ display_output │ │ │ │",
|
||||
"│ │ │ │ ╰────────────────┴────────────────────╯ │ │",
|
||||
"│ │ │ │ ╭───┬───────────────────────────┬────────────────────────┬────────┬───┬─────╮ │ │",
|
||||
"│ │ │ menus │ │ # │ name │ only_buffer_difference │ marker │ t │ ... │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ y │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ p │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ e │ │ │ │",
|
||||
"│ │ │ │ ├───┼───────────────────────────┼────────────────────────┼────────┼───┼─────┤ │ │",
|
||||
"│ │ │ │ │ 0 │ completion_menu │ false │ | │ { │ ... │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ r │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ e │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ c │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ o │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ r │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ d │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ 4 │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ f │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ i │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ e │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ l │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ d │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ s │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ } │ │ │ │",
|
||||
"│ │ │ │ │ 1 │ history_menu │ true │ ? │ { │ ... │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ r │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ e │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ c │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ o │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ r │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ d │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ 2 │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ f │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ i │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ e │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ l │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ d │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ s │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ } │ │ │ │",
|
||||
"│ │ │ │ │ 2 │ help_menu │ true │ ? │ { │ ... │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ r │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ e │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ c │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ o │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ r │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ d │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ 6 │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ f │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ i │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ e │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ l │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ d │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ s │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ } │ │ │ │",
|
||||
"│ │ │ │ │ 3 │ commands_menu │ false │ # │ { │ ... │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ r │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ e │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ c │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ o │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ r │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ d │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ 4 │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ f │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ i │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ e │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ l │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ d │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ s │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ } │ │ │ │",
|
||||
"│ │ │ │ │ 4 │ vars_menu │ true │ # │ { │ ... │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ r │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ e │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ c │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ o │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ r │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ d │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ 2 │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ f │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ i │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ e │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ l │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ d │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ s │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ } │ │ │ │",
|
||||
"│ │ │ │ │ 5 │ commands_with_description │ true │ # │ { │ ... │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ r │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ e │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ c │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ o │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ r │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ d │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ 6 │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ f │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ i │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ e │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ l │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ d │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ s │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ } │ │ │ │",
|
||||
"│ │ │ │ ╰───┴───────────────────────────┴────────────────────────┴────────┴───┴─────╯ │ │",
|
||||
"│ │ │ │ ╭────┬───────────────────────────┬──────────┬─────────┬───────────────┬─────╮ │ │",
|
||||
"│ │ │ keybindings │ │ # │ name │ modifier │ keycode │ mode │ eve │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ nt │ │ │",
|
||||
"│ │ │ │ ├────┼───────────────────────────┼──────────┼─────────┼───────────────┼─────┤ │ │",
|
||||
"│ │ │ │ │ 0 │ completion_menu │ none │ tab │ ╭───┬───────╮ │ {re │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 0 │ emacs │ │ cor │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 1 │ vi_no │ │ d 1 │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ rmal │ │ fi │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 2 │ vi_in │ │ eld │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ sert │ │ } │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ ╰───┴───────╯ │ │ │ │",
|
||||
"│ │ │ │ │ 1 │ completion_previous │ shift │ backtab │ ╭───┬───────╮ │ {re │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 0 │ emacs │ │ cor │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 1 │ vi_no │ │ d 1 │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ rmal │ │ fi │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 2 │ vi_in │ │ eld │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ sert │ │ } │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ ╰───┴───────╯ │ │ │ │",
|
||||
"│ │ │ │ │ 2 │ history_menu │ control │ char_r │ emacs │ {re │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ cor │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ d 2 │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ eld │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ s} │ │ │",
|
||||
"│ │ │ │ │ 3 │ next_page │ control │ char_x │ emacs │ {re │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ cor │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ d 1 │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ eld │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ } │ │ │",
|
||||
"│ │ │ │ │ 4 │ undo_or_previous_page │ control │ char_z │ emacs │ {re │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ cor │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ d 1 │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ eld │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ } │ │ │",
|
||||
"│ │ │ │ │ 5 │ yank │ control │ char_y │ emacs │ {re │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ cor │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ d 1 │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ eld │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ } │ │ │",
|
||||
"│ │ │ │ │ 6 │ unix-line-discard │ control │ char_u │ ╭───┬───────╮ │ {re │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 0 │ emacs │ │ cor │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 1 │ vi_no │ │ d 1 │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ rmal │ │ fi │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 2 │ vi_in │ │ eld │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ sert │ │ } │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ ╰───┴───────╯ │ │ │ │",
|
||||
"│ │ │ │ │ 7 │ kill-line │ control │ char_k │ ╭───┬───────╮ │ {re │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 0 │ emacs │ │ cor │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 1 │ vi_no │ │ d 1 │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ rmal │ │ fi │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 2 │ vi_in │ │ eld │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ sert │ │ } │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ ╰───┴───────╯ │ │ │ │",
|
||||
"│ │ │ │ │ 8 │ commands_menu │ control │ char_t │ ╭───┬───────╮ │ {re │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 0 │ emacs │ │ cor │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 1 │ vi_no │ │ d 2 │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ rmal │ │ fi │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 2 │ vi_in │ │ eld │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ sert │ │ s} │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ ╰───┴───────╯ │ │ │ │",
|
||||
"│ │ │ │ │ 9 │ vars_menu │ alt │ char_o │ ╭───┬───────╮ │ {re │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 0 │ emacs │ │ cor │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 1 │ vi_no │ │ d 2 │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ rmal │ │ fi │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 2 │ vi_in │ │ eld │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ sert │ │ s} │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ ╰───┴───────╯ │ │ │ │",
|
||||
"│ │ │ │ │ 10 │ commands_with_description │ control │ char_s │ ╭───┬───────╮ │ {re │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 0 │ emacs │ │ cor │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 1 │ vi_no │ │ d 2 │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ rmal │ │ fi │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 2 │ vi_in │ │ eld │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ sert │ │ s} │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ ╰───┴───────╯ │ │ │ │",
|
||||
"│ │ │ │ ╰────┴───────────────────────────┴──────────┴─────────┴───────────────┴─────╯ │ │",
|
||||
"│ │ │ │ ╭───┬───────────────────────────┬────────────────────────┬────────┬─────╮ │ │",
|
||||
"│ │ │ menus │ │ # │ name │ only_buffer_difference │ marker │ ... │ │ │",
|
||||
"│ │ │ │ ├───┼───────────────────────────┼────────────────────────┼────────┼─────┤ │ │",
|
||||
"│ │ │ │ │ 0 │ completion_menu │ false │ | │ ... │ │ │",
|
||||
"│ │ │ │ │ 1 │ history_menu │ true │ ? │ ... │ │ │",
|
||||
"│ │ │ │ │ 2 │ help_menu │ true │ ? │ ... │ │ │",
|
||||
"│ │ │ │ │ 3 │ commands_menu │ false │ # │ ... │ │ │",
|
||||
"│ │ │ │ │ 4 │ vars_menu │ true │ # │ ... │ │ │",
|
||||
"│ │ │ │ │ 5 │ commands_with_description │ true │ # │ ... │ │ │",
|
||||
"│ │ │ │ ╰───┴───────────────────────────┴────────────────────────┴────────┴─────╯ │ │",
|
||||
"│ │ │ │ ╭────┬───────────────────────────┬──────────┬─────────┬────────────────┬────╮ │ │",
|
||||
"│ │ │ keybindings │ │ # │ name │ modifier │ keycode │ mode │ ev │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ en │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ t │ │ │",
|
||||
"│ │ │ │ ├────┼───────────────────────────┼──────────┼─────────┼────────────────┼────┤ │ │",
|
||||
"│ │ │ │ │ 0 │ completion_menu │ none │ tab │ ╭───┬────────╮ │ {r │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 0 │ emacs │ │ ec │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 1 │ vi_nor │ │ or │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ mal │ │ d │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 2 │ vi_ins │ │ 1 │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ ert │ │ fi │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ ╰───┴────────╯ │ el │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ d} │ │ │",
|
||||
"│ │ │ │ │ 1 │ completion_previous │ shift │ backtab │ ╭───┬────────╮ │ {r │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 0 │ emacs │ │ ec │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 1 │ vi_nor │ │ or │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ mal │ │ d │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 2 │ vi_ins │ │ 1 │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ ert │ │ fi │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ ╰───┴────────╯ │ el │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ d} │ │ │",
|
||||
"│ │ │ │ │ 2 │ history_menu │ control │ char_r │ emacs │ {r │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ ec │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ or │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ d │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 2 │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ el │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ ds │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ } │ │ │",
|
||||
"│ │ │ │ │ 3 │ next_page │ control │ char_x │ emacs │ {r │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ ec │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ or │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ d │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 1 │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ el │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ d} │ │ │",
|
||||
"│ │ │ │ │ 4 │ undo_or_previous_page │ control │ char_z │ emacs │ {r │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ ec │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ or │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ d │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 1 │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ el │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ d} │ │ │",
|
||||
"│ │ │ │ │ 5 │ yank │ control │ char_y │ emacs │ {r │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ ec │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ or │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ d │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 1 │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ el │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ d} │ │ │",
|
||||
"│ │ │ │ │ 6 │ unix-line-discard │ control │ char_u │ ╭───┬────────╮ │ {r │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 0 │ emacs │ │ ec │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 1 │ vi_nor │ │ or │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ mal │ │ d │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 2 │ vi_ins │ │ 1 │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ ert │ │ fi │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ ╰───┴────────╯ │ el │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ d} │ │ │",
|
||||
"│ │ │ │ │ 7 │ kill-line │ control │ char_k │ ╭───┬────────╮ │ {r │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 0 │ emacs │ │ ec │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 1 │ vi_nor │ │ or │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ mal │ │ d │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 2 │ vi_ins │ │ 1 │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ ert │ │ fi │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ ╰───┴────────╯ │ el │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ d} │ │ │",
|
||||
"│ │ │ │ │ 8 │ commands_menu │ control │ char_t │ ╭───┬────────╮ │ {r │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 0 │ emacs │ │ ec │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 1 │ vi_nor │ │ or │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ mal │ │ d │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 2 │ vi_ins │ │ 2 │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ ert │ │ fi │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ ╰───┴────────╯ │ el │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ ds │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ } │ │ │",
|
||||
"│ │ │ │ │ 9 │ vars_menu │ alt │ char_o │ ╭───┬────────╮ │ {r │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 0 │ emacs │ │ ec │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 1 │ vi_nor │ │ or │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ mal │ │ d │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 2 │ vi_ins │ │ 2 │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ ert │ │ fi │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ ╰───┴────────╯ │ el │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ ds │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ } │ │ │",
|
||||
"│ │ │ │ │ 10 │ commands_with_description │ control │ char_s │ ╭───┬────────╮ │ {r │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 0 │ emacs │ │ ec │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 1 │ vi_nor │ │ or │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ mal │ │ d │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 2 │ vi_ins │ │ 2 │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ ert │ │ fi │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ ╰───┴────────╯ │ el │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ ds │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ } │ │ │",
|
||||
"│ │ │ │ ╰────┴───────────────────────────┴──────────┴─────────┴────────────────┴────╯ │ │",
|
||||
"│ │ ╰──────────────────────────────────┴───────────────────────────────────────────────────────────────────────────────╯ │",
|
||||
"╰────────────────────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯",
|
||||
])
|
||||
@ -1929,77 +1852,220 @@ fn table_expande_with_no_header_internally_1() {
|
||||
"│ │ │ │ │ 4 │ vars_menu │ true │ # │ ... │ │ │",
|
||||
"│ │ │ │ │ 5 │ commands_with_description │ true │ # │ ... │ │ │",
|
||||
"│ │ │ │ ╰───┴───────────────────────────┴────────────────────────┴───────┴─────╯ │ │",
|
||||
"│ │ │ │ ╭────┬───────────────────────────┬──────────┬─────────┬──────────┬─────╮ │ │",
|
||||
"│ │ │ keybindings │ │ # │ name │ modifier │ keycode │ mode │ eve │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ nt │ │ │",
|
||||
"│ │ │ │ ├────┼───────────────────────────┼──────────┼─────────┼──────────┼─────┤ │ │",
|
||||
"│ │ │ │ │ 0 │ completion_menu │ none │ tab │ [list 3 │ {re │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ items] │ cor │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ d 1 │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ eld │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ } │ │ │",
|
||||
"│ │ │ │ │ 1 │ completion_previous │ shift │ backtab │ [list 3 │ {re │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ items] │ cor │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ d 1 │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ eld │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ } │ │ │",
|
||||
"│ │ │ │ │ 2 │ history_menu │ control │ char_r │ emacs │ {re │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ cor │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ d 2 │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ eld │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ s} │ │ │",
|
||||
"│ │ │ │ │ 3 │ next_page │ control │ char_x │ emacs │ {re │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ cor │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ d 1 │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ eld │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ } │ │ │",
|
||||
"│ │ │ │ │ 4 │ undo_or_previous_page │ control │ char_z │ emacs │ {re │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ cor │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ d 1 │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ eld │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ } │ │ │",
|
||||
"│ │ │ │ │ 5 │ yank │ control │ char_y │ emacs │ {re │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ cor │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ d 1 │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ eld │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ } │ │ │",
|
||||
"│ │ │ │ │ 6 │ unix-line-discard │ control │ char_u │ [list 3 │ {re │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ items] │ cor │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ d 1 │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ eld │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ } │ │ │",
|
||||
"│ │ │ │ │ 7 │ kill-line │ control │ char_k │ [list 3 │ {re │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ items] │ cor │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ d 1 │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ eld │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ } │ │ │",
|
||||
"│ │ │ │ │ 8 │ commands_menu │ control │ char_t │ [list 3 │ {re │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ items] │ cor │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ d 2 │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ eld │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ s} │ │ │",
|
||||
"│ │ │ │ │ 9 │ vars_menu │ alt │ char_o │ [list 3 │ {re │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ items] │ cor │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ d 2 │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ eld │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ s} │ │ │",
|
||||
"│ │ │ │ │ 10 │ commands_with_description │ control │ char_s │ [list 3 │ {re │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ items] │ cor │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ d 2 │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ eld │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ s} │ │ │",
|
||||
"│ │ │ │ ╰────┴───────────────────────────┴──────────┴─────────┴──────────┴─────╯ │ │",
|
||||
"│ │ │ │ ╭────┬───────────────────────────┬──────────┬─────────┬───────────┬────╮ │ │",
|
||||
"│ │ │ keybindings │ │ # │ name │ modifier │ keycode │ mode │ ev │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ en │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ t │ │ │",
|
||||
"│ │ │ │ ├────┼───────────────────────────┼──────────┼─────────┼───────────┼────┤ │ │",
|
||||
"│ │ │ │ │ 0 │ completion_menu │ none │ tab │ ╭───┬───╮ │ {r │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 0 │ e │ │ ec │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ m │ │ or │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ a │ │ d │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ c │ │ 1 │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ s │ │ fi │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 1 │ v │ │ el │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ i │ │ d} │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ _ │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ n │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ o │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ r │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ m │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ a │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ l │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 2 │ v │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ i │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ _ │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ i │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ n │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ s │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ e │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ r │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ t │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ ╰───┴───╯ │ │ │ │",
|
||||
"│ │ │ │ │ 1 │ completion_previous │ shift │ backtab │ ╭───┬───╮ │ {r │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 0 │ e │ │ ec │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ m │ │ or │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ a │ │ d │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ c │ │ 1 │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ s │ │ fi │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 1 │ v │ │ el │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ i │ │ d} │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ _ │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ n │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ o │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ r │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ m │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ a │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ l │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 2 │ v │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ i │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ _ │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ i │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ n │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ s │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ e │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ r │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ t │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ ╰───┴───╯ │ │ │ │",
|
||||
"│ │ │ │ │ 2 │ history_menu │ control │ char_r │ emacs │ {r │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ ec │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ or │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ d │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 2 │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ el │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ ds │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ } │ │ │",
|
||||
"│ │ │ │ │ 3 │ next_page │ control │ char_x │ emacs │ {r │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ ec │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ or │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ d │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 1 │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ el │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ d} │ │ │",
|
||||
"│ │ │ │ │ 4 │ undo_or_previous_page │ control │ char_z │ emacs │ {r │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ ec │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ or │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ d │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 1 │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ el │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ d} │ │ │",
|
||||
"│ │ │ │ │ 5 │ yank │ control │ char_y │ emacs │ {r │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ ec │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ or │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ d │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 1 │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ el │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ d} │ │ │",
|
||||
"│ │ │ │ │ 6 │ unix-line-discard │ control │ char_u │ ╭───┬───╮ │ {r │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 0 │ e │ │ ec │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ m │ │ or │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ a │ │ d │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ c │ │ 1 │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ s │ │ fi │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 1 │ v │ │ el │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ i │ │ d} │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ _ │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ n │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ o │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ r │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ m │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ a │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ l │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 2 │ v │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ i │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ _ │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ i │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ n │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ s │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ e │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ r │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ t │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ ╰───┴───╯ │ │ │ │",
|
||||
"│ │ │ │ │ 7 │ kill-line │ control │ char_k │ ╭───┬───╮ │ {r │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 0 │ e │ │ ec │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ m │ │ or │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ a │ │ d │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ c │ │ 1 │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ s │ │ fi │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 1 │ v │ │ el │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ i │ │ d} │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ _ │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ n │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ o │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ r │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ m │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ a │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ l │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 2 │ v │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ i │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ _ │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ i │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ n │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ s │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ e │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ r │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ t │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ ╰───┴───╯ │ │ │ │",
|
||||
"│ │ │ │ │ 8 │ commands_menu │ control │ char_t │ ╭───┬───╮ │ {r │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 0 │ e │ │ ec │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ m │ │ or │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ a │ │ d │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ c │ │ 2 │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ s │ │ fi │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 1 │ v │ │ el │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ i │ │ ds │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ _ │ │ } │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ n │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ o │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ r │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ m │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ a │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ l │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 2 │ v │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ i │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ _ │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ i │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ n │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ s │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ e │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ r │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ t │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ ╰───┴───╯ │ │ │ │",
|
||||
"│ │ │ │ │ 9 │ vars_menu │ alt │ char_o │ ╭───┬───╮ │ {r │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 0 │ e │ │ ec │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ m │ │ or │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ a │ │ d │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ c │ │ 2 │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ s │ │ fi │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 1 │ v │ │ el │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ i │ │ ds │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ _ │ │ } │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ n │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ o │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ r │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ m │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ a │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ l │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 2 │ v │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ i │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ _ │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ i │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ n │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ s │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ e │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ r │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ t │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ ╰───┴───╯ │ │ │ │",
|
||||
"│ │ │ │ │ 10 │ commands_with_description │ control │ char_s │ ╭───┬───╮ │ {r │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 0 │ e │ │ ec │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ m │ │ or │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ a │ │ d │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ c │ │ 2 │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ s │ │ fi │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 1 │ v │ │ el │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ i │ │ ds │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ _ │ │ } │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ n │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ o │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ r │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ m │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ a │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ l │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ 2 │ v │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ i │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ _ │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ i │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ n │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ s │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ e │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ r │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ │ │ t │ │ │ │ │",
|
||||
"│ │ │ │ │ │ │ │ │ ╰───┴───╯ │ │ │ │",
|
||||
"│ │ │ │ ╰────┴───────────────────────────┴──────────┴─────────┴───────────┴────╯ │ │",
|
||||
"│ │ ╰──────────────────────────────────┴──────────────────────────────────────────────────────────────────────────╯ │",
|
||||
"╰────────────────────┴─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯",
|
||||
])
|
||||
|
@ -269,6 +269,7 @@ pub fn eval_expression_with_input<D: DebugContext>(
|
||||
input = eval_subexpression::<D>(engine_state, stack, block, input)?
|
||||
.into_value(*span)?
|
||||
.follow_cell_path(&full_cell_path.tail, false)?
|
||||
.into_owned()
|
||||
.into_pipeline_data()
|
||||
} else {
|
||||
input = eval_subexpression::<D>(engine_state, stack, block, input)?;
|
||||
@ -604,7 +605,7 @@ impl Eval for EvalRuntime {
|
||||
|
||||
let is_config = original_key == "config";
|
||||
|
||||
stack.add_env_var(original_key, value);
|
||||
stack.add_env_var(original_key, value.into_owned());
|
||||
|
||||
// Trigger the update to config, if we modified that.
|
||||
if is_config {
|
||||
|
@ -694,9 +694,8 @@ fn eval_instruction<D: DebugContext>(
|
||||
let value = ctx.clone_reg_value(*src, *span)?;
|
||||
let path = ctx.take_reg(*path);
|
||||
if let PipelineData::Value(Value::CellPath { val: path, .. }, _) = path {
|
||||
// TODO: make follow_cell_path() not have to take ownership, probably using Cow
|
||||
let value = value.follow_cell_path(&path.members, true)?;
|
||||
ctx.put_reg(*dst, value.into_pipeline_data());
|
||||
ctx.put_reg(*dst, value.into_owned().into_pipeline_data());
|
||||
Ok(Continue)
|
||||
} else if let PipelineData::Value(Value::Error { error, .. }, _) = path {
|
||||
Err(*error)
|
||||
|
@ -63,7 +63,7 @@ impl LanguageServer {
|
||||
let var = working_set.get_variable(*var_id);
|
||||
Some(
|
||||
var.const_val
|
||||
.clone()
|
||||
.as_ref()
|
||||
.and_then(|val| val.follow_cell_path(cell_path, false).ok())
|
||||
.map(|val| val.span())
|
||||
.unwrap_or(var.declaration_span),
|
||||
|
@ -160,16 +160,15 @@ impl LanguageServer {
|
||||
let var = working_set.get_variable(var_id);
|
||||
markdown_hover(
|
||||
var.const_val
|
||||
.clone()
|
||||
.as_ref()
|
||||
.and_then(|val| val.follow_cell_path(&cell_path, false).ok())
|
||||
.map(|val| {
|
||||
let ty = val.get_type().clone();
|
||||
let value_string = val
|
||||
.coerce_into_string()
|
||||
.ok()
|
||||
.map(|s| format!("\n---\n{}", s))
|
||||
.unwrap_or_default();
|
||||
format!("```\n{}\n```{}", ty, value_string)
|
||||
let ty = val.get_type();
|
||||
if let Ok(s) = val.coerce_str() {
|
||||
format!("```\n{}\n```\n---\n{}", ty, s)
|
||||
} else {
|
||||
format!("```\n{}\n```", ty)
|
||||
}
|
||||
})
|
||||
.unwrap_or("`unknown`".into()),
|
||||
)
|
||||
|
@ -20,6 +20,7 @@ pub enum TableMode {
|
||||
Restructured,
|
||||
AsciiRounded,
|
||||
BasicCompact,
|
||||
Single,
|
||||
}
|
||||
|
||||
impl FromStr for TableMode {
|
||||
@ -44,7 +45,8 @@ impl FromStr for TableMode {
|
||||
"restructured" => Ok(Self::Restructured),
|
||||
"ascii_rounded" => Ok(Self::AsciiRounded),
|
||||
"basic_compact" => Ok(Self::BasicCompact),
|
||||
_ => Err("'basic', 'thin', 'light', 'compact', 'with_love', 'compact_double', 'rounded', 'reinforced', 'heavy', 'none', 'psql', 'markdown', 'dots', 'restructured', 'ascii_rounded', or 'basic_compact'"),
|
||||
"single" => Ok(Self::Single),
|
||||
_ => Err("'basic', 'thin', 'light', 'compact', 'with_love', 'compact_double', 'rounded', 'reinforced', 'heavy', 'none', 'psql', 'markdown', 'dots', 'restructured', 'ascii_rounded', 'basic_compact' or 'single'"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ use crate::{
|
||||
debugger::DebugContext,
|
||||
BlockId, Config, GetSpan, Range, Record, ShellError, Span, Value, VarId, ENV_VARIABLE_ID,
|
||||
};
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use std::{borrow::Cow, collections::HashMap, sync::Arc};
|
||||
|
||||
/// To share implementations for regular eval and const eval
|
||||
pub trait Eval {
|
||||
@ -43,11 +43,8 @@ pub trait Eval {
|
||||
|
||||
// Cell paths are usually case-sensitive, but we give $env
|
||||
// special treatment.
|
||||
if cell_path.head.expr == Expr::Var(ENV_VARIABLE_ID) {
|
||||
value.follow_cell_path(&cell_path.tail, true)
|
||||
} else {
|
||||
value.follow_cell_path(&cell_path.tail, false)
|
||||
}
|
||||
let insensitive = cell_path.head.expr == Expr::Var(ENV_VARIABLE_ID);
|
||||
value.follow_cell_path(&cell_path.tail, insensitive).map(Cow::into_owned)
|
||||
}
|
||||
Expr::DateTime(dt) => Ok(Value::date(*dt, expr_span)),
|
||||
Expr::List(list) => {
|
||||
|
@ -128,7 +128,6 @@ impl Module {
|
||||
} else {
|
||||
// Import pattern was just name without any members
|
||||
let mut decls = vec![];
|
||||
let mut const_vids = vec![];
|
||||
let mut const_rows = vec![];
|
||||
let mut errors = vec![];
|
||||
|
||||
@ -154,7 +153,6 @@ impl Module {
|
||||
decls.push((new_name, sub_decl_id));
|
||||
}
|
||||
|
||||
const_vids.extend(sub_results.constants);
|
||||
const_rows.extend(sub_results.constant_values);
|
||||
}
|
||||
|
||||
@ -162,10 +160,7 @@ impl Module {
|
||||
|
||||
for (name, var_id) in self.consts() {
|
||||
match working_set.get_constant(var_id) {
|
||||
Ok(const_val) => {
|
||||
const_vids.push((name.clone(), var_id));
|
||||
const_rows.push((name, const_val.clone()))
|
||||
}
|
||||
Ok(const_val) => const_rows.push((name, const_val.clone())),
|
||||
Err(err) => errors.push(err),
|
||||
}
|
||||
}
|
||||
@ -192,7 +187,7 @@ impl Module {
|
||||
ResolvedImportPattern::new(
|
||||
decls,
|
||||
vec![(final_name.clone(), self_id)],
|
||||
const_vids,
|
||||
vec![],
|
||||
constant_values,
|
||||
),
|
||||
errors,
|
||||
|
@ -6,7 +6,7 @@ use crate::{
|
||||
ByteStream, ByteStreamType, Config, ListStream, OutDest, PipelineMetadata, Range, ShellError,
|
||||
Signals, Span, Type, Value,
|
||||
};
|
||||
use std::io::Write;
|
||||
use std::{borrow::Cow, io::Write};
|
||||
|
||||
const LINE_ENDING_PATTERN: &[char] = &['\r', '\n'];
|
||||
|
||||
@ -416,8 +416,11 @@ impl PipelineData {
|
||||
match self {
|
||||
// FIXME: there are probably better ways of doing this
|
||||
PipelineData::ListStream(stream, ..) => Value::list(stream.into_iter().collect(), head)
|
||||
.follow_cell_path(cell_path, insensitive),
|
||||
PipelineData::Value(v, ..) => v.follow_cell_path(cell_path, insensitive),
|
||||
.follow_cell_path(cell_path, insensitive)
|
||||
.map(Cow::into_owned),
|
||||
PipelineData::Value(v, ..) => v
|
||||
.follow_cell_path(cell_path, insensitive)
|
||||
.map(Cow::into_owned),
|
||||
PipelineData::Empty => Err(ShellError::IncompatiblePathAccess {
|
||||
type_name: "empty pipeline".to_string(),
|
||||
span: head,
|
||||
|
@ -38,7 +38,7 @@ use std::{
|
||||
borrow::Cow,
|
||||
cmp::Ordering,
|
||||
fmt::{Debug, Display, Write},
|
||||
ops::Bound,
|
||||
ops::{Bound, ControlFlow, Deref},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
@ -1080,224 +1080,83 @@ impl Value {
|
||||
}
|
||||
|
||||
/// Follow a given cell path into the value: for example accessing select elements in a stream or list
|
||||
pub fn follow_cell_path(
|
||||
self,
|
||||
pub fn follow_cell_path<'out>(
|
||||
&'out self,
|
||||
cell_path: &[PathMember],
|
||||
insensitive: bool,
|
||||
) -> Result<Value, ShellError> {
|
||||
let mut current = self;
|
||||
) -> Result<Cow<'out, Value>, ShellError> {
|
||||
enum MultiLife<'out, 'local, T>
|
||||
where
|
||||
'out: 'local,
|
||||
T: ?Sized,
|
||||
{
|
||||
Out(&'out T),
|
||||
Local(&'local T),
|
||||
}
|
||||
|
||||
for member in cell_path {
|
||||
match member {
|
||||
PathMember::Int {
|
||||
val: count,
|
||||
span: origin_span,
|
||||
optional,
|
||||
} => {
|
||||
// Treat a numeric path member as `select <val>`
|
||||
match current {
|
||||
Value::List { mut vals, .. } => {
|
||||
if *count < vals.len() {
|
||||
// `vals` is owned and will be dropped right after this,
|
||||
// so we can `swap_remove` the value at index `count`
|
||||
// without worrying about preserving order.
|
||||
current = vals.swap_remove(*count);
|
||||
} else if *optional {
|
||||
return Ok(Value::nothing(*origin_span)); // short-circuit
|
||||
} else if vals.is_empty() {
|
||||
return Err(ShellError::AccessEmptyContent { span: *origin_span });
|
||||
} else {
|
||||
return Err(ShellError::AccessBeyondEnd {
|
||||
max_idx: vals.len() - 1,
|
||||
span: *origin_span,
|
||||
});
|
||||
}
|
||||
}
|
||||
Value::Binary { val, .. } => {
|
||||
if let Some(item) = val.get(*count) {
|
||||
current = Value::int(*item as i64, *origin_span);
|
||||
} else if *optional {
|
||||
return Ok(Value::nothing(*origin_span)); // short-circuit
|
||||
} else if val.is_empty() {
|
||||
return Err(ShellError::AccessEmptyContent { span: *origin_span });
|
||||
} else {
|
||||
return Err(ShellError::AccessBeyondEnd {
|
||||
max_idx: val.len() - 1,
|
||||
span: *origin_span,
|
||||
});
|
||||
}
|
||||
}
|
||||
Value::Range { ref val, .. } => {
|
||||
if let Some(item) = val
|
||||
.into_range_iter(current.span(), Signals::empty())
|
||||
.nth(*count)
|
||||
{
|
||||
current = item;
|
||||
} else if *optional {
|
||||
return Ok(Value::nothing(*origin_span)); // short-circuit
|
||||
} else {
|
||||
return Err(ShellError::AccessBeyondEndOfStream {
|
||||
span: *origin_span,
|
||||
});
|
||||
}
|
||||
}
|
||||
Value::Custom { ref val, .. } => {
|
||||
current =
|
||||
match val.follow_path_int(current.span(), *count, *origin_span) {
|
||||
Ok(val) => val,
|
||||
Err(err) => {
|
||||
if *optional {
|
||||
return Ok(Value::nothing(*origin_span));
|
||||
// short-circuit
|
||||
} else {
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
Value::Nothing { .. } if *optional => {
|
||||
return Ok(Value::nothing(*origin_span)); // short-circuit
|
||||
}
|
||||
// Records (and tables) are the only built-in which support column names,
|
||||
// so only use this message for them.
|
||||
Value::Record { .. } => {
|
||||
return Err(ShellError::TypeMismatch {
|
||||
err_message:"Can't access record values with a row index. Try specifying a column name instead".into(),
|
||||
span: *origin_span,
|
||||
});
|
||||
}
|
||||
Value::Error { error, .. } => return Err(*error),
|
||||
x => {
|
||||
return Err(ShellError::IncompatiblePathAccess {
|
||||
type_name: format!("{}", x.get_type()),
|
||||
span: *origin_span,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
PathMember::String {
|
||||
val: column_name,
|
||||
span: origin_span,
|
||||
optional,
|
||||
} => {
|
||||
let span = current.span();
|
||||
impl<'out, 'local, T> Deref for MultiLife<'out, 'local, T>
|
||||
where
|
||||
'out: 'local,
|
||||
T: ?Sized,
|
||||
{
|
||||
type Target = T;
|
||||
|
||||
match current {
|
||||
Value::Record { mut val, .. } => {
|
||||
// Make reverse iterate to avoid duplicate column leads to first value, actually last value is expected.
|
||||
if let Some(found) = val.to_mut().iter_mut().rev().find(|x| {
|
||||
if insensitive {
|
||||
x.0.eq_ignore_case(column_name)
|
||||
} else {
|
||||
x.0 == column_name
|
||||
}
|
||||
}) {
|
||||
current = std::mem::take(found.1);
|
||||
} else if *optional {
|
||||
return Ok(Value::nothing(*origin_span)); // short-circuit
|
||||
} else if let Some(suggestion) =
|
||||
did_you_mean(val.columns(), column_name)
|
||||
{
|
||||
return Err(ShellError::DidYouMean {
|
||||
suggestion,
|
||||
span: *origin_span,
|
||||
});
|
||||
} else {
|
||||
return Err(ShellError::CantFindColumn {
|
||||
col_name: column_name.clone(),
|
||||
span: Some(*origin_span),
|
||||
src_span: span,
|
||||
});
|
||||
}
|
||||
}
|
||||
// String access of Lists always means Table access.
|
||||
// Create a List which contains each matching value for contained
|
||||
// records in the source list.
|
||||
Value::List { vals, .. } => {
|
||||
let list = vals
|
||||
.into_iter()
|
||||
.map(|val| {
|
||||
let val_span = val.span();
|
||||
match val {
|
||||
Value::Record { mut val, .. } => {
|
||||
if let Some(found) =
|
||||
val.to_mut().iter_mut().rev().find(|x| {
|
||||
if insensitive {
|
||||
x.0.eq_ignore_case(column_name)
|
||||
} else {
|
||||
x.0 == column_name
|
||||
}
|
||||
})
|
||||
{
|
||||
Ok(std::mem::take(found.1))
|
||||
} else if *optional {
|
||||
Ok(Value::nothing(*origin_span))
|
||||
} else if let Some(suggestion) =
|
||||
did_you_mean(val.columns(), column_name)
|
||||
{
|
||||
Err(ShellError::DidYouMean {
|
||||
suggestion,
|
||||
span: *origin_span,
|
||||
})
|
||||
} else {
|
||||
Err(ShellError::CantFindColumn {
|
||||
col_name: column_name.clone(),
|
||||
span: Some(*origin_span),
|
||||
src_span: val_span,
|
||||
})
|
||||
}
|
||||
}
|
||||
Value::Nothing { .. } if *optional => {
|
||||
Ok(Value::nothing(*origin_span))
|
||||
}
|
||||
_ => Err(ShellError::CantFindColumn {
|
||||
col_name: column_name.clone(),
|
||||
span: Some(*origin_span),
|
||||
src_span: val_span,
|
||||
}),
|
||||
}
|
||||
})
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
current = Value::list(list, span);
|
||||
}
|
||||
Value::Custom { ref val, .. } => {
|
||||
current = match val.follow_path_string(
|
||||
current.span(),
|
||||
column_name.clone(),
|
||||
*origin_span,
|
||||
) {
|
||||
Ok(val) => val,
|
||||
Err(err) => {
|
||||
if *optional {
|
||||
return Ok(Value::nothing(*origin_span));
|
||||
// short-circuit
|
||||
} else {
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Value::Nothing { .. } if *optional => {
|
||||
return Ok(Value::nothing(*origin_span)); // short-circuit
|
||||
}
|
||||
Value::Error { error, .. } => return Err(*error),
|
||||
x => {
|
||||
return Err(ShellError::IncompatiblePathAccess {
|
||||
type_name: format!("{}", x.get_type()),
|
||||
span: *origin_span,
|
||||
});
|
||||
}
|
||||
}
|
||||
fn deref(&self) -> &Self::Target {
|
||||
match *self {
|
||||
MultiLife::Out(x) => x,
|
||||
MultiLife::Local(x) => x,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A dummy value is required, otherwise rust doesn't allow references, which we need for
|
||||
// the `std::ptr::eq` comparison
|
||||
let mut store: Value = Value::test_nothing();
|
||||
let mut current: MultiLife<'out, '_, Value> = MultiLife::Out(self);
|
||||
|
||||
for member in cell_path {
|
||||
current = match current {
|
||||
MultiLife::Out(current) => match get_value_member(current, member, insensitive)? {
|
||||
ControlFlow::Break(span) => return Ok(Cow::Owned(Value::nothing(span))),
|
||||
ControlFlow::Continue(x) => match x {
|
||||
Cow::Borrowed(x) => MultiLife::Out(x),
|
||||
Cow::Owned(x) => {
|
||||
store = x;
|
||||
MultiLife::Local(&store)
|
||||
}
|
||||
},
|
||||
},
|
||||
MultiLife::Local(current) => {
|
||||
match get_value_member(current, member, insensitive)? {
|
||||
ControlFlow::Break(span) => return Ok(Cow::Owned(Value::nothing(span))),
|
||||
ControlFlow::Continue(x) => match x {
|
||||
Cow::Borrowed(x) => MultiLife::Local(x),
|
||||
Cow::Owned(x) => {
|
||||
store = x;
|
||||
MultiLife::Local(&store)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// If a single Value::Error was produced by the above (which won't happen if nullify_errors is true), unwrap it now.
|
||||
// Note that Value::Errors inside Lists remain as they are, so that the rest of the list can still potentially be used.
|
||||
if let Value::Error { error, .. } = current {
|
||||
Err(*error)
|
||||
if let Value::Error { error, .. } = &*current {
|
||||
Err(error.as_ref().clone())
|
||||
} else {
|
||||
Ok(current)
|
||||
Ok(match current {
|
||||
MultiLife::Out(x) => Cow::Borrowed(x),
|
||||
MultiLife::Local(x) => {
|
||||
let x = if std::ptr::eq(x, &store) {
|
||||
store
|
||||
} else {
|
||||
x.clone()
|
||||
};
|
||||
Cow::Owned(x)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -1307,9 +1166,7 @@ impl Value {
|
||||
cell_path: &[PathMember],
|
||||
callback: Box<dyn FnOnce(&Value) -> Value>,
|
||||
) -> Result<(), ShellError> {
|
||||
let orig = self.clone();
|
||||
|
||||
let new_val = callback(&orig.follow_cell_path(cell_path, false)?);
|
||||
let new_val = callback(self.follow_cell_path(cell_path, false)?.as_ref());
|
||||
|
||||
match new_val {
|
||||
Value::Error { error, .. } => Err(*error),
|
||||
@ -1409,9 +1266,7 @@ impl Value {
|
||||
cell_path: &[PathMember],
|
||||
callback: Box<dyn FnOnce(&Value) -> Value + 'a>,
|
||||
) -> Result<(), ShellError> {
|
||||
let orig = self.clone();
|
||||
|
||||
let new_val = callback(&orig.follow_cell_path(cell_path, false)?);
|
||||
let new_val = callback(self.follow_cell_path(cell_path, false)?.as_ref());
|
||||
|
||||
match new_val {
|
||||
Value::Error { error, .. } => Err(*error),
|
||||
@ -2147,6 +2002,198 @@ impl Value {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_value_member<'a>(
|
||||
current: &'a Value,
|
||||
member: &PathMember,
|
||||
insensitive: bool,
|
||||
) -> Result<ControlFlow<Span, Cow<'a, Value>>, ShellError> {
|
||||
match member {
|
||||
PathMember::Int {
|
||||
val: count,
|
||||
span: origin_span,
|
||||
optional,
|
||||
} => {
|
||||
// Treat a numeric path member as `select <val>`
|
||||
match current {
|
||||
Value::List { vals, .. } => {
|
||||
if *count < vals.len() {
|
||||
Ok(ControlFlow::Continue(Cow::Borrowed(&vals[*count])))
|
||||
} else if *optional {
|
||||
Ok(ControlFlow::Break(*origin_span))
|
||||
// short-circuit
|
||||
} else if vals.is_empty() {
|
||||
Err(ShellError::AccessEmptyContent { span: *origin_span })
|
||||
} else {
|
||||
Err(ShellError::AccessBeyondEnd {
|
||||
max_idx: vals.len() - 1,
|
||||
span: *origin_span,
|
||||
})
|
||||
}
|
||||
}
|
||||
Value::Binary { val, .. } => {
|
||||
if let Some(item) = val.get(*count) {
|
||||
Ok(ControlFlow::Continue(Cow::Owned(Value::int(
|
||||
*item as i64,
|
||||
*origin_span,
|
||||
))))
|
||||
} else if *optional {
|
||||
Ok(ControlFlow::Break(*origin_span))
|
||||
// short-circuit
|
||||
} else if val.is_empty() {
|
||||
Err(ShellError::AccessEmptyContent { span: *origin_span })
|
||||
} else {
|
||||
Err(ShellError::AccessBeyondEnd {
|
||||
max_idx: val.len() - 1,
|
||||
span: *origin_span,
|
||||
})
|
||||
}
|
||||
}
|
||||
Value::Range { ref val, .. } => {
|
||||
if let Some(item) = val
|
||||
.into_range_iter(current.span(), Signals::empty())
|
||||
.nth(*count)
|
||||
{
|
||||
Ok(ControlFlow::Continue(Cow::Owned(item)))
|
||||
} else if *optional {
|
||||
Ok(ControlFlow::Break(*origin_span))
|
||||
// short-circuit
|
||||
} else {
|
||||
Err(ShellError::AccessBeyondEndOfStream {
|
||||
span: *origin_span,
|
||||
})
|
||||
}
|
||||
}
|
||||
Value::Custom { ref val, .. } => {
|
||||
match val.follow_path_int(current.span(), *count, *origin_span)
|
||||
{
|
||||
Ok(val) => Ok(ControlFlow::Continue(Cow::Owned(val))),
|
||||
Err(err) => {
|
||||
if *optional {
|
||||
Ok(ControlFlow::Break(*origin_span))
|
||||
// short-circuit
|
||||
} else {
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Value::Nothing { .. } if *optional => Ok(ControlFlow::Break(*origin_span)),
|
||||
// Records (and tables) are the only built-in which support column names,
|
||||
// so only use this message for them.
|
||||
Value::Record { .. } => Err(ShellError::TypeMismatch {
|
||||
err_message:"Can't access record values with a row index. Try specifying a column name instead".into(),
|
||||
span: *origin_span,
|
||||
}),
|
||||
Value::Error { error, .. } => Err(*error.clone()),
|
||||
x => Err(ShellError::IncompatiblePathAccess { type_name: format!("{}", x.get_type()), span: *origin_span }),
|
||||
}
|
||||
}
|
||||
PathMember::String {
|
||||
val: column_name,
|
||||
span: origin_span,
|
||||
optional,
|
||||
} => {
|
||||
let span = current.span();
|
||||
match current {
|
||||
Value::Record { val, .. } => {
|
||||
if let Some(found) = val.iter().rev().find(|x| {
|
||||
if insensitive {
|
||||
x.0.eq_ignore_case(column_name)
|
||||
} else {
|
||||
x.0 == column_name
|
||||
}
|
||||
}) {
|
||||
Ok(ControlFlow::Continue(Cow::Borrowed(found.1)))
|
||||
} else if *optional {
|
||||
Ok(ControlFlow::Break(*origin_span))
|
||||
// short-circuit
|
||||
} else if let Some(suggestion) = did_you_mean(val.columns(), column_name) {
|
||||
Err(ShellError::DidYouMean {
|
||||
suggestion,
|
||||
span: *origin_span,
|
||||
})
|
||||
} else {
|
||||
Err(ShellError::CantFindColumn {
|
||||
col_name: column_name.clone(),
|
||||
span: Some(*origin_span),
|
||||
src_span: span,
|
||||
})
|
||||
}
|
||||
}
|
||||
// String access of Lists always means Table access.
|
||||
// Create a List which contains each matching value for contained
|
||||
// records in the source list.
|
||||
Value::List { vals, .. } => {
|
||||
let list = vals
|
||||
.iter()
|
||||
.map(|val| {
|
||||
let val_span = val.span();
|
||||
match val {
|
||||
Value::Record { val, .. } => {
|
||||
if let Some(found) = val.iter().rev().find(|x| {
|
||||
if insensitive {
|
||||
x.0.eq_ignore_case(column_name)
|
||||
} else {
|
||||
x.0 == column_name
|
||||
}
|
||||
}) {
|
||||
Ok(found.1.clone())
|
||||
} else if *optional {
|
||||
Ok(Value::nothing(*origin_span))
|
||||
} else if let Some(suggestion) =
|
||||
did_you_mean(val.columns(), column_name)
|
||||
{
|
||||
Err(ShellError::DidYouMean {
|
||||
suggestion,
|
||||
span: *origin_span,
|
||||
})
|
||||
} else {
|
||||
Err(ShellError::CantFindColumn {
|
||||
col_name: column_name.clone(),
|
||||
span: Some(*origin_span),
|
||||
src_span: val_span,
|
||||
})
|
||||
}
|
||||
}
|
||||
Value::Nothing { .. } if *optional => {
|
||||
Ok(Value::nothing(*origin_span))
|
||||
}
|
||||
_ => Err(ShellError::CantFindColumn {
|
||||
col_name: column_name.clone(),
|
||||
span: Some(*origin_span),
|
||||
src_span: val_span,
|
||||
}),
|
||||
}
|
||||
})
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
Ok(ControlFlow::Continue(Cow::Owned(Value::list(list, span))))
|
||||
}
|
||||
Value::Custom { ref val, .. } => {
|
||||
match val.follow_path_string(current.span(), column_name.clone(), *origin_span)
|
||||
{
|
||||
Ok(val) => Ok(ControlFlow::Continue(Cow::Owned(val))),
|
||||
Err(err) => {
|
||||
if *optional {
|
||||
Ok(ControlFlow::Break(*origin_span))
|
||||
// short-circuit
|
||||
} else {
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Value::Nothing { .. } if *optional => Ok(ControlFlow::Break(*origin_span)),
|
||||
Value::Error { error, .. } => Err(error.as_ref().clone()),
|
||||
x => Err(ShellError::IncompatiblePathAccess {
|
||||
type_name: format!("{}", x.get_type()),
|
||||
span: *origin_span,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Value {
|
||||
fn default() -> Self {
|
||||
Value::Nothing {
|
||||
|
@ -21,10 +21,12 @@ fn main() {
|
||||
let headers = to_cell_info_vec(&table_headers);
|
||||
let rows = to_cell_info_vec(&row_data);
|
||||
|
||||
let mut rows = vec![rows; 3];
|
||||
rows.insert(0, headers);
|
||||
let mut table = NuTable::new(4, 3);
|
||||
table.set_row(0, headers);
|
||||
|
||||
let mut table = NuTable::from(rows);
|
||||
for i in 0..3 {
|
||||
table.set_row(i + 1, rows.clone());
|
||||
}
|
||||
|
||||
table.set_data_style(TextStyle::basic_left());
|
||||
table.set_header_style(TextStyle::basic_center().style(Style::new().on(Color::Blue)));
|
||||
|
@ -186,6 +186,7 @@ pub fn load_theme(mode: TableMode) -> TableTheme {
|
||||
TableMode::Restructured => TableTheme::restructured(),
|
||||
TableMode::AsciiRounded => TableTheme::ascii_rounded(),
|
||||
TableMode::BasicCompact => TableTheme::basic_compact(),
|
||||
TableMode::Single => TableTheme::single(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
// NOTE: TODO the above we could expose something like [`WidthCtrl`] in which case we could also laverage the width list build right away.
|
||||
// currently it seems like we do recacalculate it for `table -e`?
|
||||
|
||||
use std::{cmp::min, collections::HashMap};
|
||||
use std::cmp::{max, min};
|
||||
|
||||
use nu_ansi_term::Style;
|
||||
use nu_color_config::TextStyle;
|
||||
@ -13,13 +13,14 @@ use tabled::{
|
||||
builder::Builder,
|
||||
grid::{
|
||||
ansi::ANSIBuf,
|
||||
colors::Colors,
|
||||
config::{
|
||||
AlignmentHorizontal, ColoredConfig, Entity, Indent, Position, Sides, SpannedConfig,
|
||||
},
|
||||
dimension::{CompleteDimensionVecRecords, SpannedGridDimension},
|
||||
records::{
|
||||
vec_records::{Text, VecRecords},
|
||||
ExactRecords, Records,
|
||||
vec_records::{Cell, Text, VecRecords},
|
||||
IntoRecords, IterRecords, Records,
|
||||
},
|
||||
},
|
||||
settings::{
|
||||
@ -43,24 +44,30 @@ pub type NuRecordsValue = Text<String>;
|
||||
/// NuTable is a table rendering implementation.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NuTable {
|
||||
data: NuRecords,
|
||||
data: Vec<Vec<NuRecordsValue>>,
|
||||
widths: Vec<usize>,
|
||||
count_rows: usize,
|
||||
count_cols: usize,
|
||||
styles: Styles,
|
||||
alignments: Alignments,
|
||||
config: TableConfig,
|
||||
}
|
||||
|
||||
impl NuTable {
|
||||
/// Creates an empty [`NuTable`] instance.
|
||||
pub fn new(count_rows: usize, count_columns: usize) -> Self {
|
||||
pub fn new(count_rows: usize, count_cols: usize) -> Self {
|
||||
Self {
|
||||
data: VecRecords::new(vec![vec![Text::default(); count_columns]; count_rows]),
|
||||
styles: Styles::default(),
|
||||
alignments: Alignments {
|
||||
data: AlignmentHorizontal::Left,
|
||||
index: AlignmentHorizontal::Right,
|
||||
header: AlignmentHorizontal::Center,
|
||||
columns: HashMap::default(),
|
||||
cells: HashMap::default(),
|
||||
data: vec![vec![Text::default(); count_cols]; count_rows],
|
||||
widths: vec![2; count_cols],
|
||||
count_rows,
|
||||
count_cols,
|
||||
styles: Styles {
|
||||
cfg: ColoredConfig::default(),
|
||||
alignments: CellConfiguration {
|
||||
data: AlignmentHorizontal::Left,
|
||||
index: AlignmentHorizontal::Right,
|
||||
header: AlignmentHorizontal::Center,
|
||||
},
|
||||
colors: CellConfiguration::default(),
|
||||
},
|
||||
config: TableConfig {
|
||||
theme: TableTheme::basic(),
|
||||
@ -76,84 +83,125 @@ impl NuTable {
|
||||
|
||||
/// Return amount of rows.
|
||||
pub fn count_rows(&self) -> usize {
|
||||
self.data.count_rows()
|
||||
self.count_rows
|
||||
}
|
||||
|
||||
/// Return amount of columns.
|
||||
pub fn count_columns(&self) -> usize {
|
||||
self.data.count_columns()
|
||||
self.count_cols
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, pos: Position, text: String) {
|
||||
self.data[pos.0][pos.1] = Text::new(text);
|
||||
}
|
||||
|
||||
pub fn insert_row(&mut self, index: usize, row: Vec<String>) {
|
||||
let data = &mut self.data[index];
|
||||
|
||||
for (col, text) in row.into_iter().enumerate() {
|
||||
data[col] = Text::new(text);
|
||||
}
|
||||
let text = Text::new(text);
|
||||
self.widths[pos.1] = max(
|
||||
self.widths[pos.1],
|
||||
text.width() + indent_sum(self.config.indent),
|
||||
);
|
||||
self.data[pos.0][pos.1] = text;
|
||||
}
|
||||
|
||||
pub fn set_row(&mut self, index: usize, row: Vec<NuRecordsValue>) {
|
||||
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),
|
||||
);
|
||||
}
|
||||
|
||||
self.data[index] = row;
|
||||
}
|
||||
|
||||
pub fn set_column_style(&mut self, column: usize, style: TextStyle) {
|
||||
if let Some(style) = style.color_style {
|
||||
let style = convert_style(style);
|
||||
self.styles.columns.insert(column, style);
|
||||
pub fn pop_column(&mut self, count: usize) {
|
||||
self.count_cols -= count;
|
||||
self.widths.truncate(self.count_cols);
|
||||
for row in &mut self.data[..] {
|
||||
row.truncate(self.count_cols);
|
||||
}
|
||||
|
||||
let alignment = convert_alignment(style.alignment);
|
||||
if alignment != self.alignments.data {
|
||||
self.alignments.columns.insert(column, alignment);
|
||||
// set to default styles of the popped columns
|
||||
for i in 0..count {
|
||||
let col = self.count_cols + i;
|
||||
for row in 0..self.count_rows {
|
||||
self.styles
|
||||
.cfg
|
||||
.set_alignment_horizontal(Entity::Cell(row, col), self.styles.alignments.data);
|
||||
self.styles
|
||||
.cfg
|
||||
.set_color(Entity::Cell(row, col), ANSIBuf::default());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push_column(&mut self, text: String) {
|
||||
let value = Text::new(text);
|
||||
|
||||
self.widths
|
||||
.push(value.width() + indent_sum(self.config.indent));
|
||||
|
||||
for row in &mut self.data[..] {
|
||||
row.push(value.clone());
|
||||
}
|
||||
|
||||
self.count_cols += 1;
|
||||
}
|
||||
|
||||
pub fn insert_style(&mut self, pos: Position, style: TextStyle) {
|
||||
if let Some(style) = style.color_style {
|
||||
let style = convert_style(style);
|
||||
self.styles.cells.insert(pos, style);
|
||||
self.styles.cfg.set_color(pos.into(), style.into());
|
||||
}
|
||||
|
||||
let alignment = convert_alignment(style.alignment);
|
||||
if alignment != self.alignments.data {
|
||||
self.alignments.cells.insert(pos, alignment);
|
||||
if alignment != self.styles.alignments.data {
|
||||
self.styles
|
||||
.cfg
|
||||
.set_alignment_horizontal(pos.into(), alignment);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_header_style(&mut self, style: TextStyle) {
|
||||
if let Some(style) = style.color_style {
|
||||
let style = convert_style(style);
|
||||
self.styles.header = style;
|
||||
self.styles.colors.header = style;
|
||||
}
|
||||
|
||||
self.alignments.header = convert_alignment(style.alignment);
|
||||
self.styles.alignments.header = convert_alignment(style.alignment);
|
||||
}
|
||||
|
||||
pub fn set_index_style(&mut self, style: TextStyle) {
|
||||
if let Some(style) = style.color_style {
|
||||
let style = convert_style(style);
|
||||
self.styles.index = style;
|
||||
self.styles.colors.index = style;
|
||||
}
|
||||
|
||||
self.alignments.index = convert_alignment(style.alignment);
|
||||
self.styles.alignments.index = convert_alignment(style.alignment);
|
||||
}
|
||||
|
||||
pub fn set_data_style(&mut self, style: TextStyle) {
|
||||
if let Some(style) = style.color_style {
|
||||
let style = convert_style(style);
|
||||
self.styles.data = style;
|
||||
if !style.is_plain() {
|
||||
let style = convert_style(style);
|
||||
self.styles.cfg.set_color(Entity::Global, style.into());
|
||||
}
|
||||
}
|
||||
|
||||
self.alignments.data = convert_alignment(style.alignment);
|
||||
let alignment = convert_alignment(style.alignment);
|
||||
self.styles
|
||||
.cfg
|
||||
.set_alignment_horizontal(Entity::Global, alignment);
|
||||
self.styles.alignments.data = alignment;
|
||||
}
|
||||
|
||||
// NOTE: Crusial to be called before data changes (todo fix interface)
|
||||
pub fn set_indent(&mut self, indent: TableIndent) {
|
||||
self.config.indent = indent;
|
||||
|
||||
let pad = indent_sum(indent);
|
||||
for w in &mut self.widths {
|
||||
*w = pad;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_theme(&mut self, theme: TableTheme) {
|
||||
@ -180,7 +228,9 @@ impl NuTable {
|
||||
self.config.border_color = (!color.is_plain()).then_some(color);
|
||||
}
|
||||
|
||||
pub fn get_records_mut(&mut self) -> &mut NuRecords {
|
||||
// NOTE: BE CAREFUL TO KEEP WIDTH UNCHANGED
|
||||
// TODO: fix interface
|
||||
pub fn get_records_mut(&mut self) -> &mut [Vec<NuRecordsValue>] {
|
||||
&mut self.data
|
||||
}
|
||||
|
||||
@ -194,32 +244,42 @@ impl NuTable {
|
||||
/// Return a total table width.
|
||||
pub fn total_width(&self) -> usize {
|
||||
let config = create_config(&self.config.theme, false, None);
|
||||
let pad = indent_sum(self.config.indent);
|
||||
let widths = build_width(&self.data, pad);
|
||||
get_total_width2(&widths, &config)
|
||||
get_total_width2(&self.widths, &config)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<Vec<Text<String>>>> for NuTable {
|
||||
fn from(value: Vec<Vec<Text<String>>>) -> Self {
|
||||
let mut nutable = Self::new(0, 0);
|
||||
nutable.data = VecRecords::new(value);
|
||||
let count_rows = value.len();
|
||||
let count_cols = if value.is_empty() { 0 } else { value[0].len() };
|
||||
|
||||
nutable
|
||||
let mut t = Self::new(count_rows, count_cols);
|
||||
t.data = value;
|
||||
table_recalculate_widths(&mut t);
|
||||
|
||||
t
|
||||
}
|
||||
}
|
||||
|
||||
type Alignments = CellConfiguration<AlignmentHorizontal>;
|
||||
fn table_recalculate_widths(t: &mut NuTable) {
|
||||
let pad = indent_sum(t.config.indent);
|
||||
let records = IterRecords::new(&t.data, t.count_cols, Some(t.count_rows));
|
||||
let widths = build_width(records, pad);
|
||||
t.widths = widths;
|
||||
}
|
||||
|
||||
type Styles = CellConfiguration<Color>;
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Copy, Hash)]
|
||||
struct CellConfiguration<Value> {
|
||||
data: Value,
|
||||
index: Value,
|
||||
header: Value,
|
||||
columns: HashMap<usize, Value>,
|
||||
cells: HashMap<Position, Value>,
|
||||
data: Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
struct Styles {
|
||||
cfg: ColoredConfig,
|
||||
colors: CellConfiguration<Color>,
|
||||
alignments: CellConfiguration<AlignmentHorizontal>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@ -304,49 +364,67 @@ fn table_insert_footer_if(t: &mut NuTable) {
|
||||
}
|
||||
|
||||
fn table_truncate(t: &mut NuTable, termwidth: usize) -> Option<WidthEstimation> {
|
||||
let widths = maybe_truncate_columns(&mut t.data, &t.config, termwidth);
|
||||
let widths = maybe_truncate_columns(&mut t.data, t.widths.clone(), &t.config, termwidth);
|
||||
if widths.needed.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// reset style for last column which is a trail one
|
||||
if widths.trail {
|
||||
let col = widths.needed.len() - 1;
|
||||
for row in 0..t.count_rows {
|
||||
t.styles
|
||||
.cfg
|
||||
.set_alignment_horizontal(Entity::Cell(row, col), t.styles.alignments.data);
|
||||
t.styles
|
||||
.cfg
|
||||
.set_color(Entity::Cell(row, col), ANSIBuf::default());
|
||||
}
|
||||
}
|
||||
|
||||
Some(widths)
|
||||
}
|
||||
|
||||
fn remove_header(t: &mut NuTable) -> HeadInfo {
|
||||
let head: Vec<String> = t
|
||||
// move settings by one row down
|
||||
for row in 1..t.data.len() {
|
||||
for col in 0..t.count_cols {
|
||||
let alignment = *t
|
||||
.styles
|
||||
.cfg
|
||||
.get_alignment_horizontal(Entity::Cell(row, col));
|
||||
if alignment != t.styles.alignments.data {
|
||||
t.styles
|
||||
.cfg
|
||||
.set_alignment_horizontal(Entity::Cell(row - 1, col), alignment);
|
||||
}
|
||||
|
||||
// TODO: use get_color from upstream (when released)
|
||||
let color = t.styles.cfg.get_colors().get_color((row, col)).cloned();
|
||||
if let Some(color) = color {
|
||||
t.styles.cfg.set_color(Entity::Cell(row - 1, col), color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let head = t
|
||||
.data
|
||||
.remove(0)
|
||||
.into_iter()
|
||||
.map(|s| s.to_string())
|
||||
.collect();
|
||||
let align = t.alignments.header;
|
||||
let color = if is_color_empty(&t.styles.header) {
|
||||
None
|
||||
} else {
|
||||
Some(t.styles.header.clone())
|
||||
};
|
||||
|
||||
// move settings by one row down
|
||||
t.alignments.cells = t
|
||||
.alignments
|
||||
.cells
|
||||
.drain()
|
||||
.filter(|(k, _)| k.0 != 0)
|
||||
.map(|(k, v)| ((k.0 - 1, k.1), v))
|
||||
.collect();
|
||||
t.alignments.header = AlignmentHorizontal::Center;
|
||||
// WE NEED TO RELCULATE WIDTH.
|
||||
// TODO: cause we have configuration beforehand we can just not calculate it in?
|
||||
table_recalculate_widths(t);
|
||||
|
||||
// move settings by one row down
|
||||
t.styles.cells = t
|
||||
.styles
|
||||
.cells
|
||||
.drain()
|
||||
.filter(|(k, _)| k.0 != 0)
|
||||
.map(|(k, v)| ((k.0 - 1, k.1), v))
|
||||
.collect();
|
||||
t.styles.header = Color::empty();
|
||||
let alignment = t.styles.alignments.header;
|
||||
let color = get_color_if_exists(&t.styles.colors.header);
|
||||
|
||||
HeadInfo::new(head, align, color)
|
||||
t.styles.alignments.header = AlignmentHorizontal::Center;
|
||||
t.styles.colors.header = Color::empty();
|
||||
|
||||
HeadInfo::new(head, alignment, color)
|
||||
}
|
||||
|
||||
fn draw_table(
|
||||
@ -359,19 +437,24 @@ fn draw_table(
|
||||
structure.with_footer = structure.with_footer && head.is_none();
|
||||
let sep_color = t.config.border_color;
|
||||
|
||||
let data: Vec<Vec<_>> = t.data.into();
|
||||
let data = t.data;
|
||||
let mut table = Builder::from_vec(data).build();
|
||||
|
||||
set_styles(&mut table, t.styles, &structure);
|
||||
set_indent(&mut table, t.config.indent);
|
||||
load_theme(&mut table, &t.config.theme, &structure, sep_color);
|
||||
align_table(&mut table, t.alignments, &structure);
|
||||
colorize_table(&mut table, t.styles, &structure);
|
||||
truncate_table(&mut table, &t.config, width, termwidth);
|
||||
table_set_border_header(&mut table, head, &t.config);
|
||||
|
||||
table_to_string(table, termwidth)
|
||||
}
|
||||
|
||||
fn set_styles(table: &mut Table, styles: Styles, structure: &TableStructure) {
|
||||
table.with(styles.cfg);
|
||||
align_table(table, styles.alignments, structure);
|
||||
colorize_table(table, styles.colors, structure);
|
||||
}
|
||||
|
||||
fn table_set_border_header(table: &mut Table, head: Option<HeadInfo>, cfg: &TableConfig) {
|
||||
let head = match head {
|
||||
Some(head) => head,
|
||||
@ -420,7 +503,6 @@ fn set_indent(table: &mut Table, indent: TableIndent) {
|
||||
|
||||
fn table_to_string(table: Table, termwidth: usize) -> Option<String> {
|
||||
let total_width = table.total_width();
|
||||
|
||||
if total_width > termwidth {
|
||||
None
|
||||
} else {
|
||||
@ -462,15 +544,23 @@ struct WidthEstimation {
|
||||
#[allow(dead_code)]
|
||||
total: usize,
|
||||
truncate: bool,
|
||||
trail: bool,
|
||||
}
|
||||
|
||||
impl WidthEstimation {
|
||||
fn new(original: Vec<usize>, needed: Vec<usize>, total: usize, truncate: bool) -> Self {
|
||||
fn new(
|
||||
original: Vec<usize>,
|
||||
needed: Vec<usize>,
|
||||
total: usize,
|
||||
truncate: bool,
|
||||
trail: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
original,
|
||||
needed,
|
||||
total,
|
||||
truncate,
|
||||
trail,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -546,17 +636,12 @@ fn width_ctrl_truncate(
|
||||
dims.set_widths(ctrl.width.needed);
|
||||
}
|
||||
|
||||
fn align_table(table: &mut Table, alignments: Alignments, structure: &TableStructure) {
|
||||
fn align_table(
|
||||
table: &mut Table,
|
||||
alignments: CellConfiguration<AlignmentHorizontal>,
|
||||
structure: &TableStructure,
|
||||
) {
|
||||
table.with(AlignmentStrategy::PerLine);
|
||||
table.with(Alignment::from(alignments.data));
|
||||
|
||||
for (column, alignment) in alignments.columns {
|
||||
table.modify(Columns::single(column), Alignment::from(alignment));
|
||||
}
|
||||
|
||||
for (pos, alignment) in alignments.cells {
|
||||
table.modify(pos, Alignment::from(alignment));
|
||||
}
|
||||
|
||||
if structure.with_header {
|
||||
table.modify(Rows::first(), Alignment::from(alignments.header));
|
||||
@ -571,23 +656,7 @@ fn align_table(table: &mut Table, alignments: Alignments, structure: &TableStruc
|
||||
}
|
||||
}
|
||||
|
||||
fn colorize_table(table: &mut Table, styles: Styles, structure: &TableStructure) {
|
||||
if !is_color_empty(&styles.data) {
|
||||
table.with(styles.data);
|
||||
}
|
||||
|
||||
for (column, color) in styles.columns {
|
||||
if !is_color_empty(&color) {
|
||||
table.modify(Columns::single(column), color);
|
||||
}
|
||||
}
|
||||
|
||||
for (pos, color) in styles.cells {
|
||||
if !is_color_empty(&color) {
|
||||
table.modify(pos, color);
|
||||
}
|
||||
}
|
||||
|
||||
fn colorize_table(table: &mut Table, styles: CellConfiguration<Color>, structure: &TableStructure) {
|
||||
if structure.with_index && !is_color_empty(&styles.index) {
|
||||
table.modify(Columns::first(), styles.index);
|
||||
}
|
||||
@ -629,7 +698,8 @@ fn load_theme(
|
||||
}
|
||||
|
||||
fn maybe_truncate_columns(
|
||||
data: &mut NuRecords,
|
||||
data: &mut Vec<Vec<NuRecordsValue>>,
|
||||
widths: Vec<usize>,
|
||||
cfg: &TableConfig,
|
||||
termwidth: usize,
|
||||
) -> WidthEstimation {
|
||||
@ -639,15 +709,16 @@ fn maybe_truncate_columns(
|
||||
let preserve_content = termwidth > TERMWIDTH_THRESHOLD;
|
||||
|
||||
if preserve_content {
|
||||
truncate_columns_by_columns(data, &cfg.theme, pad, termwidth)
|
||||
truncate_columns_by_columns(data, widths, &cfg.theme, pad, termwidth)
|
||||
} else {
|
||||
truncate_columns_by_content(data, &cfg.theme, pad, termwidth)
|
||||
truncate_columns_by_content(data, widths, &cfg.theme, pad, termwidth)
|
||||
}
|
||||
}
|
||||
|
||||
// VERSION where we are showing AS LITTLE COLUMNS AS POSSIBLE but WITH AS MUCH CONTENT AS POSSIBLE.
|
||||
fn truncate_columns_by_content(
|
||||
data: &mut NuRecords,
|
||||
data: &mut Vec<Vec<NuRecordsValue>>,
|
||||
widths: Vec<usize>,
|
||||
theme: &TableTheme,
|
||||
pad: usize,
|
||||
termwidth: usize,
|
||||
@ -658,13 +729,14 @@ fn truncate_columns_by_content(
|
||||
let trailing_column_width = TRAILING_COLUMN_WIDTH + pad;
|
||||
let min_column_width = MIN_ACCEPTABLE_WIDTH + pad;
|
||||
|
||||
let count_columns = data[0].len();
|
||||
|
||||
let config = create_config(theme, false, None);
|
||||
let widths_original = build_width(data, pad);
|
||||
let widths_original = widths;
|
||||
let mut widths = vec![];
|
||||
|
||||
let borders = config.get_borders();
|
||||
let vertical = borders.has_vertical() as usize;
|
||||
let count_columns = data.count_columns();
|
||||
|
||||
let mut width = borders.has_left() as usize + borders.has_right() as usize;
|
||||
let mut truncate_pos = 0;
|
||||
@ -685,7 +757,7 @@ fn truncate_columns_by_content(
|
||||
}
|
||||
|
||||
if truncate_pos == count_columns {
|
||||
return WidthEstimation::new(widths_original, widths, width, false);
|
||||
return WidthEstimation::new(widths_original, widths, width, false, false);
|
||||
}
|
||||
|
||||
if truncate_pos == 0 {
|
||||
@ -702,11 +774,11 @@ fn truncate_columns_by_content(
|
||||
widths.push(trailing_column_width);
|
||||
width += trailing_column_width + vertical;
|
||||
|
||||
return WidthEstimation::new(widths_original, widths, width, true);
|
||||
return WidthEstimation::new(widths_original, widths, width, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
return WidthEstimation::new(widths_original, widths, width, false);
|
||||
return WidthEstimation::new(widths_original, widths, width, false, false);
|
||||
}
|
||||
|
||||
let available = termwidth - width;
|
||||
@ -718,7 +790,7 @@ fn truncate_columns_by_content(
|
||||
widths.push(w);
|
||||
width += w + vertical;
|
||||
|
||||
return WidthEstimation::new(widths_original, widths, width, true);
|
||||
return WidthEstimation::new(widths_original, widths, width, true, false);
|
||||
}
|
||||
|
||||
// special case where the last column is smaller then a trailing column
|
||||
@ -736,7 +808,7 @@ fn truncate_columns_by_content(
|
||||
widths.push(next_column_width);
|
||||
width += next_column_width + vertical;
|
||||
|
||||
return WidthEstimation::new(widths_original, widths, width, true);
|
||||
return WidthEstimation::new(widths_original, widths, width, true, false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -753,7 +825,7 @@ fn truncate_columns_by_content(
|
||||
widths.push(trailing_column_width);
|
||||
width += trailing_column_width + vertical;
|
||||
|
||||
return WidthEstimation::new(widths_original, widths, width, true);
|
||||
return WidthEstimation::new(widths_original, widths, width, true, true);
|
||||
}
|
||||
|
||||
if available >= trailing_column_width + vertical {
|
||||
@ -763,7 +835,7 @@ fn truncate_columns_by_content(
|
||||
widths.push(trailing_column_width);
|
||||
width += trailing_column_width + vertical;
|
||||
|
||||
return WidthEstimation::new(widths_original, widths, width, false);
|
||||
return WidthEstimation::new(widths_original, widths, width, false, true);
|
||||
}
|
||||
|
||||
let last_width = widths.last().cloned().expect("ok");
|
||||
@ -787,7 +859,7 @@ fn truncate_columns_by_content(
|
||||
widths.push(trailing_column_width);
|
||||
width += trailing_column_width + vertical;
|
||||
|
||||
return WidthEstimation::new(widths_original, widths, width, true);
|
||||
return WidthEstimation::new(widths_original, widths, width, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -801,10 +873,10 @@ fn truncate_columns_by_content(
|
||||
|
||||
if widths.len() == 1 {
|
||||
// nothing to show anyhow
|
||||
return WidthEstimation::new(widths_original, vec![], width, false);
|
||||
return WidthEstimation::new(widths_original, vec![], width, false, true);
|
||||
}
|
||||
|
||||
WidthEstimation::new(widths_original, widths, width, false)
|
||||
WidthEstimation::new(widths_original, widths, width, false, true)
|
||||
}
|
||||
|
||||
// VERSION where we are showing AS MANY COLUMNS AS POSSIBLE but as a side affect they MIGHT CONTAIN AS LITTLE CONTENT AS POSSIBLE
|
||||
@ -818,7 +890,8 @@ fn truncate_columns_by_content(
|
||||
// Point being of the column needs more space we do can give it a little more based on it's distance from the start.
|
||||
// Percentage wise.
|
||||
fn truncate_columns_by_columns(
|
||||
data: &mut NuRecords,
|
||||
data: &mut Vec<Vec<NuRecordsValue>>,
|
||||
widths: Vec<usize>,
|
||||
theme: &TableTheme,
|
||||
pad: usize,
|
||||
termwidth: usize,
|
||||
@ -829,13 +902,14 @@ fn truncate_columns_by_columns(
|
||||
let trailing_column_width = TRAILING_COLUMN_WIDTH + pad;
|
||||
let min_column_width = MIN_ACCEPTABLE_WIDTH + pad;
|
||||
|
||||
let count_columns = data[0].len();
|
||||
|
||||
let config = create_config(theme, false, None);
|
||||
let widths_original = build_width(data, pad);
|
||||
let widths_original = widths;
|
||||
let mut widths = vec![];
|
||||
|
||||
let borders = config.get_borders();
|
||||
let vertical = borders.has_vertical() as usize;
|
||||
let count_columns = data.count_columns();
|
||||
|
||||
let mut width = borders.has_left() as usize + borders.has_right() as usize;
|
||||
let mut truncate_pos = 0;
|
||||
@ -857,7 +931,7 @@ fn truncate_columns_by_columns(
|
||||
}
|
||||
|
||||
if truncate_pos == 0 {
|
||||
return WidthEstimation::new(widths_original, widths, width, false);
|
||||
return WidthEstimation::new(widths_original, widths, width, false, false);
|
||||
}
|
||||
|
||||
let mut available = termwidth - width;
|
||||
@ -882,7 +956,7 @@ fn truncate_columns_by_columns(
|
||||
}
|
||||
|
||||
if truncate_pos == count_columns {
|
||||
return WidthEstimation::new(widths_original, widths, width, true);
|
||||
return WidthEstimation::new(widths_original, widths, width, true, false);
|
||||
}
|
||||
|
||||
if available >= trailing_column_width + vertical {
|
||||
@ -892,7 +966,7 @@ fn truncate_columns_by_columns(
|
||||
widths.push(trailing_column_width);
|
||||
width += trailing_column_width + vertical;
|
||||
|
||||
return WidthEstimation::new(widths_original, widths, width, true);
|
||||
return WidthEstimation::new(widths_original, widths, width, true, true);
|
||||
}
|
||||
|
||||
truncate_rows(data, truncate_pos - 1);
|
||||
@ -903,7 +977,7 @@ fn truncate_columns_by_columns(
|
||||
widths.push(trailing_column_width);
|
||||
width += trailing_column_width;
|
||||
|
||||
WidthEstimation::new(widths_original, widths, width, true)
|
||||
WidthEstimation::new(widths_original, widths, width, true, true)
|
||||
}
|
||||
|
||||
fn get_total_width2(widths: &[usize], cfg: &ColoredConfig) -> usize {
|
||||
@ -921,37 +995,22 @@ fn create_config(theme: &TableTheme, with_header: bool, color: Option<Style>) ->
|
||||
table.get_config().clone()
|
||||
}
|
||||
|
||||
fn push_empty_column(data: &mut NuRecords) {
|
||||
let records = std::mem::take(data);
|
||||
let mut inner: Vec<Vec<_>> = records.into();
|
||||
|
||||
fn push_empty_column(data: &mut Vec<Vec<NuRecordsValue>>) {
|
||||
let empty_cell = Text::new(String::from(EMPTY_COLUMN_TEXT));
|
||||
for row in &mut inner {
|
||||
for row in data {
|
||||
row.push(empty_cell.clone());
|
||||
}
|
||||
|
||||
*data = VecRecords::new(inner);
|
||||
}
|
||||
|
||||
fn duplicate_row(data: &mut NuRecords, row: usize) {
|
||||
let records = std::mem::take(data);
|
||||
let mut inner: Vec<Vec<_>> = records.into();
|
||||
|
||||
let duplicate = inner[row].clone();
|
||||
inner.push(duplicate);
|
||||
|
||||
*data = VecRecords::new(inner);
|
||||
fn duplicate_row(data: &mut Vec<Vec<NuRecordsValue>>, row: usize) {
|
||||
let duplicate = data[row].clone();
|
||||
data.push(duplicate);
|
||||
}
|
||||
|
||||
fn truncate_rows(data: &mut NuRecords, count: usize) {
|
||||
let records = std::mem::take(data);
|
||||
let mut inner: Vec<Vec<_>> = records.into();
|
||||
|
||||
for row in &mut inner {
|
||||
fn truncate_rows(data: &mut Vec<Vec<NuRecordsValue>>, count: usize) {
|
||||
for row in data {
|
||||
row.truncate(count);
|
||||
}
|
||||
|
||||
*data = VecRecords::new(inner);
|
||||
}
|
||||
|
||||
fn convert_alignment(alignment: nu_color_config::Alignment) -> AlignmentHorizontal {
|
||||
@ -971,7 +1030,11 @@ impl<R> TableOption<R, ColoredConfig, CompleteDimensionVecRecords<'_>> for SetDi
|
||||
}
|
||||
}
|
||||
|
||||
fn build_width(records: &NuRecords, pad: usize) -> Vec<usize> {
|
||||
fn build_width<R>(records: R, pad: usize) -> Vec<usize>
|
||||
where
|
||||
R: Records,
|
||||
<R::Iter as IntoRecords>::Cell: AsRef<str>,
|
||||
{
|
||||
// TODO: Expose not spaned version (could be optimized).
|
||||
let mut cfg = SpannedConfig::default();
|
||||
let padding = Sides {
|
||||
@ -981,6 +1044,7 @@ fn build_width(records: &NuRecords, pad: usize) -> Vec<usize> {
|
||||
|
||||
cfg.set_padding(Entity::Global, padding);
|
||||
|
||||
// TODO: Use peekable width
|
||||
SpannedGridDimension::width(records, &cfg)
|
||||
}
|
||||
|
||||
@ -1059,3 +1123,11 @@ impl<R, C> TableOption<R, C, CompleteDimensionVecRecords<'_>> for &mut GetDims {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_color_if_exists(c: &Color) -> Option<Color> {
|
||||
if !is_color_empty(c) {
|
||||
Some(c.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
@ -156,6 +156,16 @@ impl TableTheme {
|
||||
Self::new(theme, full)
|
||||
}
|
||||
|
||||
pub fn single() -> TableTheme {
|
||||
let full = Style::modern()
|
||||
.corner_top_left('┌')
|
||||
.corner_top_right('┐')
|
||||
.corner_bottom_left('└')
|
||||
.corner_bottom_right('┘');
|
||||
|
||||
Self::new(Style::sharp(), full)
|
||||
}
|
||||
|
||||
pub fn none() -> TableTheme {
|
||||
Self::new(Style::blank(), Style::blank())
|
||||
}
|
||||
|
@ -1,11 +1,9 @@
|
||||
use std::{cmp::max, collections::HashMap};
|
||||
use std::cmp::max;
|
||||
|
||||
use nu_color_config::{Alignment, StyleComputer, TextStyle};
|
||||
use nu_engine::column::get_columns;
|
||||
use nu_protocol::{Config, Record, ShellError, Span, Value};
|
||||
|
||||
use tabled::grid::config::Position;
|
||||
|
||||
use crate::{
|
||||
common::{
|
||||
check_value, configure_table, error_sign, get_header_style, get_index_style, load_theme,
|
||||
@ -14,7 +12,7 @@ use crate::{
|
||||
},
|
||||
string_width,
|
||||
types::has_index,
|
||||
NuRecordsValue, NuTable, TableOpts, TableOutput,
|
||||
NuTable, TableOpts, TableOutput,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@ -106,7 +104,7 @@ fn expand_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
|
||||
const PADDING_SPACE: usize = 2;
|
||||
const SPLIT_LINE_SPACE: usize = 1;
|
||||
const ADDITIONAL_CELL_SPACE: usize = PADDING_SPACE + SPLIT_LINE_SPACE;
|
||||
const MIN_CELL_CONTENT_WIDTH: usize = 1;
|
||||
const MIN_CELL_CONTENT_WIDTH: usize = 3;
|
||||
const TRUNCATE_CONTENT_WIDTH: usize = 3;
|
||||
const TRUNCATE_CELL_WIDTH: usize = TRUNCATE_CONTENT_WIDTH + PADDING_SPACE;
|
||||
|
||||
@ -124,10 +122,7 @@ fn expand_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
|
||||
}
|
||||
|
||||
let headers = get_columns(input);
|
||||
|
||||
let with_index = has_index(&cfg.opts, &headers);
|
||||
let row_offset = cfg.opts.index_offset;
|
||||
let mut rows_count = 0usize;
|
||||
|
||||
// The header with the INDEX is removed from the table headers since
|
||||
// it is added to the natural table index
|
||||
@ -135,162 +130,171 @@ fn expand_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
|
||||
.into_iter()
|
||||
.filter(|header| header != INDEX_COLUMN_NAME)
|
||||
.collect();
|
||||
|
||||
let with_header = !headers.is_empty();
|
||||
let row_offset = cfg.opts.index_offset;
|
||||
|
||||
let mut data = vec![vec![]; input.len() + with_header as usize];
|
||||
let mut data_styles = HashMap::new();
|
||||
let mut total_rows = 0usize;
|
||||
|
||||
if with_index {
|
||||
if with_header {
|
||||
data[0].push(NuRecordsValue::exact(String::from("#"), 1, vec![]));
|
||||
}
|
||||
|
||||
for (row, item) in input.iter().enumerate() {
|
||||
cfg.opts.signals.check(cfg.opts.span)?;
|
||||
check_value(item)?;
|
||||
|
||||
let index = row + row_offset;
|
||||
let text = item
|
||||
.as_record()
|
||||
.ok()
|
||||
.and_then(|val| val.get(INDEX_COLUMN_NAME))
|
||||
.map(|value| value.to_expanded_string("", cfg.opts.config))
|
||||
.unwrap_or_else(|| index.to_string());
|
||||
|
||||
let row = row + with_header as usize;
|
||||
let value = NuRecordsValue::new(text);
|
||||
data[row].push(value);
|
||||
}
|
||||
|
||||
let column_width = string_width(data[data.len() - 1][0].as_ref());
|
||||
|
||||
if column_width + ADDITIONAL_CELL_SPACE > available_width {
|
||||
available_width = 0;
|
||||
} else {
|
||||
available_width -= column_width + ADDITIONAL_CELL_SPACE;
|
||||
}
|
||||
}
|
||||
|
||||
if !with_header {
|
||||
if available_width > ADDITIONAL_CELL_SPACE {
|
||||
available_width -= PADDING_SPACE;
|
||||
} else {
|
||||
if !with_index && !with_header {
|
||||
if available_width <= ADDITIONAL_CELL_SPACE {
|
||||
// it means we have no space left for actual content;
|
||||
// which means there's no point in index itself if it was even used.
|
||||
// so we do not print it.
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
available_width -= PADDING_SPACE;
|
||||
|
||||
let mut table = NuTable::new(input.len(), 1);
|
||||
table.set_index_style(get_index_style(&cfg.opts.style_computer));
|
||||
table.set_header_style(get_header_style(&cfg.opts.style_computer));
|
||||
table.set_indent(cfg.opts.config.table.padding);
|
||||
|
||||
for (row, item) in input.iter().enumerate() {
|
||||
cfg.opts.signals.check(cfg.opts.span)?;
|
||||
check_value(item)?;
|
||||
|
||||
let inner_cfg = cfg_expand_reset_table(cfg.clone(), available_width);
|
||||
let mut cell = expand_entry(item, inner_cfg);
|
||||
let cell = expand_entry(item, inner_cfg);
|
||||
|
||||
let value_width = string_width(&cell.text);
|
||||
if value_width > available_width {
|
||||
// it must only happen when a string is produced, so we can safely wrap it.
|
||||
// (it might be string table representation as well) (I guess I mean default { table ...} { list ...})
|
||||
//
|
||||
// todo: Maybe convert_to_table2_entry could do for strings to not mess caller code?
|
||||
table.insert((row, 0), cell.text);
|
||||
table.insert_style((row, 0), cell.style);
|
||||
|
||||
cell.text = wrap_text(&cell.text, available_width, cfg.opts.config);
|
||||
}
|
||||
|
||||
let value = NuRecordsValue::new(cell.text);
|
||||
data[row].push(value);
|
||||
data_styles.insert((row, with_index as usize), cell.style);
|
||||
|
||||
rows_count = rows_count.saturating_add(cell.size);
|
||||
total_rows = total_rows.saturating_add(cell.size);
|
||||
}
|
||||
|
||||
let mut table = NuTable::from(data);
|
||||
table.set_indent(cfg.opts.config.table.padding);
|
||||
table.set_index_style(get_index_style(&cfg.opts.style_computer));
|
||||
set_data_styles(&mut table, data_styles);
|
||||
|
||||
return Ok(Some(TableOutput::new(table, false, with_index, rows_count)));
|
||||
return Ok(Some(TableOutput::new(table, false, false, total_rows)));
|
||||
}
|
||||
|
||||
if !headers.is_empty() {
|
||||
let mut pad_space = PADDING_SPACE;
|
||||
if headers.len() > 1 {
|
||||
pad_space += SPLIT_LINE_SPACE;
|
||||
if !with_header && with_index {
|
||||
let mut table = NuTable::new(input.len(), 2);
|
||||
table.set_index_style(get_index_style(&cfg.opts.style_computer));
|
||||
table.set_header_style(get_header_style(&cfg.opts.style_computer));
|
||||
table.set_indent(cfg.opts.config.table.padding);
|
||||
|
||||
let mut index_column_width = 0;
|
||||
|
||||
for (row, item) in input.iter().enumerate() {
|
||||
cfg.opts.signals.check(cfg.opts.span)?;
|
||||
check_value(item)?;
|
||||
|
||||
let index = row + row_offset;
|
||||
let index_value = item
|
||||
.as_record()
|
||||
.ok()
|
||||
.and_then(|val| val.get(INDEX_COLUMN_NAME))
|
||||
.map(|value| value.to_expanded_string("", cfg.opts.config))
|
||||
.unwrap_or_else(|| index.to_string());
|
||||
let index_width = string_width(&index_value);
|
||||
if available_width <= index_width + ADDITIONAL_CELL_SPACE + PADDING_SPACE {
|
||||
// NOTE: we don't wanna wrap index; so we return
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
table.insert((row, 0), index_value);
|
||||
|
||||
index_column_width = max(index_column_width, index_width);
|
||||
}
|
||||
|
||||
if available_width < pad_space {
|
||||
// there's no space for actual data so we don't return index if it's present.
|
||||
// (also see the comment after the loop)
|
||||
available_width -= index_column_width + ADDITIONAL_CELL_SPACE + PADDING_SPACE;
|
||||
|
||||
for (row, item) in input.iter().enumerate() {
|
||||
cfg.opts.signals.check(cfg.opts.span)?;
|
||||
check_value(item)?;
|
||||
|
||||
let inner_cfg = cfg_expand_reset_table(cfg.clone(), available_width);
|
||||
let cell = expand_entry(item, inner_cfg);
|
||||
|
||||
table.insert((row, 1), cell.text);
|
||||
table.insert_style((row, 1), cell.style);
|
||||
|
||||
total_rows = total_rows.saturating_add(cell.size);
|
||||
}
|
||||
|
||||
return Ok(Some(TableOutput::new(table, false, true, total_rows)));
|
||||
}
|
||||
|
||||
// NOTE: redefine to not break above logic (fixme)
|
||||
let mut available_width = cfg.opts.width - SPLIT_LINE_SPACE;
|
||||
|
||||
let mut table = NuTable::new(input.len() + 1, headers.len() + with_index as usize);
|
||||
table.set_index_style(get_index_style(&cfg.opts.style_computer));
|
||||
table.set_header_style(get_header_style(&cfg.opts.style_computer));
|
||||
table.set_indent(cfg.opts.config.table.padding);
|
||||
|
||||
let mut widths = Vec::new();
|
||||
|
||||
if with_index {
|
||||
table.insert((0, 0), String::from("#"));
|
||||
|
||||
let mut index_column_width = 1;
|
||||
|
||||
for (row, item) in input.iter().enumerate() {
|
||||
cfg.opts.signals.check(cfg.opts.span)?;
|
||||
check_value(item)?;
|
||||
|
||||
let index = row + row_offset;
|
||||
let index_value = item
|
||||
.as_record()
|
||||
.ok()
|
||||
.and_then(|val| val.get(INDEX_COLUMN_NAME))
|
||||
.map(|value| value.to_expanded_string("", cfg.opts.config))
|
||||
.unwrap_or_else(|| index.to_string());
|
||||
let index_width = string_width(&index_value);
|
||||
|
||||
table.insert((row + 1, 0), index_value);
|
||||
index_column_width = max(index_column_width, index_width);
|
||||
}
|
||||
|
||||
if available_width <= index_column_width + ADDITIONAL_CELL_SPACE {
|
||||
// NOTE: we don't wanna wrap index; so we return
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
available_width -= index_column_width + ADDITIONAL_CELL_SPACE;
|
||||
widths.push(index_column_width);
|
||||
}
|
||||
|
||||
let count_columns = headers.len();
|
||||
let mut widths = Vec::new();
|
||||
let mut truncate = false;
|
||||
let mut rendered_column = 0;
|
||||
for (col, header) in headers.into_iter().enumerate() {
|
||||
let column = col + with_index as usize;
|
||||
let extra_space = PADDING_SPACE + SPLIT_LINE_SPACE;
|
||||
|
||||
if available_width <= extra_space {
|
||||
table.pop_column(table.count_columns() - column);
|
||||
widths.pop();
|
||||
truncate = true;
|
||||
break;
|
||||
}
|
||||
|
||||
let mut available = available_width - extra_space;
|
||||
|
||||
// We want to reserver some space for next column
|
||||
// If we can't fit it in it will be popped anyhow.
|
||||
let is_last_column = col + 1 == count_columns;
|
||||
let mut pad_space = PADDING_SPACE;
|
||||
if !is_last_column {
|
||||
pad_space += SPLIT_LINE_SPACE;
|
||||
if !is_last_column && available > TRUNCATE_CELL_WIDTH {
|
||||
available -= TRUNCATE_CELL_WIDTH;
|
||||
}
|
||||
|
||||
let mut available = available_width - pad_space;
|
||||
let mut total_column_rows = 0usize;
|
||||
let mut column_width = 0;
|
||||
|
||||
if !is_last_column {
|
||||
// we need to make sure that we have a space for a next column if we use available width
|
||||
// so we might need to decrease a bit it.
|
||||
|
||||
// we consider a header width be a minimum width
|
||||
let pad_space = PADDING_SPACE + TRUNCATE_CONTENT_WIDTH;
|
||||
|
||||
if available > pad_space {
|
||||
// In we have no space for a next column,
|
||||
// We consider showing something better then nothing,
|
||||
// So we try to decrease the width to show at least a truncution column
|
||||
|
||||
available -= pad_space;
|
||||
} else {
|
||||
truncate = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if available < column_width {
|
||||
truncate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let mut column_rows = 0usize;
|
||||
|
||||
for (row, item) in input.iter().enumerate() {
|
||||
cfg.opts.signals.check(cfg.opts.span)?;
|
||||
check_value(item)?;
|
||||
|
||||
let inner_cfg = cfg_expand_reset_table(cfg.clone(), available);
|
||||
let mut cell = expand_entry_with_header(item, &header, inner_cfg);
|
||||
|
||||
let mut value_width = string_width(&cell.text);
|
||||
if value_width > available {
|
||||
// it must only happen when a string is produced, so we can safely wrap it.
|
||||
// (it might be string table representation as well)
|
||||
|
||||
cell.text = wrap_text(&cell.text, available, cfg.opts.config);
|
||||
value_width = available;
|
||||
}
|
||||
let cell = expand_entry_with_header(item, &header, inner_cfg);
|
||||
let value_width = string_width(&cell.text); // TODO: optimize cause when we expand we alrready know the width (most of the time or all)
|
||||
|
||||
column_width = max(column_width, value_width);
|
||||
|
||||
let value = NuRecordsValue::new(cell.text);
|
||||
data[row + 1].push(value);
|
||||
data_styles.insert((row + 1, col + with_index as usize), cell.style);
|
||||
table.insert((row + 1, column), cell.text);
|
||||
table.insert_style((row + 1, column), cell.style);
|
||||
|
||||
column_rows = column_rows.saturating_add(cell.size);
|
||||
total_column_rows = total_column_rows.saturating_add(cell.size);
|
||||
}
|
||||
|
||||
let mut head_width = string_width(&header);
|
||||
@ -300,50 +304,37 @@ fn expand_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
|
||||
head_width = available;
|
||||
}
|
||||
|
||||
let head_cell = NuRecordsValue::new(header);
|
||||
data[0].push(head_cell);
|
||||
table.insert((0, column), header);
|
||||
|
||||
column_width = max(column_width, head_width);
|
||||
|
||||
if column_width > available {
|
||||
// remove the column we just inserted
|
||||
for row in &mut data {
|
||||
row.pop();
|
||||
}
|
||||
|
||||
truncate = true;
|
||||
break;
|
||||
}
|
||||
assert!(column_width <= available);
|
||||
|
||||
widths.push(column_width);
|
||||
|
||||
available_width -= pad_space + column_width;
|
||||
available_width -= column_width + extra_space;
|
||||
rendered_column += 1;
|
||||
|
||||
rows_count = std::cmp::max(rows_count, column_rows);
|
||||
}
|
||||
|
||||
if truncate && rendered_column == 0 {
|
||||
// it means that no actual data was rendered, there might be only index present,
|
||||
// so there's no point in rendering the table.
|
||||
//
|
||||
// It's actually quite important in case it's called recursively,
|
||||
// cause we will back up to the basic table view as a string e.g. '[table 123 columns]'.
|
||||
//
|
||||
// But potentially if its reached as a 1st called function we might would love to see the index.
|
||||
|
||||
return Ok(None);
|
||||
total_rows = std::cmp::max(total_rows, total_column_rows);
|
||||
}
|
||||
|
||||
if truncate {
|
||||
if rendered_column == 0 {
|
||||
// it means that no actual data was rendered, there might be only index present,
|
||||
// so there's no point in rendering the table.
|
||||
//
|
||||
// It's actually quite important in case it's called recursively,
|
||||
// cause we will back up to the basic table view as a string e.g. '[table 123 columns]'.
|
||||
//
|
||||
// But potentially if its reached as a 1st called function we might would love to see the index.
|
||||
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if available_width < TRUNCATE_CELL_WIDTH {
|
||||
// back up by removing last column.
|
||||
// it's LIKELY that removing only 1 column will leave us enough space for a shift column.
|
||||
|
||||
while let Some(width) = widths.pop() {
|
||||
for row in &mut data {
|
||||
row.pop();
|
||||
}
|
||||
table.pop_column(1);
|
||||
|
||||
available_width += width + PADDING_SPACE;
|
||||
if !widths.is_empty() {
|
||||
@ -364,22 +355,12 @@ fn expand_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
|
||||
|
||||
let is_last_column = widths.len() == count_columns;
|
||||
if !is_last_column {
|
||||
let shift = NuRecordsValue::exact(String::from("..."), 3, vec![]);
|
||||
for row in &mut data {
|
||||
row.push(shift.clone());
|
||||
}
|
||||
|
||||
table.push_column(String::from("..."));
|
||||
widths.push(3);
|
||||
}
|
||||
}
|
||||
|
||||
let mut table = NuTable::from(data);
|
||||
table.set_index_style(get_index_style(&cfg.opts.style_computer));
|
||||
table.set_header_style(get_header_style(&cfg.opts.style_computer));
|
||||
table.set_indent(cfg.opts.config.table.padding);
|
||||
set_data_styles(&mut table, data_styles);
|
||||
|
||||
Ok(Some(TableOutput::new(table, true, with_index, rows_count)))
|
||||
Ok(Some(TableOutput::new(table, true, with_index, total_rows)))
|
||||
}
|
||||
|
||||
fn expanded_table_kv(record: &Record, cfg: Cfg<'_>) -> CellResult {
|
||||
@ -400,10 +381,13 @@ fn expanded_table_kv(record: &Record, cfg: Cfg<'_>) -> CellResult {
|
||||
|
||||
let value_width = cfg.opts.width - key_width - count_borders - padding - padding;
|
||||
|
||||
let mut count_rows = 0usize;
|
||||
let mut total_rows = 0usize;
|
||||
|
||||
let mut data = Vec::with_capacity(record.len());
|
||||
for (key, value) in record {
|
||||
let mut table = NuTable::new(record.len(), 2);
|
||||
table.set_index_style(get_key_style(&cfg));
|
||||
table.set_indent(cfg.opts.config.table.padding);
|
||||
|
||||
for (i, (key, value)) in record.iter().enumerate() {
|
||||
cfg.opts.signals.check(cfg.opts.span)?;
|
||||
|
||||
let cell = match expand_value(value, value_width, &cfg)? {
|
||||
@ -411,29 +395,24 @@ fn expanded_table_kv(record: &Record, cfg: Cfg<'_>) -> CellResult {
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
let value = cell.text;
|
||||
let mut key = key.to_owned();
|
||||
|
||||
// we want to have a key being aligned to 2nd line,
|
||||
// we could use Padding for it but,
|
||||
// the easiest way to do so is just push a new_line char before
|
||||
let mut key = key.to_owned();
|
||||
let is_key_on_next_line = !key.is_empty() && cell.is_expanded && theme.borders_has_top();
|
||||
if is_key_on_next_line {
|
||||
key.insert(0, '\n');
|
||||
}
|
||||
|
||||
let key = NuRecordsValue::new(key);
|
||||
let val = NuRecordsValue::new(cell.text);
|
||||
let row = vec![key, val];
|
||||
table.insert((i, 0), key);
|
||||
table.insert((i, 1), value);
|
||||
|
||||
data.push(row);
|
||||
|
||||
count_rows = count_rows.saturating_add(cell.size);
|
||||
total_rows = total_rows.saturating_add(cell.size);
|
||||
}
|
||||
|
||||
let mut table = NuTable::from(data);
|
||||
table.set_index_style(get_key_style(&cfg));
|
||||
table.set_indent(cfg.opts.config.table.padding);
|
||||
|
||||
let mut out = TableOutput::new(table, false, true, count_rows);
|
||||
let mut out = TableOutput::new(table, false, true, total_rows);
|
||||
|
||||
configure_table(
|
||||
&mut out,
|
||||
@ -443,7 +422,7 @@ fn expanded_table_kv(record: &Record, cfg: Cfg<'_>) -> CellResult {
|
||||
);
|
||||
|
||||
maybe_expand_table(out, cfg.opts.width)
|
||||
.map(|value| value.map(|value| CellOutput::clean(value, count_rows, false)))
|
||||
.map(|value| value.map(|value| CellOutput::clean(value, total_rows, false)))
|
||||
}
|
||||
|
||||
// the flag is used as an optimization to not do `value.lines().count()` search.
|
||||
@ -519,6 +498,7 @@ fn expand_entry_with_header(item: &Value, header: &str, cfg: Cfg<'_>) -> CellOut
|
||||
fn expand_entry(item: &Value, cfg: Cfg<'_>) -> CellOutput {
|
||||
if is_limit_reached(&cfg) {
|
||||
let value = nu_value_to_string_clean(item, cfg.opts.config, &cfg.opts.style_computer);
|
||||
let value = nutext_wrap(value, &cfg);
|
||||
return CellOutput::styled(value);
|
||||
}
|
||||
|
||||
@ -527,6 +507,7 @@ fn expand_entry(item: &Value, cfg: Cfg<'_>) -> CellOutput {
|
||||
Value::Record { val: record, .. } => {
|
||||
if record.is_empty() {
|
||||
let value = nu_value_to_string(item, cfg.opts.config, &cfg.opts.style_computer);
|
||||
let value = nutext_wrap(value, &cfg);
|
||||
return CellOutput::styled(value);
|
||||
}
|
||||
|
||||
@ -538,6 +519,7 @@ fn expand_entry(item: &Value, cfg: Cfg<'_>) -> CellOutput {
|
||||
Ok(Some(table)) => table,
|
||||
_ => {
|
||||
let value = nu_value_to_string(item, cfg.opts.config, &cfg.opts.style_computer);
|
||||
let value = nutext_wrap(value, &cfg);
|
||||
CellOutput::styled(value)
|
||||
}
|
||||
}
|
||||
@ -560,6 +542,7 @@ fn expand_entry(item: &Value, cfg: Cfg<'_>) -> CellOutput {
|
||||
Ok(Some(out)) => out,
|
||||
_ => {
|
||||
let value = nu_value_to_string(item, cfg.opts.config, &cfg.opts.style_computer);
|
||||
let value = nutext_wrap(value, &cfg);
|
||||
return CellOutput::styled(value);
|
||||
}
|
||||
};
|
||||
@ -571,17 +554,28 @@ fn expand_entry(item: &Value, cfg: Cfg<'_>) -> CellOutput {
|
||||
Some(table) => CellOutput::clean(table, out.count_rows, false),
|
||||
None => {
|
||||
let value = nu_value_to_string(item, cfg.opts.config, &cfg.opts.style_computer);
|
||||
let value = nutext_wrap(value, &cfg);
|
||||
CellOutput::styled(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let value = nu_value_to_string_clean(item, cfg.opts.config, &cfg.opts.style_computer);
|
||||
let value = nutext_wrap(value, &cfg);
|
||||
CellOutput::styled(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn nutext_wrap(mut text: NuText, cfg: &Cfg<'_>) -> NuText {
|
||||
let width = string_width(&text.0);
|
||||
if width > cfg.opts.width {
|
||||
text.0 = wrap_text(&text.0, cfg.opts.width, cfg.opts.config);
|
||||
}
|
||||
|
||||
text
|
||||
}
|
||||
|
||||
fn is_limit_reached(cfg: &Cfg<'_>) -> bool {
|
||||
matches!(cfg.format.expand_limit, Some(0))
|
||||
}
|
||||
@ -626,12 +620,6 @@ fn maybe_expand_table(mut out: TableOutput, term_width: usize) -> StringResult {
|
||||
Ok(table)
|
||||
}
|
||||
|
||||
fn set_data_styles(table: &mut NuTable, styles: HashMap<Position, TextStyle>) {
|
||||
for (pos, style) in styles {
|
||||
table.insert_style(pos, style);
|
||||
}
|
||||
}
|
||||
|
||||
fn table_apply_config(out: &mut TableOutput, cfg: &Cfg<'_>) {
|
||||
configure_table(
|
||||
out,
|
||||
|
@ -15,23 +15,23 @@ use crate::{
|
||||
pub struct JustTable;
|
||||
|
||||
impl JustTable {
|
||||
pub fn table(input: &[Value], opts: TableOpts<'_>) -> StringResult {
|
||||
pub fn table(input: Vec<Value>, opts: TableOpts<'_>) -> StringResult {
|
||||
list_table(input, opts)
|
||||
}
|
||||
|
||||
pub fn kv_table(record: &Record, opts: TableOpts<'_>) -> StringResult {
|
||||
pub fn kv_table(record: Record, opts: TableOpts<'_>) -> StringResult {
|
||||
kv_table(record, opts)
|
||||
}
|
||||
}
|
||||
|
||||
fn list_table(input: &[Value], opts: TableOpts<'_>) -> Result<Option<String>, ShellError> {
|
||||
let mut out = match create_table(input, &opts)? {
|
||||
fn list_table(input: Vec<Value>, opts: TableOpts<'_>) -> Result<Option<String>, ShellError> {
|
||||
let output = create_table(input, &opts)?;
|
||||
let mut out = match output {
|
||||
Some(out) => out,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
out.table.set_indent(opts.config.table.padding);
|
||||
|
||||
// TODO: It would be WAY more effitient to do right away instead of second pass over the data.
|
||||
colorize_space(out.table.get_records_mut(), &opts.style_computer);
|
||||
|
||||
configure_table(&mut out, opts.config, &opts.style_computer, opts.mode);
|
||||
@ -40,25 +40,20 @@ fn list_table(input: &[Value], opts: TableOpts<'_>) -> Result<Option<String>, Sh
|
||||
Ok(table)
|
||||
}
|
||||
|
||||
fn kv_table(record: &Record, opts: TableOpts<'_>) -> StringResult {
|
||||
let mut data = vec![Vec::with_capacity(2); record.len()];
|
||||
|
||||
for ((column, value), row) in record.iter().zip(data.iter_mut()) {
|
||||
opts.signals.check(opts.span)?;
|
||||
|
||||
let key = NuRecordsValue::new(column.to_string());
|
||||
|
||||
let value = nu_value_to_string_colored(value, opts.config, &opts.style_computer);
|
||||
let value = NuRecordsValue::new(value);
|
||||
|
||||
row.push(key);
|
||||
row.push(value);
|
||||
}
|
||||
|
||||
let mut table = NuTable::from(data);
|
||||
fn kv_table(record: Record, opts: TableOpts<'_>) -> StringResult {
|
||||
let mut table = NuTable::new(record.len(), 2);
|
||||
table.set_index_style(TextStyle::default_field());
|
||||
table.set_indent(opts.config.table.padding);
|
||||
|
||||
for (i, (key, value)) in record.into_iter().enumerate() {
|
||||
opts.signals.check(opts.span)?;
|
||||
|
||||
let value = nu_value_to_string_colored(&value, opts.config, &opts.style_computer);
|
||||
|
||||
table.insert((i, 0), key);
|
||||
table.insert((i, 1), value);
|
||||
}
|
||||
|
||||
let mut out = TableOutput::from_table(table, false, true);
|
||||
configure_table(&mut out, opts.config, &opts.style_computer, opts.mode);
|
||||
let table = out.table.draw(opts.width);
|
||||
@ -66,12 +61,12 @@ fn kv_table(record: &Record, opts: TableOpts<'_>) -> StringResult {
|
||||
Ok(table)
|
||||
}
|
||||
|
||||
fn create_table(input: &[Value], opts: &TableOpts<'_>) -> TableResult {
|
||||
fn create_table(input: Vec<Value>, opts: &TableOpts<'_>) -> TableResult {
|
||||
if input.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let headers = get_columns(input);
|
||||
let headers = get_columns(&input);
|
||||
let with_index = has_index(opts, &headers);
|
||||
let with_header = !headers.is_empty();
|
||||
let row_offset = opts.index_offset;
|
||||
@ -89,27 +84,23 @@ fn create_table(input: &[Value], opts: &TableOpts<'_>) -> TableResult {
|
||||
}
|
||||
|
||||
fn create_table_with_header(
|
||||
input: &[Value],
|
||||
input: Vec<Value>,
|
||||
headers: Vec<String>,
|
||||
opts: &TableOpts<'_>,
|
||||
) -> Result<Option<NuTable>, ShellError> {
|
||||
let headers = collect_headers(headers, false);
|
||||
|
||||
let count_rows = input.len() + 1;
|
||||
let count_columns = headers.len();
|
||||
let mut table = NuTable::new(count_rows, count_columns);
|
||||
|
||||
table.set_header_style(get_header_style(&opts.style_computer));
|
||||
table.set_index_style(get_index_style(&opts.style_computer));
|
||||
table.set_indent(opts.config.table.padding);
|
||||
|
||||
table.set_row(0, headers.clone());
|
||||
|
||||
for (row, item) in input.iter().enumerate() {
|
||||
for (row, item) in input.into_iter().enumerate() {
|
||||
opts.signals.check(opts.span)?;
|
||||
check_value(item)?;
|
||||
check_value(&item)?;
|
||||
|
||||
for (col, header) in headers.iter().enumerate() {
|
||||
let (text, style) = get_string_value_with_header(item, header.as_ref(), opts);
|
||||
let (text, style) = get_string_value_with_header(&item, header, opts);
|
||||
|
||||
let pos = (row + 1, col);
|
||||
table.insert(pos, text);
|
||||
@ -117,35 +108,39 @@ fn create_table_with_header(
|
||||
}
|
||||
}
|
||||
|
||||
let headers = collect_headers(headers, false);
|
||||
table.set_row(0, headers);
|
||||
|
||||
Ok(Some(table))
|
||||
}
|
||||
|
||||
fn create_table_with_header_and_index(
|
||||
input: &[Value],
|
||||
input: Vec<Value>,
|
||||
headers: Vec<String>,
|
||||
row_offset: usize,
|
||||
opts: &TableOpts<'_>,
|
||||
) -> Result<Option<NuTable>, ShellError> {
|
||||
let headers = collect_headers(headers, true);
|
||||
let head = collect_headers(headers, true);
|
||||
|
||||
let count_rows = input.len() + 1;
|
||||
let count_columns = headers.len();
|
||||
let mut table = NuTable::new(count_rows, count_columns);
|
||||
let count_columns = head.len();
|
||||
|
||||
let mut table = NuTable::new(count_rows, count_columns);
|
||||
table.set_header_style(get_header_style(&opts.style_computer));
|
||||
table.set_index_style(get_index_style(&opts.style_computer));
|
||||
table.set_indent(opts.config.table.padding);
|
||||
|
||||
table.set_row(0, headers.clone());
|
||||
table.set_row(0, head.clone());
|
||||
|
||||
for (row, item) in input.iter().enumerate() {
|
||||
for (row, item) in input.into_iter().enumerate() {
|
||||
opts.signals.check(opts.span)?;
|
||||
check_value(item)?;
|
||||
check_value(&item)?;
|
||||
|
||||
let text = get_table_row_index(item, opts.config, row, row_offset);
|
||||
let text = get_table_row_index(&item, opts.config, row, row_offset);
|
||||
table.insert((row + 1, 0), text);
|
||||
|
||||
for (col, header) in headers.iter().enumerate().skip(1) {
|
||||
let (text, style) = get_string_value_with_header(item, header.as_ref(), opts);
|
||||
for (col, head) in head.iter().enumerate().skip(1) {
|
||||
let (text, style) = get_string_value_with_header(&item, head.as_ref(), opts);
|
||||
|
||||
let pos = (row + 1, col);
|
||||
table.insert(pos, text);
|
||||
@ -157,46 +152,45 @@ fn create_table_with_header_and_index(
|
||||
}
|
||||
|
||||
fn create_table_with_no_header(
|
||||
input: &[Value],
|
||||
input: Vec<Value>,
|
||||
opts: &TableOpts<'_>,
|
||||
) -> Result<Option<NuTable>, ShellError> {
|
||||
let mut table = NuTable::new(input.len(), 1);
|
||||
table.set_index_style(get_index_style(&opts.style_computer));
|
||||
table.set_indent(opts.config.table.padding);
|
||||
|
||||
for (row, item) in input.iter().enumerate() {
|
||||
for (row, item) in input.into_iter().enumerate() {
|
||||
opts.signals.check(opts.span)?;
|
||||
check_value(item)?;
|
||||
check_value(&item)?;
|
||||
|
||||
let (text, style) = get_string_value(item, opts);
|
||||
let (text, style) = get_string_value(&item, opts);
|
||||
|
||||
let pos = (row, 0);
|
||||
table.insert(pos, text);
|
||||
table.insert_style(pos, style);
|
||||
table.insert((row, 0), text);
|
||||
table.insert_style((row, 0), style);
|
||||
}
|
||||
|
||||
Ok(Some(table))
|
||||
}
|
||||
|
||||
fn create_table_with_no_header_and_index(
|
||||
input: &[Value],
|
||||
input: Vec<Value>,
|
||||
row_offset: usize,
|
||||
opts: &TableOpts<'_>,
|
||||
) -> Result<Option<NuTable>, ShellError> {
|
||||
let mut table = NuTable::new(input.len(), 1 + 1);
|
||||
table.set_index_style(get_index_style(&opts.style_computer));
|
||||
table.set_indent(opts.config.table.padding);
|
||||
|
||||
for (row, item) in input.iter().enumerate() {
|
||||
for (row, item) in input.into_iter().enumerate() {
|
||||
opts.signals.check(opts.span)?;
|
||||
check_value(item)?;
|
||||
check_value(&item)?;
|
||||
|
||||
let text = get_table_row_index(item, opts.config, row, row_offset);
|
||||
table.insert((row, 0), text);
|
||||
let index = get_table_row_index(&item, opts.config, row, row_offset);
|
||||
let (value, style) = get_string_value(&item, opts);
|
||||
|
||||
let (text, style) = get_string_value(item, opts);
|
||||
|
||||
let pos = (row, 1);
|
||||
table.insert(pos, text);
|
||||
table.insert_style(pos, style);
|
||||
table.insert((row, 0), index);
|
||||
table.insert((row, 1), value);
|
||||
table.insert_style((row, 1), style);
|
||||
}
|
||||
|
||||
Ok(Some(table))
|
||||
|
@ -88,7 +88,14 @@ where
|
||||
}
|
||||
|
||||
pub fn create_table(data: Data, case: TestCase) -> Option<String> {
|
||||
let mut table = NuTable::from(data);
|
||||
let count_rows = data.len();
|
||||
let count_cols = data[0].len();
|
||||
|
||||
let mut table = NuTable::new(count_rows, count_cols);
|
||||
for (i, row) in data.into_iter().enumerate() {
|
||||
table.set_row(i, row);
|
||||
}
|
||||
|
||||
table.set_theme(case.theme);
|
||||
table.set_structure(case.with_index, case.with_header, case.with_footer);
|
||||
table.set_trim(case.strategy);
|
||||
|
@ -451,6 +451,52 @@ fn test_with_love() {
|
||||
assert_eq!(create_table_with_size(vec![], true, theme::with_love()), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_single() {
|
||||
assert_eq!(
|
||||
create_table(vec![row(4); 3], true, theme::single()),
|
||||
"┌───┬───┬───┬───┐\n\
|
||||
│ 0 │ 1 │ 2 │ 3 │\n\
|
||||
├───┼───┼───┼───┤\n\
|
||||
│ 0 │ 1 │ 2 │ 3 │\n\
|
||||
│ 0 │ 1 │ 2 │ 3 │\n\
|
||||
└───┴───┴───┴───┘"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
create_table(vec![row(4); 2], true, theme::single()),
|
||||
"┌───┬───┬───┬───┐\n\
|
||||
│ 0 │ 1 │ 2 │ 3 │\n\
|
||||
├───┼───┼───┼───┤\n\
|
||||
│ 0 │ 1 │ 2 │ 3 │\n\
|
||||
└───┴───┴───┴───┘"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
create_table(vec![row(4); 1], true, theme::single()),
|
||||
"┌───┬───┬───┬───┐\n\
|
||||
│ 0 │ 1 │ 2 │ 3 │\n\
|
||||
└───┴───┴───┴───┘"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
create_table(vec![row(4); 1], false, theme::single()),
|
||||
"┌───┬───┬───┬───┐\n\
|
||||
│ 0 │ 1 │ 2 │ 3 │\n\
|
||||
└───┴───┴───┴───┘"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
create_table(vec![row(4); 2], false, theme::single()),
|
||||
"┌───┬───┬───┬───┐\n\
|
||||
│ 0 │ 1 │ 2 │ 3 │\n\
|
||||
│ 0 │ 1 │ 2 │ 3 │\n\
|
||||
└───┴───┴───┴───┘"
|
||||
);
|
||||
|
||||
assert_eq!(create_table_with_size(vec![], true, theme::single()), "");
|
||||
}
|
||||
|
||||
fn create_table(data: Vec<Vec<Text<String>>>, with_header: bool, theme: theme) -> String {
|
||||
let mut case = TestCase::new(usize::MAX).theme(theme);
|
||||
if with_header {
|
||||
|
@ -300,7 +300,7 @@ $env.config.footer_mode = 25
|
||||
# Specifies the visual display style of a table
|
||||
# One of: "default", "basic", "compact", "compact_double", "heavy", "light", "none", "reinforced",
|
||||
# "rounded", "thin", "with_love", "psql", "markdown", "dots", "restructured", "ascii_rounded",
|
||||
# or "basic_compact"
|
||||
# "basic_compact" or "single"
|
||||
# Can be overridden by passing a table to `| table --theme/-t`
|
||||
$env.config.table.mode = "default"
|
||||
|
||||
|
@ -91,8 +91,7 @@ impl Inc {
|
||||
|
||||
pub fn inc(&self, head: Span, value: &Value) -> Result<Value, LabeledError> {
|
||||
if let Some(cell_path) = &self.cell_path {
|
||||
let working_value = value.clone();
|
||||
let cell_value = working_value.follow_cell_path(&cell_path.members, false)?;
|
||||
let cell_value = value.follow_cell_path(&cell_path.members, false)?;
|
||||
|
||||
let cell_value = self.inc_value(head, &cell_value)?;
|
||||
|
||||
|
@ -0,0 +1,191 @@
|
||||
use crate::{
|
||||
values::{Column, CustomValueSupport, NuDataFrame, NuExpression},
|
||||
PolarsPlugin,
|
||||
};
|
||||
|
||||
use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand};
|
||||
use nu_protocol::{
|
||||
Category, Example, LabeledError, PipelineData, ShellError, Signature, Span, Spanned,
|
||||
SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
use polars::lazy::dsl::{
|
||||
all_horizontal, any_horizontal, max_horizontal, mean_horizontal, min_horizontal, sum_horizontal,
|
||||
};
|
||||
use polars::prelude::Expr;
|
||||
|
||||
enum HorizontalType {
|
||||
All,
|
||||
Any,
|
||||
Min,
|
||||
Max,
|
||||
Sum,
|
||||
Mean,
|
||||
}
|
||||
|
||||
impl HorizontalType {
|
||||
fn from_str(roll_type: &str, span: Span) -> Result<Self, ShellError> {
|
||||
match roll_type {
|
||||
"all" => Ok(Self::All),
|
||||
"any" => Ok(Self::Any),
|
||||
"min" => Ok(Self::Min),
|
||||
"max" => Ok(Self::Max),
|
||||
"sum" => Ok(Self::Sum),
|
||||
"mean" => Ok(Self::Mean),
|
||||
_ => Err(ShellError::GenericError {
|
||||
error: "Wrong operation".into(),
|
||||
msg: "Operation not valid for cumulative".into(),
|
||||
span: Some(span),
|
||||
help: Some("Allowed values: all, any, max, min, sum, mean".into()),
|
||||
inner: vec![],
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Horizontal;
|
||||
|
||||
impl PluginCommand for Horizontal {
|
||||
type Plugin = PolarsPlugin;
|
||||
|
||||
fn name(&self) -> &str {
|
||||
"polars horizontal"
|
||||
}
|
||||
|
||||
fn description(&self) -> &str {
|
||||
"Horizontal calculation across multiple columns."
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.input_output_type(Type::Any, Type::Custom("expression".into()))
|
||||
.required(
|
||||
"type",
|
||||
SyntaxShape::String,
|
||||
"horizontal operation. Values of all, any, min, max, sum, and mean are accepted.",
|
||||
)
|
||||
.rest(
|
||||
"Group-by expressions",
|
||||
SyntaxShape::Any,
|
||||
"Expression(s) that define the lazy group-by",
|
||||
)
|
||||
.switch(
|
||||
"nulls",
|
||||
"If set, null value in the input will lead to null output",
|
||||
Some('n'),
|
||||
)
|
||||
.category(Category::Custom("expression".into()))
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Horizontal sum across two columns (ignore nulls by default)",
|
||||
example: "[[a b]; [1 2] [2 3] [3 4] [4 5] [5 null]]
|
||||
| polars into-df
|
||||
| polars select (polars horizontal sum a b)
|
||||
| polars collect",
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(
|
||||
vec![Column::new(
|
||||
"sum".to_string(),
|
||||
vec![
|
||||
Value::test_int(3),
|
||||
Value::test_int(5),
|
||||
Value::test_int(7),
|
||||
Value::test_int(9),
|
||||
Value::test_int(5),
|
||||
],
|
||||
)],
|
||||
None,
|
||||
)
|
||||
.expect("simple df for test should not fail")
|
||||
.into_value(Span::test_data()),
|
||||
),
|
||||
},
|
||||
Example {
|
||||
description: "Horizontal sum across two columns while accounting for nulls",
|
||||
example: "[[a b]; [1 2] [2 3] [3 4] [4 5] [5 null]]
|
||||
| polars into-df
|
||||
| polars select (polars horizontal sum a b --nulls)
|
||||
| polars collect",
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(
|
||||
vec![Column::new(
|
||||
"sum".to_string(),
|
||||
vec![
|
||||
Value::test_int(3),
|
||||
Value::test_int(5),
|
||||
Value::test_int(7),
|
||||
Value::test_int(9),
|
||||
Value::test_nothing(),
|
||||
],
|
||||
)],
|
||||
None,
|
||||
)
|
||||
.expect("simple df for test should not fail")
|
||||
.into_value(Span::test_data()),
|
||||
),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
plugin: &Self::Plugin,
|
||||
engine: &EngineInterface,
|
||||
call: &EvaluatedCall,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, LabeledError> {
|
||||
let func_type: Spanned<String> = call.req(0)?;
|
||||
let func_type = HorizontalType::from_str(&func_type.item, func_type.span)?;
|
||||
|
||||
let vals: Vec<Value> = call.rest(1)?;
|
||||
let expr_value = Value::list(vals, call.head);
|
||||
let exprs = NuExpression::extract_exprs(plugin, expr_value)?;
|
||||
|
||||
let ignore_nulls = !call.has_flag("nulls")?;
|
||||
|
||||
command(plugin, engine, call, func_type, exprs, ignore_nulls).map_err(LabeledError::from)
|
||||
}
|
||||
}
|
||||
|
||||
fn command(
|
||||
plugin: &PolarsPlugin,
|
||||
engine: &EngineInterface,
|
||||
call: &EvaluatedCall,
|
||||
func_type: HorizontalType,
|
||||
exprs: Vec<Expr>,
|
||||
ignore_nulls: bool,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let res: NuExpression = match func_type {
|
||||
HorizontalType::All => all_horizontal(exprs),
|
||||
HorizontalType::Any => any_horizontal(exprs),
|
||||
HorizontalType::Max => max_horizontal(exprs),
|
||||
HorizontalType::Min => min_horizontal(exprs),
|
||||
HorizontalType::Sum => sum_horizontal(exprs, ignore_nulls),
|
||||
HorizontalType::Mean => mean_horizontal(exprs, ignore_nulls),
|
||||
}
|
||||
.map_err(|e| ShellError::GenericError {
|
||||
error: "Cannot apply horizontal aggregation".to_string(),
|
||||
msg: "".into(),
|
||||
span: Some(call.head),
|
||||
help: Some(e.to_string()),
|
||||
inner: vec![],
|
||||
})?
|
||||
.into();
|
||||
|
||||
res.to_pipeline_data(plugin, engine, call.head)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::test::test_polars_plugin_command;
|
||||
|
||||
#[test]
|
||||
fn test_examples() -> Result<(), ShellError> {
|
||||
test_polars_plugin_command(&Horizontal)
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ mod aggregate;
|
||||
mod count;
|
||||
mod cumulative;
|
||||
pub mod groupby;
|
||||
mod horizontal;
|
||||
mod implode;
|
||||
mod max;
|
||||
mod mean;
|
||||
@ -25,6 +26,7 @@ use nu_plugin::PluginCommand;
|
||||
pub use aggregate::LazyAggregate;
|
||||
use count::ExprCount;
|
||||
pub use cumulative::Cumulative;
|
||||
pub use horizontal::Horizontal;
|
||||
use implode::ExprImplode;
|
||||
use max::ExprMax;
|
||||
use mean::ExprMean;
|
||||
@ -45,19 +47,20 @@ pub(crate) fn aggregation_commands() -> Vec<Box<dyn PluginCommand<Plugin = Polar
|
||||
Box::new(ExprCount),
|
||||
Box::new(ExprImplode),
|
||||
Box::new(ExprMax),
|
||||
Box::new(ExprMin),
|
||||
Box::new(ExprSum),
|
||||
Box::new(ExprMean),
|
||||
Box::new(ExprMin),
|
||||
Box::new(ExprStd),
|
||||
Box::new(ExprSum),
|
||||
Box::new(ExprVar),
|
||||
Box::new(Horizontal),
|
||||
Box::new(LazyAggregate),
|
||||
Box::new(median::LazyMedian),
|
||||
Box::new(quantile::LazyQuantile),
|
||||
Box::new(groupby::ToLazyGroupBy),
|
||||
Box::new(NNull),
|
||||
Box::new(NUnique),
|
||||
Box::new(Over),
|
||||
Box::new(Rolling),
|
||||
Box::new(ValueCount),
|
||||
Box::new(NNull),
|
||||
Box::new(NUnique),
|
||||
Box::new(groupby::ToLazyGroupBy),
|
||||
Box::new(median::LazyMedian),
|
||||
Box::new(quantile::LazyQuantile),
|
||||
]
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ The Nushell team runs **testing of Nushell for the following platforms** through
|
||||
|
||||
- macOS (latest version available through GitHub CI)
|
||||
- Windows (10 and 11)
|
||||
- Linux (our test runners use `ubuntu-20.04` to represent distributions with not the latest glibc versions.)
|
||||
- Linux (our test runners use `ubuntu-22.04` to represent distributions with not the latest glibc versions.)
|
||||
|
||||
All PR level tests are performed on x86/AMD64 (at least at the time of writing the default macOS runner was not yet using arm64).
|
||||
|
||||
|
@ -143,6 +143,10 @@ fn export_module_which_defined_const() -> TestResult {
|
||||
run_test(
|
||||
r#"module spam { export const b = 3; export const c = 4 }; use spam; $spam.b + $spam.c"#,
|
||||
"7",
|
||||
)?;
|
||||
fail_test(
|
||||
r#"module spam { export const b = 3; export const c = 4 }; use spam; $b"#,
|
||||
"variable not found",
|
||||
)
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user