Use internal find.rs code for help --find (#15982)

# Description

Currently, `help --find` uses it's own code for looking for the keyword
in a string and highlighting it. This code duplicates a lot of the logic
found in the code of `find`.

This commit re-uses the code of `find` in `help` commands instead.

# User-Facing Changes

This should not affect the behavior of `help`.
This commit is contained in:
new-years-eve 2025-06-16 21:29:32 +02:00 committed by GitHub
parent 52604f8b00
commit 2e484156e0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 77 additions and 218 deletions

View File

@ -574,6 +574,46 @@ fn split_string_if_multiline(input: PipelineData, head_span: Span) -> PipelineDa
}
}
/// function for using find from other commands
pub fn find_internal(
input: PipelineData,
engine_state: &EngineState,
stack: &mut Stack,
search_term: &str,
columns_to_search: &[&str],
highlight: bool,
) -> Result<PipelineData, ShellError> {
let span = input.span().unwrap_or(Span::unknown());
let style_computer = StyleComputer::from_config(engine_state, stack);
let string_style = style_computer.compute("string", &Value::string("search result", span));
let highlight_style =
style_computer.compute("search_result", &Value::string("search result", span));
let regex_str = format!("(?i){}", escape(search_term));
let regex = Regex::new(regex_str.as_str()).map_err(|e| ShellError::TypeMismatch {
err_message: format!("invalid regex: {e}"),
span: Span::unknown(),
})?;
let pattern = MatchPattern {
regex,
lower_terms: vec![search_term.to_lowercase()],
highlight,
invert: false,
string_style,
highlight_style,
};
let columns_to_search = columns_to_search
.iter()
.map(|str| String::from(*str))
.collect();
find_in_pipelinedata(pattern, columns_to_search, engine_state, stack, input)
}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -70,7 +70,7 @@ pub use empty::empty;
pub use enumerate::Enumerate;
pub use every::Every;
pub use filter::Filter;
pub use find::Find;
pub use find::{Find, find_internal};
pub use first::First;
pub use flatten::Flatten;
pub use get::Get;

View File

@ -1,8 +1,5 @@
use crate::help::{help_aliases, help_commands, help_modules};
use fancy_regex::{Regex, escape};
use nu_ansi_term::Style;
use nu_engine::command_prelude::*;
use nu_utils::IgnoreCaseExt;
#[derive(Clone)]
pub struct Help;
@ -125,132 +122,3 @@ You can also learn more at https://www.nushell.sh/book/"#;
]
}
}
pub fn highlight_search_in_table(
table: Vec<Value>, // list of records
search_string: &str,
searched_cols: &[&str],
string_style: &Style,
highlight_style: &Style,
) -> Result<Vec<Value>, ShellError> {
let orig_search_string = search_string;
let search_string = search_string.to_folded_case();
let mut matches = vec![];
for mut value in table {
let Value::Record {
val: ref mut record,
..
} = value
else {
return Err(ShellError::NushellFailedSpanned {
msg: "Expected record".to_string(),
label: format!("got {}", value.get_type()),
span: value.span(),
});
};
let has_match = record.to_mut().iter_mut().try_fold(
false,
|acc: bool, (col, val)| -> Result<bool, ShellError> {
if !searched_cols.contains(&col.as_str()) {
// don't search this column
return Ok(acc);
}
let span = val.span();
if let Value::String { val: s, .. } = val {
if s.to_folded_case().contains(&search_string) {
*val = Value::string(
highlight_search_string(
s,
orig_search_string,
string_style,
highlight_style,
)?,
span,
);
return Ok(true);
}
}
// column does not contain the searched string
// ignore non-string values
Ok(acc)
},
)?;
if has_match {
matches.push(value);
}
}
Ok(matches)
}
// Highlight the search string using ANSI escape sequences and regular expressions.
pub fn highlight_search_string(
haystack: &str,
needle: &str,
string_style: &Style,
highlight_style: &Style,
) -> Result<String, ShellError> {
let escaped_needle = escape(needle);
let regex_string = format!("(?i){escaped_needle}");
let regex = match Regex::new(&regex_string) {
Ok(regex) => regex,
Err(err) => {
return Err(ShellError::GenericError {
error: "Could not compile regex".into(),
msg: err.to_string(),
span: Some(Span::test_data()),
help: None,
inner: vec![],
});
}
};
// strip haystack to remove existing ansi style
let stripped_haystack = nu_utils::strip_ansi_string_unlikely(haystack.to_string());
let mut last_match_end = 0;
let mut highlighted = String::new();
for cap in regex.captures_iter(stripped_haystack.as_ref()) {
match cap {
Ok(capture) => {
let start = match capture.get(0) {
Some(acap) => acap.start(),
None => 0,
};
let end = match capture.get(0) {
Some(acap) => acap.end(),
None => 0,
};
highlighted.push_str(
&string_style
.paint(&stripped_haystack[last_match_end..start])
.to_string(),
);
highlighted.push_str(
&highlight_style
.paint(&stripped_haystack[start..end])
.to_string(),
);
last_match_end = end;
}
Err(e) => {
return Err(ShellError::GenericError {
error: "Error with regular expression capture".into(),
msg: e.to_string(),
span: None,
help: None,
inner: vec![],
});
}
}
}
highlighted.push_str(
&string_style
.paint(&stripped_haystack[last_match_end..])
.to_string(),
);
Ok(highlighted)
}

View File

@ -1,5 +1,4 @@
use crate::help::highlight_search_in_table;
use nu_color_config::StyleComputer;
use crate::filters::find_internal;
use nu_engine::{command_prelude::*, scope::ScopeData};
#[derive(Clone)]
@ -72,31 +71,20 @@ pub fn help_aliases(
let find: Option<Spanned<String>> = call.get_flag(engine_state, stack, "find")?;
let rest: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
// 🚩The following two-lines are copied from filters/find.rs:
let style_computer = StyleComputer::from_config(engine_state, stack);
// Currently, search results all use the same style.
// Also note that this sample string is passed into user-written code (the closure that may or may not be
// defined for "string").
let string_style = style_computer.compute("string", &Value::string("search result", head));
let highlight_style =
style_computer.compute("search_result", &Value::string("search result", head));
if let Some(f) = find {
let all_cmds_vec = build_help_aliases(engine_state, stack, head);
let found_cmds_vec = highlight_search_in_table(
return find_internal(
all_cmds_vec,
engine_state,
stack,
&f.item,
&["name", "description"],
&string_style,
&highlight_style,
)?;
return Ok(Value::list(found_cmds_vec, head).into_pipeline_data());
true,
);
}
if rest.is_empty() {
let found_cmds_vec = build_help_aliases(engine_state, stack, head);
Ok(Value::list(found_cmds_vec, head).into_pipeline_data())
Ok(build_help_aliases(engine_state, stack, head))
} else {
let mut name = String::new();
@ -152,11 +140,11 @@ pub fn help_aliases(
}
}
fn build_help_aliases(engine_state: &EngineState, stack: &Stack, span: Span) -> Vec<Value> {
fn build_help_aliases(engine_state: &EngineState, stack: &Stack, span: Span) -> PipelineData {
let mut scope_data = ScopeData::new(engine_state, stack);
scope_data.populate_decls();
scope_data.collect_aliases(span)
Value::list(scope_data.collect_aliases(span), span).into_pipeline_data()
}
#[cfg(test)]

View File

@ -1,5 +1,4 @@
use crate::help::highlight_search_in_table;
use nu_color_config::StyleComputer;
use crate::filters::find_internal;
use nu_engine::{command_prelude::*, get_full_help};
#[derive(Clone)]
@ -52,31 +51,20 @@ pub fn help_commands(
let find: Option<Spanned<String>> = call.get_flag(engine_state, stack, "find")?;
let rest: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
// 🚩The following two-lines are copied from filters/find.rs:
let style_computer = StyleComputer::from_config(engine_state, stack);
// Currently, search results all use the same style.
// Also note that this sample string is passed into user-written code (the closure that may or may not be
// defined for "string").
let string_style = style_computer.compute("string", &Value::string("search result", head));
let highlight_style =
style_computer.compute("search_result", &Value::string("search result", head));
if let Some(f) = find {
let all_cmds_vec = build_help_commands(engine_state, head);
let found_cmds_vec = highlight_search_in_table(
return find_internal(
all_cmds_vec,
engine_state,
stack,
&f.item,
&["name", "description", "search_terms"],
&string_style,
&highlight_style,
)?;
return Ok(Value::list(found_cmds_vec, head).into_pipeline_data());
true,
);
}
if rest.is_empty() {
let found_cmds_vec = build_help_commands(engine_state, head);
Ok(Value::list(found_cmds_vec, head).into_pipeline_data())
Ok(build_help_commands(engine_state, head))
} else {
let mut name = String::new();
@ -99,7 +87,7 @@ pub fn help_commands(
}
}
fn build_help_commands(engine_state: &EngineState, span: Span) -> Vec<Value> {
fn build_help_commands(engine_state: &EngineState, span: Span) -> PipelineData {
let commands = engine_state.get_decls_sorted(false);
let mut found_cmds_vec = Vec::new();
@ -215,7 +203,7 @@ fn build_help_commands(engine_state: &EngineState, span: Span) -> Vec<Value> {
found_cmds_vec.push(Value::record(record, span));
}
found_cmds_vec
Value::list(found_cmds_vec, span).into_pipeline_data()
}
#[cfg(test)]

View File

@ -1,5 +1,4 @@
use crate::help::highlight_search_in_table;
use nu_color_config::StyleComputer;
use crate::filters::find_internal;
use nu_engine::{command_prelude::*, get_full_help, scope::ScopeData};
#[derive(Clone)]
@ -72,31 +71,20 @@ pub fn help_externs(
let find: Option<Spanned<String>> = call.get_flag(engine_state, stack, "find")?;
let rest: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
// 🚩The following two-lines are copied from filters/find.rs:
let style_computer = StyleComputer::from_config(engine_state, stack);
// Currently, search results all use the same style.
// Also note that this sample string is passed into user-written code (the closure that may or may not be
// defined for "string").
let string_style = style_computer.compute("string", &Value::string("search result", head));
let highlight_style =
style_computer.compute("search_result", &Value::string("search result", head));
if let Some(f) = find {
let all_cmds_vec = build_help_externs(engine_state, stack, head);
let found_cmds_vec = highlight_search_in_table(
return find_internal(
all_cmds_vec,
engine_state,
stack,
&f.item,
&["name", "description"],
&string_style,
&highlight_style,
)?;
return Ok(Value::list(found_cmds_vec, head).into_pipeline_data());
true,
);
}
if rest.is_empty() {
let found_cmds_vec = build_help_externs(engine_state, stack, head);
Ok(Value::list(found_cmds_vec, head).into_pipeline_data())
Ok(build_help_externs(engine_state, stack, head))
} else {
let mut name = String::new();
@ -119,10 +107,10 @@ pub fn help_externs(
}
}
fn build_help_externs(engine_state: &EngineState, stack: &Stack, span: Span) -> Vec<Value> {
fn build_help_externs(engine_state: &EngineState, stack: &Stack, span: Span) -> PipelineData {
let mut scope = ScopeData::new(engine_state, stack);
scope.populate_decls();
scope.collect_externs(span)
Value::list(scope.collect_externs(span), span).into_pipeline_data()
}
#[cfg(test)]

View File

@ -1,5 +1,4 @@
use crate::help::highlight_search_in_table;
use nu_color_config::StyleComputer;
use crate::filters::find_internal;
use nu_engine::{command_prelude::*, scope::ScopeData};
use nu_protocol::DeclId;
@ -79,31 +78,20 @@ pub fn help_modules(
let find: Option<Spanned<String>> = call.get_flag(engine_state, stack, "find")?;
let rest: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
// 🚩The following two-lines are copied from filters/find.rs:
let style_computer = StyleComputer::from_config(engine_state, stack);
// Currently, search results all use the same style.
// Also note that this sample string is passed into user-written code (the closure that may or may not be
// defined for "string").
let string_style = style_computer.compute("string", &Value::string("search result", head));
let highlight_style =
style_computer.compute("search_result", &Value::string("search result", head));
if let Some(f) = find {
let all_cmds_vec = build_help_modules(engine_state, stack, head);
let found_cmds_vec = highlight_search_in_table(
return find_internal(
all_cmds_vec,
engine_state,
stack,
&f.item,
&["name", "description"],
&string_style,
&highlight_style,
)?;
return Ok(Value::list(found_cmds_vec, head).into_pipeline_data());
true,
);
}
if rest.is_empty() {
let found_cmds_vec = build_help_modules(engine_state, stack, head);
Ok(Value::list(found_cmds_vec, head).into_pipeline_data())
Ok(build_help_modules(engine_state, stack, head))
} else {
let mut name = String::new();
@ -239,11 +227,11 @@ pub fn help_modules(
}
}
fn build_help_modules(engine_state: &EngineState, stack: &Stack, span: Span) -> Vec<Value> {
fn build_help_modules(engine_state: &EngineState, stack: &Stack, span: Span) -> PipelineData {
let mut scope_data = ScopeData::new(engine_state, stack);
scope_data.populate_modules();
scope_data.collect_modules(span)
Value::list(scope_data.collect_modules(span), span).into_pipeline_data()
}
#[cfg(test)]

View File

@ -16,7 +16,6 @@ pub use help_modules::HelpModules;
pub use help_operators::HelpOperators;
pub use help_pipe_and_redirect::HelpPipeAndRedirect;
pub(crate) use help_::highlight_search_in_table;
pub(crate) use help_aliases::help_aliases;
pub(crate) use help_commands::help_commands;
pub(crate) use help_modules::help_modules;