Add manual score adjustment

This commit adds '-i' and '-d' options to the 'zoxide add' command.
'-i' increments the base score by a specified amount.
'-d' decrements the base score by a specified amount.

Also added unit tests covering the new functionality combined with
recency scoring.

Fixes/Supports #392
This commit is contained in:
James Falcon 2022-07-10 22:04:49 -05:00
parent 31c44f0649
commit 7c6e003ded
10 changed files with 133 additions and 12 deletions

View File

@ -30,6 +30,10 @@ _zoxide() {
case $line[1] in case $line[1] in
(add) (add)
_arguments "${_arguments_options[@]}" \ _arguments "${_arguments_options[@]}" \
'(-d --decrement)-i+[Increment path(s) score by specified amount]:INCREMENT: ' \
'(-d --decrement)--increment=[Increment path(s) score by specified amount]:INCREMENT: ' \
'(-i --increment)-d+[Decrement path(s) score by specified amount. Score won'\''t go below 0]:DECREMENT: ' \
'(-i --increment)--decrement=[Decrement path(s) score by specified amount. Score won'\''t go below 0]:DECREMENT: ' \
'-h[Print help information]' \ '-h[Print help information]' \
'--help[Print help information]' \ '--help[Print help information]' \
'-V[Print version information]' \ '-V[Print version information]' \

View File

@ -33,6 +33,10 @@ Register-ArgumentCompleter -Native -CommandName 'zoxide' -ScriptBlock {
break break
} }
'zoxide;add' { 'zoxide;add' {
[CompletionResult]::new('-i', 'i', [CompletionResultType]::ParameterName, 'Increment path(s) score by specified amount')
[CompletionResult]::new('--increment', 'increment', [CompletionResultType]::ParameterName, 'Increment path(s) score by specified amount')
[CompletionResult]::new('-d', 'd', [CompletionResultType]::ParameterName, 'Decrement path(s) score by specified amount. Score won''t go below 0')
[CompletionResult]::new('--decrement', 'decrement', [CompletionResultType]::ParameterName, 'Decrement path(s) score by specified amount. Score won''t go below 0')
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help information') [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help information')
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help information') [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help information')
[CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Print version information') [CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Print version information')

View File

@ -48,12 +48,28 @@ _zoxide() {
return 0 return 0
;; ;;
zoxide__add) zoxide__add)
opts="-h -V --help --version <PATHS>..." opts="-i -d -h -V --increment --decrement --help --version <PATHS>..."
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0 return 0
fi fi
case "${prev}" in case "${prev}" in
--increment)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
-i)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--decrement)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
-d)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
*) *)
COMPREPLY=() COMPREPLY=()
;; ;;

View File

@ -29,6 +29,10 @@ set edit:completion:arg-completer[zoxide] = {|@words|
cand remove 'Remove a directory from the database' cand remove 'Remove a directory from the database'
} }
&'zoxide;add'= { &'zoxide;add'= {
cand -i 'Increment path(s) score by specified amount'
cand --increment 'Increment path(s) score by specified amount'
cand -d 'Decrement path(s) score by specified amount. Score won''t go below 0'
cand --decrement 'Decrement path(s) score by specified amount. Score won''t go below 0'
cand -h 'Print help information' cand -h 'Print help information'
cand --help 'Print help information' cand --help 'Print help information'
cand -V 'Print version information' cand -V 'Print version information'

View File

@ -5,6 +5,8 @@ complete -c zoxide -n "__fish_use_subcommand" -f -a "import" -d 'Import entries
complete -c zoxide -n "__fish_use_subcommand" -f -a "init" -d 'Generate shell configuration' complete -c zoxide -n "__fish_use_subcommand" -f -a "init" -d 'Generate shell configuration'
complete -c zoxide -n "__fish_use_subcommand" -f -a "query" -d 'Search for a directory in the database' complete -c zoxide -n "__fish_use_subcommand" -f -a "query" -d 'Search for a directory in the database'
complete -c zoxide -n "__fish_use_subcommand" -f -a "remove" -d 'Remove a directory from the database' complete -c zoxide -n "__fish_use_subcommand" -f -a "remove" -d 'Remove a directory from the database'
complete -c zoxide -n "__fish_seen_subcommand_from add" -s i -l increment -d 'Increment path(s) score by specified amount' -r
complete -c zoxide -n "__fish_seen_subcommand_from add" -s d -l decrement -d 'Decrement path(s) score by specified amount. Score won\'t go below 0' -r
complete -c zoxide -n "__fish_seen_subcommand_from add" -s h -l help -d 'Print help information' complete -c zoxide -n "__fish_seen_subcommand_from add" -s h -l help -d 'Print help information'
complete -c zoxide -n "__fish_seen_subcommand_from add" -s V -l version -d 'Print version information' complete -c zoxide -n "__fish_seen_subcommand_from add" -s V -l version -d 'Print version information'
complete -c zoxide -n "__fish_seen_subcommand_from import" -l from -d 'Application to import from' -r -f -a "{autojump ,z }" complete -c zoxide -n "__fish_seen_subcommand_from import" -l from -d 'Application to import from' -r -f -a "{autojump ,z }"

View File

@ -6,6 +6,30 @@ const completion: Fig.Spec = {
name: "add", name: "add",
description: "Add a new directory or increment its rank", description: "Add a new directory or increment its rank",
options: [ options: [
{
name: ["-i", "--increment"],
description: "Increment path(s) score by specified amount",
exclusiveOn: [
"-d",
"--decrement",
],
args: {
name: "increment",
isOptional: true,
},
},
{
name: ["-d", "--decrement"],
description: "Decrement path(s) score by specified amount. Score won't go below 0",
exclusiveOn: [
"-i",
"--increment",
],
args: {
name: "decrement",
isOptional: true,
},
},
{ {
name: ["-h", "--help"], name: ["-h", "--help"],
description: "Print help information", description: "Print help information",

View File

@ -33,6 +33,14 @@ pub enum Cmd {
pub struct Add { pub struct Add {
#[clap(min_values = 1, required = true, value_hint = ValueHint::DirPath)] #[clap(min_values = 1, required = true, value_hint = ValueHint::DirPath)]
pub paths: Vec<PathBuf>, pub paths: Vec<PathBuf>,
/// Increment path(s) score by specified amount.
#[clap(long, short, conflicts_with = "decrement")]
pub increment: Option<u16>,
/// Decrement path(s) score by specified amount. Score won't go below 0.
#[clap(long, short, conflicts_with = "increment")]
pub decrement: Option<u16>,
} }
/// Import entries from another application /// Import entries from another application

View File

@ -16,6 +16,13 @@ impl Run for Add {
let exclude_dirs = config::exclude_dirs()?; let exclude_dirs = config::exclude_dirs()?;
let max_age = config::maxage()?; let max_age = config::maxage()?;
let now = util::current_time()?; let now = util::current_time()?;
let increment = if let Some(num) = self.increment {
num as f64
} else if let Some(num) = self.decrement {
-(num as f64)
} else {
1.0
};
let mut db = DatabaseFile::new(data_dir); let mut db = DatabaseFile::new(data_dir);
let mut db = db.open()?; let mut db = db.open()?;
@ -31,7 +38,7 @@ impl Run for Add {
if !Path::new(path).is_dir() { if !Path::new(path).is_dir() {
bail!("not a directory: {}", path); bail!("not a directory: {}", path);
} }
db.add(path, now); db.add(path, now, increment);
} }
if db.modified { if db.modified {

View File

@ -6,6 +6,10 @@ use anyhow::{bail, Context, Result};
use bincode::Options as _; use bincode::Options as _;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
pub const HOUR: Epoch = 60 * 60;
pub const DAY: Epoch = 24 * HOUR;
pub const WEEK: Epoch = 7 * DAY;
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize)]
pub struct DirList<'a>(#[serde(borrow)] pub Vec<Dir<'a>>); pub struct DirList<'a>(#[serde(borrow)] pub Vec<Dir<'a>>);
@ -89,10 +93,6 @@ pub struct Dir<'a> {
impl Dir<'_> { impl Dir<'_> {
pub fn score(&self, now: Epoch) -> Rank { pub fn score(&self, now: Epoch) -> Rank {
const HOUR: Epoch = 60 * 60;
const DAY: Epoch = 24 * HOUR;
const WEEK: Epoch = 7 * DAY;
// The older the entry, the lesser its importance. // The older the entry, the lesser its importance.
let duration = now.saturating_sub(self.last_accessed); let duration = now.saturating_sub(self.last_accessed);
if duration < HOUR { if duration < HOUR {

View File

@ -32,16 +32,19 @@ impl<'file> Database<'file> {
} }
/// Adds a new directory or increments its rank. Also updates its last accessed time. /// Adds a new directory or increments its rank. Also updates its last accessed time.
pub fn add<S: AsRef<str>>(&mut self, path: S, now: Epoch) { pub fn add<S: AsRef<str>>(&mut self, path: S, now: Epoch, increment: f64) {
let path = path.as_ref(); let path = path.as_ref();
match self.dirs.iter_mut().find(|dir| dir.path == path) { match self.dirs.iter_mut().find(|dir| dir.path == path) {
None => { None => {
self.dirs.push(Dir { path: path.to_string().into(), last_accessed: now, rank: 1.0 }); self.dirs.push(Dir { path: path.to_string().into(), last_accessed: now, rank: increment });
} }
Some(dir) => { Some(dir) => {
dir.last_accessed = now; dir.last_accessed = now;
dir.rank += 1.0; dir.rank += increment;
if dir.rank < 0.0 {
dir.rank = 0.0;
}
} }
}; };
@ -148,7 +151,9 @@ fn db_path<P: AsRef<Path>>(data_dir: P) -> PathBuf {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::dir::{DAY, HOUR, WEEK};
use super::*; use super::*;
use rstest::rstest;
#[test] #[test]
fn add() { fn add() {
@ -159,8 +164,8 @@ mod tests {
{ {
let mut db = DatabaseFile::new(data_dir.path()); let mut db = DatabaseFile::new(data_dir.path());
let mut db = db.open().unwrap(); let mut db = db.open().unwrap();
db.add(path, now); db.add(path, now, 1.0);
db.add(path, now); db.add(path, now, 1.0);
db.save().unwrap(); db.save().unwrap();
} }
{ {
@ -174,6 +179,53 @@ mod tests {
} }
} }
#[rstest]
// Nominal case
#[case(1.0, None, HOUR - 10, 4.0)]
#[case(1.0, None, DAY - 10, 2.0)]
#[case(1.0, None, WEEK - 10, 0.5)]
#[case(1.0, None, WEEK + 10, 0.25)]
// Start with higher priority
#[case(10.0, None, HOUR - 10, 40.0)]
#[case(10.0, None, DAY - 10, 20.0)]
#[case(10.0, None, WEEK - 10, 5.0)]
#[case(10.0, None, WEEK + 10, 2.5)]
// Increment priority
#[case(1.0, Some(9.0), HOUR - 10, 40.0)]
#[case(1.0, Some(9.0), DAY - 10, 20.0)]
#[case(1.0, Some(9.0), WEEK - 10, 5.0)]
#[case(1.0, Some(9.0), WEEK + 10, 2.5)]
// Decrement priority
#[case(10.0, Some(-5.0), HOUR - 10, 20.0)]
#[case(10.0, Some(-5.0), DAY - 10, 10.0)]
#[case(10.0, Some(-5.0), WEEK - 10, 2.5)]
#[case(10.0, Some(-5.0), WEEK + 10, 1.25)]
// Attempt decrement < 0
#[case(1.0, Some(-5.0), HOUR - 10, 0.0)]
#[case(1.0, Some(-5.0), DAY - 10, 0.0)]
#[case(1.0, Some(-5.0), WEEK - 10, 0.0)]
#[case(1.0, Some(-5.0), WEEK + 10, 0.0)]
fn add_increment(
#[case] first_increment: f64,
#[case] second_increment: Option<f64>,
#[case] accessed: u64,
#[case] expected: f64,
) {
let path = if cfg!(windows) { r"C:\foo\bar" } else { "/foo/bar" };
let data_dir = tempfile::tempdir().unwrap();
let mut db = DatabaseFile::new(data_dir.path());
let mut db = db.open().unwrap();
let now = 10000000;
db.add(path, now - accessed, first_increment);
if let Some(more) = second_increment {
db.add(path, now - accessed, more);
}
let dir = &db.dirs[0];
// Float version of assert dir.score(now) == expected
assert!((dir.score(now) - expected).abs() < 0.0001);
}
#[test] #[test]
fn remove() { fn remove() {
let path = if cfg!(windows) { r"C:\foo\bar" } else { "/foo/bar" }; let path = if cfg!(windows) { r"C:\foo\bar" } else { "/foo/bar" };
@ -183,7 +235,7 @@ mod tests {
{ {
let mut db = DatabaseFile::new(data_dir.path()); let mut db = DatabaseFile::new(data_dir.path());
let mut db = db.open().unwrap(); let mut db = db.open().unwrap();
db.add(path, now); db.add(path, now, 1.0);
db.save().unwrap(); db.save().unwrap();
} }
{ {