mirror of
https://github.com/fish-shell/fish-shell.git
synced 2026-05-29 18:51:15 -03:00
Refactor wildcard expansion state
Prior to this commit, the WildCardExpander had one scoped variable, leading to the annoying "zelf" pattern. Factor this into its own object and pass it around explicitly, to clean this up.
This commit is contained in:
@@ -432,19 +432,27 @@ fn wildcard_test_flags_then_complete(
|
||||
== WildcardResult::Match
|
||||
}
|
||||
|
||||
use expander::WildCardExpander;
|
||||
mod expander {
|
||||
use libc::F_OK;
|
||||
|
||||
use crate::{
|
||||
common::scoped_push,
|
||||
path::append_path_component,
|
||||
wutil::{dir_iter::DirIter, normalize_path, DevInode},
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
pub struct WildCardExpander<'e> {
|
||||
// State associated with expanding a path.
|
||||
// This may be different for different paths discovered during expansion,
|
||||
// and so it is passed along in the recursive invocations and not stored.
|
||||
#[derive(Copy, Clone, Default)]
|
||||
pub(super) struct ParentInfo {
|
||||
// Whether some parent expansion is fuzzy, and therefore completions always prepend their prefix.
|
||||
has_fuzzy_ancestor: bool,
|
||||
}
|
||||
|
||||
// A stateful object for expanding wildcards.
|
||||
pub(super) struct WildCardExpander<'e> {
|
||||
/// A function to call to check cancellation.
|
||||
cancel_checker: &'e mut dyn FnMut() -> bool,
|
||||
/// The working directory to resolve paths against
|
||||
@@ -463,10 +471,6 @@ pub struct WildCardExpander<'e> {
|
||||
did_overflow: bool,
|
||||
/// Whether we have successfully added any completions.
|
||||
did_add: bool,
|
||||
/// Whether some parent expansion is fuzzy, and therefore completions always prepend their prefix
|
||||
/// This variable is a little suspicious - it should be passed along, not stored here
|
||||
/// If we ever try to do parallel wildcard expansion we'll have to remove this
|
||||
has_fuzzy_ancestor: bool,
|
||||
}
|
||||
|
||||
impl<'e> WildCardExpander<'e> {
|
||||
@@ -489,7 +493,6 @@ pub fn new(
|
||||
did_add: false,
|
||||
did_interrupt: false,
|
||||
did_overflow: false,
|
||||
has_fuzzy_ancestor: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -505,7 +508,14 @@ pub fn new(
|
||||
/// effective_prefix: the string that should be prepended for completions that replace their token.
|
||||
/// This is usually the same thing as the original wildcard, but for fuzzy matching, we
|
||||
/// expand intermediate segments. effective_prefix is always either empty, or ends with a slash
|
||||
pub fn expand(&mut self, base_dir: &wstr, wc: &wstr, effective_prefix: &wstr) {
|
||||
/// info: the parent info for expansion of this path, such as whether we have a fuzzy ancestor.
|
||||
pub fn expand(
|
||||
&mut self,
|
||||
base_dir: &wstr,
|
||||
wc: &wstr,
|
||||
effective_prefix: &wstr,
|
||||
info: ParentInfo,
|
||||
) {
|
||||
if self.interrupted_or_overflowed() {
|
||||
return;
|
||||
}
|
||||
@@ -525,11 +535,11 @@ pub fn expand(&mut self, base_dir: &wstr, wc: &wstr, effective_prefix: &wstr) {
|
||||
if wc_segment.is_empty() {
|
||||
assert!(!segment_has_wildcards);
|
||||
if is_last_segment {
|
||||
self.expand_trailing_slash(base_dir, effective_prefix);
|
||||
self.expand_trailing_slash(base_dir, effective_prefix, info);
|
||||
} else {
|
||||
let mut prefix = effective_prefix.to_owned();
|
||||
prefix.push('/');
|
||||
self.expand(base_dir, wc_remainder.unwrap(), &prefix);
|
||||
self.expand(base_dir, wc_remainder.unwrap(), &prefix, info);
|
||||
}
|
||||
} else if !segment_has_wildcards && !is_last_segment {
|
||||
// Literal intermediate match. Note that we may not be able to actually read the directory
|
||||
@@ -542,7 +552,7 @@ pub fn expand(&mut self, base_dir: &wstr, wc: &wstr, effective_prefix: &wstr) {
|
||||
// This just trumps everything
|
||||
let before = self.resolved_completions.len();
|
||||
let prefix: WString = effective_prefix.to_owned() + wc_segment + L!("/");
|
||||
self.expand(&intermediate_dirpath, wc_remainder, &prefix);
|
||||
self.expand(&intermediate_dirpath, wc_remainder, &prefix, info);
|
||||
|
||||
// Maybe try a fuzzy match (#94) if nothing was found with the literal match. Respect
|
||||
// EXPAND_NO_DIRECTORY_ABBREVIATIONS (issue #2413).
|
||||
@@ -562,10 +572,12 @@ pub fn expand(&mut self, base_dir: &wstr, wc: &wstr, effective_prefix: &wstr) {
|
||||
wc_segment,
|
||||
wc_remainder,
|
||||
effective_prefix,
|
||||
info,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Either a wildcard intermediate segment or the last segment.
|
||||
assert!(!wc_segment.is_empty() && (segment_has_wildcards || is_last_segment));
|
||||
|
||||
if !is_last_segment && matches!(wc_segment.as_char_slice(), [ANY_STRING_RECURSIVE])
|
||||
@@ -576,7 +588,7 @@ pub fn expand(&mut self, base_dir: &wstr, wc: &wstr, effective_prefix: &wstr) {
|
||||
// Implement this by matching the wildcard tail only, in this directory.
|
||||
// Note if the segment is not exactly ANY_STRING_RECURSIVE then the segment may only
|
||||
// match subdirectories.
|
||||
self.expand(base_dir, wc_remainder.unwrap(), effective_prefix);
|
||||
self.expand(base_dir, wc_remainder.unwrap(), effective_prefix, info);
|
||||
if self.interrupted_or_overflowed() {
|
||||
return;
|
||||
}
|
||||
@@ -598,10 +610,17 @@ pub fn expand(&mut self, base_dir: &wstr, wc: &wstr, effective_prefix: &wstr) {
|
||||
wc_segment,
|
||||
wc_remainder,
|
||||
effective_prefix,
|
||||
info,
|
||||
);
|
||||
} else {
|
||||
// Last wildcard segment, nonempty wildcard.
|
||||
self.expand_last_segment(base_dir, &mut dir, wc_segment, effective_prefix);
|
||||
self.expand_last_segment(
|
||||
base_dir,
|
||||
&mut dir,
|
||||
wc_segment,
|
||||
effective_prefix,
|
||||
info,
|
||||
);
|
||||
}
|
||||
|
||||
let Some(asr_idx) = wc_segment.find_char(ANY_STRING_RECURSIVE) else {
|
||||
@@ -624,6 +643,7 @@ pub fn expand(&mut self, base_dir: &wstr, wc: &wstr, effective_prefix: &wstr) {
|
||||
head_any,
|
||||
any_tail,
|
||||
effective_prefix,
|
||||
info,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -643,7 +663,7 @@ pub fn status_code(&self) -> WildcardResult {
|
||||
|
||||
impl<'e> WildCardExpander<'e> {
|
||||
/// We are a trailing slash - expand at the end.
|
||||
fn expand_trailing_slash(&mut self, base_dir: &wstr, prefix: &wstr) {
|
||||
fn expand_trailing_slash(&mut self, base_dir: &wstr, prefix: &wstr, info: ParentInfo) {
|
||||
if self.interrupted_or_overflowed() {
|
||||
return;
|
||||
}
|
||||
@@ -681,6 +701,7 @@ fn expand_trailing_slash(&mut self, base_dir: &wstr, prefix: &wstr) {
|
||||
L!(""),
|
||||
prefix,
|
||||
entry,
|
||||
info,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -697,6 +718,7 @@ fn expand_intermediate_segment(
|
||||
wc_segment: &wstr,
|
||||
wc_remainder: &wstr,
|
||||
prefix: &wstr,
|
||||
info: ParentInfo,
|
||||
) {
|
||||
let is_final = wc_remainder.is_empty() && !wc_segment.contains(ANY_STRING_RECURSIVE);
|
||||
while !self.interrupted_or_overflowed() {
|
||||
@@ -722,7 +744,7 @@ fn expand_intermediate_segment(
|
||||
let full_path: WString = base_dir.to_owned() + entry.name.as_utfstr() + L!("/");
|
||||
let prefix: WString = prefix.to_owned() + wc_segment + L!("/");
|
||||
|
||||
self.expand(&full_path, wc_remainder, &prefix);
|
||||
self.expand(&full_path, wc_remainder, &prefix, info);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -738,7 +760,7 @@ fn expand_intermediate_segment(
|
||||
let full_path: WString = base_dir.to_owned() + entry.name.as_utfstr() + L!("/");
|
||||
let prefix: WString = prefix.to_owned() + wc_segment + L!("/");
|
||||
|
||||
self.expand(&full_path, wc_remainder, &prefix);
|
||||
self.expand(&full_path, wc_remainder, &prefix, info);
|
||||
|
||||
// Now remove the visited file. This is for #2414: only directories "beneath" us should be
|
||||
// considered visited.
|
||||
@@ -755,10 +777,11 @@ fn expand_literal_intermediate_segment_with_fuzz(
|
||||
wc_segment: &wstr,
|
||||
wc_remainder: &wstr,
|
||||
prefix: &wstr,
|
||||
mut info: ParentInfo,
|
||||
) {
|
||||
// Mark that we are fuzzy for the duration of this function
|
||||
let mut zelf = scoped_push(self, |e| &mut e.has_fuzzy_ancestor, true);
|
||||
while !zelf.interrupted_or_overflowed() {
|
||||
// Mark that all children of this directory have a fuzzy ancestor.
|
||||
info.has_fuzzy_ancestor = true;
|
||||
while !self.interrupted_or_overflowed() {
|
||||
let Some(Ok(entry)) = base_dir_iter.next() else {
|
||||
break;
|
||||
};
|
||||
@@ -789,12 +812,12 @@ fn expand_literal_intermediate_segment_with_fuzz(
|
||||
let new_full_path: WString = base_dir.to_owned() + entry.name.as_utfstr() + L!("/");
|
||||
|
||||
// Ok, this directory matches. Recurse to it. Then mark each resulting completion as fuzzy.
|
||||
let before = zelf.resolved_completions.len();
|
||||
zelf.expand(&new_full_path, wc_remainder, &child_prefix);
|
||||
let after = zelf.resolved_completions.len();
|
||||
let before = self.resolved_completions.len();
|
||||
self.expand(&new_full_path, wc_remainder, &child_prefix, info);
|
||||
let after = self.resolved_completions.len();
|
||||
|
||||
assert!(before <= after);
|
||||
for c in zelf.resolved_completions[before..after].iter_mut() {
|
||||
for c in self.resolved_completions[before..after].iter_mut() {
|
||||
// Mark the completion as replacing.
|
||||
if !c.replaces_token() {
|
||||
c.flags |= CompleteFlags::REPLACES_TOKEN;
|
||||
@@ -821,6 +844,7 @@ fn expand_last_segment(
|
||||
base_dir_iter: &mut DirIter,
|
||||
wc: &wstr,
|
||||
prefix: &wstr,
|
||||
info: ParentInfo,
|
||||
) {
|
||||
let need_dir = self.flags.contains(ExpandFlags::DIRECTORIES_ONLY);
|
||||
|
||||
@@ -840,6 +864,7 @@ fn expand_last_segment(
|
||||
wc,
|
||||
prefix,
|
||||
entry,
|
||||
info,
|
||||
);
|
||||
} else {
|
||||
// Normal wildcard expansion, not for completions.
|
||||
@@ -935,6 +960,7 @@ fn try_add_completion_result(
|
||||
wildcard: &wstr,
|
||||
prefix: &wstr,
|
||||
entry: &DirEntry,
|
||||
info: ParentInfo,
|
||||
) {
|
||||
// This function is only for the completions case.
|
||||
assert!(self.flags.contains(ExpandFlags::FOR_COMPLETIONS));
|
||||
@@ -961,7 +987,7 @@ fn try_add_completion_result(
|
||||
// Note that prepend_token_prefix is a no-op unless COMPLETE_REPLACES_TOKEN is set
|
||||
let after = self.resolved_completions.len();
|
||||
for c in self.resolved_completions[before..after].iter_mut() {
|
||||
if self.has_fuzzy_ancestor && !(c.flags.contains(CompleteFlags::REPLACES_TOKEN))
|
||||
if info.has_fuzzy_ancestor && !(c.flags.contains(CompleteFlags::REPLACES_TOKEN))
|
||||
{
|
||||
c.flags |= CompleteFlags::REPLACES_TOKEN;
|
||||
c.prepend_token_prefix(wildcard);
|
||||
@@ -1023,7 +1049,8 @@ fn open_dir(&self, base_dir: &wstr, dotdot: bool) -> std::io::Result<DirIter> {
|
||||
/// \param working_directory The working directory
|
||||
/// \param flags flags for the search. Can be any combination of for_completions and
|
||||
/// executables_only
|
||||
/// \param output The list in which to put the output
|
||||
/// \param cancel_checker A function to call to check for cancellation
|
||||
/// \param output The completion receiver to receive expanded wildcards
|
||||
///
|
||||
pub fn wildcard_expand_string<'closure>(
|
||||
wc: &wstr,
|
||||
@@ -1032,6 +1059,7 @@ pub fn wildcard_expand_string<'closure>(
|
||||
mut cancel_checker: impl FnMut() -> bool + 'closure,
|
||||
output: &mut CompletionReceiver,
|
||||
) -> WildcardResult {
|
||||
use expander::{ParentInfo, WildCardExpander};
|
||||
// Fuzzy matching only if we're doing completions.
|
||||
assert!(
|
||||
flags.contains(ExpandFlags::FOR_COMPLETIONS) || !flags.contains(ExpandFlags::FUZZY_MATCH)
|
||||
@@ -1046,10 +1074,8 @@ pub fn wildcard_expand_string<'closure>(
|
||||
&& (!flags.contains(ExpandFlags::GEN_DESCRIPTIONS)))
|
||||
);
|
||||
|
||||
// Hackish fix for issue #1631. We are about to call c_str(), which will produce a string
|
||||
// truncated at any embedded nulls. We could fix this by passing around the size, etc. However
|
||||
// embedded nulls are never allowed in a filename, so we just check for them and return 0 (no
|
||||
// matches) if there is an embedded null.
|
||||
// Hackish fix for issue #1631. Embedded nulls are never allowed in a filename,
|
||||
// so we just check for them and return 0 (no matches) if there is an embedded null.
|
||||
if wc.contains('\0') {
|
||||
return WildcardResult::NoMatch;
|
||||
}
|
||||
@@ -1075,7 +1101,7 @@ pub fn wildcard_expand_string<'closure>(
|
||||
};
|
||||
|
||||
let mut expander = WildCardExpander::new(prefix, flags, &mut cancel_checker, output);
|
||||
expander.expand(base_dir, effective_wc, base_dir);
|
||||
expander.expand(base_dir, effective_wc, base_dir, ParentInfo::default());
|
||||
return expander.status_code();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user