Merge branch 'main' into nushell_completion

This commit is contained in:
Juhan280 2026-03-04 20:03:59 +06:00
commit 59155c8ee0
17 changed files with 180 additions and 86 deletions

16
.github/workflows/cd.yml vendored Normal file
View File

@ -0,0 +1,16 @@
name: Continuous Deployment
on:
push:
tags:
- "v*.*.*"
jobs:
publish-crates-io:
name: Publish on crates.io
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v6
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Publish
run: cargo publish --token ${{ secrets.CARGO_TOKEN }}

View File

@ -21,7 +21,7 @@ jobs:
matrix:
os: [ubuntu-latest]
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
with:
fetch-depth: 0
- uses: actions-rs/toolchain@v1
@ -46,7 +46,7 @@ jobs:
authToken: ${{ env.CACHIX_AUTH_TOKEN }}
name: zoxide
- name: Setup cache
uses: Swatinem/rust-cache@v2.8.1
uses: Swatinem/rust-cache@v2.8.2
with:
key: ${{ matrix.os }}
- name: Install just

View File

@ -32,6 +32,8 @@ jobs:
deb: true
- os: ubuntu-latest
target: aarch64-linux-android
- os: ubuntu-latest
target: armv7-linux-androideabi
- os: macos-latest
target: x86_64-apple-darwin
- os: macos-latest
@ -42,7 +44,7 @@ jobs:
target: aarch64-pc-windows-msvc
steps:
- name: Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Get version
@ -59,7 +61,7 @@ jobs:
override: true
target: ${{ matrix.target }}
- name: Setup cache
uses: Swatinem/rust-cache@v2.8.1
uses: Swatinem/rust-cache@v2.8.2
with:
key: ${{ matrix.target }}
- name: Install cross
@ -100,7 +102,7 @@ jobs:
CHANGELOG.md LICENSE README.md ./man/ ./contrib/completions/ `
./target/${{ matrix.target }}/release/zoxide.exe
- name: Upload artifact
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: ${{ matrix.target }}
path: |

View File

@ -7,6 +7,32 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
### Added
- POSIX: support for non-Cygwin Windows environments (e.g. Busybox).
### Fixed
- Bash/Fish/POSIX/Zsh: resolve symlinks on Windows.
## [0.9.9] - 2026-01-31
### Added
- Support for Android ARMv7.
- Fish: support for v4.1.0+.
### Fixed
- Nushell: use sigil operator when calling external commands.
- Zsh: support multiple digits in `z +N` and `z -N` dirstack commands.
- Bash: avoid downcasting `$PROMPT_COMMAND` array into a string.
- Bash: avoid overwriting `$PIPESTATUS`.
- POSIX: remove non-POSIX compliant calls to `builtin`.
- Fish: clear existing completions when defining `z` command.
## [0.9.8] - 2025-05-27
### Added
@ -22,7 +48,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- Bash: doctor now handles `PROMPT_COMMAND` being an array.
- Bash: doctor now handles `$PROMPT_COMMAND` being an array.
- Bash: doctor now handles Visual Studio Code's shell integration.
- Bash: completions now work with `ble.sh`.
- Nushell: stop ignoring symlinks when `cd`-ing into a directory.
@ -35,7 +61,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Nushell: support for 0.102.0.
- Nushell: support for v0.102.0.
- Bash / Zsh: add doctor to diagnose common issues.
### Fixed
@ -302,7 +328,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `zoxide query --all` for listing deleted directories.
- Lazy deletion for removed directories that have not been accessed in > 90
days.
- Nushell: support for 0.32.0+.
- Nushell: support for v0.32.0+.
### Fixed
@ -536,6 +562,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- GitHub Actions pipeline to build and upload releases.
- Add support for Zsh.
[0.9.9]: https://github.com/ajeetdsouza/zoxide/compare/v0.9.8...v0.9.9
[0.9.8]: https://github.com/ajeetdsouza/zoxide/compare/v0.9.7...v0.9.8
[0.9.7]: https://github.com/ajeetdsouza/zoxide/compare/v0.9.6...v0.9.7
[0.9.6]: https://github.com/ajeetdsouza/zoxide/compare/v0.9.5...v0.9.6

2
Cargo.lock generated
View File

@ -983,7 +983,7 @@ dependencies = [
[[package]]
name = "zoxide"
version = "0.9.8"
version = "0.9.9"
dependencies = [
"anyhow",
"askama",

View File

@ -10,7 +10,7 @@ name = "zoxide"
readme = "README.md"
repository = "https://github.com/ajeetdsouza/zoxide"
rust-version = "1.85.0"
version = "0.9.8"
version = "0.9.9"
[badges]
maintenance = { status = "actively-developed" }

View File

@ -11,15 +11,21 @@
<sup>Special thanks to:</sup>
<!-- markdownlint-disable-next-line MD013 -->
<div><a href="https://go.warp.dev/zoxide"><img alt="Sponsored by Warp" width="230" src="https://raw.githubusercontent.com/warpdotdev/brand-assets/refs/heads/main/Github/Sponsor/Warp-Github-LG-03.png" /></a></div>
<div><sup><b>Warp, built for coding with multiple AI agents.</b></sup></div>
<div><sup>Available for macOS, Linux, and Windows.</sup></div>
<div><sup>
Visit
<a href="https://go.warp.dev/zoxide"><u>warp.dev</u></a>
to learn more.
</sup></div>
<table>
<tr>
<td align="center">
<!-- markdownlint-disable-next-line MD013 -->
<a href="https://go.warp.dev/zoxide"><img alt="Sponsored by Warp" width="230" src="https://raw.githubusercontent.com/warpdotdev/brand-assets/refs/heads/main/Github/Sponsor/Warp-Github-LG-03.png" /></a>
<div><sup><b>Warp, built for coding with multiple AI agents.</b></sup></div>
<div><sup>Available for macOS, Linux, and Windows.</sup></div>
<div><sup>
Visit
<a href="https://go.warp.dev/zoxide"><u>warp.dev</u></a>
to learn more.
</sup></div>
</td>
</tr>
</table>
<hr />
@ -222,7 +228,7 @@ zoxide can be installed in 4 easy steps:
> eval (zoxide init elvish | slurp)
> ```
>
> **Note**
> **Note:**
> zoxide only supports elvish v0.18.0 and above.
</details>
@ -256,7 +262,7 @@ zoxide can be installed in 4 easy steps:
> source ~/.zoxide.nu
> ```
>
> **Note**
> **Note:**
> zoxide only supports Nushell v0.106.0+.
</details>
@ -322,12 +328,16 @@ zoxide can be installed in 4 easy steps:
</details>
> **Note:**
> [Warp Terminal] users need to set `Input Type` to `Shell (PS1)` under
> `Settings > Appearance > Input` for completions to work.
3. **Install fzf** <sup>(optional)</sup>
[fzf] is a command-line fuzzy finder, used by zoxide for completions /
interactive selection. It can be installed from [here][fzf-installation].
> **Note**
> **Note:**
> The minimum supported fzf version is v0.51.0.
4. **Import your data** <sup>(optional)</sup>
@ -554,6 +564,7 @@ Environment variables[^2] can be used for configuration. They must be set before
[ubuntu packages]: https://packages.ubuntu.com/jammy/zoxide
[vim]: https://github.com/vim/vim
[void linux packages]: https://github.com/void-linux/void-packages/tree/master/srcpkgs/zoxide
[warp terminal]: https://www.warp.dev
[wiki-env]: https://github.com/ajeetdsouza/zoxide/wiki/HOWTO:-set-environment-variables "HOWTO: set environment variables"
[xplr]: https://github.com/sayanarijit/xplr
[xxh-plugin-prerun-zoxide]: https://github.com/xxh/xxh-plugin-prerun-zoxide

View File

@ -19,7 +19,7 @@ main() {
parse_args "$@"
local _arch
_arch="${ARCH:-$(ensure get_architecture)}"
_arch="${_ZOXIDE_ARCH:-$(get_architecture)}" || exit $?
assert_nz "${_arch}" "arch"
echo "Detected architecture: ${_arch}"
@ -31,12 +31,12 @@ main() {
# Create and enter a temporary directory.
local _tmp_dir
_tmp_dir="$(mktemp -d)" || err "mktemp: could not create temporary directory"
_tmp_dir="$(mktemp -d /tmp/zoxide_XXXXXX)" || err "mktemp: could not create temporary directory"
cd "${_tmp_dir}" || err "cd: failed to enter directory: ${_tmp_dir}"
# Download and extract zoxide.
local _package
_package="$(ensure download_zoxide "${_arch}")"
_package="$(download_zoxide "${_arch}")" || exit $?
assert_nz "${_package}" "package"
echo "Downloaded package: ${_package}"
case "${_package}" in
@ -54,44 +54,44 @@ main() {
esac
# Install binary.
ensure try_sudo mkdir -p -- "${BIN_DIR}"
ensure try_sudo cp -- "${_bin_name}" "${BIN_DIR}/${_bin_name}"
ensure try_sudo chmod +x "${BIN_DIR}/${_bin_name}"
echo "Installed zoxide to ${BIN_DIR}"
ensure try_sudo mkdir -p -- "${_ZOXIDE_BIN_DIR}"
ensure try_sudo cp -- "${_bin_name}" "${_ZOXIDE_BIN_DIR}/${_bin_name}"
ensure try_sudo chmod +x "${_ZOXIDE_BIN_DIR}/${_bin_name}"
echo "Installed zoxide to ${_ZOXIDE_BIN_DIR}"
# Install manpages.
ensure try_sudo mkdir -p -- "${MAN_DIR}/man1"
ensure try_sudo cp -- "man/man1/"* "${MAN_DIR}/man1/"
echo "Installed manpages to ${MAN_DIR}"
ensure try_sudo mkdir -p -- "${_ZOXIDE_MAN_DIR}/man1"
ensure try_sudo cp -- "man/man1/"* "${_ZOXIDE_MAN_DIR}/man1/"
echo "Installed manpages to ${_ZOXIDE_MAN_DIR}"
# Print success message and check $PATH.
echo ""
echo "zoxide is installed!"
if ! echo ":${PATH}:" | grep -Fq ":${BIN_DIR}:"; then
echo "Note: ${BIN_DIR} is not on your \$PATH. zoxide will not work unless it is added to \$PATH."
if ! echo ":${PATH}:" | grep -Fq ":${_ZOXIDE_BIN_DIR}:"; then
echo "Note: ${_ZOXIDE_BIN_DIR} is not on your \$PATH. zoxide will not work unless it is added to \$PATH."
fi
}
# Parse the arguments passed and set variables accordingly.
parse_args() {
BIN_DIR_DEFAULT="${HOME}/.local/bin"
MAN_DIR_DEFAULT="${HOME}/.local/share/man"
SUDO_DEFAULT="sudo"
_ZOXIDE_BIN_DIR_DEFAULT="${HOME}/.local/bin"
_ZOXIDE_MAN_DIR_DEFAULT="${HOME}/.local/share/man"
_ZOXIDE_SUDO_DEFAULT="sudo"
BIN_DIR="${BIN_DIR_DEFAULT}"
MAN_DIR="${MAN_DIR_DEFAULT}"
SUDO="${SUDO_DEFAULT}"
_ZOXIDE_BIN_DIR="${_ZOXIDE_BIN_DIR_DEFAULT}"
_ZOXIDE_MAN_DIR="${_ZOXIDE_MAN_DIR_DEFAULT}"
_ZOXIDE_SUDO="${_ZOXIDE_SUDO_DEFAULT}"
while [ "$#" -gt 0 ]; do
case "$1" in
--arch) ARCH="$2" && shift 2 ;;
--arch=*) ARCH="${1#*=}" && shift 1 ;;
--bin-dir) BIN_DIR="$2" && shift 2 ;;
--bin-dir=*) BIN_DIR="${1#*=}" && shift 1 ;;
--man-dir) MAN_DIR="$2" && shift 2 ;;
--man-dir=*) MAN_DIR="${1#*=}" && shift 1 ;;
--sudo) SUDO="$2" && shift 2 ;;
--sudo=*) SUDO="${1#*=}" && shift 1 ;;
--arch) _ZOXIDE_ARCH="$2" && shift 2 ;;
--arch=*) _ZOXIDE_ARCH="${1#*=}" && shift 1 ;;
--bin-dir) _ZOXIDE_BIN_DIR="$2" && shift 2 ;;
--bin-dir=*) _ZOXIDE_BIN_DIR="${1#*=}" && shift 1 ;;
--man-dir) _ZOXIDE_MAN_DIR="$2" && shift 2 ;;
--man-dir=*) _ZOXIDE_MAN_DIR="${1#*=}" && shift 1 ;;
--sudo) _ZOXIDE_SUDO="$2" && shift 2 ;;
--sudo=*) _ZOXIDE_SUDO="${1#*=}" && shift 1 ;;
-h | --help) usage && exit 0 ;;
*) err "Unknown option: $1" ;;
esac
@ -119,9 +119,9 @@ ${_text_heading}Usage:${_text_reset}
${_text_heading}Options:${_text_reset}
--arch Override the architecture identified by the installer [current: ${_arch}]
--bin-dir Override the installation directory [default: ${BIN_DIR_DEFAULT}]
--man-dir Override the manpage installation directory [default: ${MAN_DIR_DEFAULT}]
--sudo Override the command used to elevate to root privileges [default: ${SUDO_DEFAULT}]
--bin-dir Override the installation directory [default: ${_ZOXIDE_BIN_DIR_DEFAULT}]
--man-dir Override the manpage installation directory [default: ${_ZOXIDE_MAN_DIR_DEFAULT}]
--sudo Override the command used to elevate to root privileges [default: ${_ZOXIDE_SUDO_DEFAULT}]
-h, --help Print help"
}
@ -176,19 +176,19 @@ try_sudo() {
fi
need_sudo
"${SUDO}" "$@"
"${_ZOXIDE_SUDO}" "$@"
}
need_sudo() {
if ! check_cmd "${SUDO}"; then
if ! check_cmd "${_ZOXIDE_SUDO}"; then
err "\
could not find the command \`${SUDO}\` needed to get permissions for install.
could not find the command \`${_ZOXIDE_SUDO}\` needed to get permissions for install.
If you are on Windows, please run your shell as an administrator, then rerun this script.
Otherwise, please run this script as root, or install \`sudo\`."
fi
if ! "${SUDO}" -v; then
if ! "${_ZOXIDE_SUDO}" -v; then
err "sudo permissions not granted, aborting installation"
fi
}

View File

@ -37,7 +37,7 @@ fn import_autojump(db: &mut Database, buffer: &str) -> Result<()> {
let mut rank = rank.parse::<f64>().with_context(|| format!("invalid rank: {rank}"))?;
// Normalize the rank using a sigmoid function. Don't import actual ranks from
// autojump, since its scoring algorithm is very different and might
// take a while to get normalized.
// take a while to normalize.
rank = sigmoid(rank);
db.add_unchecked(path, rank, 0);

View File

@ -76,8 +76,9 @@ 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.
/// 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<str> + Into<String>, rank: Rank, now: Epoch) {
self.with_dirs_mut(|dirs| {
dirs.push(Dir { path: path.into().into(), rank, last_accessed: now })

View File

@ -70,7 +70,7 @@ impl<'a> Stream<'a> {
}
// The logic here is reversed - if we resolve symlinks when adding entries to
// the database, we should not return symlinks when querying back from
// the database, we should not return symlinks when querying from
// the database.
let resolver =
if self.options.resolve_symlinks { fs::symlink_metadata } else { fs::metadata };

View File

@ -100,7 +100,7 @@ mod tests {
let mut source = String::new();
// Filter out lines using edit:*, since those functions are only available in
// the interactive editor.
// interactive editor mode.
for line in Elvish(&opts).render().unwrap().lines().filter(|line| !line.contains("edit:")) {
source.push_str(line);
source.push('\n');

View File

@ -9,12 +9,16 @@
# pwd based on the value of _ZO_RESOLVE_SYMLINKS.
function __zoxide_pwd() {
{%- let pwd -%}
{%- if resolve_symlinks -%}
{%- let pwd = "\\builtin pwd -P" -%}
{%- else -%}
{%- let pwd = "\\builtin pwd -L" -%}
{%- endif -%}
{%- if cfg!(windows) %}
\command cygpath -w "$(\builtin pwd -P)"
{%- else if resolve_symlinks %}
\builtin pwd -P
\command cygpath -w "{{ pwd }}"
{%- else %}
\builtin pwd -L
{{ pwd }}
{%- endif %}
}
@ -56,10 +60,12 @@ function __zoxide_hook() {
# Initialize hook.
if [[ ${PROMPT_COMMAND:=} != *'__zoxide_hook'* ]]; then
if [[ "$(declare -p PROMPT_COMMAND 2>&1)" == "declare -a"* ]]; then
PROMPT_COMMAND=(__zoxide_hook "${PROMPT_COMMAND[@]}")
PROMPT_COMMAND=("${PROMPT_COMMAND[@]}" __zoxide_hook)
else
# shellcheck disable=SC2178
PROMPT_COMMAND="__zoxide_hook;${PROMPT_COMMAND#;}"
# shellcheck disable=SC2128,SC2178
PROMPT_COMMAND="${PROMPT_COMMAND%"${PROMPT_COMMAND##*[![:space:];]}"}"
# shellcheck disable=SC2128,SC2178
PROMPT_COMMAND="${PROMPT_COMMAND:+${PROMPT_COMMAND};}__zoxide_hook"
fi
fi

View File

@ -7,19 +7,27 @@
# pwd based on the value of _ZO_RESOLVE_SYMLINKS.
function __zoxide_pwd
{%- let pwd -%}
{%- if resolve_symlinks -%}
{%- let pwd = "builtin pwd -P" -%}
{%- else -%}
{%- let pwd = "builtin pwd -L" -%}
{%- endif -%}
{%- if cfg!(windows) %}
command cygpath -w (builtin pwd -P)
{%- else if resolve_symlinks %}
builtin pwd -P
command cygpath -w ({{ pwd }})
{%- else %}
builtin pwd -L
{{ pwd }}
{%- endif %}
end
# 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
if status list-files functions/cd.fish &>/dev/null
status get-file functions/cd.fish | string replace --regex -- '^function cd\s' 'function __zoxide_cd_internal ' | source
else
string replace --regex -- '^function cd\s' 'function __zoxide_cd_internal ' <$__fish_data_dir/functions/cd.fish | source
end
end
# cd + custom logic based on the value of _ZO_ECHO.
@ -113,9 +121,11 @@ end
{%- when Some with (cmd) %}
abbr --erase {{cmd}} &>/dev/null
complete --erase --command {{cmd}}
alias {{cmd}}=__zoxide_z
abbr --erase {{cmd}}i &>/dev/null
complete --erase --command {{cmd}}i
alias {{cmd}}i=__zoxide_zi
{%- when None %}

View File

@ -8,15 +8,28 @@
#
# pwd based on the value of _ZO_RESOLVE_SYMLINKS.
__zoxide_pwd() {
{%- let pwd -%}
{%- if resolve_symlinks -%}
{%- let pwd = "\\command pwd -P" -%}
{%- else -%}
{%- let pwd = "\\command pwd -L" -%}
{%- endif -%}
{%- if cfg!(windows) %}
\command cygpath -w "$(\builtin pwd -P)"
{%- else if resolve_symlinks %}
\command pwd -P
if \command -v cygpath >/dev/null
then
__zoxide_pwd() {
\command cygpath -w "$({{ pwd }})"
}
else
__zoxide_pwd() {
{{ pwd }}
}
fi
{%- else %}
\command pwd -L
{%- endif %}
__zoxide_pwd() {
{{ pwd }}
}
{%- endif %}
# cd + custom logic based on the value of _ZO_ECHO.
__zoxide_cd() {
@ -35,7 +48,7 @@ __zoxide_cd() {
{%- when InitHook::Prompt -%}
# Hook to add new entries to the database.
__zoxide_hook() {
\command zoxide add -- "$(__zoxide_pwd || \builtin true)"
\command zoxide add -- "$(__zoxide_pwd || \command true)"
}
# Initialize hook.
@ -95,7 +108,7 @@ __zoxide_z() {
elif [ "$#" -eq 1 ] && [ -d "$1" ]; then
__zoxide_cd "$1"
else
__zoxide_result="$(\command zoxide query --exclude "$(__zoxide_pwd || \builtin true)" -- "$@")" &&
__zoxide_result="$(\command zoxide query --exclude "$(__zoxide_pwd || \command true)" -- "$@")" &&
__zoxide_cd "${__zoxide_result}"
fi
}

View File

@ -28,7 +28,11 @@ function global:__zoxide_pwd {
# cd + custom logic based on the value of _ZO_ECHO.
function global:__zoxide_cd($dir, $literal) {
$dir = if ($literal) {
Set-Location -LiteralPath $dir -Passthru -ErrorAction Stop
if ($null -eq $dir) {
Set-Location
} else {
Set-Location -LiteralPath $dir -Passthru -ErrorAction Stop
}
} else {
if ($dir -eq '-' -and ($PSVersionTable.PSVersion -lt 6.1)) {
Write-Error "cd - is not supported below PowerShell 6.1. Please upgrade your version of PowerShell."
@ -101,7 +105,7 @@ if ($global:__zoxide_hooked -ne 1) {
# Jump to a directory using only keywords.
function global:__zoxide_z {
if ($args.Length -eq 0) {
__zoxide_cd ~ $true
__zoxide_cd $null $true
}
elseif ($args.Length -eq 1 -and ($args[0] -eq '-' -or $args[0] -eq '+')) {
__zoxide_cd $args[0] $false

View File

@ -9,12 +9,16 @@
# pwd based on the value of _ZO_RESOLVE_SYMLINKS.
function __zoxide_pwd() {
{%- let pwd -%}
{%- if resolve_symlinks -%}
{%- let pwd = "\\builtin pwd -P" -%}
{%- else -%}
{%- let pwd = "\\builtin pwd -L" -%}
{%- endif -%}
{%- if cfg!(windows) %}
\command cygpath -w "$(\builtin pwd -P)"
{%- else if resolve_symlinks %}
\builtin pwd -P
\command cygpath -w "{{ pwd }}"
{%- else %}
\builtin pwd -L
{{ pwd }}
{%- endif %}
}