Merge branch 'main' of github.com:azaleacolburn/zoxide into bookmark-cmd

This commit is contained in:
Azalea Colburn 2025-08-29 21:22:11 -07:00
commit f7d92b7a98
No known key found for this signature in database
32 changed files with 762 additions and 503 deletions

View File

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

View File

@ -42,7 +42,7 @@ jobs:
target: aarch64-pc-windows-msvc target: aarch64-pc-windows-msvc
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v5
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Get version - name: Get version
@ -59,7 +59,7 @@ jobs:
override: true override: true
target: ${{ matrix.target }} target: ${{ matrix.target }}
- name: Setup cache - name: Setup cache
uses: Swatinem/rust-cache@v2.7.7 uses: Swatinem/rust-cache@v2.8.0
with: with:
key: ${{ matrix.target }} key: ${{ matrix.target }}
- name: Install cross - name: Install cross
@ -67,7 +67,7 @@ jobs:
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
command: install command: install
args: --color=always --git=https://github.com/cross-rs/cross.git --locked --rev=02bf930e0cb0c6f1beffece0788f3932ecb2c7eb --verbose cross args: --color=always --git=https://github.com/cross-rs/cross.git --locked --rev=e281947ca900da425e4ecea7483cfde646c8a1ea --verbose cross
- name: Build binary - name: Build binary
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:

View File

@ -7,11 +7,29 @@ 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/), 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). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased ## [0.9.8] - 2025-05-27
### Added
- Support for Tcsh.
- Added `--score` flag to `zoxide add`.
- POSIX: add doctor to diagnose common issues.
- Nushell: add CLI completions.
### Changed ### Changed
- Bash: zoxide will now rewrite the prompt when using Space-Tab completions. - Bash: zoxide will now automatically `cd` when selecting Space-Tab completions.
### Fixed
- 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.
- Fzf: updated minimum supported version to v0.51.0.
- PowerShell: avoid setting `$error` when defining `__zoxide_hooked`.
- PowerShell: handle special characters in file paths when `cd`-ing into them.
- Database corruption issue when the filesystem is 100% full.
## [0.9.7] - 2025-02-10 ## [0.9.7] - 2025-02-10
@ -518,6 +536,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- GitHub Actions pipeline to build and upload releases. - GitHub Actions pipeline to build and upload releases.
- Add support for Zsh. - Add support for Zsh.
[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.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 [0.9.6]: https://github.com/ajeetdsouza/zoxide/compare/v0.9.5...v0.9.6
[0.9.5]: https://github.com/ajeetdsouza/zoxide/compare/v0.9.4...v0.9.5 [0.9.5]: https://github.com/ajeetdsouza/zoxide/compare/v0.9.4...v0.9.5

647
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -2,52 +2,56 @@
authors = ["Ajeet D'Souza <98ajeet@gmail.com>"] authors = ["Ajeet D'Souza <98ajeet@gmail.com>"]
categories = ["command-line-utilities", "filesystem"] categories = ["command-line-utilities", "filesystem"]
description = "A smarter cd command for your terminal" description = "A smarter cd command for your terminal"
edition = "2021" edition = "2024"
homepage = "https://github.com/ajeetdsouza/zoxide" homepage = "https://github.com/ajeetdsouza/zoxide"
keywords = ["cli", "filesystem", "shell", "tool", "utility"] keywords = ["cli", "filesystem", "shell", "tool", "utility"]
license = "MIT" license = "MIT"
name = "zoxide" name = "zoxide"
readme = "README.md" readme = "README.md"
repository = "https://github.com/ajeetdsouza/zoxide" repository = "https://github.com/ajeetdsouza/zoxide"
rust-version = "1.74.1" rust-version = "1.85.0"
version = "0.9.7" version = "0.9.8"
[badges] [badges]
maintenance = { status = "actively-developed" } maintenance = { status = "actively-developed" }
[dependencies] [dependencies]
anyhow = "1.0.32" anyhow = "1.0.32"
askama = { version = "0.14.0", default-features = false, features = [
"derive",
"std",
] }
bincode = "1.3.1" bincode = "1.3.1"
clap = { version = "4.3.0", features = ["derive"] } clap = { version = "4.3.0", features = ["derive"] }
color-print = "0.3.4" color-print = "0.3.4"
dirs = "5.0.0" dirs = "6.0.0"
dunce = "1.0.1" dunce = "1.0.1"
fastrand = "2.0.0" fastrand = "2.0.0"
glob = "0.3.0" glob = "0.3.0"
ouroboros = "0.18.3" ouroboros = "0.18.3"
rinja = { version = "0.3.2", default-features = false }
serde = { version = "1.0.116", features = ["derive"] } serde = { version = "1.0.116", features = ["derive"] }
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
nix = { version = "0.29.0", default-features = false, features = [ nix = { version = "0.30.1", default-features = false, features = [
"fs", "fs",
"user", "user",
] } ] }
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
which = "6.0.0" which = "7.0.3"
[build-dependencies] [build-dependencies]
clap = { version = "4.3.0", features = ["derive"] } clap = { version = "4.3.0", features = ["derive"] }
clap_complete = "4.3.0" clap_complete = "4.5.50"
clap_complete_fig = "4.3.0" clap_complete_fig = "4.5.2"
clap_complete_nushell = "4.5.5"
color-print = "0.3.4" color-print = "0.3.4"
[dev-dependencies] [dev-dependencies]
assert_cmd = "2.0.0" assert_cmd = "2.0.0"
rstest = { version = "0.23.0", default-features = false } rstest = { version = "0.26.0", default-features = false }
rstest_reuse = "0.7.0" rstest_reuse = "0.7.0"
tempfile = "3.1.0" tempfile = "3.15.0"
[features] [features]
default = [] default = []

View File

@ -87,7 +87,7 @@ zoxide can be installed in 4 easy steps:
> Or, you can use a package manager: > Or, you can use a package manager:
> >
> | Distribution | Repository | Instructions | > | Distribution | Repository | Instructions |
> | ------------------- | ----------------------- | ----------------------------------------------------------------------------------------------------- | > | ------------------- | ------------------------- | ----------------------------------------------------------------------------------------------------- |
> | **_Any_** | **[crates.io]** | `cargo install zoxide --locked` | > | **_Any_** | **[crates.io]** | `cargo install zoxide --locked` |
> | _Any_ | [asdf] | `asdf plugin add zoxide https://github.com/nyrst/asdf-zoxide.git` <br /> `asdf install zoxide latest` | > | _Any_ | [asdf] | `asdf plugin add zoxide https://github.com/nyrst/asdf-zoxide.git` <br /> `asdf install zoxide latest` |
> | _Any_ | [conda-forge] | `conda install -c conda-forge zoxide` | > | _Any_ | [conda-forge] | `conda install -c conda-forge zoxide` |
@ -103,6 +103,7 @@ zoxide can be installed in 4 easy steps:
> | Exherbo Linux | [Exherbo packages] | `cave resolve -x repository/rust` <br /> `cave resolve -x zoxide` | > | Exherbo Linux | [Exherbo packages] | `cave resolve -x repository/rust` <br /> `cave resolve -x zoxide` |
> | Fedora 32+ | [Fedora Packages] | `dnf install zoxide` | > | Fedora 32+ | [Fedora Packages] | `dnf install zoxide` |
> | Gentoo | [Gentoo Packages] | `emerge app-shells/zoxide` | > | Gentoo | [Gentoo Packages] | `emerge app-shells/zoxide` |
> | Linux Mint | [apt.cli.rs] (unofficial) | [Setup the repository][apt.cli.rs-setup], then `apt install zoxide` |
> | Manjaro | | `pacman -S zoxide` | > | Manjaro | | `pacman -S zoxide` |
> | openSUSE Tumbleweed | [openSUSE Factory] | `zypper install zoxide` | > | openSUSE Tumbleweed | [openSUSE Factory] | `zypper install zoxide` |
> | ~Parrot OS~[^1] | | ~`apt install zoxide`~ | > | ~Parrot OS~[^1] | | ~`apt install zoxide`~ |
@ -112,7 +113,7 @@ zoxide can be installed in 4 easy steps:
> | Rocky Linux | | `dnf install zoxide` | > | Rocky Linux | | `dnf install zoxide` |
> | Slackware 15.0+ | [SlackBuilds] | [Instructions][slackbuilds-howto] | > | Slackware 15.0+ | [SlackBuilds] | [Instructions][slackbuilds-howto] |
> | Solus | [Solus Packages] | `eopkg install zoxide` | > | Solus | [Solus Packages] | `eopkg install zoxide` |
> | ~Ubuntu 21.04+~[^1] | ~[Ubuntu Packages]~ | ~`apt install zoxide`~ | > | Ubuntu | [apt.cli.rs] (unofficial) | [Setup the repository][apt.cli.rs-setup], then `apt install zoxide` |
> | Void Linux | [Void Linux Packages] | `xbps-install -S zoxide` | > | Void Linux | [Void Linux Packages] | `xbps-install -S zoxide` |
</details> </details>
@ -280,6 +281,18 @@ zoxide can be installed in 4 easy steps:
</details> </details>
<details>
<summary>Tcsh</summary>
> Add this to the <ins>**end**</ins> of your config file (usually `~/.tcshrc`):
>
> ```sh
> zoxide init tcsh > ~/.zoxide.tcsh
> source ~/.zoxide.tcsh
> ```
</details>
<details> <details>
<summary>Xonsh</summary> <summary>Xonsh</summary>
@ -323,7 +336,7 @@ zoxide can be installed in 4 easy steps:
interactive selection. It can be installed from [here][fzf-installation]. interactive selection. It can be installed from [here][fzf-installation].
> **Note** > **Note**
> zoxide only supports fzf v0.33.0 and above. > The minimum supported fzf version is v0.51.0.
4. **Import your data** <sup>(optional)</sup> 4. **Import your data** <sup>(optional)</sup>
@ -461,6 +474,7 @@ Environment variables[^2] can be used for configuration. They must be set before
| [lf] | File manager | See the [wiki][lf-wiki] | | [lf] | File manager | See the [wiki][lf-wiki] |
| [nnn] | File manager | [nnn-autojump] | | [nnn] | File manager | [nnn-autojump] |
| [ranger] | File manager | [ranger-zoxide] | | [ranger] | File manager | [ranger-zoxide] |
| [raycast] | macOS launcher | [raycast-zoxide] |
| [rfm] | File manager | Natively supported | | [rfm] | File manager | Natively supported |
| [sesh] | `tmux` session manager | Natively supported | | [sesh] | `tmux` session manager | Natively supported |
| [telescope.nvim] | Fuzzy finder for Neovim | [telescope-zoxide] | | [telescope.nvim] | Fuzzy finder for Neovim | [telescope-zoxide] |
@ -475,7 +489,7 @@ Environment variables[^2] can be used for configuration. They must be set before
| [zsh-autocomplete] | Realtime completions for zsh | Natively supported | | [zsh-autocomplete] | Realtime completions for zsh | Natively supported |
[^1]: [^1]:
Debian / Ubuntu derivatives update their packages very slowly. If you're Debian and its derivatives update their packages very slowly. If you're
using one of these distributions, consider using the install script instead. using one of these distributions, consider using the install script instead.
[^2]: [^2]:
@ -488,6 +502,8 @@ Environment variables[^2] can be used for configuration. They must be set before
[algorithm-aging]: https://github.com/ajeetdsouza/zoxide/wiki/Algorithm#aging [algorithm-aging]: https://github.com/ajeetdsouza/zoxide/wiki/Algorithm#aging
[algorithm-matching]: https://github.com/ajeetdsouza/zoxide/wiki/Algorithm#matching [algorithm-matching]: https://github.com/ajeetdsouza/zoxide/wiki/Algorithm#matching
[alpine linux packages]: https://pkgs.alpinelinux.org/packages?name=zoxide [alpine linux packages]: https://pkgs.alpinelinux.org/packages?name=zoxide
[apt.cli.rs]: https://apt.cli.rs/
[apt.cli.rs-setup]: https://github.com/emmatyping/apt.cli.rs#how-to-add-the-repo
[arch linux extra]: https://archlinux.org/packages/extra/x86_64/zoxide/ [arch linux extra]: https://archlinux.org/packages/extra/x86_64/zoxide/
[asdf]: https://github.com/asdf-vm/asdf [asdf]: https://github.com/asdf-vm/asdf
[builtwithnix-badge]: https://img.shields.io/badge/builtwith-nix-7d81f7?logo=nixos&logoColor=white&style=flat-square [builtwithnix-badge]: https://img.shields.io/badge/builtwith-nix-7d81f7?logo=nixos&logoColor=white&style=flat-square
@ -521,7 +537,7 @@ Environment variables[^2] can be used for configuration. They must be set before
[linuxbrew]: https://formulae.brew.sh/formula-linux/zoxide [linuxbrew]: https://formulae.brew.sh/formula-linux/zoxide
[macports]: https://ports.macports.org/port/zoxide/summary [macports]: https://ports.macports.org/port/zoxide/summary
[neovim]: https://github.com/neovim/neovim [neovim]: https://github.com/neovim/neovim
[nixpkgs]: https://github.com/NixOS/nixpkgs/blob/master/pkgs/tools/misc/zoxide/default.nix [nixpkgs]: https://github.com/NixOS/nixpkgs/blob/master/pkgs/by-name/zo/zoxide/package.nix
[nnn-autojump]: https://github.com/jarun/nnn/blob/master/plugins/autojump [nnn-autojump]: https://github.com/jarun/nnn/blob/master/plugins/autojump
[nnn]: https://github.com/jarun/nnn [nnn]: https://github.com/jarun/nnn
[opensuse factory]: https://build.opensuse.org/package/show/openSUSE:Factory/zoxide [opensuse factory]: https://build.opensuse.org/package/show/openSUSE:Factory/zoxide
@ -530,6 +546,8 @@ Environment variables[^2] can be used for configuration. They must be set before
[ranger-zoxide]: https://github.com/jchook/ranger-zoxide [ranger-zoxide]: https://github.com/jchook/ranger-zoxide
[ranger]: https://github.com/ranger/ranger [ranger]: https://github.com/ranger/ranger
[raspbian packages]: https://archive.raspbian.org/raspbian/pool/main/r/rust-zoxide/ [raspbian packages]: https://archive.raspbian.org/raspbian/pool/main/r/rust-zoxide/
[raycast]: https://www.raycast.com/
[raycast-zoxide]: https://www.raycast.com/mrpunkin/raycast-zoxide
[releases]: https://github.com/ajeetdsouza/zoxide/releases [releases]: https://github.com/ajeetdsouza/zoxide/releases
[rfm]: https://github.com/dsxmachina/rfm [rfm]: https://github.com/dsxmachina/rfm
[scoop]: https://github.com/ScoopInstaller/Main/tree/master/bucket/zoxide.json [scoop]: https://github.com/ScoopInstaller/Main/tree/master/bucket/zoxide.json
@ -543,7 +561,6 @@ Environment variables[^2] can be used for configuration. They must be set before
[tmux-session-wizard]: https://github.com/27medkamal/tmux-session-wizard [tmux-session-wizard]: https://github.com/27medkamal/tmux-session-wizard
[tmux-sessionx]: https://github.com/omerxx/tmux-sessionx [tmux-sessionx]: https://github.com/omerxx/tmux-sessionx
[tutorial]: contrib/tutorial.webp [tutorial]: contrib/tutorial.webp
[ubuntu packages]: https://packages.ubuntu.com/jammy/zoxide
[vim]: https://github.com/vim/vim [vim]: https://github.com/vim/vim
[void linux packages]: https://github.com/void-linux/void-packages/tree/master/srcpkgs/zoxide [void linux packages]: https://github.com/void-linux/void-packages/tree/master/srcpkgs/zoxide
[wiki-env]: https://github.com/ajeetdsouza/zoxide/wiki/HOWTO:-set-environment-variables "HOWTO: set environment variables" [wiki-env]: https://github.com/ajeetdsouza/zoxide/wiki/HOWTO:-set-environment-variables "HOWTO: set environment variables"

View File

@ -6,6 +6,7 @@ use std::{env, io};
use clap::CommandFactory as _; use clap::CommandFactory as _;
use clap_complete::shells::{Bash, Elvish, Fish, PowerShell, Zsh}; use clap_complete::shells::{Bash, Elvish, Fish, PowerShell, Zsh};
use clap_complete_fig::Fig; use clap_complete_fig::Fig;
use clap_complete_nushell::Nushell;
use cmd::Cmd; use cmd::Cmd;
fn main() -> io::Result<()> { fn main() -> io::Result<()> {
@ -27,6 +28,7 @@ fn generate_completions() -> io::Result<()> {
clap_complete::generate_to(Elvish, cmd, BIN_NAME, OUT_DIR)?; clap_complete::generate_to(Elvish, cmd, BIN_NAME, OUT_DIR)?;
clap_complete::generate_to(Fig, cmd, BIN_NAME, OUT_DIR)?; clap_complete::generate_to(Fig, cmd, BIN_NAME, OUT_DIR)?;
clap_complete::generate_to(Fish, cmd, BIN_NAME, OUT_DIR)?; clap_complete::generate_to(Fish, cmd, BIN_NAME, OUT_DIR)?;
clap_complete::generate_to(Nushell, cmd, BIN_NAME, OUT_DIR)?;
clap_complete::generate_to(PowerShell, cmd, BIN_NAME, OUT_DIR)?; clap_complete::generate_to(PowerShell, cmd, BIN_NAME, OUT_DIR)?;
clap_complete::generate_to(Zsh, cmd, BIN_NAME, OUT_DIR)?; clap_complete::generate_to(Zsh, cmd, BIN_NAME, OUT_DIR)?;

View File

@ -30,6 +30,8 @@ _zoxide() {
case $line[1] in case $line[1] in
(add) (add)
_arguments "${_arguments_options[@]}" : \ _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' \
'-h[Print help]' \ '-h[Print help]' \
'--help[Print help]' \ '--help[Print help]' \
'-V[Print version]' \ '-V[Print version]' \
@ -59,7 +61,7 @@ _arguments "${_arguments_options[@]}" : \
'--help[Print help]' \ '--help[Print help]' \
'-V[Print version]' \ '-V[Print version]' \
'--version[Print version]' \ '--version[Print version]' \
':path:' \ ':path:_default' \
&& ret=0 && ret=0
;; ;;
(delete) (delete)
@ -68,7 +70,7 @@ _arguments "${_arguments_options[@]}" : \
'--help[Print help]' \ '--help[Print help]' \
'-V[Print version]' \ '-V[Print version]' \
'--version[Print version]' \ '--version[Print version]' \
':path:' \ ':path:_default' \
&& ret=0 && ret=0
;; ;;
(increment) (increment)
@ -77,7 +79,7 @@ _arguments "${_arguments_options[@]}" : \
'--help[Print help]' \ '--help[Print help]' \
'-V[Print version]' \ '-V[Print version]' \
'--version[Print version]' \ '--version[Print version]' \
':path:' \ ':path:_default' \
&& ret=0 && ret=0
;; ;;
(reload) (reload)
@ -105,19 +107,20 @@ _arguments "${_arguments_options[@]}" : \
;; ;;
(init) (init)
_arguments "${_arguments_options[@]}" : \ _arguments "${_arguments_options[@]}" : \
'--cmd=[Changes the prefix of the \`z\` and \`zi\` commands]:CMD: ' \ '--cmd=[Changes the prefix of the \`z\` and \`zi\` commands]:CMD:_default' \
'--hook=[Changes how often zoxide increments a directory'\''s score]:HOOK:(none prompt pwd)' \ '--hook=[Changes how often zoxide increments a directory'\''s score]:HOOK:(none prompt pwd)' \
'--no-cmd[Prevents zoxide from defining the \`z\` and \`zi\` commands]' \ '--no-cmd[Prevents zoxide from defining the \`z\` and \`zi\` commands]' \
'-h[Print help]' \ '-h[Print help]' \
'--help[Print help]' \ '--help[Print help]' \
'-V[Print version]' \ '-V[Print version]' \
'--version[Print version]' \ '--version[Print version]' \
':shell:(bash elvish fish nushell posix powershell xonsh zsh)' \ ':shell:(bash elvish fish nushell posix powershell tcsh xonsh zsh)' \
&& ret=0 && ret=0
;; ;;
(query) (query)
_arguments "${_arguments_options[@]}" : \ _arguments "${_arguments_options[@]}" : \
'--exclude=[Exclude the current directory]:path:_files -/' \ '--exclude=[Exclude the current directory]:path:_files -/' \
'--base-dir=[Only search within this directory]:path:_files -/' \
'-a[Show unavailable directories]' \ '-a[Show unavailable directories]' \
'--all[Show unavailable directories]' \ '--all[Show unavailable directories]' \
'(-l --list)-i[Use interactive selection]' \ '(-l --list)-i[Use interactive selection]' \
@ -130,7 +133,7 @@ _arguments "${_arguments_options[@]}" : \
'--help[Print help]' \ '--help[Print help]' \
'-V[Print version]' \ '-V[Print version]' \
'--version[Print version]' \ '--version[Print version]' \
'*::keywords:' \ '*::keywords:_default' \
&& ret=0 && ret=0
;; ;;
(remove) (remove)
@ -148,7 +151,7 @@ _arguments "${_arguments_options[@]}" : \
'--help[Print help]' \ '--help[Print help]' \
'-V[Print version]' \ '-V[Print version]' \
'--version[Print version]' \ '--version[Print version]' \
':bookmark_id:' \ ':bookmark_id:_default' \
':path:_files -/' \ ':path:_files -/' \
&& ret=0 && ret=0
;; ;;

View File

@ -35,6 +35,8 @@ Register-ArgumentCompleter -Native -CommandName 'zoxide' -ScriptBlock {
break break
} }
'zoxide;add' { '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('-h', '-h', [CompletionResultType]::ParameterName, 'Print help') [CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help') [CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('-V', '-V ', [CompletionResultType]::ParameterName, 'Print version') [CompletionResult]::new('-V', '-V ', [CompletionResultType]::ParameterName, 'Print version')
@ -101,6 +103,7 @@ Register-ArgumentCompleter -Native -CommandName 'zoxide' -ScriptBlock {
} }
'zoxide;query' { 'zoxide;query' {
[CompletionResult]::new('--exclude', '--exclude', [CompletionResultType]::ParameterName, 'Exclude the current directory') [CompletionResult]::new('--exclude', '--exclude', [CompletionResultType]::ParameterName, 'Exclude the current directory')
[CompletionResult]::new('--base-dir', '--base-dir', [CompletionResultType]::ParameterName, 'Only search within this directory')
[CompletionResult]::new('-a', '-a', [CompletionResultType]::ParameterName, 'Show unavailable directories') [CompletionResult]::new('-a', '-a', [CompletionResultType]::ParameterName, 'Show unavailable directories')
[CompletionResult]::new('--all', '--all', [CompletionResultType]::ParameterName, 'Show unavailable directories') [CompletionResult]::new('--all', '--all', [CompletionResultType]::ParameterName, 'Show unavailable directories')
[CompletionResult]::new('-i', '-i', [CompletionResultType]::ParameterName, 'Use interactive selection') [CompletionResult]::new('-i', '-i', [CompletionResultType]::ParameterName, 'Use interactive selection')

View File

@ -1,12 +1,16 @@
_zoxide() { _zoxide() {
local i cur prev opts cmd local i cur prev opts cmd
COMPREPLY=() COMPREPLY=()
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
cur="$2"
else
cur="${COMP_WORDS[COMP_CWORD]}" cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}" fi
prev="$3"
cmd="" cmd=""
opts="" opts=""
for i in ${COMP_WORDS[@]} for i in "${COMP_WORDS[@]:0:COMP_CWORD}"
do do
case "${cmd},${i}" in case "${cmd},${i}" in
",$1") ",$1")
@ -66,12 +70,20 @@ _zoxide() {
return 0 return 0
;; ;;
zoxide__add) zoxide__add)
opts="-h -V --help --version <PATHS>..." opts="-s -h -V --score --help --version <PATHS>..."
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0 return 0
fi fi
case "${prev}" in case "${prev}" in
--score)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
-s)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
*) *)
COMPREPLY=() COMPREPLY=()
;; ;;
@ -182,7 +194,7 @@ _zoxide() {
return 0 return 0
;; ;;
zoxide__init) zoxide__init)
opts="-h -V --no-cmd --cmd --hook --help --version bash elvish fish nushell posix powershell xonsh zsh" opts="-h -V --no-cmd --cmd --hook --help --version bash elvish fish nushell posix powershell tcsh xonsh zsh"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0 return 0
@ -204,7 +216,7 @@ _zoxide() {
return 0 return 0
;; ;;
zoxide__query) zoxide__query)
opts="-a -i -l -s -h -V --all --interactive --list --score --exclude --help --version [KEYWORDS]..." opts="-a -i -l -s -h -V --all --interactive --list --score --exclude --base-dir --help --version [KEYWORDS]..."
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0 return 0
@ -217,6 +229,13 @@ _zoxide() {
fi fi
return 0 return 0
;; ;;
--base-dir)
COMPREPLY=()
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
compopt -o plusdirs
fi
return 0
;;
*) *)
COMPREPLY=() COMPREPLY=()
;; ;;

View File

@ -31,6 +31,8 @@ set edit:completion:arg-completer[zoxide] = {|@words|
cand bookmark 'bookmark' cand bookmark 'bookmark'
} }
&'zoxide;add'= { &'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 -h 'Print help' cand -h 'Print help'
cand --help 'Print help' cand --help 'Print help'
cand -V 'Print version' cand -V 'Print version'
@ -89,6 +91,7 @@ set edit:completion:arg-completer[zoxide] = {|@words|
} }
&'zoxide;query'= { &'zoxide;query'= {
cand --exclude 'Exclude the current directory' cand --exclude 'Exclude the current directory'
cand --base-dir 'Only search within this directory'
cand -a 'Show unavailable directories' cand -a 'Show unavailable directories'
cand --all 'Show unavailable directories' cand --all 'Show unavailable directories'
cand -i 'Use interactive selection' cand -i 'Use interactive selection'

View File

@ -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 "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_needs_command" -f -a "remove" -d 'Remove a directory from the database'
complete -c zoxide -n "__fish_zoxide_needs_command" -f -a "bookmark" complete -c zoxide -n "__fish_zoxide_needs_command" -f -a "bookmark"
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 h -l help -d 'Print help' 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" -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 h -l help -d 'Print help'
@ -49,16 +50,20 @@ complete -c zoxide -n "__fish_zoxide_using_subcommand edit; and __fish_seen_subc
complete -c zoxide -n "__fish_zoxide_using_subcommand edit; and __fish_seen_subcommand_from increment" -s V -l version -d 'Print version' complete -c zoxide -n "__fish_zoxide_using_subcommand edit; and __fish_seen_subcommand_from increment" -s V -l version -d 'Print version'
complete -c zoxide -n "__fish_zoxide_using_subcommand edit; and __fish_seen_subcommand_from reload" -s h -l help -d 'Print help' complete -c zoxide -n "__fish_zoxide_using_subcommand edit; and __fish_seen_subcommand_from reload" -s h -l help -d 'Print help'
complete -c zoxide -n "__fish_zoxide_using_subcommand edit; and __fish_seen_subcommand_from reload" -s V -l version -d 'Print version' complete -c zoxide -n "__fish_zoxide_using_subcommand edit; and __fish_seen_subcommand_from reload" -s V -l version -d 'Print version'
complete -c zoxide -n "__fish_zoxide_using_subcommand import" -l from -d 'Application to import from' -r -f -a "{autojump\t'',z\t''}" complete -c zoxide -n "__fish_zoxide_using_subcommand import" -l from -d 'Application to import from' -r -f -a "autojump\t''
z\t''"
complete -c zoxide -n "__fish_zoxide_using_subcommand import" -l merge -d 'Merge into existing database' complete -c zoxide -n "__fish_zoxide_using_subcommand import" -l merge -d 'Merge into existing database'
complete -c zoxide -n "__fish_zoxide_using_subcommand import" -s h -l help -d 'Print help' complete -c zoxide -n "__fish_zoxide_using_subcommand import" -s h -l help -d 'Print help'
complete -c zoxide -n "__fish_zoxide_using_subcommand import" -s V -l version -d 'Print version' complete -c zoxide -n "__fish_zoxide_using_subcommand import" -s V -l version -d 'Print version'
complete -c zoxide -n "__fish_zoxide_using_subcommand init" -l cmd -d 'Changes the prefix of the `z` and `zi` commands' -r complete -c zoxide -n "__fish_zoxide_using_subcommand init" -l cmd -d 'Changes the prefix of the `z` and `zi` commands' -r
complete -c zoxide -n "__fish_zoxide_using_subcommand init" -l hook -d 'Changes how often zoxide increments a directory\'s score' -r -f -a "{none\t'',prompt\t'',pwd\t''}" complete -c zoxide -n "__fish_zoxide_using_subcommand init" -l hook -d 'Changes how often zoxide increments a directory\'s score' -r -f -a "none\t''
prompt\t''
pwd\t''"
complete -c zoxide -n "__fish_zoxide_using_subcommand init" -l no-cmd -d 'Prevents zoxide from defining the `z` and `zi` commands' complete -c zoxide -n "__fish_zoxide_using_subcommand init" -l no-cmd -d 'Prevents zoxide from defining the `z` and `zi` commands'
complete -c zoxide -n "__fish_zoxide_using_subcommand init" -s h -l help -d 'Print help' complete -c zoxide -n "__fish_zoxide_using_subcommand init" -s h -l help -d 'Print help'
complete -c zoxide -n "__fish_zoxide_using_subcommand init" -s V -l version -d 'Print version' complete -c zoxide -n "__fish_zoxide_using_subcommand init" -s V -l version -d 'Print version'
complete -c zoxide -n "__fish_zoxide_using_subcommand query" -l exclude -d 'Exclude the current directory' -r -f -a "(__fish_complete_directories)" complete -c zoxide -n "__fish_zoxide_using_subcommand query" -l exclude -d 'Exclude the current directory' -r -f -a "(__fish_complete_directories)"
complete -c zoxide -n "__fish_zoxide_using_subcommand query" -l base-dir -d 'Only search within this directory' -r -f -a "(__fish_complete_directories)"
complete -c zoxide -n "__fish_zoxide_using_subcommand query" -s a -l all -d 'Show unavailable directories' complete -c zoxide -n "__fish_zoxide_using_subcommand query" -s a -l all -d 'Show unavailable directories'
complete -c zoxide -n "__fish_zoxide_using_subcommand query" -s i -l interactive -d 'Use interactive selection' complete -c zoxide -n "__fish_zoxide_using_subcommand query" -s i -l interactive -d 'Use interactive selection'
complete -c zoxide -n "__fish_zoxide_using_subcommand query" -s l -l list -d 'List all matching directories' complete -c zoxide -n "__fish_zoxide_using_subcommand query" -s l -l list -d 'List all matching directories'

106
contrib/completions/zoxide.nu generated Normal file
View File

@ -0,0 +1,106 @@
module completions {
# A smarter cd command for your terminal
export extern zoxide [
--help(-h) # Print help
--version(-V) # Print version
]
# Add a new directory or increment its rank
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
--help(-h) # Print help
--version(-V) # Print version
]
# Edit the database
export extern "zoxide edit" [
--help(-h) # Print help
--version(-V) # Print version
]
export extern "zoxide edit decrement" [
path: string
--help(-h) # Print help
--version(-V) # Print version
]
export extern "zoxide edit delete" [
path: string
--help(-h) # Print help
--version(-V) # Print version
]
export extern "zoxide edit increment" [
path: string
--help(-h) # Print help
--version(-V) # Print version
]
export extern "zoxide edit reload" [
--help(-h) # Print help
--version(-V) # Print version
]
def "nu-complete zoxide import from" [] {
[ "autojump" "z" ]
}
# Import entries from another application
export extern "zoxide import" [
path: path
--from: string@"nu-complete zoxide import from" # Application to import from
--merge # Merge into existing database
--help(-h) # Print help
--version(-V) # Print version
]
def "nu-complete zoxide init shell" [] {
[ "bash" "elvish" "fish" "nushell" "posix" "powershell" "tcsh" "xonsh" "zsh" ]
}
def "nu-complete zoxide init hook" [] {
[ "none" "prompt" "pwd" ]
}
# Generate shell configuration
export extern "zoxide init" [
shell: string@"nu-complete zoxide init shell"
--no-cmd # Prevents zoxide from defining the `z` and `zi` commands
--cmd: string # Changes the prefix of the `z` and `zi` commands
--hook: string@"nu-complete zoxide init hook" # Changes how often zoxide increments a directory's score
--help(-h) # Print help
--version(-V) # Print version
]
# Search for a directory in the database
export extern "zoxide query" [
...keywords: string
--all(-a) # Show unavailable directories
--interactive(-i) # Use interactive selection
--list(-l) # List all matching directories
--score(-s) # Print score with results
--exclude: path # Exclude the current directory
--base-dir: path # Only search within this directory
--help(-h) # Print help
--version(-V) # Print version
]
# Remove a directory from the database
export extern "zoxide remove" [
...paths: path
--help(-h) # Print help
--version(-V) # Print version
]
export extern "zoxide bookmark" [
bookmark_id: string
path: path
--help(-h) # Print help
--version(-V) # Print version
]
}
export use completions *

View File

@ -6,6 +6,15 @@ const completion: Fig.Spec = {
name: "add", name: "add",
description: "Add a new directory or increment its rank", description: "Add a new directory or increment its rank",
options: [ options: [
{
name: ["-s", "--score"],
description: "The rank to increment the entry if it exists or initialize it with if it doesn't",
isRepeatable: true,
args: {
name: "score",
isOptional: true,
},
},
{ {
name: ["-h", "--help"], name: ["-h", "--help"],
description: "Print help", description: "Print help",
@ -185,6 +194,7 @@ const completion: Fig.Spec = {
"nushell", "nushell",
"posix", "posix",
"powershell", "powershell",
"tcsh",
"xonsh", "xonsh",
"zsh", "zsh",
], ],
@ -204,6 +214,16 @@ const completion: Fig.Spec = {
template: "folders", template: "folders",
}, },
}, },
{
name: "--base-dir",
description: "Only search within this directory",
isRepeatable: true,
args: {
name: "base_dir",
isOptional: true,
template: "folders",
},
},
{ {
name: ["-a", "--all"], name: ["-a", "--all"],
description: "Show unavailable directories", description: "Show unavailable directories",

View File

@ -55,6 +55,14 @@ $profile\fR in PowerShell):
\fBInvoke-Expression (& { (zoxide init powershell | Out-String) })\fR \fBInvoke-Expression (& { (zoxide init powershell | Out-String) })\fR
.fi .fi
.TP .TP
.B tcsh
Add this to the \fBend\fR of your config file (usually \fB~/.tcshrc\fR):
.sp
.nf
\fBzoxide init tcsh > ~/.zoxide.tcsh\fR
\fBsource ~/.zoxide.tcsh\fR
.fi
.TP
.B xonsh .B xonsh
Add this to the \fBend\fR of your config file (usually \fB~/.xonshrc\fR): Add this to the \fBend\fR of your config file (usually \fB~/.xonshrc\fR):
.sp .sp

View File

@ -1,10 +1,10 @@
let let
pkgs = import (builtins.fetchTarball pkgs = import (builtins.fetchTarball
"https://github.com/NixOS/nixpkgs/archive/056faf24027e12f0ba6edebe299ed136e030d29a.tar.gz") { "https://github.com/NixOS/nixpkgs/archive/ec9ef366451af88284d7dfd18ee017b7e86a0710.tar.gz") {
overlays = [ rust ]; overlays = [ rust ];
}; };
rust = import (builtins.fetchTarball rust = import (builtins.fetchTarball
"https://github.com/oxalica/rust-overlay/archive/f61820fa2c3844d6940cce269a6afdec30aa2e6c.tar.gz"); "https://github.com/oxalica/rust-overlay/archive/026e8fedefd6b167d92ed04b195c658d95ffc7a5.tar.gz");
rust-nightly = rust-nightly =
pkgs.rust-bin.selectLatestNightlyWith (toolchain: toolchain.minimal); pkgs.rust-bin.selectLatestNightlyWith (toolchain: toolchain.minimal);
@ -27,6 +27,7 @@ in pkgs.mkShell {
pkgs.ksh pkgs.ksh
pkgs.nushell pkgs.nushell
pkgs.powershell pkgs.powershell
pkgs.tcsh
pkgs.xonsh pkgs.xonsh
pkgs.zsh pkgs.zsh

View File

@ -33,7 +33,9 @@ impl Run for Add {
if !Path::new(path).is_dir() { if !Path::new(path).is_dir() {
bail!("not a directory: {path}"); bail!("not a directory: {path}");
} }
db.add_update(path, 1.0, now);
let by = self.score.unwrap_or(1.0);
db.add_update(path, by, now);
} }
if db.dirty() { if db.dirty() {

View File

@ -59,6 +59,11 @@ pub enum Cmd {
pub struct Add { pub struct Add {
#[clap(num_args = 1.., required = true, value_hint = ValueHint::DirPath)] #[clap(num_args = 1.., required = true, value_hint = ValueHint::DirPath)]
pub paths: Vec<PathBuf>, pub paths: Vec<PathBuf>,
/// The rank to increment the entry if it exists or initialize it with if it
/// doesn't
#[clap(short, long)]
pub score: Option<f64>,
} }
/// Edit the database /// Edit the database
@ -149,6 +154,7 @@ pub enum InitShell {
#[clap(alias = "ksh")] #[clap(alias = "ksh")]
Posix, Posix,
Powershell, Powershell,
Tcsh,
Xonsh, Xonsh,
Zsh, Zsh,
} }
@ -181,6 +187,10 @@ pub struct Query {
/// Exclude the current directory /// Exclude the current directory
#[clap(long, value_hint = ValueHint::DirPath, value_name = "path")] #[clap(long, value_hint = ValueHint::DirPath, value_name = "path")]
pub exclude: Option<String>, pub exclude: Option<String>,
/// Only search within this directory
#[clap(long, value_hint = ValueHint::DirPath, value_name = "path")]
pub base_dir: Option<String>,
} }
/// Remove a directory from the database /// Remove a directory from the database

View File

@ -1,12 +1,12 @@
use std::io::{self, Write}; use std::io::{self, Write};
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use rinja::Template; use askama::Template;
use crate::cmd::{Init, InitShell, Run}; use crate::cmd::{Init, InitShell, Run};
use crate::config; use crate::config;
use crate::error::BrokenPipeHandler; use crate::error::BrokenPipeHandler;
use crate::shell::{Bash, Elvish, Fish, Nushell, Opts, Posix, Powershell, Xonsh, Zsh}; use crate::shell::{Bash, Elvish, Fish, Nushell, Opts, Posix, Powershell, Tcsh, Xonsh, Zsh};
impl Run for Init { impl Run for Init {
fn run(&self) -> Result<()> { fn run(&self) -> Result<()> {
@ -22,6 +22,7 @@ impl Run for Init {
InitShell::Nushell => Nushell(opts).render(), InitShell::Nushell => Nushell(opts).render(),
InitShell::Posix => Posix(opts).render(), InitShell::Posix => Posix(opts).render(),
InitShell::Powershell => Powershell(opts).render(), InitShell::Powershell => Powershell(opts).render(),
InitShell::Tcsh => Tcsh(opts).render(),
InitShell::Xonsh => Xonsh(opts).render(), InitShell::Xonsh => Xonsh(opts).render(),
InitShell::Zsh => Zsh(opts).render(), InitShell::Zsh => Zsh(opts).render(),
} }

View File

@ -101,7 +101,8 @@ impl Query {
fn get_stream<'a>(&self, db: &'a mut Database, now: Epoch) -> Result<Stream<'a>> { fn get_stream<'a>(&self, db: &'a mut Database, now: Epoch) -> Result<Stream<'a>> {
let mut options = StreamOptions::new(now) let mut options = StreamOptions::new(now)
.with_keywords(self.keywords.iter().map(|s| s.as_str())) .with_keywords(self.keywords.iter().map(|s| s.as_str()))
.with_exclude(config::exclude_dirs()?); .with_exclude(config::exclude_dirs()?)
.with_base_dir(self.base_dir.clone());
if !self.all { if !self.all {
let resolve_symlinks = config::resolve_symlinks(); let resolve_symlinks = config::resolve_symlinks();
options = options.with_exists(true).with_resolve_symlinks(resolve_symlinks); options = options.with_exists(true).with_resolve_symlinks(resolve_symlinks);

View File

@ -190,7 +190,7 @@ impl Database {
*self.borrow_dirty() *self.borrow_dirty()
} }
pub fn dirs(&self) -> &[Dir] { pub fn dirs(&self) -> &[Dir<'_>] {
&self.borrow_dirs().0 &self.borrow_dirs().0
} }
@ -223,7 +223,10 @@ impl Database {
.context("could not serialize database") .context("could not serialize database")
} }
fn deserialize(bytes: &[u8], path: PathBuf) -> Result<(Vec<Dir>, HashMap<String, PathBuf>)> { fn deserialize(
bytes: &[u8],
path: PathBuf,
) -> Result<(Vec<Dir<'_>>, HashMap<String, PathBuf>)> {
// Assume a maximum size for the database. This prevents bincode from throwing // Assume a maximum size for the database. This prevents bincode from throwing
// strange errors when it encounters invalid data. // strange errors when it encounters invalid data.
const MAX_SIZE: u64 = 32 << 20; // 32 MiB const MAX_SIZE: u64 = 32 << 20; // 32 MiB

View File

@ -1,5 +1,6 @@
use std::iter::Rev; use std::iter::Rev;
use std::ops::Range; use std::ops::Range;
use std::path::Path;
use std::{fs, path}; use std::{fs, path};
use glob::Pattern; use glob::Pattern;
@ -20,7 +21,7 @@ impl<'a> Stream<'a> {
Stream { db, idxs, options } Stream { db, idxs, options }
} }
pub fn next(&mut self) -> Option<&Dir> { pub fn next(&mut self) -> Option<&Dir<'_>> {
while let Some(idx) = self.idxs.next() { while let Some(idx) = self.idxs.next() {
let dir = &self.db.dirs()[idx]; let dir = &self.db.dirs()[idx];
@ -28,11 +29,16 @@ impl<'a> Stream<'a> {
continue; continue;
} }
if !self.filter_by_base_dir(&dir.path) {
continue;
}
if !self.filter_by_exclude(&dir.path) { if !self.filter_by_exclude(&dir.path) {
self.db.swap_remove(idx); self.db.swap_remove(idx);
continue; continue;
} }
// Exists queries are slow, this should always be checked last.
if !self.filter_by_exists(&dir.path) { if !self.filter_by_exists(&dir.path) {
if dir.last_accessed < self.options.ttl { if dir.last_accessed < self.options.ttl {
self.db.swap_remove(idx); self.db.swap_remove(idx);
@ -47,6 +53,30 @@ impl<'a> Stream<'a> {
None None
} }
fn filter_by_base_dir(&self, path: &str) -> bool {
match &self.options.base_dir {
Some(base_dir) => Path::new(path).starts_with(base_dir),
None => true,
}
}
fn filter_by_exclude(&self, path: &str) -> bool {
!self.options.exclude.iter().any(|pattern| pattern.matches(path))
}
fn filter_by_exists(&self, path: &str) -> bool {
if !self.options.exists {
return true;
}
// 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.
let resolver =
if self.options.resolve_symlinks { fs::symlink_metadata } else { fs::metadata };
resolver(path).map(|metadata| metadata.is_dir()).unwrap_or_default()
}
fn filter_by_keywords(&self, path: &str) -> bool { fn filter_by_keywords(&self, path: &str) -> bool {
let (keywords_last, keywords) = match self.options.keywords.split_last() { let (keywords_last, keywords) = match self.options.keywords.split_last() {
Some(split) => split, Some(split) => split,
@ -74,23 +104,6 @@ impl<'a> Stream<'a> {
true true
} }
fn filter_by_exclude(&self, path: &str) -> bool {
!self.options.exclude.iter().any(|pattern| pattern.matches(path))
}
fn filter_by_exists(&self, path: &str) -> bool {
if !self.options.exists {
return true;
}
// 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.
let resolver =
if self.options.resolve_symlinks { fs::symlink_metadata } else { fs::metadata };
resolver(path).map(|metadata| metadata.is_dir()).unwrap_or_default()
}
} }
pub struct StreamOptions { pub struct StreamOptions {
@ -112,6 +125,10 @@ pub struct StreamOptions {
/// Directories that do not exist and haven't been accessed since TTL will /// Directories that do not exist and haven't been accessed since TTL will
/// be lazily removed. /// be lazily removed.
ttl: Epoch, ttl: Epoch,
/// Only return directories within this parent directory
/// Does not check if the path exists
base_dir: Option<String>,
} }
impl StreamOptions { impl StreamOptions {
@ -123,6 +140,7 @@ impl StreamOptions {
exists: false, exists: false,
resolve_symlinks: false, resolve_symlinks: false,
ttl: now.saturating_sub(3 * MONTH), ttl: now.saturating_sub(3 * MONTH),
base_dir: None,
} }
} }
@ -149,6 +167,11 @@ impl StreamOptions {
self.resolve_symlinks = resolve_symlinks; self.resolve_symlinks = resolve_symlinks;
self self
} }
pub fn with_base_dir(mut self, base_dir: Option<String>) -> Self {
self.base_dir = base_dir;
self
}
} }
#[cfg(test)] #[cfg(test)]

View File

@ -18,8 +18,8 @@ use crate::error::SilentExit;
pub fn main() -> ExitCode { pub fn main() -> ExitCode {
// Forcibly disable backtraces. // Forcibly disable backtraces.
env::remove_var("RUST_LIB_BACKTRACE"); unsafe { env::remove_var("RUST_LIB_BACKTRACE") };
env::remove_var("RUST_BACKTRACE"); unsafe { env::remove_var("RUST_BACKTRACE") };
match Cmd::parse().run() { match Cmd::parse().run() {
Ok(()) => ExitCode::SUCCESS, Ok(()) => ExitCode::SUCCESS,

View File

@ -10,7 +10,7 @@ pub struct Opts<'a> {
macro_rules! make_template { macro_rules! make_template {
($name:ident, $path:expr) => { ($name:ident, $path:expr) => {
#[derive(::std::fmt::Debug, ::rinja::Template)] #[derive(::std::fmt::Debug, ::askama::Template)]
#[template(path = $path)] #[template(path = $path)]
pub struct $name<'a>(pub &'a self::Opts<'a>); pub struct $name<'a>(pub &'a self::Opts<'a>);
@ -29,14 +29,15 @@ make_template!(Fish, "fish.txt");
make_template!(Nushell, "nushell.txt"); make_template!(Nushell, "nushell.txt");
make_template!(Posix, "posix.txt"); make_template!(Posix, "posix.txt");
make_template!(Powershell, "powershell.txt"); make_template!(Powershell, "powershell.txt");
make_template!(Tcsh, "tcsh.txt");
make_template!(Xonsh, "xonsh.txt"); make_template!(Xonsh, "xonsh.txt");
make_template!(Zsh, "zsh.txt"); make_template!(Zsh, "zsh.txt");
#[cfg(feature = "nix-dev")] #[cfg(feature = "nix-dev")]
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use askama::Template;
use assert_cmd::Command; use assert_cmd::Command;
use rinja::Template;
use rstest::rstest; use rstest::rstest;
use rstest_reuse::{apply, template}; use rstest_reuse::{apply, template};
@ -96,7 +97,7 @@ mod tests {
#[apply(opts)] #[apply(opts)]
fn elvish_elvish(cmd: Option<&str>, hook: InitHook, echo: bool, resolve_symlinks: bool) { fn elvish_elvish(cmd: Option<&str>, hook: InitHook, echo: bool, resolve_symlinks: bool) {
let opts = Opts { cmd, hook, echo, resolve_symlinks }; let opts = Opts { cmd, hook, echo, resolve_symlinks };
let mut source = String::default(); let mut source = String::new();
// Filter out lines using edit:*, since those functions are only available in // Filter out lines using edit:*, since those functions are only available in
// the interactive editor. // the interactive editor.
@ -248,6 +249,20 @@ mod tests {
.stderr(""); .stderr("");
} }
#[apply(opts)]
fn tcsh_tcsh(cmd: Option<&str>, hook: InitHook, echo: bool, resolve_symlinks: bool) {
let opts = Opts { cmd, hook, echo, resolve_symlinks };
let source = Tcsh(&opts).render().unwrap();
Command::new("tcsh")
.args(["-e", "-f", "-s"])
.write_stdin(source)
.assert()
.success()
.stdout("")
.stderr("");
}
#[apply(opts)] #[apply(opts)]
fn xonsh_black(cmd: Option<&str>, hook: InitHook, echo: bool, resolve_symlinks: bool) { fn xonsh_black(cmd: Option<&str>, hook: InitHook, echo: bool, resolve_symlinks: bool) {
let opts = Opts { cmd, hook, echo, resolve_symlinks }; let opts = Opts { cmd, hook, echo, resolve_symlinks };

View File

@ -135,7 +135,7 @@ impl FzfChild {
mem::drop(self.0.stdin.take()); mem::drop(self.0.stdin.take());
let mut stdout = self.0.stdout.take().unwrap(); let mut stdout = self.0.stdout.take().unwrap();
let mut output = String::default(); let mut output = String::new();
stdout.read_to_string(&mut output).context("failed to read from fzf")?; stdout.read_to_string(&mut output).context("failed to read from fzf")?;
let status = self.0.wait().context("wait failed on fzf")?; let status = self.0.wait().context("wait failed on fzf")?;
@ -169,16 +169,21 @@ pub fn write(path: impl AsRef<Path>, contents: impl AsRef<[u8]>) -> Result<()> {
#[cfg(unix)] #[cfg(unix)]
if let Ok(metadata) = path.metadata() { if let Ok(metadata) = path.metadata() {
use std::os::unix::fs::MetadataExt; use std::os::unix::fs::MetadataExt;
use std::os::unix::io::AsRawFd;
use nix::unistd::{self, Gid, Uid}; use nix::unistd::{self, Gid, Uid};
let uid = Uid::from_raw(metadata.uid()); let uid = Uid::from_raw(metadata.uid());
let gid = Gid::from_raw(metadata.gid()); let gid = Gid::from_raw(metadata.gid());
_ = unistd::fchown(tmp_file.as_raw_fd(), Some(uid), Some(gid)); _ = unistd::fchown(&tmp_file, Some(uid), Some(gid));
} }
// Close and rename the tmpfile. // Close and rename the tmpfile.
// In some cases, errors from the last write() are reported only on close().
// Rust ignores errors from close(), since it occurs inside `Drop`. To
// catch these errors, we manually call `File::sync_all()` first.
tmp_file
.sync_all()
.with_context(|| format!("could not sync writes to file: {}", tmp_path.display()))?;
mem::drop(tmp_file); mem::drop(tmp_file);
rename(&tmp_path, path) rename(&tmp_path, path)
})(); })();

View File

@ -66,8 +66,11 @@ function __zoxide_doctor() {
return 0 return 0
{%- else %} {%- else %}
[[ ${_ZO_DOCTOR:-1} -ne 0 ]] || return 0 [[ ${_ZO_DOCTOR:-1} -eq 0 ]] && return 0
[[ ${PROMPT_COMMAND:=} != *'__zoxide_hook'* ]] || return 0 # shellcheck disable=SC2199
[[ ${PROMPT_COMMAND[@]:-} == *'__zoxide_hook'* ]] && return 0
# shellcheck disable=SC2199
[[ ${__vsc_original_prompt_command[@]:-} == *'__zoxide_hook'* ]] && return 0
_ZO_DOCTOR=0 _ZO_DOCTOR=0
\builtin printf '%s\n' \ \builtin printf '%s\n' \
@ -86,6 +89,8 @@ function __zoxide_doctor() {
# When using zoxide with --no-cmd, alias these internal functions as desired. # When using zoxide with --no-cmd, alias these internal functions as desired.
# #
__zoxide_z_prefix='z#'
# Jump to a directory using only keywords. # Jump to a directory using only keywords.
function __zoxide_z() { function __zoxide_z() {
__zoxide_doctor __zoxide_doctor
@ -99,6 +104,10 @@ function __zoxide_z() {
__zoxide_cd "$1" __zoxide_cd "$1"
elif [[ $# -eq 2 && $1 == '--' ]]; then elif [[ $# -eq 2 && $1 == '--' ]]; then
__zoxide_cd "$2" __zoxide_cd "$2"
elif [[ ${@: -1} == "${__zoxide_z_prefix}"?* ]]; then
# shellcheck disable=SC2124
\builtin local result="${@: -1}"
__zoxide_cd "{{ "${result:${#__zoxide_z_prefix}}" }}"
else else
\builtin local result \builtin local result
# shellcheck disable=SC2312 # shellcheck disable=SC2312
@ -142,6 +151,8 @@ if [[ ${BASH_VERSINFO[0]:-0} -eq 4 && ${BASH_VERSINFO[1]:-0} -ge 4 || ${BASH_VER
function __zoxide_z_complete_helper() { function __zoxide_z_complete_helper() {
READLINE_LINE="{{ cmd }} ${__zoxide_result@Q}" READLINE_LINE="{{ cmd }} ${__zoxide_result@Q}"
READLINE_POINT={{ "${#READLINE_LINE}" }} READLINE_POINT={{ "${#READLINE_LINE}" }}
bind '"\e[0n": accept-line'
\builtin printf '\e[5n' >/dev/tty
} }
function __zoxide_z_complete() { function __zoxide_z_complete() {
@ -157,11 +168,16 @@ if [[ ${BASH_VERSINFO[0]:-0} -eq 4 && ${BASH_VERSINFO[1]:-0} -ge 4 || ${BASH_VER
elif [[ -z ${COMP_WORDS[-1]} ]]; then elif [[ -z ${COMP_WORDS[-1]} ]]; then
# shellcheck disable=SC2312 # shellcheck disable=SC2312
__zoxide_result="$(\command zoxide query --exclude "$(__zoxide_pwd)" --interactive -- "{{ "${COMP_WORDS[@]:1:${#COMP_WORDS[@]}-2}" }}")" && { __zoxide_result="$(\command zoxide query --exclude "$(__zoxide_pwd)" --interactive -- "{{ "${COMP_WORDS[@]:1:${#COMP_WORDS[@]}-2}" }}")" && {
\builtin bind '"\e[0n": redraw-current-line' # In case the terminal does not respond to \e[5n or another
\builtin printf '\e[5n' # mechanism steals the response, it is still worth completing
# the directory in the command line.
COMPREPLY=("${__zoxide_z_prefix}${__zoxide_result}/")
\builtin bind -x '"\e[0n": __zoxide_z_complete_helper "${result}"' # Note: We here call "bind" without prefixing "\builtin" to be
\builtin printf '\e[5n' # compatible with frameworks like ble.sh, which emulates Bash's
# builtin "bind".
bind -x '"\e[0n": __zoxide_z_complete_helper'
\builtin printf '\e[5n' >/dev/tty
} }
fi fi
} }

View File

@ -26,7 +26,7 @@ export-env {
if not $__zoxide_hooked { if not $__zoxide_hooked {
$env.config.hooks.pre_prompt = ($env.config.hooks.pre_prompt | append { $env.config.hooks.pre_prompt = ($env.config.hooks.pre_prompt | append {
__zoxide_hook: true, __zoxide_hook: true,
code: {|| zoxide add -- $env.PWD} code: {|| ^zoxide add -- $env.PWD}
}) })
} }
{%- else if hook == InitHook::Pwd %} {%- else if hook == InitHook::Pwd %}
@ -43,7 +43,7 @@ export-env {
if not $__zoxide_hooked { if not $__zoxide_hooked {
$env.config.hooks.env_change.PWD = ($env.config.hooks.env_change.PWD | append { $env.config.hooks.env_change.PWD = ($env.config.hooks.env_change.PWD | append {
__zoxide_hook: true, __zoxide_hook: true,
code: {|_, dir| zoxide add -- $dir} code: {|_, dir| ^zoxide add -- $dir}
}) })
} }
{%- endif %} {%- endif %}
@ -60,9 +60,9 @@ def --env --wrapped __zoxide_z [...rest: string] {
let path = match $rest { let path = match $rest {
[] => {'~'}, [] => {'~'},
[ '-' ] => {'-'}, [ '-' ] => {'-'},
[ $arg ] if ($arg | path type) == 'dir' => {$arg} [ $arg ] if ($arg | path expand | path type) == 'dir' => {$arg}
_ => { _ => {
zoxide query --exclude $env.PWD -- ...$rest | str trim -r -c "\n" ^zoxide query --exclude $env.PWD -- ...$rest | str trim -r -c "\n"
} }
} }
cd $path cd $path
@ -73,7 +73,7 @@ def --env --wrapped __zoxide_z [...rest: string] {
# Jump to a directory using interactive search. # Jump to a directory using interactive search.
def --env --wrapped __zoxide_zi [...rest:string] { def --env --wrapped __zoxide_zi [...rest:string] {
cd $'(zoxide query --interactive -- ...$rest | str trim -r -c "\n")' cd $'(^zoxide query --interactive -- ...$rest | str trim -r -c "\n")'
{%- if echo %} {%- if echo %}
echo $env.PWD echo $env.PWD
{%- endif %} {%- endif %}

View File

@ -43,6 +43,30 @@ if [ "${PS1:=}" = "${PS1#*\$(__zoxide_hook)}" ]; then
PS1="${PS1}\$(__zoxide_hook)" PS1="${PS1}\$(__zoxide_hook)"
fi fi
# Report common issues.
__zoxide_doctor() {
{%- if hook != InitHook::Prompt %}
return 0
{%- else %}
[ "${_ZO_DOCTOR:-1}" -eq 0 ] && return 0
case "${PS1:-}" in
*__zoxide_hook*) return 0 ;;
*) ;;
esac
_ZO_DOCTOR=0
\command printf '%s\n' \
'zoxide: detected a possible configuration issue.' \
'Please ensure that zoxide is initialized right at the end of your shell configuration file.' \
'' \
'If the issue persists, consider filing an issue at:' \
'https://github.com/ajeetdsouza/zoxide/issues' \
'' \
'Disable this message by setting _ZO_DOCTOR=0.' \
'' >&2
{%- endif %}
}
{%- when InitHook::Pwd -%} {%- when InitHook::Pwd -%}
\command printf "%s\n%s\n" \ \command printf "%s\n%s\n" \
"zoxide: PWD hooks are not supported on POSIX shells." \ "zoxide: PWD hooks are not supported on POSIX shells." \
@ -56,6 +80,8 @@ fi
# Jump to a directory using only keywords. # Jump to a directory using only keywords.
__zoxide_z() { __zoxide_z() {
__zoxide_doctor
if [ "$#" -eq 0 ]; then if [ "$#" -eq 0 ]; then
__zoxide_cd ~ __zoxide_cd ~
elif [ "$#" -eq 1 ] && [ "$1" = '-' ]; then elif [ "$#" -eq 1 ] && [ "$1" = '-' ]; then
@ -76,6 +102,7 @@ __zoxide_z() {
# Jump to a directory using interactive search. # Jump to a directory using interactive search.
__zoxide_zi() { __zoxide_zi() {
__zoxide_doctor
__zoxide_result="$(\command zoxide query --interactive -- "$@")" && __zoxide_cd "${__zoxide_result}" __zoxide_result="$(\command zoxide query --interactive -- "$@")" && __zoxide_cd "${__zoxide_result}"
} }

View File

@ -80,7 +80,7 @@ function global:__zoxide_hook {
{%- endif %} {%- endif %}
# Initialize hook. # Initialize hook.
$global:__zoxide_hooked = (Get-Variable __zoxide_hooked -ErrorAction SilentlyContinue -ValueOnly) $global:__zoxide_hooked = (Get-Variable __zoxide_hooked -ErrorAction Ignore -ValueOnly)
if ($global:__zoxide_hooked -ne 1) { if ($global:__zoxide_hooked -ne 1) {
$global:__zoxide_hooked = 1 $global:__zoxide_hooked = 1
$global:__zoxide_prompt_old = $function:prompt $global:__zoxide_prompt_old = $function:prompt
@ -106,9 +106,12 @@ function global:__zoxide_z {
elseif ($args.Length -eq 1 -and ($args[0] -eq '-' -or $args[0] -eq '+')) { elseif ($args.Length -eq 1 -and ($args[0] -eq '-' -or $args[0] -eq '+')) {
__zoxide_cd $args[0] $false __zoxide_cd $args[0] $false
} }
elseif ($args.Length -eq 1 -and (Test-Path $args[0] -PathType Container)) { elseif ($args.Length -eq 1 -and (Test-Path -PathType Container -LiteralPath $args[0])) {
__zoxide_cd $args[0] $true __zoxide_cd $args[0] $true
} }
elseif ($args.Length -eq 1 -and (Test-Path -PathType Container -Path $args[0] )) {
__zoxide_cd $args[0] $false
}
else { else {
$result = __zoxide_pwd $result = __zoxide_pwd
if ($null -ne $result) { if ($null -ne $result) {

74
templates/tcsh.txt Normal file
View File

@ -0,0 +1,74 @@
{%- let section = "# =============================================================================\n#" -%}
{%- let not_configured = "# -- not configured --" -%}
{%- let pwd_cmd -%}
{%- if resolve_symlinks -%}
{%- let pwd_cmd = "pwd -P" -%}
{%- else -%}
{%- let pwd_cmd = "pwd -L" -%}
{%- endif -%}
{{ section }}
# Hook configuration for zoxide.
#
{%- if hook != InitHook::None %}
# Hook to add new entries to the database.
{%- if hook == InitHook::Prompt %}
alias __zoxide_hook 'zoxide add -- "`{{ pwd_cmd }}`"'
{%- else if hook == InitHook::Pwd %}
set __zoxide_pwd_old = `{{ pwd_cmd }}`
alias __zoxide_hook 'set __zoxide_pwd_tmp = "`{{ pwd_cmd }}`"; test "$__zoxide_pwd_tmp" != "$__zoxide_pwd_old" && zoxide add -- "$__zoxide_pwd_tmp"; set __zoxide_pwd_old = "$__zoxide_pwd_tmp"'
{%- endif %}
# Initialize hook.
alias precmd ';__zoxide_hook'
{%- endif %}
{{ section }}
# When using zoxide with --no-cmd, alias these internal functions as desired.
#
# Jump to a directory using only keywords.
alias __zoxide_z 'set __zoxide_args = (\!*)\
if ("$#__zoxide_args" == 0) then\
cd ~\
else\
if ("$#__zoxide_args" == 1 && "$__zoxide_args[1]" == "-") then\
cd -\
else if ("$#__zoxide_args" == 1 && -d "$__zoxide_args[1]") then\
cd "$__zoxide_args[1]"\
else\
set __zoxide_pwd = `{{ pwd_cmd }}`\
set __zoxide_result = "`zoxide query --exclude '"'"'$__zoxide_pwd'"'"' -- $__zoxide_args`" && cd "$__zoxide_result"\
endif\
endif'
# Jump to a directory using interactive search.
alias __zoxide_zi 'set __zoxide_args = (\!*)\
set __zoxide_pwd = `{{ pwd_cmd }}`\
set __zoxide_result = "`zoxide query --exclude '"'"'$__zoxide_pwd'"'"' --interactive -- $__zoxide_args`" && cd "$__zoxide_result"'
{{ section }}
# Commands for zoxide. Disable these using --no-cmd.
#
{%- match cmd %}
{%- when Some with (cmd) %}
alias {{cmd}} __zoxide_z
alias {{cmd}}i __zoxide_zi
{%- when None %}
{{ not_configured }}
{%- endmatch %}
{{ section }}
# To initialize zoxide, add this to your shell configuration file (usually ~/.tcshrc):
#
# zoxide init tcsh > ~/.zoxide.tcsh
# source ~/.zoxide.tcsh

View File

@ -64,7 +64,7 @@ class ZoxideSilentException(Exception):
def __zoxide_errhandler( def __zoxide_errhandler(
func: typing.Callable[[list[str]], None] func: typing.Callable[[list[str]], None],
) -> typing.Callable[[list[str]], int]: ) -> typing.Callable[[list[str]], int]:
"""Print exception and exit with error code 1.""" """Print exception and exit with error code 1."""

View File

@ -84,7 +84,7 @@ function __zoxide_z() {
__zoxide_doctor __zoxide_doctor
if [[ "$#" -eq 0 ]]; then if [[ "$#" -eq 0 ]]; then
__zoxide_cd ~ __zoxide_cd ~
elif [[ "$#" -eq 1 ]] && { [[ -d "$1" ]] || [[ "$1" = '-' ]] || [[ "$1" =~ ^[-+][0-9]$ ]]; }; then elif [[ "$#" -eq 1 ]] && { [[ -d "$1" ]] || [[ "$1" = '-' ]] || [[ "$1" =~ ^[-+][0-9]+$ ]]; }; then
__zoxide_cd "$1" __zoxide_cd "$1"
elif [[ "$#" -eq 2 ]] && [[ "$1" = "--" ]]; then elif [[ "$#" -eq 2 ]] && [[ "$1" = "--" ]]; then
__zoxide_cd "$2" __zoxide_cd "$2"