Merge branch 'main' into interrupt

This commit is contained in:
Ian Manske 2024-07-07 15:17:53 -07:00
commit ae2cff8c24
28 changed files with 800 additions and 562 deletions

View File

@ -99,13 +99,13 @@ jobs:
extra: msi
os: windows-latest
- target: x86_64-unknown-linux-gnu
os: ubuntu-20.04
os: ubuntu-22.04
- target: x86_64-unknown-linux-musl
os: ubuntu-20.04
os: ubuntu-22.04
- target: aarch64-unknown-linux-gnu
os: ubuntu-20.04
os: ubuntu-22.04
- target: armv7-unknown-linux-gnueabihf
os: ubuntu-20.04
os: ubuntu-22.04
- target: riscv64gc-unknown-linux-gnu
os: ubuntu-latest

View File

@ -49,13 +49,13 @@ jobs:
extra: msi
os: windows-latest
- target: x86_64-unknown-linux-gnu
os: ubuntu-20.04
os: ubuntu-22.04
- target: x86_64-unknown-linux-musl
os: ubuntu-20.04
os: ubuntu-22.04
- target: aarch64-unknown-linux-gnu
os: ubuntu-20.04
os: ubuntu-22.04
- target: armv7-unknown-linux-gnueabihf
os: ubuntu-20.04
os: ubuntu-22.04
- target: riscv64gc-unknown-linux-gnu
os: ubuntu-latest

7
Cargo.lock generated
View File

@ -2382,9 +2382,9 @@ dependencies = [
[[package]]
name = "libloading"
version = "0.8.3"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19"
checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d"
dependencies = [
"cfg-if",
"windows-targets 0.52.5",
@ -4988,8 +4988,7 @@ dependencies = [
[[package]]
name = "reedline"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abf59e4c97b5049ba96b052cdb652368305a2eddcbce9bf1c16f9d003139eeea"
source = "git+https://github.com/nushell/reedline?branch=main#480059a3f52cf919341cda88e8c544edd846bc73"
dependencies = [
"arboard",
"chrono",

View File

@ -197,7 +197,6 @@ nu-protocol = { path = "./crates/nu-protocol", version = "0.95.1" }
nu-std = { path = "./crates/nu-std", version = "0.95.1" }
nu-system = { path = "./crates/nu-system", version = "0.95.1" }
nu-utils = { path = "./crates/nu-utils", version = "0.95.1" }
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
crossterm = { workspace = true }
@ -304,7 +303,7 @@ bench = false
# To use a development version of a dependency please use a global override here
# changing versions in each sub-crate of the workspace is tedious
[patch.crates-io]
# reedline = { git = "https://github.com/nushell/reedline", branch = "main" }
reedline = { git = "https://github.com/nushell/reedline", branch = "main" }
# nu-ansi-term = {git = "https://github.com/nushell/nu-ansi-term.git", branch = "main"}
# Run all benchmarks with `cargo bench`

View File

@ -333,6 +333,13 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
.with_quick_completions(config.quick_completions)
.with_partial_completions(config.partial_completions)
.with_ansi_colors(config.use_ansi_coloring)
.with_cwd(Some(
engine_state
.cwd(None)
.unwrap_or_default()
.to_string_lossy()
.to_string(),
))
.with_cursor_config(cursor_config);
perf!("reedline builder", start_time, use_color);
@ -663,13 +670,14 @@ fn prepare_history_metadata(
line_editor: &mut Reedline,
) {
if !s.is_empty() && line_editor.has_last_command_context() {
#[allow(deprecated)]
let result = line_editor
.update_last_command_context(&|mut c| {
c.start_timestamp = Some(chrono::Utc::now());
c.hostname = hostname.map(str::to_string);
c.cwd = Some(StateWorkingSet::new(engine_state).get_cwd());
c.cwd = engine_state
.cwd(None)
.ok()
.map(|path| path.to_string_lossy().to_string());
c
})
.into_diagnostic();

View File

@ -1,6 +1,9 @@
use std::io::{self, Read, Write};
use nu_cmd_base::input_handler::{operate, CmdArgument};
use nu_engine::command_prelude::*;
use nu_protocol::Signals;
use num_traits::ToPrimitive;
pub struct Arguments {
@ -118,15 +121,46 @@ fn into_bits(
let cell_paths = call.rest(engine_state, stack, 0)?;
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
if let PipelineData::ByteStream(stream, ..) = input {
// TODO: in the future, we may want this to stream out, converting each to bytes
Ok(Value::binary(stream.into_bytes()?, head).into_pipeline_data())
if let PipelineData::ByteStream(stream, metadata) = input {
Ok(PipelineData::ByteStream(
byte_stream_to_bits(stream, head),
metadata,
))
} else {
let args = Arguments { cell_paths };
operate(action, args, input, call.head, engine_state.signals())
}
}
fn byte_stream_to_bits(stream: ByteStream, head: Span) -> ByteStream {
if let Some(mut reader) = stream.reader() {
let mut is_first = true;
ByteStream::from_fn(
head,
Signals::empty(),
ByteStreamType::String,
move |buffer| {
let mut byte = [0];
if reader.read(&mut byte[..]).err_span(head)? > 0 {
// Format the byte as bits
if is_first {
is_first = false;
} else {
buffer.push(b' ');
}
write!(buffer, "{:08b}", byte[0]).expect("format failed");
Ok(true)
} else {
// EOF
Ok(false)
}
},
)
} else {
ByteStream::read(io::empty(), head, Signals::empty(), ByteStreamType::String)
}
}
fn convert_to_smallest_number_type(num: i64, span: Span) -> Value {
if let Some(v) = num.to_i8() {
let bytes = v.to_ne_bytes();

View File

@ -0,0 +1,13 @@
use nu_test_support::nu;
#[test]
fn byte_stream_into_bits() {
let result = nu!("[0x[01] 0x[02 03]] | bytes collect | into bits");
assert_eq!("00000001 00000010 00000011", result.out);
}
#[test]
fn byte_stream_into_bits_is_stream() {
let result = nu!("[0x[01] 0x[02 03]] | bytes collect | into bits | describe");
assert_eq!("string (stream)", result.out);
}

View File

@ -0,0 +1 @@
mod into;

View File

@ -1 +1,2 @@
mod bits;
mod bytes;

View File

@ -1,5 +1,5 @@
use nu_engine::command_prelude::*;
use std::str::FromStr;
use toml::value::{Datetime, Offset};
#[derive(Clone)]
pub struct FromToml;
@ -56,6 +56,54 @@ b = [1, 2]' | from toml",
}
}
fn convert_toml_datetime_to_value(dt: &Datetime, span: Span) -> Value {
match &dt.clone() {
toml::value::Datetime {
date: Some(_),
time: _,
offset: _,
} => (),
_ => return Value::string(dt.to_string(), span),
}
let date = match dt.date {
Some(date) => {
chrono::NaiveDate::from_ymd_opt(date.year.into(), date.month.into(), date.day.into())
}
None => Some(chrono::NaiveDate::default()),
};
let time = match dt.time {
Some(time) => chrono::NaiveTime::from_hms_nano_opt(
time.hour.into(),
time.minute.into(),
time.second.into(),
time.nanosecond,
),
None => Some(chrono::NaiveTime::default()),
};
let tz = match dt.offset {
Some(offset) => match offset {
Offset::Z => chrono::FixedOffset::east_opt(0),
Offset::Custom { minutes: min } => chrono::FixedOffset::east_opt(min as i32 * 60),
},
None => chrono::FixedOffset::east_opt(0),
};
let datetime = match (date, time, tz) {
(Some(date), Some(time), Some(tz)) => chrono::NaiveDateTime::new(date, time)
.and_local_timezone(tz)
.earliest(),
_ => None,
};
match datetime {
Some(datetime) => Value::date(datetime, span),
None => Value::string(dt.to_string(), span),
}
}
fn convert_toml_to_value(value: &toml::Value, span: Span) -> Value {
match value {
toml::Value::Array(array) => {
@ -76,13 +124,7 @@ fn convert_toml_to_value(value: &toml::Value, span: Span) -> Value {
span,
),
toml::Value::String(s) => Value::string(s.clone(), span),
toml::Value::Datetime(d) => match chrono::DateTime::from_str(&d.to_string()) {
Ok(nushell_date) => Value::date(nushell_date, span),
// in the unlikely event that parsing goes wrong, this function still returns a valid
// nushell date (however the default one). This decision was made to make the output of
// this function uniform amongst all eventualities
Err(_) => Value::date(chrono::DateTime::default(), span),
},
toml::Value::Datetime(dt) => convert_toml_datetime_to_value(dt, span),
}
}
@ -113,32 +155,6 @@ mod tests {
test_examples(FromToml {})
}
#[test]
fn from_toml_creates_nushell_date() {
let toml_date = toml::Value::Datetime(Datetime {
date: Option::from(toml::value::Date {
year: 1980,
month: 10,
day: 12,
}),
time: Option::from(toml::value::Time {
hour: 10,
minute: 12,
second: 44,
nanosecond: 0,
}),
offset: Option::from(toml::value::Offset::Custom { minutes: 120 }),
});
let span = Span::test_data();
let reference_date = Value::date(Default::default(), Span::test_data());
let result = convert_toml_to_value(&toml_date, span);
//positive test (from toml returns a nushell date)
assert_eq!(result.get_type(), reference_date.get_type());
}
#[test]
fn from_toml_creates_correct_date() {
let toml_date = toml::Value::Datetime(Datetime {
@ -206,4 +222,113 @@ mod tests {
assert!(result.is_err());
}
#[test]
fn convert_toml_datetime_to_value_date_time_offset() {
let toml_date = Datetime {
date: Option::from(toml::value::Date {
year: 2000,
month: 1,
day: 1,
}),
time: Option::from(toml::value::Time {
hour: 12,
minute: 12,
second: 12,
nanosecond: 0,
}),
offset: Option::from(toml::value::Offset::Custom { minutes: 120 }),
};
let span = Span::test_data();
let reference_date = Value::date(
chrono::FixedOffset::east_opt(60 * 120)
.unwrap()
.with_ymd_and_hms(2000, 1, 1, 12, 12, 12)
.unwrap(),
span,
);
let result = convert_toml_datetime_to_value(&toml_date, span);
assert_eq!(result, reference_date);
}
#[test]
fn convert_toml_datetime_to_value_date_time() {
let toml_date = Datetime {
date: Option::from(toml::value::Date {
year: 2000,
month: 1,
day: 1,
}),
time: Option::from(toml::value::Time {
hour: 12,
minute: 12,
second: 12,
nanosecond: 0,
}),
offset: None,
};
let span = Span::test_data();
let reference_date = Value::date(
chrono::FixedOffset::east_opt(0)
.unwrap()
.with_ymd_and_hms(2000, 1, 1, 12, 12, 12)
.unwrap(),
span,
);
let result = convert_toml_datetime_to_value(&toml_date, span);
assert_eq!(result, reference_date);
}
#[test]
fn convert_toml_datetime_to_value_date() {
let toml_date = Datetime {
date: Option::from(toml::value::Date {
year: 2000,
month: 1,
day: 1,
}),
time: None,
offset: None,
};
let span = Span::test_data();
let reference_date = Value::date(
chrono::FixedOffset::east_opt(0)
.unwrap()
.with_ymd_and_hms(2000, 1, 1, 0, 0, 0)
.unwrap(),
span,
);
let result = convert_toml_datetime_to_value(&toml_date, span);
assert_eq!(result, reference_date);
}
#[test]
fn convert_toml_datetime_to_value_only_time() {
let toml_date = Datetime {
date: None,
time: Option::from(toml::value::Time {
hour: 12,
minute: 12,
second: 12,
nanosecond: 0,
}),
offset: None,
};
let span = Span::test_data();
let reference_date = Value::string(toml_date.to_string(), span);
let result = convert_toml_datetime_to_value(&toml_date, span);
assert_eq!(result, reference_date);
}
}

View File

@ -1,4 +1,5 @@
use nu_engine::command_prelude::*;
use nu_protocol::ast::{Assignment, Bits, Boolean, Comparison, Math, Operator};
#[derive(Clone)]
pub struct HelpOperators;
@ -27,282 +28,148 @@ impl Command for HelpOperators {
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let head = call.head;
let op_info = generate_operator_info();
let mut recs = vec![];
for op in op_info {
recs.push(Value::record(
let mut operators = [
Operator::Assignment(Assignment::Assign),
Operator::Assignment(Assignment::PlusAssign),
Operator::Assignment(Assignment::AppendAssign),
Operator::Assignment(Assignment::MinusAssign),
Operator::Assignment(Assignment::MultiplyAssign),
Operator::Assignment(Assignment::DivideAssign),
Operator::Comparison(Comparison::Equal),
Operator::Comparison(Comparison::NotEqual),
Operator::Comparison(Comparison::LessThan),
Operator::Comparison(Comparison::GreaterThan),
Operator::Comparison(Comparison::LessThanOrEqual),
Operator::Comparison(Comparison::GreaterThanOrEqual),
Operator::Comparison(Comparison::RegexMatch),
Operator::Comparison(Comparison::NotRegexMatch),
Operator::Comparison(Comparison::In),
Operator::Comparison(Comparison::NotIn),
Operator::Comparison(Comparison::StartsWith),
Operator::Comparison(Comparison::EndsWith),
Operator::Math(Math::Plus),
Operator::Math(Math::Append),
Operator::Math(Math::Minus),
Operator::Math(Math::Multiply),
Operator::Math(Math::Divide),
Operator::Math(Math::Modulo),
Operator::Math(Math::FloorDivision),
Operator::Math(Math::Pow),
Operator::Bits(Bits::BitOr),
Operator::Bits(Bits::BitXor),
Operator::Bits(Bits::BitAnd),
Operator::Bits(Bits::ShiftLeft),
Operator::Bits(Bits::ShiftRight),
Operator::Boolean(Boolean::And),
Operator::Boolean(Boolean::Or),
Operator::Boolean(Boolean::Xor),
]
.into_iter()
.map(|op| {
Value::record(
record! {
"type" => Value::string(op.op_type, head),
"operator" => Value::string(op.operator, head),
"name" => Value::string(op.name, head),
"description" => Value::string(op.description, head),
"precedence" => Value::int(op.precedence, head),
"type" => Value::string(op_type(&op), head),
"operator" => Value::string(op.to_string(), head),
"name" => Value::string(name(&op), head),
"description" => Value::string(description(&op), head),
"precedence" => Value::int(op.precedence().into(), head),
},
head,
));
}
)
})
.collect::<Vec<_>>();
Ok(Value::list(recs, head).into_pipeline_data())
operators.push(Value::record(
record! {
"type" => Value::string("Boolean", head),
"operator" => Value::string("not", head),
"name" => Value::string("Not", head),
"description" => Value::string("Negates a value or expression.", head),
"precedence" => Value::int(0, head),
},
head,
));
Ok(Value::list(operators, head).into_pipeline_data())
}
}
struct OperatorInfo {
op_type: String,
operator: String,
name: String,
description: String,
precedence: i64,
fn op_type(operator: &Operator) -> &'static str {
match operator {
Operator::Comparison(_) => "Comparison",
Operator::Math(_) => "Math",
Operator::Boolean(_) => "Boolean",
Operator::Bits(_) => "Bitwise",
Operator::Assignment(_) => "Assignment",
}
}
fn generate_operator_info() -> Vec<OperatorInfo> {
vec![
OperatorInfo {
op_type: "Assignment".into(),
operator: "=".into(),
name: "Assign".into(),
description: "Assigns a value to a variable.".into(),
precedence: 10,
},
OperatorInfo {
op_type: "Assignment".into(),
operator: "+=".into(),
name: "PlusAssign".into(),
description: "Adds a value to a variable.".into(),
precedence: 10,
},
OperatorInfo {
op_type: "Assignment".into(),
operator: "++=".into(),
name: "AppendAssign".into(),
description: "Appends a list or a value to a variable.".into(),
precedence: 10,
},
OperatorInfo {
op_type: "Assignment".into(),
operator: "-=".into(),
name: "MinusAssign".into(),
description: "Subtracts a value from a variable.".into(),
precedence: 10,
},
OperatorInfo {
op_type: "Assignment".into(),
operator: "*=".into(),
name: "MultiplyAssign".into(),
description: "Multiplies a variable by a value.".into(),
precedence: 10,
},
OperatorInfo {
op_type: "Assignment".into(),
operator: "/=".into(),
name: "DivideAssign".into(),
description: "Divides a variable by a value.".into(),
precedence: 10,
},
OperatorInfo {
op_type: "Comparison".into(),
operator: "==".into(),
name: "Equal".into(),
description: "Checks if two values are equal.".into(),
precedence: 80,
},
OperatorInfo {
op_type: "Comparison".into(),
operator: "!=".into(),
name: "NotEqual".into(),
description: "Checks if two values are not equal.".into(),
precedence: 80,
},
OperatorInfo {
op_type: "Comparison".into(),
operator: "<".into(),
name: "LessThan".into(),
description: "Checks if a value is less than another.".into(),
precedence: 80,
},
OperatorInfo {
op_type: "Comparison".into(),
operator: "<=".into(),
name: "LessThanOrEqual".into(),
description: "Checks if a value is less than or equal to another.".into(),
precedence: 80,
},
OperatorInfo {
op_type: "Comparison".into(),
operator: ">".into(),
name: "GreaterThan".into(),
description: "Checks if a value is greater than another.".into(),
precedence: 80,
},
OperatorInfo {
op_type: "Comparison".into(),
operator: ">=".into(),
name: "GreaterThanOrEqual".into(),
description: "Checks if a value is greater than or equal to another.".into(),
precedence: 80,
},
OperatorInfo {
op_type: "Comparison".into(),
operator: "=~".into(),
name: "RegexMatch".into(),
description: "Checks if a value matches a regular expression.".into(),
precedence: 80,
},
OperatorInfo {
op_type: "Comparison".into(),
operator: "!~".into(),
name: "NotRegexMatch".into(),
description: "Checks if a value does not match a regular expression.".into(),
precedence: 80,
},
OperatorInfo {
op_type: "Comparison".into(),
operator: "in".into(),
name: "In".into(),
description: "Checks if a value is in a list or string.".into(),
precedence: 80,
},
OperatorInfo {
op_type: "Comparison".into(),
operator: "not-in".into(),
name: "NotIn".into(),
description: "Checks if a value is not in a list or string.".into(),
precedence: 80,
},
OperatorInfo {
op_type: "Comparison".into(),
operator: "starts-with".into(),
name: "StartsWith".into(),
description: "Checks if a string starts with another.".into(),
precedence: 80,
},
OperatorInfo {
op_type: "Comparison".into(),
operator: "ends-with".into(),
name: "EndsWith".into(),
description: "Checks if a string ends with another.".into(),
precedence: 80,
},
OperatorInfo {
op_type: "Comparison".into(),
operator: "not".into(),
name: "UnaryNot".into(),
description: "Negates a value or expression.".into(),
precedence: 0,
},
OperatorInfo {
op_type: "Math".into(),
operator: "+".into(),
name: "Plus".into(),
description: "Adds two values.".into(),
precedence: 90,
},
OperatorInfo {
op_type: "Math".into(),
operator: "++".into(),
name: "Append".into(),
description: "Appends two lists or a list and a value.".into(),
precedence: 80,
},
OperatorInfo {
op_type: "Math".into(),
operator: "-".into(),
name: "Minus".into(),
description: "Subtracts two values.".into(),
precedence: 90,
},
OperatorInfo {
op_type: "Math".into(),
operator: "*".into(),
name: "Multiply".into(),
description: "Multiplies two values.".into(),
precedence: 95,
},
OperatorInfo {
op_type: "Math".into(),
operator: "/".into(),
name: "Divide".into(),
description: "Divides two values.".into(),
precedence: 95,
},
OperatorInfo {
op_type: "Math".into(),
operator: "//".into(),
name: "FloorDivision".into(),
description: "Divides two values and floors the result.".into(),
precedence: 95,
},
OperatorInfo {
op_type: "Math".into(),
operator: "mod".into(),
name: "Modulo".into(),
description: "Divides two values and returns the remainder.".into(),
precedence: 95,
},
OperatorInfo {
op_type: "Math".into(),
operator: "**".into(),
name: "Pow ".into(),
description: "Raises one value to the power of another.".into(),
precedence: 100,
},
OperatorInfo {
op_type: "Bitwise".into(),
operator: "bit-or".into(),
name: "BitOr".into(),
description: "Performs a bitwise OR on two values.".into(),
precedence: 60,
},
OperatorInfo {
op_type: "Bitwise".into(),
operator: "bit-xor".into(),
name: "BitXor".into(),
description: "Performs a bitwise XOR on two values.".into(),
precedence: 70,
},
OperatorInfo {
op_type: "Bitwise".into(),
operator: "bit-and".into(),
name: "BitAnd".into(),
description: "Performs a bitwise AND on two values.".into(),
precedence: 75,
},
OperatorInfo {
op_type: "Bitwise".into(),
operator: "bit-shl".into(),
name: "ShiftLeft".into(),
description: "Shifts a value left by another.".into(),
precedence: 85,
},
OperatorInfo {
op_type: "Bitwise".into(),
operator: "bit-shr".into(),
name: "ShiftRight".into(),
description: "Shifts a value right by another.".into(),
precedence: 85,
},
OperatorInfo {
op_type: "Boolean".into(),
operator: "and".into(),
name: "And".into(),
description: "Checks if two values are true.".into(),
precedence: 50,
},
OperatorInfo {
op_type: "Boolean".into(),
operator: "or".into(),
name: "Or".into(),
description: "Checks if either value is true.".into(),
precedence: 40,
},
OperatorInfo {
op_type: "Boolean".into(),
operator: "xor".into(),
name: "Xor".into(),
description: "Checks if one value is true and the other is false.".into(),
precedence: 45,
},
]
fn name(operator: &Operator) -> String {
match operator {
Operator::Comparison(op) => format!("{op:?}"),
Operator::Math(op) => format!("{op:?}"),
Operator::Boolean(op) => format!("{op:?}"),
Operator::Bits(op) => format!("{op:?}"),
Operator::Assignment(op) => format!("{op:?}"),
}
}
fn description(operator: &Operator) -> &'static str {
// NOTE: if you have to add an operator here, also add it to the operator list above.
match operator {
Operator::Comparison(Comparison::Equal) => "Checks if two values are equal.",
Operator::Comparison(Comparison::NotEqual) => "Checks if two values are not equal.",
Operator::Comparison(Comparison::LessThan) => "Checks if a value is less than another.",
Operator::Comparison(Comparison::GreaterThan) => {
"Checks if a value is greater than another."
}
Operator::Comparison(Comparison::LessThanOrEqual) => {
"Checks if a value is less than or equal to another."
}
Operator::Comparison(Comparison::GreaterThanOrEqual) => {
"Checks if a value is greater than or equal to another."
}
Operator::Comparison(Comparison::RegexMatch) => {
"Checks if a value matches a regular expression."
}
Operator::Comparison(Comparison::NotRegexMatch) => {
"Checks if a value does not match a regular expression."
}
Operator::Comparison(Comparison::In) => {
"Checks if a value is in a list, is part of a string, or is a key in a record."
}
Operator::Comparison(Comparison::NotIn) => {
"Checks if a value is not in a list, is not part of a string, or is not a key in a record."
}
Operator::Comparison(Comparison::StartsWith) => "Checks if a string starts with another.",
Operator::Comparison(Comparison::EndsWith) => "Checks if a string ends with another.",
Operator::Math(Math::Plus) => "Adds two values.",
Operator::Math(Math::Append) => {
"Appends two lists, a list and a value, two strings, or two binary values."
}
Operator::Math(Math::Minus) => "Subtracts two values.",
Operator::Math(Math::Multiply) => "Multiplies two values.",
Operator::Math(Math::Divide) => "Divides two values.",
Operator::Math(Math::Modulo) => "Divides two values and returns the remainder.",
Operator::Math(Math::FloorDivision) => "Divides two values and floors the result.",
Operator::Math(Math::Pow) => "Raises one value to the power of another.",
Operator::Boolean(Boolean::And) => "Checks if both values are true.",
Operator::Boolean(Boolean::Or) => "Checks if either value is true.",
Operator::Boolean(Boolean::Xor) => "Checks if one value is true and the other is false.",
Operator::Bits(Bits::BitOr) => "Performs a bitwise OR on two values.",
Operator::Bits(Bits::BitXor) => "Performs a bitwise XOR on two values.",
Operator::Bits(Bits::BitAnd) => "Performs a bitwise AND on two values.",
Operator::Bits(Bits::ShiftLeft) => "Bitwise shifts a value left by another.",
Operator::Bits(Bits::ShiftRight) => "Bitwise shifts a value right by another.",
Operator::Assignment(Assignment::Assign) => "Assigns a value to a variable.",
Operator::Assignment(Assignment::PlusAssign) => "Adds a value to a variable.",
Operator::Assignment(Assignment::AppendAssign) => {
"Appends a list, a value, a string, or a binary value to a variable."
}
Operator::Assignment(Assignment::MinusAssign) => "Subtracts a value from a variable.",
Operator::Assignment(Assignment::MultiplyAssign) => "Multiplies a variable by a value.",
Operator::Assignment(Assignment::DivideAssign) => "Divides a variable by a value.",
}
}
#[cfg(test)]

View File

@ -1,7 +1,7 @@
use super::ViewCommand;
use crate::{
nu_common::{self, collect_input},
views::Preview,
views::{Preview, ViewConfig},
};
use anyhow::Result;
use nu_color_config::StyleComputer;
@ -43,6 +43,7 @@ impl ViewCommand for ExpandCmd {
engine_state: &EngineState,
stack: &mut Stack,
value: Option<Value>,
_: &ViewConfig,
) -> Result<Self::View> {
if let Some(value) = value {
let value_as_string = convert_value_to_string(value, engine_state, stack)?;

View File

@ -1,5 +1,5 @@
use super::ViewCommand;
use crate::views::Preview;
use crate::views::{Preview, ViewConfig};
use anyhow::Result;
use nu_ansi_term::Color;
use nu_protocol::{
@ -99,7 +99,13 @@ impl ViewCommand for HelpCmd {
Ok(())
}
fn spawn(&mut self, _: &EngineState, _: &mut Stack, _: Option<Value>) -> Result<Self::View> {
fn spawn(
&mut self,
_: &EngineState,
_: &mut Stack,
_: Option<Value>,
_: &ViewConfig,
) -> Result<Self::View> {
Ok(HelpCmd::view())
}
}

View File

@ -1,3 +1,5 @@
use crate::views::ViewConfig;
use super::pager::{Pager, Transition};
use anyhow::Result;
use nu_protocol::{
@ -49,5 +51,6 @@ pub trait ViewCommand {
engine_state: &EngineState,
stack: &mut Stack,
value: Option<Value>,
config: &ViewConfig,
) -> Result<Self::View>;
}

View File

@ -48,6 +48,7 @@ impl ViewCommand for NuCmd {
engine_state: &EngineState,
stack: &mut Stack,
value: Option<Value>,
config: &ViewConfig,
) -> Result<Self::View> {
let value = value.unwrap_or_default();
@ -62,7 +63,7 @@ impl ViewCommand for NuCmd {
return Ok(NuView::Preview(Preview::new(&text)));
}
let mut view = RecordView::new(columns, values);
let mut view = RecordView::new(columns, values, config.explore_config.clone());
if is_record {
view.set_top_layer_orientation(Orientation::Left);
@ -119,11 +120,4 @@ impl View for NuView {
NuView::Preview(v) => v.exit(),
}
}
fn setup(&mut self, config: ViewConfig<'_>) {
match self {
NuView::Records(v) => v.setup(config),
NuView::Preview(v) => v.setup(config),
}
}
}

View File

@ -1,7 +1,7 @@
use super::ViewCommand;
use crate::{
nu_common::collect_input,
views::{Orientation, RecordView},
views::{Orientation, RecordView, ViewConfig},
};
use anyhow::Result;
use nu_protocol::{
@ -49,13 +49,14 @@ impl ViewCommand for TableCmd {
_: &EngineState,
_: &mut Stack,
value: Option<Value>,
config: &ViewConfig,
) -> Result<Self::View> {
let value = value.unwrap_or_default();
let is_record = matches!(value, Value::Record { .. });
let (columns, data) = collect_input(value)?;
let mut view = RecordView::new(columns, data);
let mut view = RecordView::new(columns, data, config.explore_config.clone());
if is_record {
view.set_top_layer_orientation(Orientation::Left);

View File

@ -1,5 +1,5 @@
use super::ViewCommand;
use crate::views::TryView;
use crate::views::{TryView, ViewConfig};
use anyhow::Result;
use nu_protocol::{
engine::{EngineState, Stack},
@ -43,9 +43,10 @@ impl ViewCommand for TryCmd {
engine_state: &EngineState,
stack: &mut Stack,
value: Option<Value>,
config: &ViewConfig,
) -> Result<Self::View> {
let value = value.unwrap_or_default();
let mut view = TryView::new(value);
let mut view = TryView::new(value, config.explore_config.clone());
view.init(self.command.clone());
view.try_run(engine_state, stack)?;

View File

@ -10,6 +10,7 @@ use anyhow::Result;
use commands::{ExpandCmd, HelpCmd, NuCmd, QuitCmd, TableCmd, TryCmd};
pub use default_context::add_explore_context;
pub use explore::Explore;
use explore::ExploreConfig;
use nu_common::{collect_pipeline, has_simple_value};
use nu_protocol::{
engine::{EngineState, Stack},
@ -42,7 +43,7 @@ fn run_pager(
if is_binary {
p.show_message("For help type :help");
let view = binary_view(input)?;
let view = binary_view(input, config.explore_config)?;
return p.run(engine_state, stack, Some(view), commands);
}
@ -72,7 +73,7 @@ fn create_record_view(
is_record: bool,
config: PagerConfig,
) -> Option<Page> {
let mut view = RecordView::new(columns, data);
let mut view = RecordView::new(columns, data, config.explore_config.clone());
if is_record {
view.set_top_layer_orientation(Orientation::Left);
}
@ -90,14 +91,14 @@ fn help_view() -> Option<Page> {
Some(Page::new(HelpCmd::view(), false))
}
fn binary_view(input: PipelineData) -> Result<Page> {
fn binary_view(input: PipelineData, config: &ExploreConfig) -> Result<Page> {
let data = match input {
PipelineData::Value(Value::Binary { val, .. }, _) => val,
PipelineData::ByteStream(bs, _) => bs.into_bytes()?,
_ => unreachable!("checked beforehand"),
};
let view = BinaryView::new(data);
let view = BinaryView::new(data, config);
Ok(Page::new(view, true))
}

View File

@ -88,19 +88,41 @@ impl<'a> Pager<'a> {
&mut self,
engine_state: &EngineState,
stack: &mut Stack,
mut view: Option<Page>,
view: Option<Page>,
commands: CommandRegistry,
) -> Result<Option<Value>> {
if let Some(page) = &mut view {
page.view.setup(ViewConfig::new(
self.config.nu_config,
self.config.explore_config,
self.config.style_computer,
self.config.lscolors,
))
// setup terminal
enable_raw_mode()?;
let mut stdout = io::stdout();
execute!(stdout, EnterAlternateScreen, Clear(ClearType::All))?;
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
let mut info = ViewInfo {
status: Some(Report::default()),
..Default::default()
};
if let Some(text) = self.message.take() {
info.status = Some(Report::message(text, Severity::Info));
}
run_pager(engine_state, stack, self, view, commands)
let result = render_ui(
&mut terminal,
engine_state,
stack,
self,
&mut info,
view,
commands,
)?;
// restore terminal
disable_raw_mode()?;
execute!(io::stdout(), LeaveAlternateScreen)?;
Ok(result)
}
}
@ -143,47 +165,6 @@ impl<'a> PagerConfig<'a> {
}
}
fn run_pager(
engine_state: &EngineState,
stack: &mut Stack,
pager: &mut Pager,
view: Option<Page>,
commands: CommandRegistry,
) -> Result<Option<Value>> {
// setup terminal
enable_raw_mode()?;
let mut stdout = io::stdout();
execute!(stdout, EnterAlternateScreen, Clear(ClearType::All))?;
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
let mut info = ViewInfo {
status: Some(Report::default()),
..Default::default()
};
if let Some(text) = pager.message.take() {
info.status = Some(Report::message(text, Severity::Info));
}
let result = render_ui(
&mut terminal,
engine_state,
stack,
pager,
&mut info,
view,
commands,
)?;
// restore terminal
disable_raw_mode()?;
execute!(io::stdout(), LeaveAlternateScreen)?;
Ok(result)
}
#[allow(clippy::too_many_arguments)]
fn render_ui(
term: &mut Terminal,
@ -432,15 +413,20 @@ fn run_command(
Command::View { mut cmd, stackable } => {
// what we do we just replace the view.
let value = view_stack.curr_view.as_mut().and_then(|p| p.view.exit());
let mut new_view = cmd.spawn(engine_state, stack, value)?;
let view_cfg = ViewConfig::new(
pager.config.nu_config,
pager.config.explore_config,
pager.config.style_computer,
pager.config.lscolors,
);
let new_view = cmd.spawn(engine_state, stack, value, &view_cfg)?;
if let Some(view) = view_stack.curr_view.take() {
if !view.stackable {
view_stack.stack.push(view);
}
}
setup_view(&mut new_view, &pager.config);
view_stack.curr_view = Some(Page::raw(new_view, stackable));
Ok(CmdResult::new(false, true, cmd.name().to_owned()))
@ -448,16 +434,6 @@ fn run_command(
}
}
fn setup_view(view: &mut Box<dyn View>, cfg: &PagerConfig<'_>) {
let cfg = ViewConfig::new(
cfg.nu_config,
cfg.explore_config,
cfg.style_computer,
cfg.lscolors,
);
view.setup(cfg);
}
fn set_cursor_cmd_bar(f: &mut Frame, area: Rect, pager: &Pager) {
if pager.cmd_buf.is_cmd_input {
// todo: deal with a situation where we exceed the bar width

View File

@ -1,6 +1,6 @@
use crate::{
commands::{SimpleCommand, ViewCommand},
views::View,
views::{View, ViewConfig},
};
use anyhow::Result;
@ -78,8 +78,9 @@ where
engine_state: &nu_protocol::engine::EngineState,
stack: &mut nu_protocol::engine::Stack,
value: Option<nu_protocol::Value>,
cfg: &ViewConfig,
) -> Result<Self::View> {
let view = self.0.spawn(engine_state, stack, value)?;
let view = self.0.spawn(engine_state, stack, value, cfg)?;
Ok(Box::new(view) as Box<dyn View>)
}
}

View File

@ -40,11 +40,15 @@ struct Settings {
}
impl BinaryView {
pub fn new(data: Vec<u8>) -> Self {
pub fn new(data: Vec<u8>, cfg: &ExploreConfig) -> Self {
let settings = settings_from_config(cfg);
// There's gotta be a nicer way of doing this than creating a widget just to count lines
let count_rows = BinaryWidget::new(&data, settings.opts, Default::default()).count_lines();
Self {
data,
cursor: WindowCursor2D::default(),
settings: Settings::default(),
cursor: WindowCursor2D::new(count_rows, 1).expect("Failed to create XYCursor"),
settings,
}
}
}
@ -87,15 +91,6 @@ impl View for BinaryView {
// todo: impl Cursor + peek of a value
None
}
fn setup(&mut self, cfg: ViewConfig<'_>) {
self.settings = settings_from_config(cfg.explore_config);
let count_rows =
BinaryWidget::new(&self.data, self.settings.opts, Default::default()).count_lines();
// TODO: refactor View so setup() is fallible and we don't have to panic here
self.cursor = WindowCursor2D::new(count_rows, 1).expect("Failed to create XYCursor");
}
}
fn create_binary_widget(v: &BinaryView) -> BinaryWidget<'_> {

View File

@ -99,8 +99,6 @@ pub trait View {
fn exit(&mut self) -> Option<Value> {
None
}
fn setup(&mut self, _: ViewConfig<'_>) {}
}
impl View for Box<dyn View> {
@ -131,8 +129,4 @@ impl View for Box<dyn View> {
fn show_data(&mut self, i: usize) -> bool {
self.as_mut().show_data(i)
}
fn setup(&mut self, cfg: ViewConfig<'_>) {
self.as_mut().setup(cfg)
}
}

View File

@ -36,14 +36,12 @@ pub struct RecordView {
}
impl RecordView {
pub fn new(columns: Vec<String>, records: Vec<Vec<Value>>) -> Self {
pub fn new(columns: Vec<String>, records: Vec<Vec<Value>>, cfg: ExploreConfig) -> Self {
Self {
layer_stack: vec![RecordLayer::new(columns, records)],
mode: UIMode::View,
orientation: Orientation::Top,
// TODO: It's kind of gross how this temporarily has an incorrect/default config.
// See if we can pass correct config in through the constructor
cfg: ExploreConfig::default(),
cfg,
}
}
@ -72,23 +70,6 @@ impl RecordView {
.expect("we guarantee that 1 entry is always in a list")
}
pub fn get_top_layer_orientation(&mut self) -> Orientation {
self.get_top_layer().orientation
}
pub fn set_orientation(&mut self, orientation: Orientation) {
self.orientation = orientation;
// we need to reset all indexes as we can't no more use them.
self.reset_cursors();
}
fn reset_cursors(&mut self) {
for layer in &mut self.layer_stack {
layer.reset_cursor();
}
}
pub fn set_top_layer_orientation(&mut self, orientation: Orientation) {
let layer = self.get_top_layer_mut();
layer.orientation = orientation;
@ -299,11 +280,6 @@ impl View for RecordView {
fn exit(&mut self) -> Option<Value> {
Some(build_last_value(self))
}
// todo: move the method to Command?
fn setup(&mut self, cfg: ViewConfig<'_>) {
self.cfg = cfg.explore_config.clone();
}
}
fn get_element_info(

View File

@ -1,5 +1,6 @@
use super::{record::RecordView, util::nu_style_to_tui, Layout, Orientation, View, ViewConfig};
use crate::{
explore::ExploreConfig,
nu_common::{collect_pipeline, run_command_with_value},
pager::{report::Report, Frame, Transition, ViewInfo},
};
@ -23,17 +24,19 @@ pub struct TryView {
table: Option<RecordView>,
view_mode: bool,
border_color: Style,
config: ExploreConfig,
}
impl TryView {
pub fn new(input: Value) -> Self {
pub fn new(input: Value, config: ExploreConfig) -> Self {
Self {
input,
table: None,
immediate: false,
border_color: Style::default(),
immediate: config.try_reactive,
border_color: nu_style_to_tui(config.table.separator_style),
view_mode: false,
command: String::new(),
config,
}
}
@ -42,7 +45,13 @@ impl TryView {
}
pub fn try_run(&mut self, engine_state: &EngineState, stack: &mut Stack) -> Result<()> {
let view = run_command(&self.command, &self.input, engine_state, stack)?;
let view = run_command(
&self.command,
&self.input,
engine_state,
stack,
&self.config,
)?;
self.table = Some(view);
Ok(())
}
@ -122,7 +131,6 @@ impl View for TryView {
f.render_widget(table_block, table_area);
if let Some(table) = &mut self.table {
table.setup(cfg);
let area = Rect::new(
area.x + 2,
area.y + 4,
@ -232,20 +240,6 @@ impl View for TryView {
fn show_data(&mut self, i: usize) -> bool {
self.table.as_mut().map_or(false, |v| v.show_data(i))
}
fn setup(&mut self, config: ViewConfig<'_>) {
self.border_color = nu_style_to_tui(config.explore_config.table.separator_style);
self.immediate = config.explore_config.try_reactive;
let mut r = RecordView::new(vec![], vec![]);
r.setup(config);
if let Some(view) = &mut self.table {
view.setup(config);
view.set_orientation(r.get_top_layer_orientation());
view.set_top_layer_orientation(r.get_top_layer_orientation());
}
}
}
fn run_command(
@ -253,6 +247,7 @@ fn run_command(
input: &Value,
engine_state: &EngineState,
stack: &mut Stack,
config: &ExploreConfig,
) -> Result<RecordView> {
let pipeline = run_command_with_value(command, input, engine_state, stack)?;
@ -260,7 +255,7 @@ fn run_command(
let (columns, values) = collect_pipeline(pipeline)?;
let mut view = RecordView::new(columns, values);
let mut view = RecordView::new(columns, values, config.clone());
if is_record {
view.set_top_layer_orientation(Orientation::Left);
}

View File

@ -4950,7 +4950,7 @@ pub fn parse_math_expression(
let mut expr_stack: Vec<Expression> = vec![];
let mut idx = 0;
let mut last_prec = 1000000;
let mut last_prec = u8::MAX;
let first_span = working_set.get_span_contents(spans[0]);

View File

@ -27,42 +27,9 @@ impl Expression {
}
}
pub fn precedence(&self) -> usize {
pub fn precedence(&self) -> u8 {
match &self.expr {
Expr::Operator(operator) => {
use super::operator::*;
// Higher precedence binds tighter
match operator {
Operator::Math(Math::Pow) => 100,
Operator::Math(Math::Multiply)
| Operator::Math(Math::Divide)
| Operator::Math(Math::Modulo)
| Operator::Math(Math::FloorDivision) => 95,
Operator::Math(Math::Plus) | Operator::Math(Math::Minus) => 90,
Operator::Bits(Bits::ShiftLeft) | Operator::Bits(Bits::ShiftRight) => 85,
Operator::Comparison(Comparison::NotRegexMatch)
| Operator::Comparison(Comparison::RegexMatch)
| Operator::Comparison(Comparison::StartsWith)
| Operator::Comparison(Comparison::EndsWith)
| Operator::Comparison(Comparison::LessThan)
| Operator::Comparison(Comparison::LessThanOrEqual)
| Operator::Comparison(Comparison::GreaterThan)
| Operator::Comparison(Comparison::GreaterThanOrEqual)
| Operator::Comparison(Comparison::Equal)
| Operator::Comparison(Comparison::NotEqual)
| Operator::Comparison(Comparison::In)
| Operator::Comparison(Comparison::NotIn)
| Operator::Math(Math::Append) => 80,
Operator::Bits(Bits::BitAnd) => 75,
Operator::Bits(Bits::BitXor) => 70,
Operator::Bits(Bits::BitOr) => 60,
Operator::Boolean(Boolean::And) => 50,
Operator::Boolean(Boolean::Xor) => 45,
Operator::Boolean(Boolean::Or) => 40,
Operator::Assignment(_) => 10,
}
}
Expr::Operator(operator) => operator.precedence(),
_ => 0,
}
}

View File

@ -68,6 +68,40 @@ pub enum Operator {
Assignment(Assignment),
}
impl Operator {
pub fn precedence(&self) -> u8 {
match self {
Self::Math(Math::Pow) => 100,
Self::Math(Math::Multiply)
| Self::Math(Math::Divide)
| Self::Math(Math::Modulo)
| Self::Math(Math::FloorDivision) => 95,
Self::Math(Math::Plus) | Self::Math(Math::Minus) => 90,
Self::Bits(Bits::ShiftLeft) | Self::Bits(Bits::ShiftRight) => 85,
Self::Comparison(Comparison::NotRegexMatch)
| Self::Comparison(Comparison::RegexMatch)
| Self::Comparison(Comparison::StartsWith)
| Self::Comparison(Comparison::EndsWith)
| Self::Comparison(Comparison::LessThan)
| Self::Comparison(Comparison::LessThanOrEqual)
| Self::Comparison(Comparison::GreaterThan)
| Self::Comparison(Comparison::GreaterThanOrEqual)
| Self::Comparison(Comparison::Equal)
| Self::Comparison(Comparison::NotEqual)
| Self::Comparison(Comparison::In)
| Self::Comparison(Comparison::NotIn)
| Self::Math(Math::Append) => 80,
Self::Bits(Bits::BitAnd) => 75,
Self::Bits(Bits::BitXor) => 70,
Self::Bits(Bits::BitOr) => 60,
Self::Boolean(Boolean::And) => 50,
Self::Boolean(Boolean::Xor) => 45,
Self::Boolean(Boolean::Or) => 40,
Self::Assignment(_) => 10,
}
}
}
impl Display for Operator {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
@ -95,10 +129,10 @@ impl Display for Operator {
Operator::Math(Math::Multiply) => write!(f, "*"),
Operator::Math(Math::Divide) => write!(f, "/"),
Operator::Math(Math::Modulo) => write!(f, "mod"),
Operator::Math(Math::FloorDivision) => write!(f, "fdiv"),
Operator::Math(Math::FloorDivision) => write!(f, "//"),
Operator::Math(Math::Pow) => write!(f, "**"),
Operator::Boolean(Boolean::And) => write!(f, "&&"),
Operator::Boolean(Boolean::Or) => write!(f, "||"),
Operator::Boolean(Boolean::And) => write!(f, "and"),
Operator::Boolean(Boolean::Or) => write!(f, "or"),
Operator::Boolean(Boolean::Xor) => write!(f, "xor"),
Operator::Bits(Bits::BitOr) => write!(f, "bit-or"),
Operator::Bits(Bits::BitXor) => write!(f, "bit-xor"),

View File

@ -12,13 +12,17 @@ use tabled::{
config::{AlignmentHorizontal, ColoredConfig, Entity, EntityMap, Position},
dimension::CompleteDimensionVecRecords,
records::{
vec_records::{CellInfo, VecRecords},
vec_records::{Cell, CellInfo, VecRecords},
ExactRecords, PeekableRecords, Records, Resizable,
},
},
settings::{
formatting::AlignmentStrategy, object::Segment, peaker::Peaker, themes::ColumnNames, Color,
Modify, Padding, Settings, TableOption, Width,
formatting::AlignmentStrategy,
object::{Columns, Segment},
peaker::Peaker,
themes::ColumnNames,
width::Truncate,
Color, Modify, Padding, Settings, TableOption, Width,
},
Table,
};
@ -216,7 +220,7 @@ fn build_table(
}
let pad = indent.0 + indent.1;
let widths = maybe_truncate_columns(&mut data, &cfg.theme, termwidth, pad);
let widths = maybe_truncate_columns(&mut data, &cfg, termwidth, pad);
if widths.is_empty() {
return None;
}
@ -269,20 +273,51 @@ fn set_indent(table: &mut Table, left: usize, right: usize) {
fn set_border_head(table: &mut Table, with_footer: bool, wctrl: TableWidthCtrl) {
if with_footer {
let count_rows = table.count_rows();
let last_row_index = count_rows - 1;
// note: funnily last and row must be equal at this point but we do not rely on it just in case.
let mut first_row = GetRow(0, Vec::new());
let mut head_settings = GetRowSettings(0, AlignmentHorizontal::Left, None);
let mut last_row = GetRow(last_row_index, Vec::new());
table.with(&mut first_row);
table.with(&mut head_settings);
table.with(&mut last_row);
table.with(
Settings::default()
.with(wctrl)
.with(StripColorFromRow(0))
.with(StripColorFromRow(count_rows - 1))
.with(HeaderMove((0, 0), true))
.with(HeaderMove((count_rows - 1 - 1, count_rows - 1), false)),
.with(MoveRowNext::new(0, 0))
.with(MoveRowPrev::new(last_row_index - 1, last_row_index))
.with(SetLineHeaders::new(
0,
first_row.1,
head_settings.1,
head_settings.2.clone(),
))
.with(SetLineHeaders::new(
last_row_index - 1,
last_row.1,
head_settings.1,
head_settings.2,
)),
);
} else {
let mut row = GetRow(0, Vec::new());
let mut row_opts = GetRowSettings(0, AlignmentHorizontal::Left, None);
table.with(&mut row);
table.with(&mut row_opts);
table.with(
Settings::default()
.with(wctrl)
.with(StripColorFromRow(0))
.with(HeaderMove((0, 0), true)),
.with(MoveRowNext::new(0, 0))
.with(SetLineHeaders::new(0, row.1, row_opts.1, row_opts.2)),
);
}
}
@ -324,11 +359,14 @@ impl TableOption<NuRecords, CompleteDimensionVecRecords<'_>, ColoredConfig> for
max: self.max,
strategy: self.cfg.trim,
width: self.width,
do_as_head: self.cfg.header_on_border,
}
.change(rec, cfg, dim);
} else if self.cfg.expand && self.max > total_width {
Settings::new(SetDimensions(self.width), Width::increase(self.max))
.change(rec, cfg, dim)
} else {
SetDimensions(self.width).change(rec, cfg, dim);
}
}
}
@ -337,6 +375,7 @@ struct TableTrim {
width: Vec<usize>,
strategy: TrimStrategy,
max: usize,
do_as_head: bool,
}
impl TableOption<NuRecords, CompleteDimensionVecRecords<'_>, ColoredConfig> for TableTrim {
@ -346,6 +385,44 @@ impl TableOption<NuRecords, CompleteDimensionVecRecords<'_>, ColoredConfig> for
cfg: &mut ColoredConfig,
dims: &mut CompleteDimensionVecRecords<'_>,
) {
// we already must have been estimated that it's safe to do.
// and all dims will be suffitient
if self.do_as_head {
if recs.is_empty() {
return;
}
let headers = recs[0].to_owned();
for (i, head) in headers.into_iter().enumerate() {
let head_width = CellInfo::width(&head);
match &self.strategy {
TrimStrategy::Wrap { try_to_keep_words } => {
let mut wrap = Width::wrap(head_width);
if *try_to_keep_words {
wrap = wrap.keep_words();
}
Modify::new(Columns::single(i))
.with(wrap)
.change(recs, cfg, dims);
}
TrimStrategy::Truncate { suffix } => {
let mut truncate = Width::truncate(self.max);
if let Some(suffix) = suffix {
truncate = truncate.suffix(suffix).suffix_try_color(true);
}
Modify::new(Columns::single(i))
.with(truncate)
.change(recs, cfg, dims);
}
}
}
return;
}
match self.strategy {
TrimStrategy::Wrap { try_to_keep_words } => {
let mut wrap = Width::wrap(self.max).priority::<PriorityMax>();
@ -460,19 +537,22 @@ fn load_theme(
fn maybe_truncate_columns(
data: &mut NuRecords,
theme: &TableTheme,
cfg: &NuTableConfig,
termwidth: usize,
pad: usize,
) -> Vec<usize> {
const TERMWIDTH_THRESHOLD: usize = 120;
let truncate = if termwidth > TERMWIDTH_THRESHOLD {
let preserve_content = termwidth > TERMWIDTH_THRESHOLD;
let truncate = if cfg.header_on_border {
truncate_columns_by_head
} else if preserve_content {
truncate_columns_by_columns
} else {
truncate_columns_by_content
};
truncate(data, theme, pad, termwidth)
truncate(data, &cfg.theme, pad, termwidth)
}
// VERSION where we are showing AS LITTLE COLUMNS AS POSSIBLE but WITH AS MUCH CONTENT AS POSSIBLE.
@ -627,6 +707,83 @@ fn truncate_columns_by_columns(
widths
}
// VERSION where we are showing AS LITTLE COLUMNS AS POSSIBLE but WITH AS MUCH CONTENT AS POSSIBLE.
fn truncate_columns_by_head(
data: &mut NuRecords,
theme: &TableTheme,
pad: usize,
termwidth: usize,
) -> Vec<usize> {
const TRAILING_COLUMN_WIDTH: usize = 5;
let config = get_config(theme, false, None);
let mut widths = build_width(&*data, pad);
let total_width = get_total_width2(&widths, &config);
if total_width <= termwidth {
return widths;
}
if data.is_empty() {
return widths;
}
let head = &data[0];
let borders = config.get_borders();
let has_vertical = borders.has_vertical();
let mut width = borders.has_left() as usize + borders.has_right() as usize;
let mut truncate_pos = 0;
for (i, column_header) in head.iter().enumerate() {
let column_header_width = Cell::width(column_header);
width += column_header_width;
if i > 0 {
width += has_vertical as usize;
}
if width >= termwidth {
width -= column_header_width + (i > 0 && has_vertical) as usize;
break;
}
truncate_pos += 1;
}
// we don't need any truncation then (is it possible?)
if truncate_pos == head.len() {
return widths;
}
if truncate_pos == 0 {
return vec![];
}
truncate_columns(data, truncate_pos);
widths.truncate(truncate_pos);
// Append columns with a trailing column
let min_width = width;
let diff = termwidth - min_width;
let can_trailing_column_be_pushed = diff > TRAILING_COLUMN_WIDTH + has_vertical as usize;
if !can_trailing_column_be_pushed {
if data.count_columns() == 1 {
return vec![];
}
truncate_columns(data, data.count_columns() - 1);
widths.pop();
}
push_empty_column(data);
widths.push(3 + pad);
widths
}
/// The same as [`tabled::peaker::PriorityMax`] but prioritizes left columns first in case of equal width.
#[derive(Debug, Default, Clone)]
pub struct PriorityMax;
@ -714,9 +871,8 @@ impl<R> TableOption<R, CompleteDimensionVecRecords<'_>, ColoredConfig> for SetDi
}
// it assumes no spans is used.
// todo: Could be replaced by Dimension impl usage
fn build_width(records: &NuRecords, pad: usize) -> Vec<usize> {
use tabled::grid::records::vec_records::Cell;
let count_columns = records.count_columns();
let mut widths = vec![0; count_columns];
for columns in records.iter_rows() {
@ -729,50 +885,156 @@ fn build_width(records: &NuRecords, pad: usize) -> Vec<usize> {
widths
}
struct HeaderMove((usize, usize), bool);
struct GetRow(usize, Vec<String>);
impl TableOption<NuRecords, CompleteDimensionVecRecords<'_>, ColoredConfig> for HeaderMove {
impl TableOption<NuRecords, CompleteDimensionVecRecords<'_>, ColoredConfig> for &mut GetRow {
fn change(
self,
recs: &mut NuRecords,
_: &mut ColoredConfig,
_: &mut CompleteDimensionVecRecords<'_>,
) {
let row = self.0;
self.1 = recs[row].iter().map(|c| c.as_ref().to_owned()).collect();
}
}
struct GetRowSettings(usize, AlignmentHorizontal, Option<Color>);
impl TableOption<NuRecords, CompleteDimensionVecRecords<'_>, ColoredConfig>
for &mut GetRowSettings
{
fn change(
self,
_: &mut NuRecords,
cfg: &mut ColoredConfig,
_: &mut CompleteDimensionVecRecords<'_>,
) {
let row = self.0;
self.1 = *cfg.get_alignment_horizontal(Entity::Row(row));
self.2 = cfg
.get_colors()
.get_color((row, 0))
.cloned()
.map(Color::from);
}
}
struct SetLineHeaders {
line: usize,
columns: Vec<String>,
alignment: AlignmentHorizontal,
color: Option<Color>,
}
impl SetLineHeaders {
fn new(
line: usize,
columns: Vec<String>,
alignment: AlignmentHorizontal,
color: Option<Color>,
) -> Self {
Self {
line,
columns,
alignment,
color,
}
}
}
impl TableOption<NuRecords, CompleteDimensionVecRecords<'_>, ColoredConfig> for SetLineHeaders {
fn change(
self,
recs: &mut NuRecords,
cfg: &mut ColoredConfig,
dims: &mut CompleteDimensionVecRecords<'_>,
) {
let (row, line) = self.0;
if self.1 {
move_header_on_next(recs, cfg, dims, row, line);
} else {
move_header_on_prev(recs, cfg, dims, row, line);
}
let mut columns = self.columns;
match dims.get_widths() {
Some(widths) => {
columns = columns
.into_iter()
.zip(widths.iter().map(|w| w.checked_sub(2).unwrap_or(*w))) // exclude padding; which is generally 2
.map(|(s, width)| Truncate::truncate_text(&s, width).into_owned())
.collect();
}
None => {
// we don't have widths cached; which means that NO widtds adjustmens were done
// which means we are OK to leave columns as they are.
//
// but we are actually always got to have widths at this point
}
};
set_column_names(
recs,
cfg,
dims,
columns,
self.line,
self.alignment,
self.color,
)
}
}
fn move_header_on_next(
recs: &mut NuRecords,
cfg: &mut ColoredConfig,
dims: &mut CompleteDimensionVecRecords<'_>,
struct MoveRowNext {
row: usize,
line: usize,
) {
}
impl MoveRowNext {
fn new(row: usize, line: usize) -> Self {
Self { row, line }
}
}
struct MoveRowPrev {
row: usize,
line: usize,
}
impl MoveRowPrev {
fn new(row: usize, line: usize) -> Self {
Self { row, line }
}
}
impl TableOption<NuRecords, CompleteDimensionVecRecords<'_>, ColoredConfig> for MoveRowNext {
fn change(
self,
recs: &mut NuRecords,
cfg: &mut ColoredConfig,
_: &mut CompleteDimensionVecRecords<'_>,
) {
row_shift_next(recs, cfg, self.row, self.line);
}
}
impl TableOption<NuRecords, CompleteDimensionVecRecords<'_>, ColoredConfig> for MoveRowPrev {
fn change(
self,
recs: &mut NuRecords,
cfg: &mut ColoredConfig,
_: &mut CompleteDimensionVecRecords<'_>,
) {
row_shift_prev(recs, cfg, self.row, self.line);
}
}
fn row_shift_next(recs: &mut NuRecords, cfg: &mut ColoredConfig, row: usize, line: usize) {
let count_rows = recs.count_rows();
let count_columns = recs.count_columns();
let has_line = cfg.has_horizontal(line, count_rows);
let has_next_line = cfg.has_horizontal(line + 1, count_rows);
let align = *cfg.get_alignment_horizontal(Entity::Row(row));
let color = cfg
.get_colors()
.get_color((row, 0))
.cloned()
.map(Color::from);
if !has_line && !has_next_line {
return;
}
if !has_line {
let head = remove_row(recs, row);
let _ = remove_row(recs, row);
let count_rows = recs.count_rows();
set_column_names(recs, cfg, dims, head, line, align, color);
shift_alignments_down(cfg, row, count_rows, count_columns);
shift_colors_down(cfg, row, count_rows, count_columns);
shift_lines_up(cfg, count_rows, &[line + 1]);
@ -780,47 +1042,31 @@ fn move_header_on_next(
return;
}
let head = remove_row(recs, row);
let _ = remove_row(recs, row);
let count_rows = recs.count_rows();
set_column_names(recs, cfg, dims, head, line, align, color);
shift_alignments_down(cfg, row, count_rows, count_columns);
shift_colors_down(cfg, row, count_rows, count_columns);
remove_lines(cfg, count_rows, &[line + 1]);
shift_lines_up(cfg, count_rows, &[count_rows]);
}
fn move_header_on_prev(
recs: &mut NuRecords,
cfg: &mut ColoredConfig,
dims: &mut CompleteDimensionVecRecords<'_>,
row: usize,
line: usize,
) {
fn row_shift_prev(recs: &mut NuRecords, cfg: &mut ColoredConfig, row: usize, line: usize) {
let count_rows = recs.count_rows();
let count_columns = recs.count_columns();
let has_line = cfg.has_horizontal(line, count_rows);
let has_prev_line = cfg.has_horizontal(line - 1, count_rows);
let align = *cfg.get_alignment_horizontal(Entity::Row(row));
let color = cfg
.get_colors()
.get_color((row, 0))
.cloned()
.map(Color::from);
if !has_line && !has_prev_line {
return;
}
if !has_line {
let head = remove_row(recs, row);
let _ = remove_row(recs, row);
// shift_lines_down(table, &[line - 1]);
set_column_names(recs, cfg, dims, head, line - 1, align, color);
return;
}
let head = remove_row(recs, row);
let _ = remove_row(recs, row);
let count_rows = count_rows - 1;
set_column_names(recs, cfg, dims, head, line - 1, align, color);
shift_alignments_down(cfg, row, count_rows, count_columns);
shift_colors_down(cfg, row, count_rows, count_columns);
remove_lines(cfg, count_rows, &[line - 1]);