check signals in nu-glob and ls (#15140)

Fixes #10144

# User-Facing Changes

Long running glob expansions and `ls` runs (e.g. `ls /**/*`) can now be
interrupted with ctrl-c.
This commit is contained in:
Solomon
2025-02-28 11:36:39 -07:00
committed by GitHub
parent 48bdcc71f4
commit c5a14bb8ff
14 changed files with 120 additions and 58 deletions

View File

@ -118,7 +118,7 @@ impl Command for Du {
min_size,
};
Ok(
du_for_one_pattern(args, &current_dir, tag, engine_state.signals())?
du_for_one_pattern(args, &current_dir, tag, engine_state.signals().clone())?
.into_pipeline_data(tag, engine_state.signals().clone()),
)
}
@ -137,7 +137,7 @@ impl Command for Du {
args,
&current_dir,
tag,
engine_state.signals(),
engine_state.signals().clone(),
)?)
}
@ -163,9 +163,8 @@ fn du_for_one_pattern(
args: DuArgs,
current_dir: &Path,
span: Span,
signals: &Signals,
signals: Signals,
) -> Result<impl Iterator<Item = Value> + Send, ShellError> {
let signals_clone = signals.clone();
let exclude = args.exclude.map_or(Ok(None), move |x| {
Pattern::new(x.item.as_ref())
.map(Some)
@ -176,7 +175,8 @@ fn du_for_one_pattern(
})?;
let paths = match args.path {
Some(p) => nu_engine::glob_from(&p, current_dir, span, None),
Some(p) => nu_engine::glob_from(&p, current_dir, span, None, signals.clone()),
// The * pattern should never fail.
None => nu_engine::glob_from(
&Spanned {
@ -186,6 +186,7 @@ fn du_for_one_pattern(
current_dir,
span,
None,
signals.clone(),
),
}
.map(|f| f.1)?;
@ -206,7 +207,7 @@ fn du_for_one_pattern(
Ok(paths.filter_map(move |p| match p {
Ok(a) => {
if a.is_dir() {
match DirInfo::new(a, &params, max_depth, span, &signals_clone) {
match DirInfo::new(a, &params, max_depth, span, &signals) {
Ok(v) => Some(Value::from(v)),
Err(_) => None,
}

View File

@ -285,7 +285,10 @@ fn ls_for_one_pattern(
nu_path::expand_path_with(pat.item.as_ref(), &cwd, pat.item.is_expand());
// Avoid checking and pushing "*" to the path when directory (do not show contents) flag is true
if !directory && tmp_expanded.is_dir() {
if read_dir(tmp_expanded, p_tag, use_threads)?.next().is_none() {
if read_dir(tmp_expanded, p_tag, use_threads, signals.clone())?
.next()
.is_none()
{
return Ok(Value::test_nothing().into_pipeline_data());
}
just_read_dir = !(pat.item.is_expand() && nu_glob::is_glob(pat.item.as_ref()));
@ -304,7 +307,10 @@ fn ls_for_one_pattern(
// Avoid pushing "*" to the default path when directory (do not show contents) flag is true
if directory {
(NuGlob::Expand(".".to_string()), false)
} else if read_dir(cwd.clone(), p_tag, use_threads)?.next().is_none() {
} else if read_dir(cwd.clone(), p_tag, use_threads, signals.clone())?
.next()
.is_none()
{
return Ok(Value::test_nothing().into_pipeline_data());
} else {
(NuGlob::Expand("*".to_string()), false)
@ -317,7 +323,7 @@ fn ls_for_one_pattern(
let path = pattern_arg.into_spanned(p_tag);
let (prefix, paths) = if just_read_dir {
let expanded = nu_path::expand_path_with(path.item.as_ref(), &cwd, path.item.is_expand());
let paths = read_dir(expanded.clone(), p_tag, use_threads)?;
let paths = read_dir(expanded.clone(), p_tag, use_threads, signals.clone())?;
// just need to read the directory, so prefix is path itself.
(Some(expanded), paths)
} else {
@ -330,11 +336,13 @@ fn ls_for_one_pattern(
};
Some(glob_options)
};
glob_from(&path, &cwd, call_span, glob_options)?
glob_from(&path, &cwd, call_span, glob_options, signals.clone())?
};
let mut paths_peek = paths.peekable();
if paths_peek.peek().is_none() {
let no_matches = paths_peek.peek().is_none();
signals.check(call_span)?;
if no_matches {
return Err(ShellError::GenericError {
error: format!("No matches found for {:?}", path.item),
msg: "Pattern, file or folder not found".into(),
@ -959,17 +967,21 @@ fn read_dir(
f: PathBuf,
span: Span,
use_threads: bool,
signals: Signals,
) -> Result<Box<dyn Iterator<Item = Result<PathBuf, ShellError>> + Send>, ShellError> {
let signals_clone = signals.clone();
let items = f
.read_dir()
.map_err(|err| IoError::new(err.kind(), span, f.clone()))?
.map(move |d| {
signals_clone.check(span)?;
d.map(|r| r.path())
.map_err(|err| IoError::new(err.kind(), span, f.clone()))
.map_err(ShellError::from)
});
if !use_threads {
let mut collected = items.collect::<Vec<_>>();
signals.check(span)?;
collected.sort_by(|a, b| match (a, b) {
(Ok(a), Ok(b)) => a.cmp(b),
(Ok(_), Err(_)) => Ordering::Greater,

View File

@ -95,16 +95,17 @@ impl Command for Open {
let arg_span = path.span;
// let path_no_whitespace = &path.item.trim_end_matches(|x| matches!(x, '\x09'..='\x0d'));
for path in nu_engine::glob_from(&path, &cwd, call_span, None)
.map_err(|err| match err {
ShellError::Io(mut err) => {
err.kind = err.kind.not_found_as(NotFound::File);
err.span = arg_span;
err.into()
}
_ => err,
})?
.1
for path in
nu_engine::glob_from(&path, &cwd, call_span, None, engine_state.signals().clone())
.map_err(|err| match err {
ShellError::Io(mut err) => {
err.kind = err.kind.not_found_as(NotFound::File);
err.span = arg_span;
err.into()
}
_ => err,
})?
.1
{
let path = path?;
let path = Path::new(&path);

View File

@ -260,6 +260,7 @@ fn rm(
require_literal_leading_dot: true,
..Default::default()
}),
engine_state.signals().clone(),
) {
Ok(files) => {
for file in files.1 {

View File

@ -193,7 +193,7 @@ impl Command for UCp {
for mut p in paths {
p.item = p.item.strip_ansi_string_unlikely();
let exp_files: Vec<Result<PathBuf, ShellError>> =
nu_engine::glob_from(&p, &cwd, call.head, None)
nu_engine::glob_from(&p, &cwd, call.head, None, engine_state.signals().clone())
.map(|f| f.1)?
.collect();
if exp_files.is_empty() {

View File

@ -134,7 +134,7 @@ impl Command for UMv {
for mut p in paths {
p.item = p.item.strip_ansi_string_unlikely();
let exp_files: Vec<Result<PathBuf, ShellError>> =
nu_engine::glob_from(&p, &cwd, call.head, None)
nu_engine::glob_from(&p, &cwd, call.head, None, engine_state.signals().clone())
.map(|f| f.1)?
.collect();
if exp_files.is_empty() {

View File

@ -158,14 +158,17 @@ impl Command for UTouch {
continue;
}
let mut expanded_globs = glob(&file_path.to_string_lossy())
.unwrap_or_else(|_| {
panic!(
"Failed to process file path: {}",
&file_path.to_string_lossy()
)
})
.peekable();
let mut expanded_globs = glob(
&file_path.to_string_lossy(),
Some(engine_state.signals().clone()),
)
.unwrap_or_else(|_| {
panic!(
"Failed to process file path: {}",
&file_path.to_string_lossy()
)
})
.peekable();
if expanded_globs.peek().is_none() {
let file_name = file_path.file_name().unwrap_or_else(|| {

View File

@ -382,9 +382,14 @@ pub fn eval_external_arguments(
match arg {
// Expand globs passed to run-external
Value::Glob { val, no_expand, .. } if !no_expand => args.extend(
expand_glob(&val, cwd.as_std_path(), span, engine_state.signals())?
.into_iter()
.map(|s| s.into_spanned(span)),
expand_glob(
&val,
cwd.as_std_path(),
span,
engine_state.signals().clone(),
)?
.into_iter()
.map(|s| s.into_spanned(span)),
),
other => args
.push(OsString::from(coerce_into_string(engine_state, other)?).into_spanned(span)),
@ -415,7 +420,7 @@ fn expand_glob(
arg: &str,
cwd: &Path,
span: Span,
signals: &Signals,
signals: Signals,
) -> Result<Vec<OsString>, ShellError> {
// For an argument that isn't a glob, just do the `expand_tilde`
// and `expand_ndots` expansion
@ -427,7 +432,7 @@ fn expand_glob(
// We must use `nu_engine::glob_from` here, in order to ensure we get paths from the correct
// dir
let glob = NuGlob::Expand(arg.to_owned()).into_spanned(span);
if let Ok((prefix, matches)) = nu_engine::glob_from(&glob, cwd, span, None) {
if let Ok((prefix, matches)) = nu_engine::glob_from(&glob, cwd, span, None, signals.clone()) {
let mut result: Vec<OsString> = vec![];
for m in matches {
@ -740,30 +745,30 @@ mod test {
let cwd = dirs.test().as_std_path();
let actual = expand_glob("*.txt", cwd, Span::unknown(), &Signals::empty()).unwrap();
let actual = expand_glob("*.txt", cwd, Span::unknown(), Signals::empty()).unwrap();
let expected = &["a.txt", "b.txt"];
assert_eq!(actual, expected);
let actual = expand_glob("./*.txt", cwd, Span::unknown(), &Signals::empty()).unwrap();
let actual = expand_glob("./*.txt", cwd, Span::unknown(), Signals::empty()).unwrap();
assert_eq!(actual, expected);
let actual = expand_glob("'*.txt'", cwd, Span::unknown(), &Signals::empty()).unwrap();
let actual = expand_glob("'*.txt'", cwd, Span::unknown(), Signals::empty()).unwrap();
let expected = &["'*.txt'"];
assert_eq!(actual, expected);
let actual = expand_glob(".", cwd, Span::unknown(), &Signals::empty()).unwrap();
let actual = expand_glob(".", cwd, Span::unknown(), Signals::empty()).unwrap();
let expected = &["."];
assert_eq!(actual, expected);
let actual = expand_glob("./a.txt", cwd, Span::unknown(), &Signals::empty()).unwrap();
let actual = expand_glob("./a.txt", cwd, Span::unknown(), Signals::empty()).unwrap();
let expected = &["./a.txt"];
assert_eq!(actual, expected);
let actual = expand_glob("[*.txt", cwd, Span::unknown(), &Signals::empty()).unwrap();
let actual = expand_glob("[*.txt", cwd, Span::unknown(), Signals::empty()).unwrap();
let expected = &["[*.txt"];
assert_eq!(actual, expected);
let actual = expand_glob("~/foo.txt", cwd, Span::unknown(), &Signals::empty()).unwrap();
let actual = expand_glob("~/foo.txt", cwd, Span::unknown(), Signals::empty()).unwrap();
let home = dirs::home_dir().expect("failed to get home dir");
let expected: Vec<OsString> = vec![home.join("foo.txt").into()];
assert_eq!(actual, expected);