diff --git a/crates/nu-cli/src/completions/completion_common.rs b/crates/nu-cli/src/completions/completion_common.rs index 5469ba1326b43..2b85f6091e33e 100644 --- a/crates/nu-cli/src/completions/completion_common.rs +++ b/crates/nu-cli/src/completions/completion_common.rs @@ -12,7 +12,7 @@ use nu_protocol::{ use nu_utils::get_ls_colors; use std::path::{is_separator, Component, Path, PathBuf, MAIN_SEPARATOR as SEP}; -use super::SortBy; +use super::{MatchAlgorithm, SortBy}; #[derive(Clone, Default)] pub struct PathBuiltFromString { @@ -20,12 +20,21 @@ pub struct PathBuiltFromString { isdir: bool, } -fn complete_rec( +/// Recursively goes through paths that match a given `partial`. +/// built: State struct for a valid matching path built so far. +/// +/// `isdir`: whether the current partial path has a trailing slash. +/// Parsing a path string into a pathbuf loses that bit of information. +/// +/// want_directory: Whether we want only directories as completion matches. +/// Some commands like `cd` can only be run on directories whereas others +/// like `ls` can be run on regular files as well. +pub fn complete_rec( partial: &[&str], built: &PathBuiltFromString, cwd: &Path, options: &CompletionOptions, - dir: bool, + want_directory: bool, isdir: bool, ) -> Vec { let mut completions = vec![]; @@ -35,7 +44,7 @@ fn complete_rec( let mut built = built.clone(); built.parts.push(base.to_string()); built.isdir = true; - return complete_rec(rest, &built, cwd, options, dir, isdir); + return complete_rec(rest, &built, cwd, options, want_directory, isdir); } } @@ -56,7 +65,7 @@ fn complete_rec( built.parts.push(entry_name.clone()); built.isdir = entry_isdir; - if !dir || entry_isdir { + if !want_directory || entry_isdir { entries.push((entry_name, built)); } } @@ -68,12 +77,29 @@ fn complete_rec( match partial.split_first() { Some((base, rest)) => { if matches(base, &entry_name, options) { + // We use `isdir` to confirm that the current component has + // at least one next component or a slash. + // Serves as confirmation to ignore longer completions for + // components in between. if !rest.is_empty() || isdir { - completions.extend(complete_rec(rest, &built, cwd, options, dir, isdir)); + completions.extend(complete_rec( + rest, + &built, + cwd, + options, + want_directory, + isdir, + )); } else { completions.push(built); } } + if entry_name.eq(base) + && matches!(options.match_algorithm, MatchAlgorithm::Prefix) + && isdir + { + break; + } } None => { completions.push(built);