Merge branch 'main' into path_insensitive

This commit is contained in:
Darren Schroeder 2024-11-20 13:36:13 -06:00
commit 31d3e37e3d
19 changed files with 536 additions and 184 deletions

16
Cargo.lock generated
View File

@ -3333,7 +3333,7 @@ dependencies = [
"sysinfo 0.32.0",
"tabled",
"tempfile",
"terminal_size 0.3.0",
"terminal_size 0.4.0",
"titlecase",
"toml 0.8.19",
"trash",
@ -3378,7 +3378,7 @@ dependencies = [
"nu-path",
"nu-protocol",
"nu-utils",
"terminal_size 0.3.0",
"terminal_size 0.4.0",
]
[[package]]
@ -3402,7 +3402,7 @@ dependencies = [
"nu-utils",
"ratatui",
"strip-ansi-escapes",
"terminal_size 0.3.0",
"terminal_size 0.4.0",
"unicode-width 0.1.11",
]
@ -3485,7 +3485,7 @@ dependencies = [
"nu-protocol",
"nu-utils",
"serde",
"thiserror 1.0.69",
"thiserror 2.0.3",
"typetag",
]
@ -3591,7 +3591,7 @@ dependencies = [
"strum",
"strum_macros",
"tempfile",
"thiserror 1.0.69",
"thiserror 2.0.3",
"typetag",
"windows-sys 0.48.0",
]
@ -3635,7 +3635,7 @@ dependencies = [
"nu-protocol",
"nu-utils",
"tabled",
"terminal_size 0.3.0",
"terminal_size 0.4.0",
]
[[package]]
@ -5893,9 +5893,9 @@ dependencies = [
[[package]]
name = "shadow-rs"
version = "0.35.2"
version = "0.36.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1b2328fb3ec0d5302f95915e7e77cfc2ff943714d9970bc4b66e9eacf318687"
checksum = "58cfcd0643497a9f780502063aecbcc4a3212cbe4948fd25ee8fd179c2cf9a18"
dependencies = [
"const_format",
"is_debug",

View File

@ -156,7 +156,7 @@ syn = "2.0"
sysinfo = "0.32"
tabled = { version = "0.16.0", default-features = false }
tempfile = "3.14"
terminal_size = "0.3"
terminal_size = "0.4"
titlecase = "2.0"
toml = "0.8"
trash = "5.2"

View File

@ -74,7 +74,7 @@ pub fn evaluate_commands(
if let Some(err) = working_set.compile_errors.first() {
report_compile_error(&working_set, err);
// Not a fatal error, for now
std::process::exit(1);
}
(output, working_set.render())

View File

@ -89,7 +89,7 @@ pub fn evaluate_file(
if let Some(err) = working_set.compile_errors.first() {
report_compile_error(&working_set, err);
// Not a fatal error, for now
std::process::exit(1);
}
// Look for blocks whose name starts with "main" and replace it with the filename.

View File

@ -296,7 +296,7 @@ fn evaluate_source(
if let Some(err) = working_set.compile_errors.first() {
report_compile_error(&working_set, err);
// Not a fatal error, for now
return Ok(true);
}
(output, working_set.render())

View File

@ -21,10 +21,10 @@ nu-protocol = { path = "../nu-protocol", version = "0.100.1" }
nu-utils = { path = "../nu-utils", version = "0.100.1" }
itertools = { workspace = true }
shadow-rs = { version = "0.35", default-features = false }
shadow-rs = { version = "0.36", default-features = false }
[build-dependencies]
shadow-rs = { version = "0.35", default-features = false }
shadow-rs = { version = "0.36", default-features = false }
[features]
mimalloc = []

View File

@ -1,6 +1,7 @@
use nu_engine::command_prelude::*;
use nu_parser::parse;
use nu_protocol::engine::StateWorkingSet;
use nu_parser::{flatten_block, parse};
use nu_protocol::{engine::StateWorkingSet, record};
use serde_json::{json, Value as JsonValue};
#[derive(Clone)]
pub struct Ast;
@ -16,109 +17,23 @@ impl Command for Ast {
fn signature(&self) -> Signature {
Signature::build("ast")
.input_output_types(vec![(Type::String, Type::record())])
.input_output_types(vec![
(Type::Nothing, Type::table()),
(Type::Nothing, Type::record()),
(Type::Nothing, Type::String),
])
.required(
"pipeline",
SyntaxShape::String,
"The pipeline to print the ast for.",
)
.switch("json", "serialize to json", Some('j'))
.switch("minify", "minify the nuon or json output", Some('m'))
.switch("json", "Serialize to json", Some('j'))
.switch("minify", "Minify the nuon or json output", Some('m'))
.switch("flatten", "An easier to read version of the ast", Some('f'))
.allow_variants_without_examples(true)
.category(Category::Debug)
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let pipeline: Spanned<String> = call.req(engine_state, stack, 0)?;
let to_json = call.has_flag(engine_state, stack, "json")?;
let minify = call.has_flag(engine_state, stack, "minify")?;
let mut working_set = StateWorkingSet::new(engine_state);
let block_output = parse(&mut working_set, None, pipeline.item.as_bytes(), false);
let error_output = working_set.parse_errors.first();
let block_span = match &block_output.span {
Some(span) => span,
None => &pipeline.span,
};
if to_json {
// Get the block as json
let serde_block_str = if minify {
serde_json::to_string(&*block_output)
} else {
serde_json::to_string_pretty(&*block_output)
};
let block_json = match serde_block_str {
Ok(json) => json,
Err(e) => Err(ShellError::CantConvert {
to_type: "string".to_string(),
from_type: "block".to_string(),
span: *block_span,
help: Some(format!(
"Error: {e}\nCan't convert {block_output:?} to string"
)),
})?,
};
// Get the error as json
let serde_error_str = if minify {
serde_json::to_string(&error_output)
} else {
serde_json::to_string_pretty(&error_output)
};
let error_json = match serde_error_str {
Ok(json) => json,
Err(e) => Err(ShellError::CantConvert {
to_type: "string".to_string(),
from_type: "error".to_string(),
span: *block_span,
help: Some(format!(
"Error: {e}\nCan't convert {error_output:?} to string"
)),
})?,
};
// Create a new output record, merging the block and error
let output_record = Value::record(
record! {
"block" => Value::string(block_json, *block_span),
"error" => Value::string(error_json, Span::test_data()),
},
pipeline.span,
);
Ok(output_record.into_pipeline_data())
} else {
let block_value = Value::string(
if minify {
format!("{block_output:?}")
} else {
format!("{block_output:#?}")
},
pipeline.span,
);
let error_value = Value::string(
if minify {
format!("{error_output:?}")
} else {
format!("{error_output:#?}")
},
pipeline.span,
);
let output_record = Value::record(
record! {
"block" => block_value,
"error" => error_value
},
pipeline.span,
);
Ok(output_record.into_pipeline_data())
}
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
@ -147,8 +62,247 @@ impl Command for Ast {
example: "ast 'for x in 1..10 { echo $x ' --json --minify",
result: None,
},
Example {
description: "Print the ast of a string flattened",
example: r#"ast "'hello'" --flatten"#,
result: Some(Value::test_list(vec![Value::test_record(record! {
"content" => Value::test_string("'hello'"),
"shape" => Value::test_string("shape_string"),
"span" => Value::test_record(record! {
"start" => Value::test_int(0),
"end" => Value::test_int(7),}),
})])),
},
Example {
description: "Print the ast of a string flattened, as json, minified",
example: r#"ast "'hello'" --flatten --json --minify"#,
result: Some(Value::test_string(
r#"[{"content":"'hello'","shape":"shape_string","span":{"start":0,"end":7}}]"#,
)),
},
Example {
description: "Print the ast of a pipeline flattened",
example: r#"ast 'ls | sort-by type name -i' --flatten"#,
result: Some(Value::test_list(vec![
Value::test_record(record! {
"content" => Value::test_string("ls"),
"shape" => Value::test_string("shape_external"),
"span" => Value::test_record(record! {
"start" => Value::test_int(0),
"end" => Value::test_int(2),}),
}),
Value::test_record(record! {
"content" => Value::test_string("|"),
"shape" => Value::test_string("shape_pipe"),
"span" => Value::test_record(record! {
"start" => Value::test_int(3),
"end" => Value::test_int(4),}),
}),
Value::test_record(record! {
"content" => Value::test_string("sort-by"),
"shape" => Value::test_string("shape_internalcall"),
"span" => Value::test_record(record! {
"start" => Value::test_int(5),
"end" => Value::test_int(12),}),
}),
Value::test_record(record! {
"content" => Value::test_string("type"),
"shape" => Value::test_string("shape_string"),
"span" => Value::test_record(record! {
"start" => Value::test_int(13),
"end" => Value::test_int(17),}),
}),
Value::test_record(record! {
"content" => Value::test_string("name"),
"shape" => Value::test_string("shape_string"),
"span" => Value::test_record(record! {
"start" => Value::test_int(18),
"end" => Value::test_int(22),}),
}),
Value::test_record(record! {
"content" => Value::test_string("-i"),
"shape" => Value::test_string("shape_flag"),
"span" => Value::test_record(record! {
"start" => Value::test_int(23),
"end" => Value::test_int(25),}),
}),
])),
},
]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let pipeline: Spanned<String> = call.req(engine_state, stack, 0)?;
let to_json = call.has_flag(engine_state, stack, "json")?;
let minify = call.has_flag(engine_state, stack, "minify")?;
let flatten = call.has_flag(engine_state, stack, "flatten")?;
let mut working_set = StateWorkingSet::new(engine_state);
let offset = working_set.next_span_start();
let parsed_block = parse(&mut working_set, None, pipeline.item.as_bytes(), false);
if flatten {
let flat = flatten_block(&working_set, &parsed_block);
if to_json {
let mut json_val: JsonValue = json!([]);
for (span, shape) in flat {
let content =
String::from_utf8_lossy(working_set.get_span_contents(span)).to_string();
let json = json!(
{
"content": content,
"shape": shape.to_string(),
"span": {
"start": span.start.checked_sub(offset),
"end": span.end.checked_sub(offset),
},
}
);
json_merge(&mut json_val, &json);
}
let json_string = if minify {
if let Ok(json_str) = serde_json::to_string(&json_val) {
json_str
} else {
"{}".to_string()
}
} else if let Ok(json_str) = serde_json::to_string_pretty(&json_val) {
json_str
} else {
"{}".to_string()
};
Ok(Value::string(json_string, pipeline.span).into_pipeline_data())
} else {
// let mut rec: Record = Record::new();
let mut rec = vec![];
for (span, shape) in flat {
let content =
String::from_utf8_lossy(working_set.get_span_contents(span)).to_string();
let each_rec = record! {
"content" => Value::test_string(content),
"shape" => Value::test_string(shape.to_string()),
"span" => Value::test_record(record!{
"start" => Value::test_int(match span.start.checked_sub(offset) {
Some(start) => start as i64,
None => 0
}),
"end" => Value::test_int(match span.end.checked_sub(offset) {
Some(end) => end as i64,
None => 0
}),
}),
};
rec.push(Value::test_record(each_rec));
}
Ok(Value::list(rec, pipeline.span).into_pipeline_data())
}
} else {
let error_output = working_set.parse_errors.first();
let block_span = match &parsed_block.span {
Some(span) => span,
None => &pipeline.span,
};
if to_json {
// Get the block as json
let serde_block_str = if minify {
serde_json::to_string(&*parsed_block)
} else {
serde_json::to_string_pretty(&*parsed_block)
};
let block_json = match serde_block_str {
Ok(json) => json,
Err(e) => Err(ShellError::CantConvert {
to_type: "string".to_string(),
from_type: "block".to_string(),
span: *block_span,
help: Some(format!(
"Error: {e}\nCan't convert {parsed_block:?} to string"
)),
})?,
};
// Get the error as json
let serde_error_str = if minify {
serde_json::to_string(&error_output)
} else {
serde_json::to_string_pretty(&error_output)
};
let error_json = match serde_error_str {
Ok(json) => json,
Err(e) => Err(ShellError::CantConvert {
to_type: "string".to_string(),
from_type: "error".to_string(),
span: *block_span,
help: Some(format!(
"Error: {e}\nCan't convert {error_output:?} to string"
)),
})?,
};
// Create a new output record, merging the block and error
let output_record = Value::record(
record! {
"block" => Value::string(block_json, *block_span),
"error" => Value::string(error_json, Span::test_data()),
},
pipeline.span,
);
Ok(output_record.into_pipeline_data())
} else {
let block_value = Value::string(
if minify {
format!("{parsed_block:?}")
} else {
format!("{parsed_block:#?}")
},
pipeline.span,
);
let error_value = Value::string(
if minify {
format!("{error_output:?}")
} else {
format!("{error_output:#?}")
},
pipeline.span,
);
let output_record = Value::record(
record! {
"block" => block_value,
"error" => error_value
},
pipeline.span,
);
Ok(output_record.into_pipeline_data())
}
}
}
}
fn json_merge(a: &mut JsonValue, b: &JsonValue) {
match (a, b) {
(JsonValue::Object(ref mut a), JsonValue::Object(b)) => {
for (k, v) in b {
json_merge(a.entry(k).or_insert(JsonValue::Null), v);
}
}
(JsonValue::Array(ref mut a), JsonValue::Array(b)) => {
a.extend(b.clone());
}
(JsonValue::Array(ref mut a), JsonValue::Object(b)) => {
a.extend([JsonValue::Object(b.clone())]);
}
(a, b) => {
*a = b.clone();
}
}
}
#[cfg(test)]

View File

@ -5,6 +5,8 @@ use nu_protocol::{did_you_mean, process::ChildProcess, ByteStream, NuGlob, OutDe
use nu_system::ForegroundChild;
use nu_utils::IgnoreCaseExt;
use pathdiff::diff_paths;
#[cfg(windows)]
use std::os::windows::process::CommandExt;
use std::{
borrow::Cow,
ffi::{OsStr, OsString},
@ -91,6 +93,22 @@ impl Command for External {
false
};
// let's make sure it's a .ps1 script, but only on Windows
let potential_powershell_script = if cfg!(windows) {
if let Some(executable) = which(&expanded_name, "", cwd.as_ref()) {
let ext = executable
.extension()
.unwrap_or_default()
.to_string_lossy()
.to_uppercase();
ext == "PS1"
} else {
false
}
} else {
false
};
// Find the absolute path to the executable. On Windows, set the
// executable to "cmd.exe" if it's a CMD internal command. If the
// command is not found, display a helpful error message.
@ -98,11 +116,16 @@ impl Command for External {
&& (is_cmd_internal_command(&name_str) || potential_nuscript_in_windows)
{
PathBuf::from("cmd.exe")
} else if cfg!(windows) && potential_powershell_script {
// If we're on Windows and we're trying to run a PowerShell script, we'll use
// `powershell.exe` to run it. We shouldn't have to check for powershell.exe because
// it's automatically installed on all modern windows systems.
PathBuf::from("powershell.exe")
} else {
// Determine the PATH to be used and then use `which` to find it - though this has no
// effect if it's an absolute path already
let paths = nu_engine::env::path_str(engine_state, stack, call.head)?;
let Some(executable) = which(expanded_name, &paths, cwd.as_ref()) else {
let Some(executable) = which(&expanded_name, &paths, cwd.as_ref()) else {
return Err(command_not_found(&name_str, call.head, engine_state, stack));
};
executable
@ -123,15 +146,29 @@ impl Command for External {
let args = eval_arguments_from_call(engine_state, stack, call)?;
#[cfg(windows)]
if is_cmd_internal_command(&name_str) || potential_nuscript_in_windows {
use std::os::windows::process::CommandExt;
// The /D flag disables execution of AutoRun commands from registry.
// The /C flag followed by a command name instructs CMD to execute
// that command and quit.
command.args(["/D", "/C", &name_str]);
command.args(["/D", "/C", &expanded_name.to_string_lossy()]);
for arg in &args {
command.raw_arg(escape_cmd_argument(arg)?);
}
} else if potential_powershell_script {
use nu_path::canonicalize_with;
// canonicalize the path to the script so that tests pass
let canon_path = if let Ok(cwd) = engine_state.cwd_as_string(None) {
canonicalize_with(&expanded_name, cwd)?
} else {
// If we can't get the current working directory, just provide the expanded name
expanded_name
};
// The -Command flag followed by a script name instructs PowerShell to
// execute that script and quit.
command.args(["-Command", &canon_path.to_string_lossy()]);
for arg in &args {
command.raw_arg(arg.item.clone());
}
} else {
command.args(args.into_iter().map(|s| s.item));
}

View File

@ -44,8 +44,29 @@ fn net(span: Span) -> Value {
let networks = Networks::new_with_refreshed_list()
.iter()
.map(|(iface, data)| {
let ip_addresses = data
.ip_networks()
.iter()
.map(|ip| {
let protocol = match ip.addr {
std::net::IpAddr::V4(_) => "ipv4",
std::net::IpAddr::V6(_) => "ipv6",
};
Value::record(
record! {
"address" => Value::string(ip.addr.to_string(), span),
"protocol" => Value::string(protocol, span),
"loop" => Value::bool(ip.addr.is_loopback(), span),
"multicast" => Value::bool(ip.addr.is_multicast(), span),
},
span,
)
})
.collect();
let record = record! {
"name" => Value::string(trim_cstyle_null(iface), span),
"mac" => Value::string(data.mac_address().to_string(), span),
"ip" => Value::list(ip_addresses, span),
"sent" => Value::filesize(data.total_transmitted() as i64, span),
"recv" => Value::filesize(data.total_received() as i64, span),
};

View File

@ -1088,7 +1088,7 @@ fn create_empty_placeholder(
let data = vec![vec![cell]];
let mut table = NuTable::from(data);
table.set_data_style(TextStyle::default().dimmed());
let out = TableOutput::new(table, false, false, false);
let out = TableOutput::new(table, false, false, 1);
let style_computer = &StyleComputer::from_config(engine_state, stack);
let config = create_nu_table_config(&config, style_computer, &out, false, TableMode::default());

View File

@ -355,9 +355,9 @@ fn external_command_receives_raw_binary_data() {
#[cfg(windows)]
#[test]
fn can_run_batch_files() {
fn can_run_cmd_files() {
use nu_test_support::fs::Stub::FileWithContent;
Playground::setup("run a Windows batch file", |dirs, sandbox| {
Playground::setup("run a Windows cmd file", |dirs, sandbox| {
sandbox.with_files(&[FileWithContent(
"foo.cmd",
r#"
@ -371,12 +371,30 @@ fn can_run_batch_files() {
});
}
#[cfg(windows)]
#[test]
fn can_run_batch_files() {
use nu_test_support::fs::Stub::FileWithContent;
Playground::setup("run a Windows batch file", |dirs, sandbox| {
sandbox.with_files(&[FileWithContent(
"foo.bat",
r#"
@echo off
echo Hello World
"#,
)]);
let actual = nu!(cwd: dirs.test(), pipeline("foo.bat"));
assert!(actual.out.contains("Hello World"));
});
}
#[cfg(windows)]
#[test]
fn can_run_batch_files_without_cmd_extension() {
use nu_test_support::fs::Stub::FileWithContent;
Playground::setup(
"run a Windows batch file without specifying the extension",
"run a Windows cmd file without specifying the extension",
|dirs, sandbox| {
sandbox.with_files(&[FileWithContent(
"foo.cmd",
@ -440,3 +458,20 @@ fn redirect_combine() {
assert_eq!(actual.out, "FooBar");
});
}
#[cfg(windows)]
#[test]
fn can_run_ps1_files() {
use nu_test_support::fs::Stub::FileWithContent;
Playground::setup("run_a_windows_ps_file", |dirs, sandbox| {
sandbox.with_files(&[FileWithContent(
"foo.ps1",
r#"
Write-Host Hello World
"#,
)]);
let actual = nu!(cwd: dirs.test(), pipeline("foo.ps1"));
assert!(actual.out.contains("Hello World"));
});
}

View File

@ -2941,3 +2941,123 @@ fn table_footer_inheritance() {
assert_eq!(actual.out.match_indices("x2").count(), 1);
assert_eq!(actual.out.match_indices("x3").count(), 1);
}
#[test]
fn table_footer_inheritance_kv_rows() {
let actual = nu!(
concat!(
"$env.config.table.footer_inheritance = true;",
"$env.config.footer_mode = 7;",
"[[a b]; ['kv' {0: 0, 1: 1, 2: 2, 3: 3, 4: 4} ], ['data' 0], ['data' 0] ] | table --expand --width=80",
)
);
assert_eq!(
actual.out,
"╭───┬──────┬───────────╮\
# a b \
\
0 kv \
0 0 \
1 1 \
2 2 \
3 3 \
4 4 \
\
1 data 0 \
2 data 0 \
"
);
let actual = nu!(
concat!(
"$env.config.table.footer_inheritance = true;",
"$env.config.footer_mode = 7;",
"[[a b]; ['kv' {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5} ], ['data' 0], ['data' 0] ] | table --expand --width=80",
)
);
assert_eq!(
actual.out,
"╭───┬──────┬───────────╮\
# a b \
\
0 kv \
0 0 \
1 1 \
2 2 \
3 3 \
4 4 \
5 5 \
\
1 data 0 \
2 data 0 \
\
# a b \
"
);
}
#[test]
fn table_footer_inheritance_list_rows() {
let actual = nu!(
concat!(
"$env.config.table.footer_inheritance = true;",
"$env.config.footer_mode = 7;",
"[[a b]; ['kv' {0: [[field]; [0] [1] [2] [3] [4]]} ], ['data' 0], ['data' 0] ] | table --expand --width=80",
)
);
assert_eq!(
actual.out,
"╭───┬──────┬───────────────────────╮\
# a b \
\
0 kv \
\
0 # field \
\
0 0 \
1 1 \
2 2 \
3 3 \
4 4 \
\
\
1 data 0 \
2 data 0 \
"
);
let actual = nu!(
concat!(
"$env.config.table.footer_inheritance = true;",
"$env.config.footer_mode = 7;",
"[[a b]; ['kv' {0: [[field]; [0] [1] [2] [3] [4] [5]]} ], ['data' 0], ['data' 0] ] | table --expand --width=80",
)
);
assert_eq!(
actual.out,
"╭───┬──────┬───────────────────────╮\
# a b \
\
0 kv \
\
0 # field \
\
0 0 \
1 1 \
2 2 \
3 3 \
4 4 \
5 5 \
\
\
1 data 0 \
2 data 0 \
\
# a b \
"
);
}

View File

@ -21,7 +21,7 @@ nu-plugin-core = { path = "../nu-plugin-core", version = "0.100.1", default-feat
nu-utils = { path = "../nu-utils", version = "0.100.1" }
log = { workspace = true }
thiserror = "1.0"
thiserror = "2.0"
[dev-dependencies]
serde = { workspace = true }

View File

@ -36,7 +36,7 @@ num-format = { workspace = true }
rmp-serde = { workspace = true, optional = true }
serde = { workspace = true }
serde_json = { workspace = true }
thiserror = "1.0"
thiserror = "2.0"
typetag = "0.2"
os_pipe = { workspace = true, features = ["io_safety"] }
log = { workspace = true }

View File

@ -18,8 +18,12 @@ pub fn create_nu_table_config(
expand: bool,
mode: TableMode,
) -> NuTableConfig {
let with_footer = (config.table.footer_inheritance && out.with_footer)
|| with_footer(config, out.with_header, out.table.count_rows());
let mut count_rows = out.table.count_rows();
if config.table.footer_inheritance {
count_rows = out.count_rows;
}
let with_footer = with_footer(config, out.with_header, count_rows);
NuTableConfig {
theme: load_theme(mode),

View File

@ -615,12 +615,15 @@ fn load_theme(
if let Some(style) = sep_color {
let color = convert_style(style);
let color = ANSIBuf::from(color);
// todo: use .modify(Segment::all(), color) --> it has this optimization
table.get_config_mut().set_border_color_default(color);
}
if !with_header {
// todo: remove and use theme.remove_horizontal_lines();
table.with(RemoveHorizontalLine);
} else if with_footer {
// todo: remove and set it on theme rather then here...
table.with(CopyFirstHorizontalLineAtLast);
}
}
@ -1257,6 +1260,7 @@ fn remove_row(recs: &mut NuRecords, row: usize) -> Vec<String> {
columns
}
// todo; use Format?
struct StripColorFromRow(usize);
impl TableOption<NuRecords, ColoredConfig, CompleteDimensionVecRecords<'_>> for StripColorFromRow {

View File

@ -5,7 +5,7 @@ use crate::{
NuText, StringResult, TableResult, INDEX_COLUMN_NAME,
},
string_width,
types::{has_footer, has_index},
types::has_index,
NuTable, NuTableCell, TableOpts, TableOutput,
};
use nu_color_config::{Alignment, StyleComputer, TextStyle};
@ -63,22 +63,22 @@ struct Cfg<'a> {
struct CellOutput {
text: String,
style: TextStyle,
is_big: bool,
size: usize,
is_expanded: bool,
}
impl CellOutput {
fn new(text: String, style: TextStyle, is_big: bool, is_expanded: bool) -> Self {
fn new(text: String, style: TextStyle, size: usize, is_expanded: bool) -> Self {
Self {
text,
style,
is_big,
size,
is_expanded,
}
}
fn clean(text: String, is_big: bool, is_expanded: bool) -> Self {
Self::new(text, Default::default(), is_big, is_expanded)
fn clean(text: String, size: usize, is_expanded: bool) -> Self {
Self::new(text, Default::default(), size, is_expanded)
}
fn text(text: String) -> Self {
@ -86,7 +86,7 @@ impl CellOutput {
}
fn styled(text: NuText) -> Self {
Self::new(text.0, text.1, false, false)
Self::new(text.0, text.1, 1, false)
}
}
@ -117,7 +117,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
let with_index = has_index(&cfg.opts, &headers);
let row_offset = cfg.opts.index_offset;
let mut is_footer_used = false;
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
@ -199,9 +199,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
data[row].push(value);
data_styles.insert((row, with_index as usize), cell.style);
if cell.is_big {
is_footer_used = cell.is_big;
}
rows_count = rows_count.saturating_add(cell.size);
}
let mut table = NuTable::from(data);
@ -209,12 +207,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
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,
is_footer_used,
)));
return Ok(Some(TableOutput::new(table, false, with_index, rows_count)));
}
if !headers.is_empty() {
@ -269,6 +262,8 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
}
}
let mut column_rows = 0usize;
for (row, item) in input.iter().enumerate() {
cfg.opts.signals.check(cfg.opts.span)?;
@ -294,9 +289,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
data[row + 1].push(value);
data_styles.insert((row + 1, col + with_index as usize), cell.style);
if cell.is_big {
is_footer_used = cell.is_big;
}
column_rows = column_rows.saturating_add(cell.size);
}
let head_cell = NuTableCell::new(header);
@ -316,6 +309,8 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
available_width -= pad_space + column_width;
rendered_column += 1;
rows_count = std::cmp::max(rows_count, column_rows);
}
if truncate && rendered_column == 0 {
@ -374,9 +369,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
table.set_indent(cfg.opts.indent.0, cfg.opts.indent.1);
set_data_styles(&mut table, data_styles);
let has_footer = is_footer_used || has_footer(&cfg.opts, table.count_rows() as u64);
Ok(Some(TableOutput::new(table, true, with_index, has_footer)))
Ok(Some(TableOutput::new(table, true, with_index, rows_count)))
}
fn expanded_table_kv(record: &Record, cfg: Cfg<'_>) -> CellResult {
@ -395,7 +388,7 @@ fn expanded_table_kv(record: &Record, cfg: Cfg<'_>) -> CellResult {
let value_width = cfg.opts.width - key_width - count_borders - padding - padding;
let mut with_footer = false;
let mut count_rows = 0usize;
let mut data = Vec::with_capacity(record.len());
for (key, value) in record {
@ -420,19 +413,17 @@ fn expanded_table_kv(record: &Record, cfg: Cfg<'_>) -> CellResult {
data.push(row);
if cell.is_big {
with_footer = cell.is_big;
}
count_rows = count_rows.saturating_add(cell.size);
}
let mut table = NuTable::from(data);
table.set_index_style(get_key_style(&cfg));
table.set_indent(cfg.opts.indent.0, cfg.opts.indent.1);
let out = TableOutput::new(table, false, true, with_footer);
let out = TableOutput::new(table, false, true, count_rows);
maybe_expand_table(out, cfg.opts.width, &cfg.opts)
.map(|value| value.map(|value| CellOutput::clean(value, with_footer, false)))
.map(|value| value.map(|value| CellOutput::clean(value, count_rows, false)))
}
// the flag is used as an optimization to not do `value.lines().count()` search.
@ -441,7 +432,7 @@ fn expand_table_value(value: &Value, value_width: usize, cfg: &Cfg<'_>) -> CellR
if is_limited {
return Ok(Some(CellOutput::clean(
value_to_string_clean(value, cfg),
false,
1,
false,
)));
}
@ -457,7 +448,7 @@ fn expand_table_value(value: &Value, value_width: usize, cfg: &Cfg<'_>) -> CellR
let cfg = create_table_cfg(cfg, &out);
let value = out.table.draw(cfg, value_width);
match value {
Some(value) => Ok(Some(CellOutput::clean(value, out.with_footer, true))),
Some(value) => Ok(Some(CellOutput::clean(value, out.count_rows, true))),
None => Ok(None),
}
}
@ -484,7 +475,7 @@ fn expand_table_value(value: &Value, value_width: usize, cfg: &Cfg<'_>) -> CellR
let inner_cfg = update_config(dive_options(cfg, span), value_width);
let result = expanded_table_kv(record, inner_cfg)?;
match result {
Some(result) => Ok(Some(CellOutput::clean(result.text, result.is_big, true))),
Some(result) => Ok(Some(CellOutput::clean(result.text, result.size, true))),
None => Ok(Some(CellOutput::text(value_to_wrapped_string(
value,
cfg,
@ -575,7 +566,7 @@ fn expanded_table_entry2(item: &Value, cfg: Cfg<'_>) -> CellOutput {
let table_config = create_table_cfg(&cfg, &out);
let table = out.table.draw(table_config, usize::MAX);
match table {
Some(table) => CellOutput::clean(table, out.with_footer, false),
Some(table) => CellOutput::clean(table, out.count_rows, false),
None => CellOutput::styled(nu_value_to_string(
item,
cfg.opts.config,

View File

@ -56,8 +56,9 @@ fn kv_table(record: &Record, opts: TableOpts<'_>) -> StringResult {
let mut table = NuTable::from(data);
table.set_index_style(TextStyle::default_field());
let count_rows = table.count_rows();
let mut out = TableOutput::new(table, false, true, false);
let mut out = TableOutput::new(table, false, true, count_rows);
let left = opts.config.table.padding.left;
let right = opts.config.table.padding.right;
@ -82,7 +83,10 @@ fn table(input: &[Value], opts: &TableOpts<'_>) -> TableResult {
let with_header = !headers.is_empty();
if !with_header {
let table = to_table_with_no_header(input, with_index, row_offset, opts)?;
let table = table.map(|table| TableOutput::new(table, false, with_index, false));
let table = table.map(|table| {
let count_rows = table.count_rows();
TableOutput::new(table, false, with_index, count_rows)
});
return Ok(table);
}
@ -98,7 +102,10 @@ fn table(input: &[Value], opts: &TableOpts<'_>) -> TableResult {
.collect();
let table = to_table_with_header(input, &headers, with_index, row_offset, opts)?;
let table = table.map(|table| TableOutput::new(table, true, with_index, false));
let table = table.map(|table| {
let count_rows = table.count_rows();
TableOutput::new(table, true, with_index, count_rows)
});
Ok(table)
}

View File

@ -1,8 +1,7 @@
use terminal_size::{terminal_size, Height, Width};
use nu_color_config::StyleComputer;
use nu_protocol::{Config, Signals, Span, TableIndexMode, TableMode};
use crate::{common::INDEX_COLUMN_NAME, NuTable};
use nu_color_config::StyleComputer;
use nu_protocol::{Config, FooterMode, Signals, Span, TableIndexMode, TableMode};
mod collapse;
mod expanded;
@ -16,16 +15,16 @@ pub struct TableOutput {
pub table: NuTable,
pub with_header: bool,
pub with_index: bool,
pub with_footer: bool,
pub count_rows: usize,
}
impl TableOutput {
pub fn new(table: NuTable, with_header: bool, with_index: bool, with_footer: bool) -> Self {
pub fn new(table: NuTable, with_header: bool, with_index: bool, count_rows: usize) -> Self {
Self {
table,
with_header,
with_index,
with_footer,
count_rows,
}
}
}
@ -79,23 +78,3 @@ fn has_index(opts: &TableOpts<'_>, headers: &[String]) -> bool {
with_index && !opts.index_remove
}
fn has_footer(opts: &TableOpts<'_>, count_records: u64) -> bool {
match opts.config.footer_mode {
// Only show the footer if there are more than RowCount rows
FooterMode::RowCount(limit) => count_records > limit,
// Always show the footer
FooterMode::Always => true,
// Never show the footer
FooterMode::Never => false,
// Calculate the screen height and row count, if screen height is larger than row count, don't show footer
FooterMode::Auto => {
let (_width, height) = match terminal_size() {
Some((w, h)) => (Width(w.0).0 as u64, Height(h.0).0 as u64),
None => (Width(0).0 as u64, Height(0).0 as u64),
};
height <= count_records
}
}
}