allow find command to look in specified columns only (#8937)

# Description
This PR allows the `find` command to search in specific columns using
`--columns [col1 col2 col3]`. This is really meant to help with the
`help` command in the std.nu.

There are a few more things I want to look at so this is a draft for
now.
- [x] add example
- [x] look at regex part

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass
- `cargo run -- crates/nu-std/tests/run.nu` to run the tests for the
standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
This commit is contained in:
Darren Schroeder 2023-04-20 08:13:12 -05:00 committed by GitHub
parent c8f54476c9
commit 393f424f1c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -58,6 +58,12 @@ impl Command for Find {
"dotall regex mode: allow a dot . to match newlines \\n; equivalent to (?s)", "dotall regex mode: allow a dot . to match newlines \\n; equivalent to (?s)",
Some('s'), Some('s'),
) )
.named(
"columns",
SyntaxShape::List(Box::new(SyntaxShape::String)),
"column names to be searched (with rest parameter, not regex yet)",
Some('c'),
)
.switch("invert", "invert the match", Some('v')) .switch("invert", "invert the match", Some('v'))
.rest("rest", SyntaxShape::Any, "terms to search") .rest("rest", SyntaxShape::Any, "terms to search")
.category(Category::Filters) .category(Category::Filters)
@ -134,8 +140,33 @@ impl Command for Find {
Example { Example {
description: "Remove ANSI sequences from result", description: "Remove ANSI sequences from result",
example: "[[foo bar]; [abc 123] [def 456]] | find 123 | get bar | ansi strip", example: "[[foo bar]; [abc 123] [def 456]] | find 123 | get bar | ansi strip",
result: None, result: None, // This is None because ansi strip is not available in tests
}, },
Example {
description: "Find and highlight text in specific columns",
example: "[[col1 col2 col3]; [moe larry curly] [larry curly moe]] | find moe -c [col1 col3]",
result: Some(Value::List {
vals: vec![
Value::test_record(
vec!["col1".to_string(), "col2".to_string(), "col3".to_string()],
vec![
Value::test_string("\u{1b}[37m\u{1b}[0m\u{1b}[41;37mmoe\u{1b}[0m\u{1b}[37m\u{1b}[0m".to_string()),
Value::test_string("larry".to_string()),
Value::test_string("curly".to_string()),
]
),
Value::test_record(
vec!["col1".to_string(), "col2".to_string(), "col3".to_string()],
vec![
Value::test_string("larry".to_string()),
Value::test_string("curly".to_string()),
Value::test_string("\u{1b}[37m\u{1b}[0m\u{1b}[41;37mmoe\u{1b}[0m\u{1b}[37m\u{1b}[0m".to_string()),
]
),
],
span: Span::test_data(),
}),
}
] ]
} }
@ -179,13 +210,13 @@ fn find_with_regex(
let flags = match (insensitive, multiline, dotall) { let flags = match (insensitive, multiline, dotall) {
(false, false, false) => "", (false, false, false) => "",
(true, false, false) => "(?i)", (true, false, false) => "(?i)", // case insensitive
(false, true, false) => "(?m)", (false, true, false) => "(?m)", // multi-line mode
(false, false, true) => "(?s)", (false, false, true) => "(?s)", // allow . to match \n
(true, true, false) => "(?im)", (true, true, false) => "(?im)", // case insensitive and multi-line mode
(true, false, true) => "(?is)", (true, false, true) => "(?is)", // case insensitive and allow . to match \n
(false, true, true) => "(?ms)", (false, true, true) => "(?ms)", // multi-line mode and allow . to match \n
(true, true, true) => "(?ims)", (true, true, true) => "(?ims)", // case insensitive, multi-line mode and allow . to match \n
}; };
let regex = flags.to_string() + regex.as_str(); let regex = flags.to_string() + regex.as_str();
@ -226,7 +257,9 @@ fn find_with_regex(
) )
} }
fn highlight_terms_in_record( #[allow(clippy::too_many_arguments)]
fn highlight_terms_in_record_with_search_columns(
search_cols: &Vec<String>,
cols: &mut [String], cols: &mut [String],
vals: &mut Vec<Value>, vals: &mut Vec<Value>,
span: &mut Span, span: &mut Span,
@ -235,15 +268,24 @@ fn highlight_terms_in_record(
string_style: Style, string_style: Style,
ls_colors: &LsColors, ls_colors: &LsColors,
) -> Value { ) -> Value {
let cols_to_search = if search_cols.is_empty() {
cols.to_vec()
} else {
search_cols.to_vec()
};
let mut output = vec![]; let mut output = vec![];
for val in vals { let mut potential_output = vec![];
let mut found_a_hit = false;
for (cur_col, val) in cols.iter().zip(vals) {
let val_str = val.into_string("", config); let val_str = val.into_string("", config);
let lower_val = val.into_string("", config).to_lowercase(); let lower_val = val.into_string("", config).to_lowercase();
let mut term_added_to_output = false; let mut term_added_to_output = false;
for term in terms { for term in terms {
let term_str = term.into_string("", config); let term_str = term.into_string("", config);
let lower_term = term.into_string("", config).to_lowercase(); let lower_term = term.into_string("", config).to_lowercase();
if lower_val.contains(&lower_term) { if lower_val.contains(&lower_term) && cols_to_search.contains(cur_col) {
found_a_hit = true;
term_added_to_output = true;
if config.use_ls_colors { if config.use_ls_colors {
// Get the original LS_COLORS color // Get the original LS_COLORS color
let style = ls_colors.style_for_path(val_str.clone()); let style = ls_colors.style_for_path(val_str.clone());
@ -263,11 +305,10 @@ fn highlight_terms_in_record(
Ok(hi) => hi, Ok(hi) => hi,
Err(_) => string_style.paint(term_str.to_string()).to_string(), Err(_) => string_style.paint(term_str.to_string()).to_string(),
}; };
output.push(Value::String { potential_output.push(Value::String {
val: hi, val: hi,
span: *span, span: *span,
}); });
term_added_to_output = true;
} else { } else {
// No LS_COLORS support, so just use the original value // No LS_COLORS support, so just use the original value
let hi = match highlight_search_string(&val_str, &term_str, &string_style) { let hi = match highlight_search_string(&val_str, &term_str, &string_style) {
@ -282,9 +323,14 @@ fn highlight_terms_in_record(
} }
} }
if !term_added_to_output { if !term_added_to_output {
output.push(val.clone()); potential_output.push(val.clone());
} }
} }
if found_a_hit {
output.append(&mut potential_output);
}
Value::Record { Value::Record {
cols: cols.to_vec(), cols: cols.to_vec(),
vals: output, vals: output,
@ -315,6 +361,7 @@ fn find_with_rest_and_highlight(
} }
}) })
.collect::<Vec<Value>>(); .collect::<Vec<Value>>();
let columns_to_search: Option<Vec<String>> = call.get_flag(&engine_state, stack, "columns")?;
let style_computer = StyleComputer::from_config(&engine_state, stack); let style_computer = StyleComputer::from_config(&engine_state, stack);
// Currently, search results all use the same style. // Currently, search results all use the same style.
@ -328,12 +375,19 @@ fn find_with_rest_and_highlight(
}; };
let ls_colors = get_ls_colors(ls_colors_env_str); let ls_colors = get_ls_colors(ls_colors_env_str);
let cols_to_search = match columns_to_search {
Some(cols) => cols,
None => vec![],
};
match input { match input {
PipelineData::Empty => Ok(PipelineData::Empty), PipelineData::Empty => Ok(PipelineData::Empty),
PipelineData::Value(_, _) => input PipelineData::Value(_, _) => input
.map( .map(
move |mut x| match &mut x { move |mut x| match &mut x {
Value::Record { cols, vals, span } => highlight_terms_in_record( Value::Record { cols, vals, span } => {
highlight_terms_in_record_with_search_columns(
&cols_to_search,
cols, cols,
vals, vals,
span, span,
@ -341,7 +395,8 @@ fn find_with_rest_and_highlight(
&terms, &terms,
string_style, string_style,
&ls_colors, &ls_colors,
), )
}
_ => x, _ => x,
}, },
ctrlc.clone(), ctrlc.clone(),
@ -417,7 +472,9 @@ fn find_with_rest_and_highlight(
PipelineData::ListStream(stream, meta) => Ok(ListStream::from_stream( PipelineData::ListStream(stream, meta) => Ok(ListStream::from_stream(
stream stream
.map(move |mut x| match &mut x { .map(move |mut x| match &mut x {
Value::Record { cols, vals, span } => highlight_terms_in_record( Value::Record { cols, vals, span } => {
highlight_terms_in_record_with_search_columns(
&cols_to_search,
cols, cols,
vals, vals,
span, span,
@ -425,7 +482,8 @@ fn find_with_rest_and_highlight(
&terms, &terms,
string_style, string_style,
&ls_colors, &ls_colors,
), )
}
_ => x, _ => x,
}) })
.filter(move |value| { .filter(move |value| {