diff --git a/contrib/completions/_zoxide b/contrib/completions/_zoxide index 2d57f9c..d033796 100644 --- a/contrib/completions/_zoxide +++ b/contrib/completions/_zoxide @@ -32,8 +32,6 @@ _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+[]:ALIAS:_default' \ -'--alias=[]:ALIAS:_default' \ '-h[Print help]' \ '--help[Print help]' \ '-V[Print version]' \ @@ -41,6 +39,17 @@ _arguments "${_arguments_options[@]}" : \ '*::paths:_files -/' \ && ret=0 ;; +(add-alias) +_arguments "${_arguments_options[@]}" : \ +'-p+[]:PATH:_files -/' \ +'--path=[]:PATH:_files -/' \ +'-h[Print help]' \ +'--help[Print help]' \ +'-V[Print version]' \ +'--version[Print version]' \ +'*::aliases:_default' \ +&& ret=0 +;; (edit) _arguments "${_arguments_options[@]}" : \ '-h[Print help]' \ @@ -221,6 +230,7 @@ esac _zoxide_commands() { local commands; commands=( 'add:Add a new directory or increment its rank' \ +'add-alias:Add aliases for a directory' \ 'edit:Edit the database' \ 'import:Import entries from another application' \ 'init:Generate shell configuration' \ @@ -234,6 +244,11 @@ _zoxide__subcmd__add_commands() { local commands; commands=() _describe -t commands 'zoxide add commands' commands "$@" } +(( $+functions[_zoxide__subcmd__add-alias_commands] )) || +_zoxide__subcmd__add-alias_commands() { + local commands; commands=() + _describe -t commands 'zoxide add-alias commands' commands "$@" +} (( $+functions[_zoxide__subcmd__edit_commands] )) || _zoxide__subcmd__edit_commands() { local commands; commands=( diff --git a/contrib/completions/_zoxide.ps1 b/contrib/completions/_zoxide.ps1 index 52a8f46..c282274 100644 --- a/contrib/completions/_zoxide.ps1 +++ b/contrib/completions/_zoxide.ps1 @@ -26,6 +26,7 @@ Register-ArgumentCompleter -Native -CommandName 'zoxide' -ScriptBlock { [CompletionResult]::new('-V', '-V ', [CompletionResultType]::ParameterName, 'Print version') [CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'Print version') [CompletionResult]::new('add', 'add', [CompletionResultType]::ParameterValue, 'Add a new directory or increment its rank') + [CompletionResult]::new('add-alias', 'add-alias', [CompletionResultType]::ParameterValue, 'Add aliases for a directory') [CompletionResult]::new('edit', 'edit', [CompletionResultType]::ParameterValue, 'Edit the database') [CompletionResult]::new('import', 'import', [CompletionResultType]::ParameterValue, 'Import entries from another application') [CompletionResult]::new('init', 'init', [CompletionResultType]::ParameterValue, 'Generate shell configuration') @@ -36,8 +37,15 @@ 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, 'a') - [CompletionResult]::new('--alias', '--alias', [CompletionResultType]::ParameterName, 'alias') + [CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help') + [CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help') + [CompletionResult]::new('-V', '-V ', [CompletionResultType]::ParameterName, 'Print version') + [CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'Print version') + break + } + 'zoxide;add-alias' { + [CompletionResult]::new('-p', '-p', [CompletionResultType]::ParameterName, 'p') + [CompletionResult]::new('--path', '--path', [CompletionResultType]::ParameterName, 'path') [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 24ffa9f..5e99429 100644 --- a/contrib/completions/zoxide.bash +++ b/contrib/completions/zoxide.bash @@ -19,6 +19,9 @@ _zoxide() { zoxide,add) cmd="zoxide__subcmd__add" ;; + zoxide,add-alias) + cmd="zoxide__subcmd__add__subcmd__alias" + ;; zoxide,edit) cmd="zoxide__subcmd__edit" ;; @@ -71,7 +74,7 @@ _zoxide() { case "${cmd}" in zoxide) - opts="-h -V --help --version add edit import init query remove" + opts="-h -V --help --version add add-alias edit import init query remove" if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -85,7 +88,7 @@ _zoxide() { return 0 ;; zoxide__subcmd__add) - opts="-s -a -h -V --score --alias --help --version ..." + opts="-s -h -V --score --help --version ..." if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -99,12 +102,32 @@ _zoxide() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; - --alias) - COMPREPLY=($(compgen -f "${cur}")) + *) + COMPREPLY=() + ;; + esac + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + ;; + zoxide__subcmd__add__subcmd__alias) + opts="-p -h -V --path --help --version ..." + if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + fi + case "${prev}" in + --path) + COMPREPLY=() + if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then + compopt -o plusdirs + fi return 0 ;; - -a) - COMPREPLY=($(compgen -f "${cur}")) + -p) + COMPREPLY=() + if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then + compopt -o plusdirs + fi return 0 ;; *) diff --git a/contrib/completions/zoxide.elv b/contrib/completions/zoxide.elv index b72e26b..7294b26 100644 --- a/contrib/completions/zoxide.elv +++ b/contrib/completions/zoxide.elv @@ -23,6 +23,7 @@ set edit:completion:arg-completer[zoxide] = {|@words| cand -V 'Print version' cand --version 'Print version' cand add 'Add a new directory or increment its rank' + cand add-alias 'Add aliases for a directory' cand edit 'Edit the database' cand import 'Import entries from another application' cand init 'Generate shell configuration' @@ -32,8 +33,14 @@ 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 'a' - cand --alias 'alias' + cand -h 'Print help' + cand --help 'Print help' + cand -V 'Print version' + cand --version 'Print version' + } + &'zoxide;add-alias'= { + cand -p 'p' + cand --path 'path' 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 df1ad3a..9eda694 100644 --- a/contrib/completions/zoxide.fish +++ b/contrib/completions/zoxide.fish @@ -27,15 +27,18 @@ end complete -c zoxide -n "__fish_zoxide_needs_command" -s h -l help -d 'Print help' complete -c zoxide -n "__fish_zoxide_needs_command" -s V -l version -d 'Print version' complete -c zoxide -n "__fish_zoxide_needs_command" -f -a "add" -d 'Add a new directory or increment its rank' +complete -c zoxide -n "__fish_zoxide_needs_command" -f -a "add-alias" -d 'Add aliases for a directory' complete -c zoxide -n "__fish_zoxide_needs_command" -f -a "edit" -d 'Edit the database' complete -c zoxide -n "__fish_zoxide_needs_command" -f -a "import" -d 'Import entries from another application' complete -c zoxide -n "__fish_zoxide_needs_command" -f -a "init" -d 'Generate shell configuration' 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 -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 add-alias" -s p -l path -r -f -a "(__fish_complete_directories)" +complete -c zoxide -n "__fish_zoxide_using_subcommand add-alias" -s h -l help -d 'Print help' +complete -c zoxide -n "__fish_zoxide_using_subcommand add-alias" -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' complete -c zoxide -n "__fish_zoxide_using_subcommand edit; and not __fish_seen_subcommand_from decrement delete increment reload" -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" -f -a "decrement" diff --git a/contrib/completions/zoxide.nu b/contrib/completions/zoxide.nu index c1f49ec..e25b011 100644 --- a/contrib/completions/zoxide.nu +++ b/contrib/completions/zoxide.nu @@ -9,12 +9,19 @@ module completions { # Add a new directory or increment its rank export extern "zoxide add" [ --score(-s): string # The rank to increment the entry if it exists or initialize it with if it doesn't - --alias(-a): string --help(-h) # Print help --version(-V) # Print version ...paths: path ] + # Add aliases for a directory + export extern "zoxide add-alias" [ + --path(-p): path + --help(-h) # Print help + --version(-V) # Print version + ...aliases: string + ] + # Edit the database export extern "zoxide edit" [ --help(-h) # Print help diff --git a/contrib/completions/zoxide.ts b/contrib/completions/zoxide.ts index 9070556..f34b4a1 100644 --- a/contrib/completions/zoxide.ts +++ b/contrib/completions/zoxide.ts @@ -15,14 +15,6 @@ const completion: Fig.Spec = { isOptional: true, }, }, - { - name: ["-a", "--alias"], - isRepeatable: true, - args: { - name: "alias", - isOptional: true, - }, - }, { name: ["-h", "--help"], description: "Print help", @@ -38,6 +30,32 @@ const completion: Fig.Spec = { template: "folders", }, }, + { + name: "add-alias", + description: "Add aliases for a directory", + options: [ + { + name: ["-p", "--path"], + isRepeatable: true, + args: { + name: "path", + template: "folders", + }, + }, + { + name: ["-h", "--help"], + description: "Print help", + }, + { + name: ["-V", "--version"], + description: "Print version", + }, + ], + args: { + name: "aliases", + isVariadic: true, + }, + }, { name: "edit", description: "Edit the database", diff --git a/src/cmd/add.rs b/src/cmd/add.rs index 403590b..302ae0a 100644 --- a/src/cmd/add.rs +++ b/src/cmd/add.rs @@ -18,7 +18,6 @@ impl Run for Add { let mut db = Database::open()?; - let mut first = true; for path in &self.paths { let path = if config::resolve_symlinks() { util::canonicalize } else { util::resolve_path }( @@ -36,9 +35,7 @@ impl Run for Add { } let by = self.score.unwrap_or(1.0); - let alias = if first { self.alias.clone() } else { None }; - db.add_update(path, by, now, alias); - first = false; + db.add_update(path, by, now); } if db.dirty() { diff --git a/src/cmd/add_alias.rs b/src/cmd/add_alias.rs new file mode 100644 index 0000000..fb955d0 --- /dev/null +++ b/src/cmd/add_alias.rs @@ -0,0 +1,43 @@ +use std::path::Path; + +use anyhow::{Result, bail}; + +use crate::cmd::{AddAlias, Run}; +use crate::db::Database; +use crate::{config, util}; + +impl Run for AddAlias { + fn run(&self) -> Result<()> { + // These characters can't be printed cleanly to a single line, so they can cause + // confusion when writing to stdout. + const EXCLUDE_CHARS: &[char] = &['\n', '\r']; + + let exclude_dirs = config::exclude_dirs()?; + let max_age = config::maxage()?; + let now = util::current_time()?; + + let mut db = Database::open()?; + + let path = + if config::resolve_symlinks() { util::canonicalize } else { util::resolve_path }( + &self.path, + )?; + let path = util::path_to_str(&path)?; + + // Ignore path if it contains unsupported characters, or if it's in the exclude + // list. + if path.contains(EXCLUDE_CHARS) || exclude_dirs.iter().any(|glob| glob.matches(path)) { + return Ok(()); + } + if !Path::new(path).is_dir() { + bail!("not a directory: {path}"); + } + + db.add_alias_update(path, self.aliases.iter(), now); + + if db.dirty() { + db.age(max_age); + } + db.save() + } +} diff --git a/src/cmd/cmd.rs b/src/cmd/cmd.rs index 73add2d..1d4052c 100644 --- a/src/cmd/cmd.rs +++ b/src/cmd/cmd.rs @@ -42,6 +42,7 @@ https://github.com/ajeetdsouza/zoxide )] pub enum Cmd { Add(Add), + AddAlias(AddAlias), Edit(Edit), Import(Import), Init(Init), @@ -63,9 +64,20 @@ pub struct Add { /// doesn't #[clap(short, long)] pub score: Option, +} - #[clap(short, long)] - pub alias: Option, +/// Add aliases for a directory +#[derive(Debug, Parser)] +#[clap( + author, + help_template = HelpTemplate, +)] +pub struct AddAlias { + #[clap(num_args = 1.., required = true)] + pub aliases: Vec, + + #[clap(short, long, required = true, value_hint = ValueHint::DirPath)] + pub path: PathBuf, } /// Edit the database diff --git a/src/cmd/edit.rs b/src/cmd/edit.rs index 6fb7cc6..1dc3055 100644 --- a/src/cmd/edit.rs +++ b/src/cmd/edit.rs @@ -15,15 +15,11 @@ impl Run for Edit { match &self.cmd { Some(cmd) => { match cmd { - EditCommand::Decrement { path } => { - db.add(path, -1.0, now, Option::::None) - } + EditCommand::Decrement { path } => db.add(path, -1.0, now), EditCommand::Delete { path } => { db.remove(path); } - EditCommand::Increment { path } => { - db.add(path, 1.0, now, Option::::None) - } + EditCommand::Increment { path } => db.add(path, 1.0, now), EditCommand::Reload => {} } db.save()?; diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index 5c17474..3b05c87 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -1,4 +1,5 @@ mod add; +mod add_alias; mod cmd; mod edit; mod import; @@ -18,6 +19,7 @@ impl Run for Cmd { fn run(&self) -> Result<()> { match self { Cmd::Add(cmd) => cmd.run(), + Cmd::AddAlias(cmd) => cmd.run(), Cmd::Edit(cmd) => cmd.run(), Cmd::Import(cmd) => cmd.run(), Cmd::Init(cmd) => cmd.run(), diff --git a/src/db/mod.rs b/src/db/mod.rs index 046297c..e81dd2f 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -68,33 +68,17 @@ impl Database { } /// 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, - alias: Option + Into>, - ) { + pub fn add(&mut self, path: impl AsRef + Into, by: Rank, now: Epoch) { 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); - if let Some(al) = alias { - dir.aliases.insert(al.into().into()); - } - } - None => { - let aliases = if let Some(alias) = alias { - HashSet::from([alias.into().into()]) - } else { - HashSet::new() - }; - dirs.push(DirV4 { - path: path.into().into(), - rank: by.max(0.0), - last_accessed: now, - aliases, - }) } + None => dirs.push(DirV4 { + path: path.into().into(), + rank: by.max(0.0), + last_accessed: now, + aliases: HashSet::new(), + }), }); self.with_dirty_mut(|dirty| *dirty = true); } @@ -103,58 +87,63 @@ impl Database { /// directory is already 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, - alias: Option + Into>, - ) { + pub fn add_unchecked(&mut self, path: impl AsRef + Into, rank: Rank, now: Epoch) { self.with_dirs_mut(|dirs| { - let aliases = if let Some(alias) = alias { - HashSet::from([alias.into().into()]) - } else { - HashSet::new() - }; - dirs.push(DirV4 { path: path.into().into(), rank, last_accessed: now, aliases }) + dirs.push(DirV4 { + path: path.into().into(), + rank, + last_accessed: now, + aliases: HashSet::new(), + }) }); 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, - alias: Option + Into>, - ) { + pub fn add_update(&mut self, path: impl AsRef + Into, by: Rank, now: Epoch) { 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); dir.last_accessed = now; - if let Some(al) = alias { - dir.aliases.insert(al.into().into()); - } + } + None => dirs.push(DirV4 { + path: path.into().into(), + rank: by.max(0.0), + last_accessed: now, + aliases: HashSet::new(), + }), + }); + self.with_dirty_mut(|dirty| *dirty = true); + } + + /// Adds aliases to a directory and updates its last_accessed, or + /// creates it and adds aliases to it if it does not exist. + pub fn add_alias_update( + &mut self, + path: impl AsRef + Into, + aliases: impl Iterator + Into>, + now: Epoch, + ) { + self.with_dirs_mut(|dirs| match dirs.iter_mut().find(|dir| dir.path == path.as_ref()) { + Some(dir) => { + dir.aliases.extend(aliases.map(|alias| alias.into().into())); + dir.last_accessed = now } None => { - let aliases = if let Some(alias) = alias { - HashSet::from([alias.into().into()]) - } else { - HashSet::new() - }; + let mut set = HashSet::new(); + set.extend(aliases.map(|alias| alias.into().into())); + dirs.push(DirV4 { path: path.into().into(), - rank: by.max(0.0), + rank: 0.0, last_accessed: now, - aliases, + aliases: set, }) } }); self.with_dirty_mut(|dirty| *dirty = true); } - /// Removes the directory with `path` from the store. This does not preserve /// ordering, but is O(1). pub fn remove(&mut self, path: impl AsRef) -> bool { @@ -317,8 +306,8 @@ mod tests { { let mut db = Database::open_dir(data_dir.path()).unwrap(); - db.add(path, 1.0, now, Option::::None); - db.add(path, 1.0, now, Some(String::from("foo"))); + db.add(path, 1.0, now); + db.add(path, 1.0, now); db.save().unwrap(); } @@ -341,7 +330,7 @@ mod tests { { let mut db = Database::open_dir(data_dir.path()).unwrap(); - db.add(path, 1.0, now, Option::::None); + db.add(path, 1.0, now); db.save().unwrap(); } diff --git a/src/import.rs b/src/import.rs index cd976d3..2eda0bc 100644 --- a/src/import.rs +++ b/src/import.rs @@ -60,7 +60,7 @@ pub(crate) fn run(importer: &impl Importer, db: &mut Database) -> Result<()> { if exclude_dirs.iter().any(|glob| glob.matches(&dir.path)) { continue; } - db.add_unchecked(dir.path, dir.rank, dir.last_accessed, Option::::None); + db.add_unchecked(dir.path, dir.rank, dir.last_accessed); } Err(e) => { let location = match &e.path {