From 2e3230974cbf5a67a13717d439da3a3bc54d6098 Mon Sep 17 00:00:00 2001 From: rootiest Date: Mon, 11 May 2026 23:26:33 -0400 Subject: [PATCH] feat(completions): unify cd/z completions across CWD, CDPATH, and zoxide MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previous to this commit, tab completion and auto-suggestions for cd/z were inconsistent — only one or two of: CWD, CDPATH, and zoxide frecency results would work at a time, with different sourced matches shown in tab completion, shown in auto-suggest, and execution after pressing . - Add _zoxide_z_complete in functions/zoxide.fish that merges all three sources into a single completion list (CWD via __fish_complete_cd, CDPATH via __fish_complete_directories, zoxide via query -l capped at 25) - Wire the new completer to both z and cd via complete directives in conf.d/zoxide.fish, replacing the previous incomplete approach - Add completions/zoxide.fish for full tab completion of the zoxide CLI itself (add, query, remove, import, init subcommands) - Update README to document the unified completion behavior and fix structural issues in Personalization/Attribution/Dependencies sections --- README.md | 20 ++++-- completions/zoxide.fish | 41 +++++++++++ conf.d/zoxide.fish | 152 +++++++++++++++------------------------- functions/zoxide.fish | 45 ++++++++++++ 4 files changed, 158 insertions(+), 100 deletions(-) create mode 100644 completions/zoxide.fish create mode 100644 functions/zoxide.fish diff --git a/README.md b/README.md index f61a3b2..e9d3f62 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ A feature-rich Fish shell configuration for CachyOS (Arch Linux), built around a - [Dependencies](#dependencies) - [Installation](#installation) - [Personalization](#personalization) -- [Full Requirements](#full-requirements) +- [Attribution](#attribution) - [License](#license) --- @@ -107,7 +107,14 @@ See [FZF Bindings](#fzf-bindings) under Key Bindings for the default FZF shortcu ### Zoxide -Smart `cd` replacement. `cd` (or `z`) `` jumps to the best frecency match; `cdi` (or `zi`) opens an interactive selector. +Smart `cd` replacement powered by frecency scoring. `cd`, `z`, and `cdi`/`zi` are all mapped to zoxide-backed navigation functions. + +| Command | Description | +|---|---| +| `cd ` / `z ` | Jump to a matching directory by frecency; falls back to exact path | +| `cdi` / `zi` | Open an interactive fzf selector across all frecency-ranked directories | + +Tab completions for `cd` and `z` blend standard directory entries (CWD and `CDPATH`) with zoxide frecency results, so familiar paths and frequently-visited destinations appear together in a single list. Full tab completions for the `zoxide` CLI itself (subcommands: `add`, `query`, `remove`, `import`, `init`) are provided via `completions/zoxide.fish`. ### DirEnv @@ -493,6 +500,10 @@ Named context shortcuts (e.g. `dcr`, `dck`) live in `~/.config/.user-dots/fish/l | [Kitty](https://sw.kovidgoyal.net/kitty/) / [WezTerm](https://wezfurlong.org/wezterm/) | Terminal emulator | | [WakaTime](https://wakatime.com/) | Activity tracking | +### Full Requirements + +For a complete, categorized list of all non-standard tools required or used by this configuration, see [requirements.md](requirements.md). + --- ## Installation @@ -512,7 +523,6 @@ Then open a new Fish shell — Fisher and all plugins will be installed automati A [chezmoi](https://www.chezmoi.io/) dotfile manager is also configured — secrets are sourced from `~/.config/.user-dots/fish/secrets.fish` and excluded from version control. --- - ## Personalization Sensitive credentials and machine-specific paths are kept out of version control via a secondary private directory at `~/.config/.user-dots/fish/`. Two files are sourced automatically by `config.fish` if they exist: @@ -585,9 +595,9 @@ end --- -## Full Requirements +## Attribution -For a complete, categorized list of all non-standard tools required or used by this configuration, see [requirements.md](requirements.md). +The core of the [Zoxide integration](#zoxide) in this repository was originally adapted from the [icezyclon/zoxide.fish](https://github.com/icezyclon/zoxide.fish) plugin (MIT Licensed) and has since been heavily customized for performance and Fish 4.x compatibility. --- diff --git a/completions/zoxide.fish b/completions/zoxide.fish new file mode 100644 index 0000000..3c2dad7 --- /dev/null +++ b/completions/zoxide.fish @@ -0,0 +1,41 @@ +set -l commands add help import init query remove + +# disable normal all-files completion +complete -c zoxide -f + +# onyl show base options if none was used already +complete -c zoxide -n __fish_use_subcommand -a add -d "Add a new directory or increment its rank" +complete -c zoxide -n __fish_use_subcommand -a help -d "Prints this message or the help of the given subcommand(s)" +complete -c zoxide -n __fish_use_subcommand -a import -d "Import from z database" +complete -c zoxide -n __fish_use_subcommand -a init -d "Generates shell configuration" +complete -c zoxide -n __fish_use_subcommand -a query -d "Search for a directory" +complete -c zoxide -n __fish_use_subcommand -a remove -d "Remove a directory" + +# zoxide add +complete -c zoxide -n "_zoxide_equals_first_token add" -n "__fish_is_nth_token 2" -a "(__fish_complete_directories)" + +# zoxide help +complete -c zoxide -n "_zoxide_equals_first_token help" -n "__fish_is_nth_token 2" -a "$commands" + +# zoxide import +complete -c zoxide -r -F -n "_zoxide_equals_first_token import" -n "__fish_is_nth_token 2" +complete -c zoxide -r -n "_zoxide_equals_first_token import" -l merge -d "Merge entries into existing database" + +# zoxide init +set -l initshells bash fish posix powershell zsh +complete -c zoxide -n "_zoxide_equals_first_token init" -n "__fish_is_nth_token 2" -a "$initshells" +complete -c zoxide -n "_zoxide_equals_first_token init" -l cmd -d "Renames the 'z' command and corresponding aliases [default: z]" +complete -c zoxide -n "_zoxide_equals_first_token init" -l hook -a "none prompt pwd" -d "Chooses event on which an entry is added to the database [default: pwd]" +complete -c zoxide -n "_zoxide_equals_first_token init" -l no-aliases -d "Prevents zoxide from defining any commands other than 'z'" + +# zoxide query +complete -c zoxide -r -n "_zoxide_equals_first_token query" -s i -l interactive -d "Opens an interactive selection menu using fzf" +complete -c zoxide -r -n "_zoxide_equals_first_token query" -s l -l list -d "List all matching directories" +complete -c zoxide -r -n "_zoxide_equals_first_token query" -s s -l score -d "Display score along with result" + +# zoxide remove +complete -c zoxide -n "_zoxide_equals_first_token remove" -n "__fish_is_nth_token 2" -a "(zoxide query -l)" + +# Always possible +complete -c zoxide -x -s h -l help -d "Prints help information" +complete -c zoxide -x -s V -l version -d "Prints version information" diff --git a/conf.d/zoxide.fish b/conf.d/zoxide.fish index 17f7e62..6e041df 100644 --- a/conf.d/zoxide.fish +++ b/conf.d/zoxide.fish @@ -1,106 +1,68 @@ -# ============================================================================= -# -# -# Utility functions for zoxide. -# +# Adapted from icezyclon/zoxide.fish (MIT) +# Heavily customized for Fish 4.x compatibility and performance -if not type -q zoxide - return -end +if status is-interactive -# pwd based on the value of _ZO_RESOLVE_SYMLINKS. -function __zoxide_pwd - builtin pwd -L -end + if type -q zoxide -# A copy of fish's internal cd function. This makes it possible to use -# `alias cd=z` without causing an infinite loop. -if ! builtin functions --query __zoxide_cd_internal - string replace --regex -- '^function cd\s' 'function __zoxide_cd_internal ' <$__fish_data_dir/functions/cd.fish | source -end + # ------------- + # 'zoxide init fish' is very different for different versions of zoxide + # to guarantee the same behavior we define these functions ourself, + # especially because the apt package is so old + # most of these functions were taken from https://github.com/ajeetdsouza/zoxide + # from version 0.8.1 -# cd + custom logic based on the value of _ZO_ECHO. -function __zoxide_cd - if set -q __zoxide_loop - builtin echo "zoxide: infinite loop detected" - builtin echo "Avoid aliasing `cd` to `z` directly, use `zoxide init --cmd=cd fish` instead" - return 1 - end - __zoxide_loop=1 __zoxide_cd_internal $argv -end + if ! builtin functions -q _zoxide_cd + if builtin functions -q cd + builtin functions -c cd _zoxide_cd + else + alias _zoxide_cd='builtin cd' + end + end -# ============================================================================= -# -# Hook configuration for zoxide. -# + function _zoxide_hook --on-variable PWD + test -z "$fish_private_mode" + and command zoxide add -- (builtin pwd -L) + end -# Initialize hook to add new entries to the database. -function __zoxide_hook --on-variable PWD - test -z "$fish_private_mode" - and command zoxide add -- (__zoxide_pwd) -end + function z + set argc (count $argv) + if test $argc -eq 0 + _zoxide_cd $HOME + else if test "$argv" = - + _zoxide_cd - + else if test -d $argv[-1] + _zoxide_cd $argv[-1] + else + set -l result (command zoxide query $argv) + and _zoxide_cd $result + end + end -# ============================================================================= -# -# When using zoxide with --no-cmd, alias these internal functions as desired. -# + function zi + set -l result (command zoxide query -i -- $argv) + and _zoxide_cd $result + end -# Jump to a directory using only keywords. -function __zoxide_z - set -l argc (builtin count $argv) - if test $argc -eq 0 - __zoxide_cd $HOME - else if test "$argv" = - - __zoxide_cd - - else if test $argc -eq 1 -a -d $argv[1] - __zoxide_cd $argv[1] - else if test $argc -eq 2 -a $argv[1] = -- - __zoxide_cd -- $argv[2] + # ------------- + + alias cd=z + + # use custom completion + complete -c z -f # disable files by default + complete -c z -x -a '(_zoxide_z_complete)' else - set -l result (command zoxide query --exclude (__zoxide_pwd) -- $argv) - and __zoxide_cd $result + echo "[plugin: zoxide] Command 'zoxide' cannot be found. Not installed or not in path" + end + +end + +function _zoxide_uninstall --on-event zoxide_uninstall + if alias | grep "alias cd z" >/dev/null + functions -e cd + end + if builtin functions -q _zoxide_cd && not functions -q cd + # restore old cd + builtin functions -c _zoxide_cd cd end end - -# Completions. -function __zoxide_z_complete - set -l tokens (builtin commandline --current-process --tokenize) - set -l curr_tokens (builtin commandline --cut-at-cursor --current-process --tokenize) - - if test (builtin count $tokens) -le 2 -a (builtin count $curr_tokens) -eq 1 - # If there are < 2 arguments, use `cd` completions. - complete --do-complete "'' "(builtin commandline --cut-at-cursor --current-token) | string match --regex -- '.*/$' - else if test (builtin count $tokens) -eq (builtin count $curr_tokens) - # If the last argument is empty, use interactive selection. - set -l query $tokens[2..-1] - set -l result (command zoxide query --exclude (__zoxide_pwd) --interactive -- $query) - and __zoxide_cd $result - and builtin commandline --function cancel-commandline repaint - end -end -complete --command __zoxide_z --no-files --arguments '(__zoxide_z_complete)' - -# Jump to a directory using interactive search. -function __zoxide_zi - set -l result (command zoxide query --interactive -- $argv) - and __zoxide_cd $result -end - -# ============================================================================= -# -# Commands for zoxide. Disable these using --no-cmd. -# - -abbr --erase z &>/dev/null -alias z=__zoxide_z - -abbr --erase zi &>/dev/null -alias zi=__zoxide_zi - -abbr --erase cdi &>/dev/null -alias cdi=__zoxide_zi - -# ============================================================================= -# Initialize zoxide: - -zoxide init --cmd=cd fish | source diff --git a/functions/zoxide.fish b/functions/zoxide.fish new file mode 100644 index 0000000..9c3268c --- /dev/null +++ b/functions/zoxide.fish @@ -0,0 +1,45 @@ +# Adapted from icezyclon/zoxide.fish (MIT) +# Heavily customized for Fish 4.x compatibility and performance + +function _zoxide_z_complete -d "Complete directory first or zoxide queries otherwise" --argument-names comp desc + # comp is the currently completing token + if not set -q comp[1] + set comp (commandline -ct) + end + # cmd are all tokens including the current one except the command + set -l cmd (commandline -opc) $comp + set -e cmd[1] + + # 1. Get standard completions (CWD, CDPATH, etc.) + # We call the underlying functions directly to avoid recursion. + if test (count $cmd) -le 1 + # CDPATH results + __fish_complete_cd + # Local directory results (CWD) + __fish_complete_directories "$comp" "" + end + + # 2. Get zoxide results + # Cap results to 25 to avoid overwhelming the completion engine + set -l zresults (zoxide query -l $cmd | head -n 25) + for res in $zresults + set -l bname (basename $res) + # If the basename matches the prefix, show it as a short name. + if string match -qi "$comp*" -- $bname + printf "%s/\tzoxide: %s\n" $bname $res + end + # Also provide the absolute path. Fish will filter it if it doesn't match. + printf "%s/\tzoxide\n" $res + end +end + +function _zoxide_equals_first_token -a check -d "Test if first non-switch token equals given one" + set -l tokens (commandline -co) + set -e tokens[1] + set -l tokens (string replace -r --filter '^([^-].*)' '$1' -- $tokens) + if set -q tokens[1] + test $tokens[1] = $check + else + return 1 + end +end