cmd/query: Add `--sort-by` option

Added the option `--sort-by [score|path|last-accessed]` for the `query`
subcommand with default value `score` i.e:

```bash
$ zoxide query --list --score --sort-by last-accessed
   4.0 /home/martin/playground/scala/scala-3-project-template
   3.5 /home/martin/playground/rust
   3.8 /home/martin/playground/python
  12.0 /home/martin/projects/zoxide
  11.8 /home/martin/playground
  16.5 /home/martin/projects/thesis
   6.0 /home/martin/projects/thesis/frontend
...
```

```bash
$ zoxide query --list --score --sort-by path
   0.2 /home/martin/.local/state
   0.2 /home/martin/.local/share/zoxide
   0.2 /home/martin/.local/share
   0.2 /home/martin/.config/protonmail/bridge-v3
   0.2 /home/martin/.config/protonmail
   0.5 /home
   0.5 /etc/nixos/lib/lisp
   0.8 /
...
```

```bash
$ zoxide query --list --score
  16.5 /home/martin/projects/thesis
  16.0 /home/martin/projects/zoxide
   8.0 /home/martin/projects/egcd
   8.0 /persist
   6.0 /home/martin/projects/thesis/frontend
...
```

Fixes #784 as well as #815.
This commit is contained in:
Martin Zacho 2024-07-15 17:48:07 +02:00
parent 8da8f50eaa
commit ac98f4044c
10 changed files with 71 additions and 5 deletions

View File

@ -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]' \

View File

@ -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')

View File

@ -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

View File

@ -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'

View File

@ -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'

View File

@ -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",

View File

@ -165,6 +165,10 @@ pub struct Query {
#[clap(long, short, conflicts_with = "interactive")]
pub list: bool,
/// Sort result
#[clap(long)]
pub sort_by: Option<Ordering>,
/// Print score with results
#[clap(long, short)]
pub score: bool,
@ -174,6 +178,13 @@ pub struct Query {
pub exclude: Option<String>,
}
#[derive(ValueEnum, Debug, Clone, Copy)]
pub enum Ordering {
Path,
Score,
LastAccessed,
}
/// Remove a directory from the database
#[derive(Debug, Parser)]
#[clap(

View File

@ -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<Stream<'a>> {
fn get_stream<'a>(
&self,
db: &'a mut Database,
now: Epoch,
ordering: Option<Ordering>,
) -> Result<Stream<'a>> {
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);

View File

@ -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()
}

View File

@ -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<Ordering>) -> Self {
if let Some(o) = ordering {
self.sort_by = o
};
self
}
}
#[cfg(test)]