diff --git a/contrib/completions/_zoxide b/contrib/completions/_zoxide index 312adb6..b6c0e13 100644 --- a/contrib/completions/_zoxide +++ b/contrib/completions/_zoxide @@ -30,6 +30,10 @@ _zoxide() { case $line[1] in (add) _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]' \ '--help[Print help information]' \ '-V[Print version information]' \ diff --git a/contrib/completions/_zoxide.ps1 b/contrib/completions/_zoxide.ps1 index f1e0571..feb3e00 100644 --- a/contrib/completions/_zoxide.ps1 +++ b/contrib/completions/_zoxide.ps1 @@ -33,6 +33,10 @@ Register-ArgumentCompleter -Native -CommandName 'zoxide' -ScriptBlock { break } '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('--help', 'help', [CompletionResultType]::ParameterName, 'Print help information') [CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Print version information') diff --git a/contrib/completions/zoxide.bash b/contrib/completions/zoxide.bash index 03ad7a4..379e388 100644 --- a/contrib/completions/zoxide.bash +++ b/contrib/completions/zoxide.bash @@ -48,12 +48,28 @@ _zoxide() { return 0 ;; zoxide__add) - opts="-h -V --help --version ..." + opts="-i -d -h -V --increment --decrement --help --version ..." if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 fi 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=() ;; diff --git a/contrib/completions/zoxide.elv b/contrib/completions/zoxide.elv index 2e98e78..b76dd4f 100644 --- a/contrib/completions/zoxide.elv +++ b/contrib/completions/zoxide.elv @@ -29,6 +29,10 @@ set edit:completion:arg-completer[zoxide] = {|@words| cand remove 'Remove a directory from the database' } &'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 --help 'Print help information' cand -V 'Print version information' diff --git a/contrib/completions/zoxide.fish b/contrib/completions/zoxide.fish index 16bf84a..11f6cbc 100644 --- a/contrib/completions/zoxide.fish +++ b/contrib/completions/zoxide.fish @@ -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 "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_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 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 }" diff --git a/contrib/completions/zoxide.ts b/contrib/completions/zoxide.ts index fe986b7..af96424 100644 --- a/contrib/completions/zoxide.ts +++ b/contrib/completions/zoxide.ts @@ -6,6 +6,30 @@ const completion: Fig.Spec = { name: "add", description: "Add a new directory or increment its rank", 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"], description: "Print help information", diff --git a/src/cmd/_cmd.rs b/src/cmd/_cmd.rs index f161ef0..d9964d5 100644 --- a/src/cmd/_cmd.rs +++ b/src/cmd/_cmd.rs @@ -33,6 +33,14 @@ pub enum Cmd { pub struct Add { #[clap(min_values = 1, required = true, value_hint = ValueHint::DirPath)] pub paths: Vec, + + /// Increment path(s) score by specified amount. + #[clap(long, short, conflicts_with = "decrement")] + pub increment: Option, + + /// Decrement path(s) score by specified amount. Score won't go below 0. + #[clap(long, short, conflicts_with = "increment")] + pub decrement: Option, } /// Import entries from another application diff --git a/src/cmd/add.rs b/src/cmd/add.rs index 1bbe697..034e5d1 100644 --- a/src/cmd/add.rs +++ b/src/cmd/add.rs @@ -16,6 +16,13 @@ impl Run for Add { let exclude_dirs = config::exclude_dirs()?; let max_age = config::maxage()?; 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 = db.open()?; @@ -31,7 +38,7 @@ impl Run for Add { if !Path::new(path).is_dir() { bail!("not a directory: {}", path); } - db.add(path, now); + db.add(path, now, increment); } if db.modified { diff --git a/src/db/dir.rs b/src/db/dir.rs index 793bbc5..f196e59 100644 --- a/src/db/dir.rs +++ b/src/db/dir.rs @@ -6,6 +6,10 @@ use anyhow::{bail, Context, Result}; use bincode::Options as _; 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)] pub struct DirList<'a>(#[serde(borrow)] pub Vec>); @@ -89,10 +93,6 @@ pub struct Dir<'a> { impl Dir<'_> { 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. let duration = now.saturating_sub(self.last_accessed); if duration < HOUR { diff --git a/src/db/mod.rs b/src/db/mod.rs index 7d527ab..b7ffaeb 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -32,16 +32,19 @@ impl<'file> Database<'file> { } /// Adds a new directory or increments its rank. Also updates its last accessed time. - pub fn add>(&mut self, path: S, now: Epoch) { + pub fn add>(&mut self, path: S, now: Epoch, increment: f64) { let path = path.as_ref(); match self.dirs.iter_mut().find(|dir| dir.path == path) { 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) => { 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>(data_dir: P) -> PathBuf { #[cfg(test)] mod tests { + use super::dir::{DAY, HOUR, WEEK}; use super::*; + use rstest::rstest; #[test] fn add() { @@ -159,8 +164,8 @@ mod tests { { let mut db = DatabaseFile::new(data_dir.path()); let mut db = db.open().unwrap(); - db.add(path, now); - db.add(path, now); + db.add(path, now, 1.0); + db.add(path, now, 1.0); 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, + #[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] fn remove() { 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 = db.open().unwrap(); - db.add(path, now); + db.add(path, now, 1.0); db.save().unwrap(); } {