From 2e7b1d733bf39067aa5203e3bb562625f818d948 Mon Sep 17 00:00:00 2001 From: SeeStarz Date: Mon, 17 Feb 2025 19:36:12 +0700 Subject: [PATCH] Add support for exact match --- contrib/completions/_zoxide | 2 ++ contrib/completions/_zoxide.ps1 | 2 ++ contrib/completions/zoxide.bash | 2 +- contrib/completions/zoxide.elv | 2 ++ contrib/completions/zoxide.fish | 1 + contrib/completions/zoxide.ts | 4 +++ src/cmd/cmd.rs | 4 +++ src/cmd/query.rs | 3 ++- src/db/stream.rs | 43 +++++++++++++++++++++++++++++++++ 9 files changed, 61 insertions(+), 2 deletions(-) diff --git a/contrib/completions/_zoxide b/contrib/completions/_zoxide index 69882b41d..5d15e9bb2 100644 --- a/contrib/completions/_zoxide +++ b/contrib/completions/_zoxide @@ -126,6 +126,8 @@ _arguments "${_arguments_options[@]}" : \ '(-i --interactive)--list[List all matching directories]' \ '-s[Print score with results]' \ '--score[Print score with results]' \ +'-e[Only match exact]' \ +'--exact[Only match exact]' \ '-h[Print help]' \ '--help[Print help]' \ '-V[Print version]' \ diff --git a/contrib/completions/_zoxide.ps1 b/contrib/completions/_zoxide.ps1 index af15c66dd..8af707f0c 100644 --- a/contrib/completions/_zoxide.ps1 +++ b/contrib/completions/_zoxide.ps1 @@ -108,6 +108,8 @@ Register-ArgumentCompleter -Native -CommandName 'zoxide' -ScriptBlock { [CompletionResult]::new('--list', '--list', [CompletionResultType]::ParameterName, 'List all matching directories') [CompletionResult]::new('-s', '-s', [CompletionResultType]::ParameterName, 'Print score with results') [CompletionResult]::new('--score', '--score', [CompletionResultType]::ParameterName, 'Print score with results') + [CompletionResult]::new('-e', '-e', [CompletionResultType]::ParameterName, 'Only match exact') + [CompletionResult]::new('--exact', '--exact', [CompletionResultType]::ParameterName, 'Only match exact') [CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help') [CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help') [CompletionResult]::new('-V', '-V ', [CompletionResultType]::ParameterName, 'Print version') diff --git a/contrib/completions/zoxide.bash b/contrib/completions/zoxide.bash index 73dbd45f2..ff69b55ad 100644 --- a/contrib/completions/zoxide.bash +++ b/contrib/completions/zoxide.bash @@ -187,7 +187,7 @@ _zoxide() { return 0 ;; zoxide__query) - opts="-a -i -l -s -h -V --all --interactive --list --score --exclude --help --version [KEYWORDS]..." + opts="-a -i -l -s -e -h -V --all --interactive --list --score --exclude --exact --help --version [KEYWORDS]..." if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 diff --git a/contrib/completions/zoxide.elv b/contrib/completions/zoxide.elv index 6183d37b4..51fb31890 100644 --- a/contrib/completions/zoxide.elv +++ b/contrib/completions/zoxide.elv @@ -96,6 +96,8 @@ set edit:completion:arg-completer[zoxide] = {|@words| cand --list 'List all matching directories' cand -s 'Print score with results' cand --score 'Print score with results' + cand -e 'Only match exact' + cand --exact 'Only match exact' cand -h 'Print help' cand --help 'Print help' cand -V 'Print version' diff --git a/contrib/completions/zoxide.fish b/contrib/completions/zoxide.fish index 96dd86e18..7e1d98985 100644 --- a/contrib/completions/zoxide.fish +++ b/contrib/completions/zoxide.fish @@ -62,6 +62,7 @@ complete -c zoxide -n "__fish_zoxide_using_subcommand query" -s a -l all -d 'Sho complete -c zoxide -n "__fish_zoxide_using_subcommand query" -s i -l interactive -d 'Use interactive selection' complete -c zoxide -n "__fish_zoxide_using_subcommand query" -s l -l list -d 'List all matching directories' complete -c zoxide -n "__fish_zoxide_using_subcommand query" -s s -l score -d 'Print score with results' +complete -c zoxide -n "__fish_zoxide_using_subcommand query" -s e -l exact -d 'Only match exact' complete -c zoxide -n "__fish_zoxide_using_subcommand query" -s h -l help -d 'Print help' complete -c zoxide -n "__fish_zoxide_using_subcommand query" -s V -l version -d 'Print version' complete -c zoxide -n "__fish_zoxide_using_subcommand remove" -s h -l help -d 'Print help' diff --git a/contrib/completions/zoxide.ts b/contrib/completions/zoxide.ts index 0200591b0..82eca4d4c 100644 --- a/contrib/completions/zoxide.ts +++ b/contrib/completions/zoxide.ts @@ -228,6 +228,10 @@ const completion: Fig.Spec = { name: ["-s", "--score"], description: "Print score with results", }, + { + name: ["-e", "--exact"], + description: "Only match exact", + }, { name: ["-h", "--help"], description: "Print help", diff --git a/src/cmd/cmd.rs b/src/cmd/cmd.rs index cff7e790c..533bff974 100644 --- a/src/cmd/cmd.rs +++ b/src/cmd/cmd.rs @@ -180,6 +180,10 @@ pub struct Query { /// Exclude the current directory #[clap(long, value_hint = ValueHint::DirPath, value_name = "path")] pub exclude: Option, + + /// Only match exact + #[clap(long, short)] + pub exact: bool, } /// Remove a directory from the database diff --git a/src/cmd/query.rs b/src/cmd/query.rs index 362d80a37..55a0030d1 100644 --- a/src/cmd/query.rs +++ b/src/cmd/query.rs @@ -79,7 +79,8 @@ impl Query { fn get_stream<'a>(&self, db: &'a mut Database, now: Epoch) -> Result> { let mut options = StreamOptions::new(now) .with_keywords(self.keywords.iter().map(|s| s.as_str())) - .with_exclude(config::exclude_dirs()?); + .with_exclude(config::exclude_dirs()?) + .with_exact(self.exact); if !self.all { let resolve_symlinks = config::resolve_symlinks(); options = options.with_exists(true).with_resolve_symlinks(resolve_symlinks); diff --git a/src/db/stream.rs b/src/db/stream.rs index 4af7d7a93..5db5700be 100644 --- a/src/db/stream.rs +++ b/src/db/stream.rs @@ -1,5 +1,7 @@ +use std::ffi::OsString; use std::iter::Rev; use std::ops::Range; +use std::path::{Component, PathBuf}; use std::{fs, path}; use glob::Pattern; @@ -40,6 +42,10 @@ impl<'a> Stream<'a> { continue; } + if !self.filter_by_exact(&dir.path) { + continue; + } + let dir = &self.db.dirs()[idx]; return Some(dir); } @@ -91,6 +97,34 @@ impl<'a> Stream<'a> { if self.options.resolve_symlinks { fs::symlink_metadata } else { fs::metadata }; resolver(path).map(|metadata| metadata.is_dir()).unwrap_or_default() } + + fn filter_by_exact(&self, path: &str) -> bool { + if !self.options.exact { + return true; + } + + let path = util::to_lowercase(path); + let path = PathBuf::from(path); + let mut components: Vec = path.components().collect(); + + for keyword in self.options.keywords.iter().rev().map(OsString::from) { + let idx = components.iter().rposition(|component| { + if let Component::Normal(sub_path) = component { + sub_path == &keyword + } else { + false + } + }); + + if let Some(idx) = idx { + components = components.drain(0..idx).collect(); + } else { + return false; + }; + } + + true + } } pub struct StreamOptions { @@ -109,6 +143,9 @@ pub struct StreamOptions { /// Whether to resolve symlinks when checking if a directory exists. resolve_symlinks: bool, + // Whether to only allow exact match of directory names. + exact: bool, + /// Directories that do not exist and haven't been accessed since TTL will /// be lazily removed. ttl: Epoch, @@ -122,6 +159,7 @@ impl StreamOptions { exclude: Vec::new(), exists: false, resolve_symlinks: false, + exact: false, ttl: now.saturating_sub(3 * MONTH), } } @@ -149,6 +187,11 @@ impl StreamOptions { self.resolve_symlinks = resolve_symlinks; self } + + pub fn with_exact(mut self, exact: bool) -> Self { + self.exact = exact; + self + } } #[cfg(test)]