diff --git a/contrib/completions/_zoxide b/contrib/completions/_zoxide index 97e654f..861a95c 100644 --- a/contrib/completions/_zoxide +++ b/contrib/completions/_zoxide @@ -32,6 +32,8 @@ _zoxide() { _arguments "${_arguments_options[@]}" : \ '-s+[The rank to increment the entry if it exists or initialize it with if it doesn'\''t]:SCORE:_default' \ '--score=[The rank to increment the entry if it exists or initialize it with if it doesn'\''t]:SCORE:_default' \ +'-a+[The alias for the entry to initialize without and then set afterwards or initialize with it]:ALIAS:_default' \ +'--alias=[The alias for the entry to initialize without and then set afterwards or initialize with it]:ALIAS:_default' \ '-h[Print help]' \ '--help[Print help]' \ '-V[Print version]' \ diff --git a/contrib/completions/_zoxide.ps1 b/contrib/completions/_zoxide.ps1 index bb47d3a..7cb4c2e 100644 --- a/contrib/completions/_zoxide.ps1 +++ b/contrib/completions/_zoxide.ps1 @@ -36,6 +36,8 @@ Register-ArgumentCompleter -Native -CommandName 'zoxide' -ScriptBlock { 'zoxide;add' { [CompletionResult]::new('-s', '-s', [CompletionResultType]::ParameterName, 'The rank to increment the entry if it exists or initialize it with if it doesn''t') [CompletionResult]::new('--score', '--score', [CompletionResultType]::ParameterName, 'The rank to increment the entry if it exists or initialize it with if it doesn''t') + [CompletionResult]::new('-a', '-a', [CompletionResultType]::ParameterName, 'The alias for the entry to initialize without and then set afterwards or initialize with it') + [CompletionResult]::new('--alias', '--alias', [CompletionResultType]::ParameterName, 'The alias for the entry to initialize without and then set afterwards or initialize with it') [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 82b174e..4aa8de8 100644 --- a/contrib/completions/zoxide.bash +++ b/contrib/completions/zoxide.bash @@ -67,7 +67,7 @@ _zoxide() { return 0 ;; zoxide__add) - opts="-s -h -V --score --help --version ..." + opts="-s -a -h -V --score --alias --help --version ..." if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -81,6 +81,14 @@ _zoxide() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; + --alias) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + -a) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; *) COMPREPLY=() ;; diff --git a/contrib/completions/zoxide.elv b/contrib/completions/zoxide.elv index 93c57af..53fec8d 100644 --- a/contrib/completions/zoxide.elv +++ b/contrib/completions/zoxide.elv @@ -32,6 +32,8 @@ set edit:completion:arg-completer[zoxide] = {|@words| &'zoxide;add'= { cand -s 'The rank to increment the entry if it exists or initialize it with if it doesn''t' cand --score 'The rank to increment the entry if it exists or initialize it with if it doesn''t' + cand -a 'The alias for the entry to initialize without and then set afterwards or initialize with it' + cand --alias 'The alias for the entry to initialize without and then set afterwards or initialize with it' 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 3a0bfe7..40cdb9c 100644 --- a/contrib/completions/zoxide.fish +++ b/contrib/completions/zoxide.fish @@ -33,6 +33,7 @@ complete -c zoxide -n "__fish_zoxide_needs_command" -f -a "init" -d 'Generate sh complete -c zoxide -n "__fish_zoxide_needs_command" -f -a "query" -d 'Search for a directory in the database' complete -c zoxide -n "__fish_zoxide_needs_command" -f -a "remove" -d 'Remove a directory from the database' complete -c zoxide -n "__fish_zoxide_using_subcommand add" -s s -l score -d 'The rank to increment the entry if it exists or initialize it with if it doesn\'t' -r +complete -c zoxide -n "__fish_zoxide_using_subcommand add" -s a -l alias -d 'The alias for the entry to initialize without and then set afterwards or initialize with it' -r complete -c zoxide -n "__fish_zoxide_using_subcommand add" -s h -l help -d 'Print help' complete -c zoxide -n "__fish_zoxide_using_subcommand add" -s V -l version -d 'Print version' complete -c zoxide -n "__fish_zoxide_using_subcommand edit; and not __fish_seen_subcommand_from decrement delete increment reload" -s h -l help -d 'Print help' diff --git a/contrib/completions/zoxide.nu b/contrib/completions/zoxide.nu index 642908e..36c5aac 100644 --- a/contrib/completions/zoxide.nu +++ b/contrib/completions/zoxide.nu @@ -10,6 +10,7 @@ module completions { export extern "zoxide add" [ ...paths: path --score(-s): string # The rank to increment the entry if it exists or initialize it with if it doesn't + --alias(-a): string # The alias for the entry to initialize without and then set afterwards or initialize with it --help(-h) # Print help --version(-V) # Print version ] diff --git a/contrib/completions/zoxide.ts b/contrib/completions/zoxide.ts index 1e0d404..3c5c861 100644 --- a/contrib/completions/zoxide.ts +++ b/contrib/completions/zoxide.ts @@ -15,6 +15,15 @@ const completion: Fig.Spec = { isOptional: true, }, }, + { + name: ["-a", "--alias"], + description: "The alias for the entry to initialize without and then set afterwards or initialize with it", + isRepeatable: true, + args: { + name: "alias", + isOptional: true, + }, + }, { name: ["-h", "--help"], description: "Print help", diff --git a/src/cmd/add.rs b/src/cmd/add.rs index 302ae0a..dcc0b33 100644 --- a/src/cmd/add.rs +++ b/src/cmd/add.rs @@ -15,6 +15,7 @@ impl Run for Add { let exclude_dirs = config::exclude_dirs()?; let max_age = config::maxage()?; let now = util::current_time()?; + let mut first_entry = true; let mut db = Database::open()?; @@ -35,7 +36,14 @@ impl Run for Add { } let by = self.score.unwrap_or(1.0); - db.add_update(path, by, now); + + // Adds the alias only to the first entry to avoid confusion + if first_entry { + db.add_update(path, by, now, self.alias.clone()); + first_entry = false; + } else { + db.add_update(path, by, now, None); + } } if db.dirty() { diff --git a/src/cmd/cmd.rs b/src/cmd/cmd.rs index 7359786..3d82a64 100644 --- a/src/cmd/cmd.rs +++ b/src/cmd/cmd.rs @@ -63,6 +63,11 @@ pub struct Add { /// doesn't #[clap(short, long)] pub score: Option, + + /// The alias for the entry to initialize without and then set afterwards or + /// initialize with it + #[clap(short, long)] + pub alias: Option, } /// Edit the database diff --git a/src/cmd/edit.rs b/src/cmd/edit.rs index 0f37165..f1a231e 100644 --- a/src/cmd/edit.rs +++ b/src/cmd/edit.rs @@ -15,11 +15,11 @@ impl Run for Edit { match &self.cmd { Some(cmd) => { match cmd { - EditCommand::Decrement { path } => db.add(path, -1.0, now), + EditCommand::Decrement { path } => db.add(path, -1.0, now, None), EditCommand::Delete { path } => { db.remove(path); } - EditCommand::Increment { path } => db.add(path, 1.0, now), + EditCommand::Increment { path } => db.add(path, 1.0, now, None), EditCommand::Reload => {} } db.save()?; diff --git a/src/cmd/import.rs b/src/cmd/import.rs index f13381c..c7dd95b 100644 --- a/src/cmd/import.rs +++ b/src/cmd/import.rs @@ -40,7 +40,7 @@ fn import_autojump(db: &mut Database, buffer: &str) -> Result<()> { // take a while to get normalized. rank = sigmoid(rank); - db.add_unchecked(path, rank, 0); + db.add_unchecked(path, rank, 0, None); } if db.dirty() { @@ -65,7 +65,7 @@ fn import_z(db: &mut Database, buffer: &str) -> Result<()> { let path = split.next().with_context(|| format!("invalid entry: {line}"))?; - db.add_unchecked(path, rank, last_accessed); + db.add_unchecked(path, rank, last_accessed, None); } if db.dirty() { @@ -94,7 +94,7 @@ mod tests { ("/xyzzy/thud", 8.0, 800), ("/foo/bar", 9.0, 900), ] { - db.add_unchecked(path, rank, last_accessed); + db.add_unchecked(path, rank, last_accessed, None); } let buffer = "\ @@ -107,12 +107,22 @@ mod tests { println!("got: {:?}", &db.dirs()); let exp = [ - Dir { path: "/baz".into(), rank: sigmoid(7.0), last_accessed: 0 }, - Dir { path: "/corge/grault/garply".into(), rank: 6.0, last_accessed: 600 }, - Dir { path: "/foo/bar".into(), rank: 9.0 + sigmoid(2.0), last_accessed: 900 }, - Dir { path: "/quux/quuz".into(), rank: 1.0 + sigmoid(5.0), last_accessed: 100 }, - Dir { path: "/waldo/fred/plugh".into(), rank: 3.0, last_accessed: 300 }, - Dir { path: "/xyzzy/thud".into(), rank: 8.0, last_accessed: 800 }, + Dir { path: "/baz".into(), rank: sigmoid(7.0), last_accessed: 0, alias: None }, + Dir { path: "/corge/grault/garply".into(), rank: 6.0, last_accessed: 600, alias: None }, + Dir { + path: "/foo/bar".into(), + rank: 9.0 + sigmoid(2.0), + last_accessed: 900, + alias: None, + }, + Dir { + path: "/quux/quuz".into(), + rank: 1.0 + sigmoid(5.0), + last_accessed: 100, + alias: None, + }, + Dir { path: "/waldo/fred/plugh".into(), rank: 3.0, last_accessed: 300, alias: None }, + Dir { path: "/xyzzy/thud".into(), rank: 8.0, last_accessed: 800, alias: None }, ]; println!("exp: {exp:?}"); @@ -134,7 +144,7 @@ mod tests { ("/xyzzy/thud", 8.0, 800), ("/foo/bar", 9.0, 900), ] { - db.add_unchecked(path, rank, last_accessed); + db.add_unchecked(path, rank, last_accessed, None); } let buffer = "\ @@ -148,12 +158,12 @@ mod tests { println!("got: {:?}", &db.dirs()); let exp = [ - Dir { path: "/baz".into(), rank: 7.0, last_accessed: 700 }, - Dir { path: "/corge/grault/garply".into(), rank: 6.0, last_accessed: 600 }, - Dir { path: "/foo/bar".into(), rank: 11.0, last_accessed: 900 }, - Dir { path: "/quux/quuz".into(), rank: 10.0, last_accessed: 500 }, - Dir { path: "/waldo/fred/plugh".into(), rank: 3.0, last_accessed: 300 }, - Dir { path: "/xyzzy/thud".into(), rank: 8.0, last_accessed: 800 }, + Dir { path: "/baz".into(), rank: 7.0, last_accessed: 700, alias: None }, + Dir { path: "/corge/grault/garply".into(), rank: 6.0, last_accessed: 600, alias: None }, + Dir { path: "/foo/bar".into(), rank: 11.0, last_accessed: 900, alias: None }, + Dir { path: "/quux/quuz".into(), rank: 10.0, last_accessed: 500, alias: None }, + Dir { path: "/waldo/fred/plugh".into(), rank: 3.0, last_accessed: 300, alias: None }, + Dir { path: "/xyzzy/thud".into(), rank: 8.0, last_accessed: 800, alias: None }, ]; println!("exp: {exp:?}"); diff --git a/src/db/dir.rs b/src/db/dir.rs index 5d6d62c..4c03761 100644 --- a/src/db/dir.rs +++ b/src/db/dir.rs @@ -11,6 +11,7 @@ pub struct Dir<'a> { pub path: Cow<'a, str>, pub rank: Rank, pub last_accessed: Epoch, + pub alias: Option, } impl Dir<'_> { diff --git a/src/db/mod.rs b/src/db/mod.rs index d459f39..2e54b9e 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -64,13 +64,27 @@ impl Database { Ok(()) } - /// Increments the rank of a directory, or creates it if it does not exist. - pub fn add(&mut self, path: impl AsRef + Into, by: Rank, now: Epoch) { + /// Increments the rank of a directory and updates its alias if provided, or + /// creates with an optional alias it if it does not exist. + pub fn add( + &mut self, + path: impl AsRef + Into, + by: Rank, + now: Epoch, + alias: Option, + ) { self.with_dirs_mut(|dirs| match dirs.iter_mut().find(|dir| dir.path == path.as_ref()) { - Some(dir) => dir.rank = (dir.rank + by).max(0.0), - None => { - dirs.push(Dir { path: path.into().into(), rank: by.max(0.0), last_accessed: now }) + Some(dir) if alias.is_some() => { + dir.rank = (dir.rank + by).max(0.0); + dir.alias = alias; } + Some(dir) => dir.rank = (dir.rank + by).max(0.0), + None => dirs.push(Dir { + path: path.into().into(), + rank: by.max(0.0), + last_accessed: now, + alias, + }), }); self.with_dirty_mut(|dirty| *dirty = true); } @@ -78,24 +92,44 @@ impl Database { /// Creates a new directory. This will create a duplicate entry if this /// directory is always in the database, it is expected that the user either /// does a check before calling this, or calls `dedup()` afterward. - pub fn add_unchecked(&mut self, path: impl AsRef + Into, rank: Rank, now: Epoch) { + pub fn add_unchecked( + &mut self, + path: impl AsRef + Into, + rank: Rank, + now: Epoch, + alias: Option, + ) { self.with_dirs_mut(|dirs| { - dirs.push(Dir { path: path.into().into(), rank, last_accessed: now }) + dirs.push(Dir { path: path.into().into(), rank, last_accessed: now, alias }) }); self.with_dirty_mut(|dirty| *dirty = true); } - /// Increments the rank and updates the last_accessed of a directory, or - /// creates it if it does not exist. - pub fn add_update(&mut self, path: impl AsRef + Into, by: Rank, now: Epoch) { + /// Increments the rank and updates the last_accessed and alias of a + /// directory or creates it if it does not exist. + pub fn add_update( + &mut self, + path: impl AsRef + Into, + by: Rank, + now: Epoch, + alias: Option, + ) { self.with_dirs_mut(|dirs| match dirs.iter_mut().find(|dir| dir.path == path.as_ref()) { + Some(dir) if alias.is_some() => { + dir.rank = (dir.rank + by).max(0.0); + dir.last_accessed = now; + dir.alias = alias; + } Some(dir) => { dir.rank = (dir.rank + by).max(0.0); dir.last_accessed = now; } - None => { - dirs.push(Dir { path: path.into().into(), rank: by.max(0.0), last_accessed: now }) - } + None => dirs.push(Dir { + path: path.into().into(), + rank: by.max(0.0), + last_accessed: now, + alias, + }), }); self.with_dirty_mut(|dirty| *dirty = true); } @@ -240,11 +274,14 @@ mod tests { let data_dir = tempfile::tempdir().unwrap(); let path = if cfg!(windows) { r"C:\foo\bar" } else { "/foo/bar" }; let now = 946684800; + let empty_alias: Option = None; + let alias: Option = Some(String::from("alias")); { let mut db = Database::open_dir(data_dir.path()).unwrap(); - db.add(path, 1.0, now); - db.add(path, 1.0, now); + db.add(path, 1.0, now, empty_alias.clone()); + db.add(path, 1.0, now, alias.clone()); + db.add(path, 1.0, now, empty_alias.clone()); db.save().unwrap(); } @@ -254,8 +291,10 @@ mod tests { let dir = &db.dirs()[0]; assert_eq!(dir.path, path); - assert!((dir.rank - 2.0).abs() < 0.01); + assert!((dir.rank - 3.0).abs() < 0.01); assert_eq!(dir.last_accessed, now); + assert_eq!(dir.alias, alias); + assert_ne!(dir.alias, empty_alias); } } @@ -267,7 +306,7 @@ mod tests { { let mut db = Database::open_dir(data_dir.path()).unwrap(); - db.add(path, 1.0, now); + db.add(path, 1.0, now, None); db.save().unwrap(); } diff --git a/src/db/stream.rs b/src/db/stream.rs index 4b06193..dada124 100644 --- a/src/db/stream.rs +++ b/src/db/stream.rs @@ -25,7 +25,9 @@ impl<'a> Stream<'a> { while let Some(idx) = self.idxs.next() { let dir = &self.db.dirs()[idx]; - if !self.filter_by_keywords(&dir.path) { + if !self.filter_path_by_keywords(&dir.path) + && !self.filter_alias_by_keywords(&dir.alias) + { continue; } @@ -77,7 +79,7 @@ impl<'a> Stream<'a> { resolver(path).map(|metadata| metadata.is_dir()).unwrap_or_default() } - fn filter_by_keywords(&self, path: &str) -> bool { + fn filter_path_by_keywords(&self, path: &str) -> bool { let (keywords_last, keywords) = match self.options.keywords.split_last() { Some(split) => split, None => return true, @@ -104,6 +106,16 @@ impl<'a> Stream<'a> { true } + + fn filter_alias_by_keywords(&self, alias: &Option) -> bool { + match alias { + Some(alias) => { + let alias = alias.to_lowercase(); + self.options.keywords.iter().all(|kw| alias.contains(kw)) + } + None => false, + } + } } pub struct StreamOptions { @@ -206,6 +218,6 @@ mod tests { let db = &mut Database::new(PathBuf::new(), Vec::new(), |_| Vec::new(), false); let options = StreamOptions::new(0).with_keywords(keywords.iter()); let stream = Stream::new(db, options); - assert_eq!(is_match, stream.filter_by_keywords(path)); + assert_eq!(is_match, stream.filter_path_by_keywords(path)); } }