From 802bfed173bd3adbcd8838cf0d1aa1ccb5c170da Mon Sep 17 00:00:00 2001 From: Himadri Bhattacharjee <107522312+lavafroth@users.noreply.github.com> Date: Sun, 4 Aug 2024 16:36:10 +0530 Subject: [PATCH] feat: prefer exact match when completion mode is prefix (#13302) Fixes #13204 # Description When the completion mode is set to `prefix`, path completions explicitly check for and prefer an exact match for a basename instead of longer or similar names. # User-Facing Changes Exact match is inactive since there's no trailing slash ``` ~/Public/nushell| ls crates/nu-plugin crates/nu-plugin/ crates/nu-plugin-core/ crates/nu-plugin-engine/ crates/nu-plugin-protocol/ crates/nu-plugin-test-support/ ``` Exact match is active ``` ~/Public/nushell| ls crates/nu-plugin/ crates/nu-plugin/Cargo.toml crates/nu-plugin/LICENSE crates/nu-plugin/README.md crates/nu-plugin/src/ ``` Fuzzy matching persists its behavior ``` ~/Public/nushell> $env.config.completions.algorithm = "fuzzy"; ~/Public/nushell| ls crates/nu-plugin/car crates/nu-cmd-plugin/Cargo.toml crates/nu-plugin/Cargo.toml crates/nu-plugin-core/Cargo.toml crates/nu-plugin-engine/Cargo.toml crates/nu-plugin-protocol/Cargo.toml crates/nu-plugin-test-support/Cargo.toml ``` # Tests + Formatting # After Submitting --- .../src/completions/completion_common.rs | 38 ++++++++++++++++--- 1 file changed, 32 insertions(+), 6 deletions(-) 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);