diff --git a/contrib/completions/_zoxide b/contrib/completions/_zoxide index fd898e6a..a8553897 100644 --- a/contrib/completions/_zoxide +++ b/contrib/completions/_zoxide @@ -117,6 +117,7 @@ _arguments "${_arguments_options[@]}" \ ;; (query) _arguments "${_arguments_options[@]}" \ +'--sort-by=[Sort result]:SORT_BY:(path score last-accessed)' \ '--exclude=[Exclude the current directory]:path:_files -/' \ '-a[Show unavailable directories]' \ '--all[Show unavailable directories]' \ diff --git a/contrib/completions/_zoxide.ps1 b/contrib/completions/_zoxide.ps1 index ff0e6b04..fa2be091 100644 --- a/contrib/completions/_zoxide.ps1 +++ b/contrib/completions/_zoxide.ps1 @@ -99,6 +99,7 @@ Register-ArgumentCompleter -Native -CommandName 'zoxide' -ScriptBlock { break } 'zoxide;query' { + [CompletionResult]::new('--sort-by', 'sort-by', [CompletionResultType]::ParameterName, 'Sort result') [CompletionResult]::new('--exclude', 'exclude', [CompletionResultType]::ParameterName, 'Exclude the current directory') [CompletionResult]::new('-a', 'a', [CompletionResultType]::ParameterName, 'Show unavailable directories') [CompletionResult]::new('--all', 'all', [CompletionResultType]::ParameterName, 'Show unavailable directories') diff --git a/contrib/completions/zoxide.bash b/contrib/completions/zoxide.bash index 73dbd45f..4525573d 100644 --- a/contrib/completions/zoxide.bash +++ b/contrib/completions/zoxide.bash @@ -187,12 +187,16 @@ _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 -h -V --all --interactive --list --sort-by --score --exclude --help --version [KEYWORDS]..." if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 fi case "${prev}" in + --sort-by) + COMPREPLY=($(compgen -W "path score last-accessed" -- "${cur}")) + return 0 + ;; --exclude) COMPREPLY=() if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then diff --git a/contrib/completions/zoxide.elv b/contrib/completions/zoxide.elv index 6183d37b..2f700a32 100644 --- a/contrib/completions/zoxide.elv +++ b/contrib/completions/zoxide.elv @@ -87,6 +87,7 @@ set edit:completion:arg-completer[zoxide] = {|@words| cand --version 'Print version' } &'zoxide;query'= { + cand --sort-by 'Sort result' cand --exclude 'Exclude the current directory' cand -a 'Show unavailable directories' cand --all 'Show unavailable directories' diff --git a/contrib/completions/zoxide.fish b/contrib/completions/zoxide.fish index e61a1a43..a1d25fce 100644 --- a/contrib/completions/zoxide.fish +++ b/contrib/completions/zoxide.fish @@ -31,6 +31,7 @@ complete -c zoxide -n "__fish_seen_subcommand_from init" -l hook -d 'Changes how complete -c zoxide -n "__fish_seen_subcommand_from init" -l no-cmd -d 'Prevents zoxide from defining the `z` and `zi` commands' complete -c zoxide -n "__fish_seen_subcommand_from init" -s h -l help -d 'Print help' complete -c zoxide -n "__fish_seen_subcommand_from init" -s V -l version -d 'Print version' +complete -c zoxide -n "__fish_seen_subcommand_from query" -l sort-by -d 'Sort result' -r -f -a "{path '',score '',last-accessed ''}" complete -c zoxide -n "__fish_seen_subcommand_from query" -l exclude -d 'Exclude the current directory' -r -f -a "(__fish_complete_directories)" complete -c zoxide -n "__fish_seen_subcommand_from query" -s a -l all -d 'Show unavailable directories' complete -c zoxide -n "__fish_seen_subcommand_from query" -s i -l interactive -d 'Use interactive selection' diff --git a/contrib/completions/zoxide.ts b/contrib/completions/zoxide.ts index 0200591b..e35426fc 100644 --- a/contrib/completions/zoxide.ts +++ b/contrib/completions/zoxide.ts @@ -194,6 +194,20 @@ const completion: Fig.Spec = { name: "query", description: "Search for a directory in the database", options: [ + { + name: "--sort-by", + description: "Sort result", + isRepeatable: true, + args: { + name: "sort_by", + isOptional: true, + suggestions: [ + "path", + "score", + "last-accessed", + ], + }, + }, { name: "--exclude", description: "Exclude the current directory", diff --git a/src/cmd/cmd.rs b/src/cmd/cmd.rs index 0e5f6c42..abb7e41e 100644 --- a/src/cmd/cmd.rs +++ b/src/cmd/cmd.rs @@ -165,6 +165,10 @@ pub struct Query { #[clap(long, short, conflicts_with = "interactive")] pub list: bool, + /// Sort result + #[clap(long)] + pub sort_by: Option, + /// Print score with results #[clap(long, short)] pub score: bool, @@ -174,6 +178,13 @@ pub struct Query { pub exclude: Option, } +#[derive(ValueEnum, Debug, Clone, Copy)] +pub enum Ordering { + Path, + Score, + LastAccessed, +} + /// Remove a directory from the database #[derive(Debug, Parser)] #[clap( diff --git a/src/cmd/query.rs b/src/cmd/query.rs index 362d80a3..e279cef4 100644 --- a/src/cmd/query.rs +++ b/src/cmd/query.rs @@ -2,6 +2,7 @@ use std::io::{self, Write}; use anyhow::{Context, Result}; +use super::Ordering; use crate::cmd::{Query, Run}; use crate::config; use crate::db::{Database, Epoch, Stream, StreamOptions}; @@ -18,7 +19,8 @@ impl Run for Query { impl Query { fn query(&self, db: &mut Database) -> Result<()> { let now = util::current_time()?; - let mut stream = self.get_stream(db, now)?; + let ordering = self.sort_by; + let mut stream = self.get_stream(db, now, ordering)?; if self.interactive { self.query_interactive(&mut stream, now) @@ -76,10 +78,16 @@ impl Query { writeln!(handle, "{dir}").pipe_exit("stdout") } - fn get_stream<'a>(&self, db: &'a mut Database, now: Epoch) -> Result> { + fn get_stream<'a>( + &self, + db: &'a mut Database, + now: Epoch, + ordering: Option, + ) -> 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()?) + .sort_by(ordering); if !self.all { let resolve_symlinks = config::resolve_symlinks(); options = options.with_exists(true).with_resolve_symlinks(resolve_symlinks); diff --git a/src/db/mod.rs b/src/db/mod.rs index 171b4d79..43db8ca7 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -178,6 +178,15 @@ impl Database { self.with_dirty_mut(|dirty| *dirty = true); } + pub fn sort_by_last_accessed(&mut self) { + self.with_dirs_mut(|dirs| { + dirs.sort_unstable_by(|dir1: &Dir, dir2: &Dir| { + dir1.last_accessed.cmp(&dir2.last_accessed) + }) + }); + self.with_dirty_mut(|dirty| *dirty = true); + } + pub fn dirty(&self) -> bool { *self.borrow_dirty() } diff --git a/src/db/stream.rs b/src/db/stream.rs index 9b51af13..58984a0f 100644 --- a/src/db/stream.rs +++ b/src/db/stream.rs @@ -4,6 +4,7 @@ use std::{fs, path}; use glob::Pattern; +use crate::cmd::Ordering; use crate::db::{Database, Dir, Epoch}; use crate::util::{self, MONTH}; @@ -15,7 +16,11 @@ pub struct Stream<'a> { impl<'a> Stream<'a> { pub fn new(db: &'a mut Database, options: StreamOptions) -> Self { - db.sort_by_score(options.now); + match options.sort_by { + Ordering::Path => db.sort_by_path(), + Ordering::Score => db.sort_by_score(options.now), + Ordering::LastAccessed => db.sort_by_last_accessed(), + } let idxs = (0..db.dirs().len()).rev(); Stream { db, idxs, options } } @@ -108,6 +113,9 @@ pub struct StreamOptions { /// Directories that do not exist and haven't been accessed since TTL will /// be lazily removed. ttl: Epoch, + + /// Ordering of the stream entries + sort_by: Ordering, } impl StreamOptions { @@ -119,6 +127,7 @@ impl StreamOptions { exists: false, resolve_symlinks: false, ttl: now.saturating_sub(3 * MONTH), + sort_by: Ordering::Score, } } @@ -145,6 +154,13 @@ impl StreamOptions { self.resolve_symlinks = resolve_symlinks; self } + + pub fn sort_by(mut self, ordering: Option) -> Self { + if let Some(o) = ordering { + self.sort_by = o + }; + self + } } #[cfg(test)]