Merge branch 'main' into nushell_completion

This commit is contained in:
Juhan280 2026-07-01 23:25:15 +06:00
commit 5844cc609a
32 changed files with 1742 additions and 645 deletions

View File

@ -7,10 +7,17 @@ jobs:
publish-crates-io:
name: Publish on crates.io
runs-on: ubuntu-latest
permissions:
id-token: write
steps:
- name: Checkout the repository
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Authenticate with crates.io
id: auth
uses: rust-lang/crates-io-auth-action@v1
- name: Publish
run: cargo publish --token ${{ secrets.CARGO_TOKEN }}
run: cargo publish
env:
CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}

View File

@ -21,7 +21,7 @@ jobs:
matrix:
os: [ubuntu-latest]
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
fetch-depth: 0
- uses: actions-rs/toolchain@v1
@ -40,13 +40,13 @@ jobs:
if: ${{ matrix.os != 'windows-latest' }}
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@v16
- uses: cachix/cachix-action@v17
if: ${{ matrix.os != 'windows-latest' && env.CACHIX_AUTH_TOKEN != '' }}
with:
authToken: ${{ env.CACHIX_AUTH_TOKEN }}
name: zoxide
- name: Setup cache
uses: Swatinem/rust-cache@v2.8.2
uses: Swatinem/rust-cache@v2.9.1
with:
key: ${{ matrix.os }}
- name: Install just

View File

@ -30,6 +30,9 @@ jobs:
- os: ubuntu-latest
target: i686-unknown-linux-musl
deb: true
- os: ubuntu-latest
target: riscv64gc-unknown-linux-musl
deb: true
- os: ubuntu-latest
target: aarch64-linux-android
- os: ubuntu-latest
@ -44,7 +47,7 @@ jobs:
target: aarch64-pc-windows-msvc
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Get version
@ -61,7 +64,7 @@ jobs:
override: true
target: ${{ matrix.target }}
- name: Setup cache
uses: Swatinem/rust-cache@v2.8.2
uses: Swatinem/rust-cache@v2.9.1
with:
key: ${{ matrix.target }}
- name: Install cross
@ -102,7 +105,7 @@ jobs:
CHANGELOG.md LICENSE README.md ./man/ ./contrib/completions/ `
./target/${{ matrix.target }}/release/zoxide.exe
- name: Upload artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: ${{ matrix.target }}
path: |
@ -112,7 +115,7 @@ jobs:
- name: Create release
if: |
github.ref == 'refs/heads/main' && startsWith(github.event.head_commit.message, 'chore(release)')
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@v3
with:
draft: true
files: |

View File

@ -11,11 +11,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Support for RISC-V (riscv64) Linux.
- `import` now supports fetching entries from `atuin`.
- `import` now skips directories matching `$_ZO_EXCLUDE_DIRS`.
- POSIX: support for non-Cygwin Windows environments (e.g. Busybox).
- Fish: Space-Tab completions now display and run the selected command.
### Changed
- `import` now auto-detects database files.
- Nushell: export commands so the init script can be imported with `use`.
### Fixed
- Bash/Fish/POSIX/Zsh: resolve symlinks on Windows.
- Bash: handle `$PROMPT_COMMAND` values ending in a semicolon.
- PowerShell: navigate to home directory with `z` on drives that don't define `HOME`.
- PowerShell: use fully qualified names when invoking cmdlets.
- Zsh: skip doctor diagnostics in non-interactive shells.
- Zsh: avoid inserting a trailing space when cancelling interactive Space-Tab completions.
- Bash: avoid blanking the prompt when cancelling interactive Space-Tab completions.
- Bash/Fish/Zsh: avoid drawing a new line when Space-Tab completion finds no matches.
- Tcsh: preserve any existing `precmd` alias instead of overwriting it.
- Nushell: complete only directories for `z`, instead of all files.
## [0.9.9] - 2026-01-31

689
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,7 @@ license = "MIT"
name = "zoxide"
readme = "README.md"
repository = "https://github.com/ajeetdsouza/zoxide"
rust-version = "1.85.0"
rust-version = "1.88.0"
version = "0.9.9"
[badges]
@ -17,7 +17,7 @@ maintenance = { status = "actively-developed" }
[dependencies]
anyhow = "1.0.32"
askama = { version = "0.14.0", default-features = false, features = [
askama = { version = "0.16.0", default-features = false, features = [
"derive",
"std",
] }
@ -30,15 +30,10 @@ fastrand = "2.0.0"
glob = "0.3.0"
ouroboros = "0.18.3"
serde = { version = "1.0.116", features = ["derive"] }
[target.'cfg(unix)'.dependencies]
nix = { version = "0.30.1", default-features = false, features = [
"fs",
"user",
] }
time = { version = "0.3.47", default-features = false, features = ["parsing", "macros", "std"] }
[target.'cfg(windows)'.dependencies]
which = "7.0.3"
which = "8.0.2"
[build-dependencies]
clap = { version = "4.3.0", features = ["derive"] }

144
README.md
View File

@ -24,6 +24,13 @@
to learn more.
</sup></div>
</td>
<td align="center">
<!-- markdownlint-disable-next-line MD013 -->
<a href="https://www.recall.ai/careers?ashby_jid=7b02811e-bc91-4ef2-925d-f56a5acac13b&utm_source=github&utm_medium=sponsorship&utm_campaign=zoxide"><img alt="Sponsored by Recall.ai" width="230" src="https://github.com/user-attachments/assets/0c5cb177-561c-4637-bef6-cf584939c829" /></a>
<div><sup>Processing over 3TB/s of video at peak load,</sup></div>
<!-- markdownlint-disable-next-line MD013 -->
<div><sup>now <a href="https://www.recall.ai/careers?ashby_jid=7b02811e-bc91-4ef2-925d-f56a5acac13b&utm_source=github&utm_medium=sponsorship&utm_campaign=zoxide">hiring in SF.</a></sup></div>
</td>
</tr>
</table>
@ -64,7 +71,7 @@ z - # cd into previous directory
zi foo # cd with interactive selection (using fzf)
z foo<SPACE><TAB> # show interactive completions (zoxide v0.8.0+, bash 4.4+/fish/zsh only)
z foo<SPACE><TAB> # show interactive completions (bash 4.4+/fish/zsh only)
```
Read more about the matching algorithm [here][algorithm-matching].
@ -89,30 +96,29 @@ zoxide can be installed in 4 easy steps:
>
> Or, you can use a package manager:
>
> | Distribution | Repository | Instructions |
> | ------------------- | --------------------------- | ----------------------------------------------------------------------------------------------------- |
> | **_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_ | [conda-forge] | `conda install -c conda-forge zoxide` |
> | _Any_ | [guix] | `guix install zoxide` |
> | _Any_ | [Linuxbrew] | `brew install zoxide` |
> | _Any_ | [nixpkgs] | `nix-env -iA nixpkgs.zoxide` |
> | Alpine Linux 3.13+ | [Alpine Linux Packages] | `apk add zoxide` |
> | Arch Linux | [Arch Linux Extra] | `pacman -S zoxide` |
> | ~Debian~[^1] | ~[Debian Packages]~ | ~`apt install zoxide`~ |
> | Devuan 4.0+ | [Devuan Packages] | `apt install zoxide` |
> | Exherbo Linux | [Exherbo packages] | `cave resolve -x repository/rust` <br /> `cave resolve -x zoxide` |
> | Fedora 32+ | [Fedora Packages] | `dnf install zoxide` |
> | Gentoo | [Gentoo Packages] | `emerge app-shells/zoxide` |
> | Manjaro | | `pacman -S zoxide` |
> | openSUSE Tumbleweed | [openSUSE Factory] | `zypper install zoxide` |
> | ~Parrot OS~[^1] | | ~`apt install zoxide`~ |
> | ~Raspbian~[^1] | ~[Raspbian Packages]~ | ~`apt install zoxide`~ |
> | Rhino Linux | [Pacstall Packages] | `pacstall -I zoxide-deb` |
> | Slackware 15.0+ | [SlackBuilds] | [Instructions][slackbuilds-howto] |
> | Solus | [Solus Packages] | `eopkg install zoxide` |
> | ~Ubuntu~[^1] | ~[Ubuntu Packages]~ | ~`apt install zoxide`~ |
> | Void Linux | [Void Linux Packages] | `xbps-install -S zoxide` |
> | Distribution | Repository | Instructions |
> | ------------------- | ----------------------- | ----------------------------------------------------------------- |
> | **_Any_** | **[crates.io]** | `cargo install zoxide --locked` |
> | _Any_ | [conda-forge] | `conda install -c conda-forge zoxide` |
> | _Any_ | [guix] | `guix install zoxide` |
> | _Any_ | [Linuxbrew] | `brew install zoxide` |
> | _Any_ | [nixpkgs] | `nix-env -iA nixpkgs.zoxide` |
> | Alpine Linux 3.13+ | [Alpine Linux Packages] | `apk add zoxide` |
> | Arch Linux | [Arch Linux Extra] | `pacman -S zoxide` |
> | ~~Debian~~[^1] | ~~[Debian Packages]~~ | ~~`apt install zoxide`~~ |
> | Devuan 4.0+ | [Devuan Packages] | `apt install zoxide` |
> | Exherbo Linux | [Exherbo packages] | `cave resolve -x repository/rust` <br /> `cave resolve -x zoxide` |
> | Fedora 32+ | [Fedora Packages] | `dnf install zoxide` |
> | Gentoo | [Gentoo Packages] | `emerge app-shells/zoxide` |
> | Manjaro | | `pacman -S zoxide` |
> | openSUSE Tumbleweed | [openSUSE Factory] | `zypper install zoxide` |
> | ~~Parrot OS~~[^1] | | ~~`apt install zoxide`~~ |
> | ~~Raspbian~~[^1] | ~~[Raspbian Packages]~~ | ~~`apt install zoxide`~~ |
> | Rhino Linux | [Pacstall Packages] | `pacstall -I zoxide-deb` |
> | Slackware 15.0+ | [SlackBuilds] | [Instructions][slackbuilds-howto] |
> | Solus | [Solus Packages] | `eopkg install zoxide` |
> | ~~Ubuntu~~[^1] | ~~[Ubuntu Packages]~~ | ~~`apt install zoxide`~~ |
> | Void Linux | [Void Linux Packages] | `xbps-install -S zoxide` |
</details>
@ -121,14 +127,13 @@ zoxide can be installed in 4 easy steps:
> To install zoxide, use a package manager:
>
> | Repository | Instructions |
> | --------------- | ----------------------------------------------------------------------------------------------------- |
> | **[crates.io]** | `cargo install zoxide --locked` |
> | **[Homebrew]** | `brew install zoxide` |
> | [asdf] | `asdf plugin add zoxide https://github.com/nyrst/asdf-zoxide.git` <br /> `asdf install zoxide latest` |
> | [conda-forge] | `conda install -c conda-forge zoxide` |
> | [MacPorts] | `port install zoxide` |
> | [nixpkgs] | `nix-env -iA nixpkgs.zoxide` |
> | Repository | Instructions |
> | --------------- | ------------------------------------- |
> | **[crates.io]** | `cargo install zoxide --locked` |
> | **[Homebrew]** | `brew install zoxide` |
> | [conda-forge] | `conda install -c conda-forge zoxide` |
> | [MacPorts] | `port install zoxide` |
> | [nixpkgs] | `nix-env -iA nixpkgs.zoxide` |
>
> Or, run this command in your terminal:
>
@ -329,8 +334,8 @@ 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.
> [Warp] provides its own completions, so `Space+Tab` completions are not
> supported there.
3. **Install fzf** <sup>(optional)</sup>
@ -343,61 +348,21 @@ zoxide can be installed in 4 easy steps:
4. **Import your data** <sup>(optional)</sup>
If you currently use any of these plugins, you may want to import your data
into zoxide:
into zoxide. The data file is auto-detected using each plugin's standard
conventions.
<details>
<summary>autojump</summary>
```sh
zoxide import <plugin>
```
> Run this command in your terminal:
>
> ```sh
> zoxide import --from=autojump "/path/to/autojump/db"
> ```
>
> The path usually varies according to your system:
>
> | OS | Path | Example |
> | ------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------ |
> | Linux | `$XDG_DATA_HOME/autojump/autojump.txt` or `$HOME/.local/share/autojump/autojump.txt` | `/home/alice/.local/share/autojump/autojump.txt` |
> | macOS | `$HOME/Library/autojump/autojump.txt` | `/Users/Alice/Library/autojump/autojump.txt` |
> | Windows | `%APPDATA%\autojump\autojump.txt` | `C:\Users\Alice\AppData\Roaming\autojump\autojump.txt` |
</details>
<details>
<summary>fasd, z, z.lua, zsh-z</summary>
> Run this command in your terminal:
>
> ```sh
> zoxide import --from=z "path/to/z/db"
> ```
>
> The path usually varies according to your system:
>
> | Plugin | Path |
> | ---------------- | ----------------------------------------------------------------------------------- |
> | fasd | `$_FASD_DATA` or `$HOME/.fasd` |
> | z (bash/zsh) | `$_Z_DATA` or `$HOME/.z` |
> | z (fish) | `$Z_DATA` or `$XDG_DATA_HOME/z/data` or `$HOME/.local/share/z/data` |
> | z.lua (bash/zsh) | `$_ZL_DATA` or `$HOME/.zlua` |
> | z.lua (fish) | `$XDG_DATA_HOME/zlua/zlua.txt` or `$HOME/.local/share/zlua/zlua.txt` or `$_ZL_DATA` |
> | zsh-z | `$ZSHZ_DATA` or `$_Z_DATA` or `$HOME/.z` |
</details>
<details>
<summary>ZLocation</summary>
> Run this command in PowerShell:
>
> ```powershell
> $db = New-TemporaryFile
> (Get-ZLocation).GetEnumerator() | ForEach-Object { Write-Output ($_.Name+'|'+$_.Value+'|0') } | Out-File $db
> zoxide import --from=z $db
> ```
</details>
| Plugin | Command |
| -------- | ------------------------ |
| atuin | `zoxide import atuin` |
| autojump | `zoxide import autojump` |
| fasd | `zoxide import fasd` |
| z | `zoxide import z` |
| z.lua | `zoxide import z.lua` |
| zsh-z | `zoxide import zsh-z` |
## Configuration
@ -505,7 +470,6 @@ Environment variables[^2] can be used for configuration. They must be set before
[algorithm-matching]: https://github.com/ajeetdsouza/zoxide/wiki/Algorithm#matching
[alpine linux packages]: https://pkgs.alpinelinux.org/packages?name=zoxide
[arch linux extra]: https://archlinux.org/packages/extra/x86_64/zoxide/
[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]: https://builtwithnix.org/
[chocolatey]: https://community.chocolatey.org/packages/zoxide
@ -564,7 +528,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
[warp]: 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

@ -45,7 +45,7 @@ _arguments "${_arguments_options[@]}" : \
'--help[Print help]' \
'-V[Print version]' \
'--version[Print version]' \
":: :_zoxide__edit_commands" \
":: :_zoxide__subcmd__edit_commands" \
"*::: :->edit" \
&& ret=0
@ -96,14 +96,78 @@ esac
;;
(import)
_arguments "${_arguments_options[@]}" : \
'--from=[Application to import from]:FROM:(autojump z)' \
'--merge[Merge into existing database]' \
'-h[Print help]' \
'--help[Print help]' \
'-V[Print version]' \
'--version[Print version]' \
':path:_files' \
":: :_zoxide__subcmd__import_commands" \
"*::: :->import" \
&& ret=0
case $state in
(import)
words=($line[1] "${words[@]}")
(( CURRENT += 1 ))
curcontext="${curcontext%:*:*}:zoxide-import-command-$line[1]:"
case $line[1] in
(atuin)
_arguments "${_arguments_options[@]}" : \
'--merge[Merge into existing database]' \
'-h[Print help]' \
'--help[Print help]' \
'-V[Print version]' \
'--version[Print version]' \
&& ret=0
;;
(autojump)
_arguments "${_arguments_options[@]}" : \
'--merge[Merge into existing database]' \
'-h[Print help]' \
'--help[Print help]' \
'-V[Print version]' \
'--version[Print version]' \
&& ret=0
;;
(fasd)
_arguments "${_arguments_options[@]}" : \
'--merge[Merge into existing database]' \
'-h[Print help]' \
'--help[Print help]' \
'-V[Print version]' \
'--version[Print version]' \
&& ret=0
;;
(z)
_arguments "${_arguments_options[@]}" : \
'--merge[Merge into existing database]' \
'-h[Print help]' \
'--help[Print help]' \
'-V[Print version]' \
'--version[Print version]' \
&& ret=0
;;
(z.lua)
_arguments "${_arguments_options[@]}" : \
'--merge[Merge into existing database]' \
'-h[Print help]' \
'--help[Print help]' \
'-V[Print version]' \
'--version[Print version]' \
&& ret=0
;;
(zsh-z)
_arguments "${_arguments_options[@]}" : \
'--merge[Merge into existing database]' \
'-h[Print help]' \
'--help[Print help]' \
'-V[Print version]' \
'--version[Print version]' \
&& ret=0
;;
esac
;;
esac
;;
(init)
_arguments "${_arguments_options[@]}" : \
@ -162,13 +226,13 @@ _zoxide_commands() {
)
_describe -t commands 'zoxide commands' commands "$@"
}
(( $+functions[_zoxide__add_commands] )) ||
_zoxide__add_commands() {
(( $+functions[_zoxide__subcmd__add_commands] )) ||
_zoxide__subcmd__add_commands() {
local commands; commands=()
_describe -t commands 'zoxide add commands' commands "$@"
}
(( $+functions[_zoxide__edit_commands] )) ||
_zoxide__edit_commands() {
(( $+functions[_zoxide__subcmd__edit_commands] )) ||
_zoxide__subcmd__edit_commands() {
local commands; commands=(
'decrement:' \
'delete:' \
@ -177,43 +241,80 @@ _zoxide__edit_commands() {
)
_describe -t commands 'zoxide edit commands' commands "$@"
}
(( $+functions[_zoxide__edit__decrement_commands] )) ||
_zoxide__edit__decrement_commands() {
(( $+functions[_zoxide__subcmd__edit__subcmd__decrement_commands] )) ||
_zoxide__subcmd__edit__subcmd__decrement_commands() {
local commands; commands=()
_describe -t commands 'zoxide edit decrement commands' commands "$@"
}
(( $+functions[_zoxide__edit__delete_commands] )) ||
_zoxide__edit__delete_commands() {
(( $+functions[_zoxide__subcmd__edit__subcmd__delete_commands] )) ||
_zoxide__subcmd__edit__subcmd__delete_commands() {
local commands; commands=()
_describe -t commands 'zoxide edit delete commands' commands "$@"
}
(( $+functions[_zoxide__edit__increment_commands] )) ||
_zoxide__edit__increment_commands() {
(( $+functions[_zoxide__subcmd__edit__subcmd__increment_commands] )) ||
_zoxide__subcmd__edit__subcmd__increment_commands() {
local commands; commands=()
_describe -t commands 'zoxide edit increment commands' commands "$@"
}
(( $+functions[_zoxide__edit__reload_commands] )) ||
_zoxide__edit__reload_commands() {
(( $+functions[_zoxide__subcmd__edit__subcmd__reload_commands] )) ||
_zoxide__subcmd__edit__subcmd__reload_commands() {
local commands; commands=()
_describe -t commands 'zoxide edit reload commands' commands "$@"
}
(( $+functions[_zoxide__import_commands] )) ||
_zoxide__import_commands() {
local commands; commands=()
(( $+functions[_zoxide__subcmd__import_commands] )) ||
_zoxide__subcmd__import_commands() {
local commands; commands=(
'atuin:Import from atuin' \
'autojump:Import from autojump' \
'fasd:Import from fasd' \
'z:Import from z' \
'z.lua:Import from z.lua' \
'zsh-z:Import from zsh-z' \
)
_describe -t commands 'zoxide import commands' commands "$@"
}
(( $+functions[_zoxide__init_commands] )) ||
_zoxide__init_commands() {
(( $+functions[_zoxide__subcmd__import__subcmd__atuin_commands] )) ||
_zoxide__subcmd__import__subcmd__atuin_commands() {
local commands; commands=()
_describe -t commands 'zoxide import atuin commands' commands "$@"
}
(( $+functions[_zoxide__subcmd__import__subcmd__autojump_commands] )) ||
_zoxide__subcmd__import__subcmd__autojump_commands() {
local commands; commands=()
_describe -t commands 'zoxide import autojump commands' commands "$@"
}
(( $+functions[_zoxide__subcmd__import__subcmd__fasd_commands] )) ||
_zoxide__subcmd__import__subcmd__fasd_commands() {
local commands; commands=()
_describe -t commands 'zoxide import fasd commands' commands "$@"
}
(( $+functions[_zoxide__subcmd__import__subcmd__z_commands] )) ||
_zoxide__subcmd__import__subcmd__z_commands() {
local commands; commands=()
_describe -t commands 'zoxide import z commands' commands "$@"
}
(( $+functions[_zoxide__subcmd__import__subcmd__z.lua_commands] )) ||
_zoxide__subcmd__import__subcmd__z.lua_commands() {
local commands; commands=()
_describe -t commands 'zoxide import z.lua commands' commands "$@"
}
(( $+functions[_zoxide__subcmd__import__subcmd__zsh-z_commands] )) ||
_zoxide__subcmd__import__subcmd__zsh-z_commands() {
local commands; commands=()
_describe -t commands 'zoxide import zsh-z commands' commands "$@"
}
(( $+functions[_zoxide__subcmd__init_commands] )) ||
_zoxide__subcmd__init_commands() {
local commands; commands=()
_describe -t commands 'zoxide init commands' commands "$@"
}
(( $+functions[_zoxide__query_commands] )) ||
_zoxide__query_commands() {
(( $+functions[_zoxide__subcmd__query_commands] )) ||
_zoxide__subcmd__query_commands() {
local commands; commands=()
_describe -t commands 'zoxide query commands' commands "$@"
}
(( $+functions[_zoxide__remove_commands] )) ||
_zoxide__remove_commands() {
(( $+functions[_zoxide__subcmd__remove_commands] )) ||
_zoxide__subcmd__remove_commands() {
local commands; commands=()
_describe -t commands 'zoxide remove commands' commands "$@"
}

View File

@ -82,7 +82,60 @@ Register-ArgumentCompleter -Native -CommandName 'zoxide' -ScriptBlock {
break
}
'zoxide;import' {
[CompletionResult]::new('--from', '--from', [CompletionResultType]::ParameterName, 'Application to import from')
[CompletionResult]::new('--merge', '--merge', [CompletionResultType]::ParameterName, 'Merge into existing database')
[CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('-V', '-V ', [CompletionResultType]::ParameterName, 'Print version')
[CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'Print version')
[CompletionResult]::new('atuin', 'atuin', [CompletionResultType]::ParameterValue, 'Import from atuin')
[CompletionResult]::new('autojump', 'autojump', [CompletionResultType]::ParameterValue, 'Import from autojump')
[CompletionResult]::new('fasd', 'fasd', [CompletionResultType]::ParameterValue, 'Import from fasd')
[CompletionResult]::new('z', 'z', [CompletionResultType]::ParameterValue, 'Import from z')
[CompletionResult]::new('z.lua', 'z.lua', [CompletionResultType]::ParameterValue, 'Import from z.lua')
[CompletionResult]::new('zsh-z', 'zsh-z', [CompletionResultType]::ParameterValue, 'Import from zsh-z')
break
}
'zoxide;import;atuin' {
[CompletionResult]::new('--merge', '--merge', [CompletionResultType]::ParameterName, 'Merge into existing database')
[CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('-V', '-V ', [CompletionResultType]::ParameterName, 'Print version')
[CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'Print version')
break
}
'zoxide;import;autojump' {
[CompletionResult]::new('--merge', '--merge', [CompletionResultType]::ParameterName, 'Merge into existing database')
[CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('-V', '-V ', [CompletionResultType]::ParameterName, 'Print version')
[CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'Print version')
break
}
'zoxide;import;fasd' {
[CompletionResult]::new('--merge', '--merge', [CompletionResultType]::ParameterName, 'Merge into existing database')
[CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('-V', '-V ', [CompletionResultType]::ParameterName, 'Print version')
[CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'Print version')
break
}
'zoxide;import;z' {
[CompletionResult]::new('--merge', '--merge', [CompletionResultType]::ParameterName, 'Merge into existing database')
[CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('-V', '-V ', [CompletionResultType]::ParameterName, 'Print version')
[CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'Print version')
break
}
'zoxide;import;z.lua' {
[CompletionResult]::new('--merge', '--merge', [CompletionResultType]::ParameterName, 'Merge into existing database')
[CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('-V', '-V ', [CompletionResultType]::ParameterName, 'Print version')
[CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'Print version')
break
}
'zoxide;import;zsh-z' {
[CompletionResult]::new('--merge', '--merge', [CompletionResultType]::ParameterName, 'Merge into existing database')
[CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')

View File

@ -17,34 +17,52 @@ _zoxide() {
cmd="zoxide"
;;
zoxide,add)
cmd="zoxide__add"
cmd="zoxide__subcmd__add"
;;
zoxide,edit)
cmd="zoxide__edit"
cmd="zoxide__subcmd__edit"
;;
zoxide,import)
cmd="zoxide__import"
cmd="zoxide__subcmd__import"
;;
zoxide,init)
cmd="zoxide__init"
cmd="zoxide__subcmd__init"
;;
zoxide,query)
cmd="zoxide__query"
cmd="zoxide__subcmd__query"
;;
zoxide,remove)
cmd="zoxide__remove"
cmd="zoxide__subcmd__remove"
;;
zoxide__edit,decrement)
cmd="zoxide__edit__decrement"
zoxide__subcmd__edit,decrement)
cmd="zoxide__subcmd__edit__subcmd__decrement"
;;
zoxide__edit,delete)
cmd="zoxide__edit__delete"
zoxide__subcmd__edit,delete)
cmd="zoxide__subcmd__edit__subcmd__delete"
;;
zoxide__edit,increment)
cmd="zoxide__edit__increment"
zoxide__subcmd__edit,increment)
cmd="zoxide__subcmd__edit__subcmd__increment"
;;
zoxide__edit,reload)
cmd="zoxide__edit__reload"
zoxide__subcmd__edit,reload)
cmd="zoxide__subcmd__edit__subcmd__reload"
;;
zoxide__subcmd__import,atuin)
cmd="zoxide__subcmd__import__subcmd__atuin"
;;
zoxide__subcmd__import,autojump)
cmd="zoxide__subcmd__import__subcmd__autojump"
;;
zoxide__subcmd__import,fasd)
cmd="zoxide__subcmd__import__subcmd__fasd"
;;
zoxide__subcmd__import,z)
cmd="zoxide__subcmd__import__subcmd__z"
;;
zoxide__subcmd__import,z.lua)
cmd="zoxide__subcmd__import__subcmd__z.lua"
;;
zoxide__subcmd__import,zsh-z)
cmd="zoxide__subcmd__import__subcmd__zsh__subcmd__z"
;;
*)
;;
@ -66,7 +84,7 @@ _zoxide() {
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
zoxide__add)
zoxide__subcmd__add)
opts="-s -h -V --score --help --version <PATHS>..."
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
@ -88,7 +106,7 @@ _zoxide() {
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
zoxide__edit)
zoxide__subcmd__edit)
opts="-h -V --help --version decrement delete increment reload"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
@ -102,7 +120,7 @@ _zoxide() {
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
zoxide__edit__decrement)
zoxide__subcmd__edit__subcmd__decrement)
opts="-h -V --help --version <PATH>"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
@ -116,7 +134,7 @@ _zoxide() {
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
zoxide__edit__delete)
zoxide__subcmd__edit__subcmd__delete)
opts="-h -V --help --version <PATH>"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
@ -130,7 +148,7 @@ _zoxide() {
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
zoxide__edit__increment)
zoxide__subcmd__edit__subcmd__increment)
opts="-h -V --help --version <PATH>"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
@ -144,7 +162,7 @@ _zoxide() {
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
zoxide__edit__reload)
zoxide__subcmd__edit__subcmd__reload)
opts="-h -V --help --version"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
@ -158,17 +176,13 @@ _zoxide() {
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
zoxide__import)
opts="-h -V --from --merge --help --version <PATH>"
zoxide__subcmd__import)
opts="-h -V --merge --help --version atuin autojump fasd z z.lua zsh-z"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
fi
case "${prev}" in
--from)
COMPREPLY=($(compgen -W "autojump z" -- "${cur}"))
return 0
;;
*)
COMPREPLY=()
;;
@ -176,7 +190,91 @@ _zoxide() {
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
zoxide__init)
zoxide__subcmd__import__subcmd__atuin)
opts="-h -V --merge --help --version"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
fi
case "${prev}" in
*)
COMPREPLY=()
;;
esac
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
zoxide__subcmd__import__subcmd__autojump)
opts="-h -V --merge --help --version"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
fi
case "${prev}" in
*)
COMPREPLY=()
;;
esac
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
zoxide__subcmd__import__subcmd__fasd)
opts="-h -V --merge --help --version"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
fi
case "${prev}" in
*)
COMPREPLY=()
;;
esac
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
zoxide__subcmd__import__subcmd__z)
opts="-h -V --merge --help --version"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
fi
case "${prev}" in
*)
COMPREPLY=()
;;
esac
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
zoxide__subcmd__import__subcmd__z.lua)
opts="-h -V --merge --help --version"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
fi
case "${prev}" in
*)
COMPREPLY=()
;;
esac
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
zoxide__subcmd__import__subcmd__zsh__subcmd__z)
opts="-h -V --merge --help --version"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
fi
case "${prev}" in
*)
COMPREPLY=()
;;
esac
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
zoxide__subcmd__init)
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
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
@ -198,7 +296,7 @@ _zoxide() {
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
zoxide__query)
zoxide__subcmd__query)
opts="-a -i -l -s -h -V --all --interactive --list --score --exclude --base-dir --help --version [KEYWORDS]..."
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
@ -226,7 +324,7 @@ _zoxide() {
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
zoxide__remove)
zoxide__subcmd__remove)
opts="-h -V --help --version [PATHS]..."
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )

View File

@ -72,7 +72,54 @@ set edit:completion:arg-completer[zoxide] = {|@words|
cand --version 'Print version'
}
&'zoxide;import'= {
cand --from 'Application to import from'
cand --merge 'Merge into existing database'
cand -h 'Print help'
cand --help 'Print help'
cand -V 'Print version'
cand --version 'Print version'
cand atuin 'Import from atuin'
cand autojump 'Import from autojump'
cand fasd 'Import from fasd'
cand z 'Import from z'
cand z.lua 'Import from z.lua'
cand zsh-z 'Import from zsh-z'
}
&'zoxide;import;atuin'= {
cand --merge 'Merge into existing database'
cand -h 'Print help'
cand --help 'Print help'
cand -V 'Print version'
cand --version 'Print version'
}
&'zoxide;import;autojump'= {
cand --merge 'Merge into existing database'
cand -h 'Print help'
cand --help 'Print help'
cand -V 'Print version'
cand --version 'Print version'
}
&'zoxide;import;fasd'= {
cand --merge 'Merge into existing database'
cand -h 'Print help'
cand --help 'Print help'
cand -V 'Print version'
cand --version 'Print version'
}
&'zoxide;import;z'= {
cand --merge 'Merge into existing database'
cand -h 'Print help'
cand --help 'Print help'
cand -V 'Print version'
cand --version 'Print version'
}
&'zoxide;import;z.lua'= {
cand --merge 'Merge into existing database'
cand -h 'Print help'
cand --help 'Print help'
cand -V 'Print version'
cand --version 'Print version'
}
&'zoxide;import;zsh-z'= {
cand --merge 'Merge into existing database'
cand -h 'Print help'
cand --help 'Print help'

View File

@ -49,11 +49,33 @@ 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 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 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" -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; and not __fish_seen_subcommand_from atuin autojump fasd z z.lua zsh-z" -l merge -d 'Merge into existing database'
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and not __fish_seen_subcommand_from atuin autojump fasd z z.lua zsh-z" -s h -l help -d 'Print help'
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and not __fish_seen_subcommand_from atuin autojump fasd z z.lua zsh-z" -s V -l version -d 'Print version'
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and not __fish_seen_subcommand_from atuin autojump fasd z z.lua zsh-z" -f -a "atuin" -d 'Import from atuin'
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and not __fish_seen_subcommand_from atuin autojump fasd z z.lua zsh-z" -f -a "autojump" -d 'Import from autojump'
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and not __fish_seen_subcommand_from atuin autojump fasd z z.lua zsh-z" -f -a "fasd" -d 'Import from fasd'
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and not __fish_seen_subcommand_from atuin autojump fasd z z.lua zsh-z" -f -a "z" -d 'Import from z'
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and not __fish_seen_subcommand_from atuin autojump fasd z z.lua zsh-z" -f -a "z.lua" -d 'Import from z.lua'
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and not __fish_seen_subcommand_from atuin autojump fasd z z.lua zsh-z" -f -a "zsh-z" -d 'Import from zsh-z'
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and __fish_seen_subcommand_from atuin" -l merge -d 'Merge into existing database'
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and __fish_seen_subcommand_from atuin" -s h -l help -d 'Print help'
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and __fish_seen_subcommand_from atuin" -s V -l version -d 'Print version'
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and __fish_seen_subcommand_from autojump" -l merge -d 'Merge into existing database'
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and __fish_seen_subcommand_from autojump" -s h -l help -d 'Print help'
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and __fish_seen_subcommand_from autojump" -s V -l version -d 'Print version'
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and __fish_seen_subcommand_from fasd" -l merge -d 'Merge into existing database'
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and __fish_seen_subcommand_from fasd" -s h -l help -d 'Print help'
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and __fish_seen_subcommand_from fasd" -s V -l version -d 'Print version'
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and __fish_seen_subcommand_from z" -l merge -d 'Merge into existing database'
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and __fish_seen_subcommand_from z" -s h -l help -d 'Print help'
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and __fish_seen_subcommand_from z" -s V -l version -d 'Print version'
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and __fish_seen_subcommand_from z.lua" -l merge -d 'Merge into existing database'
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and __fish_seen_subcommand_from z.lua" -s h -l help -d 'Print help'
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and __fish_seen_subcommand_from z.lua" -s V -l version -d 'Print version'
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and __fish_seen_subcommand_from zsh-z" -l merge -d 'Merge into existing database'
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and __fish_seen_subcommand_from zsh-z" -s h -l help -d 'Print help'
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and __fish_seen_subcommand_from zsh-z" -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 hook -d 'Changes how often zoxide increments a directory\'s score' -r -f -a "none\t''
prompt\t''

View File

@ -8,10 +8,10 @@ module completions {
# 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
...paths: path
]
# Edit the database
@ -21,21 +21,21 @@ module completions {
]
export extern "zoxide edit decrement" [
path: string
--help(-h) # Print help
--version(-V) # Print version
path: string
]
export extern "zoxide edit delete" [
path: string
--help(-h) # Print help
--version(-V) # Print version
path: string
]
export extern "zoxide edit increment" [
path: string
--help(-h) # Print help
--version(-V) # Print version
path: string
]
export extern "zoxide edit reload" [
@ -43,14 +43,50 @@ module completions {
--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
]
# Import from atuin
export extern "zoxide import atuin" [
--merge # Merge into existing database
--help(-h) # Print help
--version(-V) # Print version
]
# Import from autojump
export extern "zoxide import autojump" [
--merge # Merge into existing database
--help(-h) # Print help
--version(-V) # Print version
]
# Import from fasd
export extern "zoxide import fasd" [
--merge # Merge into existing database
--help(-h) # Print help
--version(-V) # Print version
]
# Import from z
export extern "zoxide import z" [
--merge # Merge into existing database
--help(-h) # Print help
--version(-V) # Print version
]
# Import from z.lua
export extern "zoxide import z.lua" [
--merge # Merge into existing database
--help(-h) # Print help
--version(-V) # Print version
]
# Import from zsh-z
export extern "zoxide import zsh-z" [
--merge # Merge into existing database
--help(-h) # Print help
--version(-V) # Print version
@ -66,17 +102,16 @@ module completions {
# 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
shell: string@"nu-complete zoxide init shell"
]
# 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
@ -85,13 +120,14 @@ module completions {
--base-dir: path # Only search within this directory
--help(-h) # Print help
--version(-V) # Print version
...keywords: string
]
# Remove a directory from the database
export extern "zoxide remove" [
...paths: path
--help(-h) # Print help
--version(-V) # Print version
...paths: path
]
}

View File

@ -114,19 +114,117 @@ const completion: Fig.Spec = {
{
name: "import",
description: "Import entries from another application",
options: [
subcommands: [
{
name: "--from",
description: "Application to import from",
isRepeatable: true,
args: {
name: "from",
suggestions: [
"autojump",
"z",
],
},
name: "atuin",
description: "Import from atuin",
options: [
{
name: "--merge",
description: "Merge into existing database",
},
{
name: ["-h", "--help"],
description: "Print help",
},
{
name: ["-V", "--version"],
description: "Print version",
},
],
},
{
name: "autojump",
description: "Import from autojump",
options: [
{
name: "--merge",
description: "Merge into existing database",
},
{
name: ["-h", "--help"],
description: "Print help",
},
{
name: ["-V", "--version"],
description: "Print version",
},
],
},
{
name: "fasd",
description: "Import from fasd",
options: [
{
name: "--merge",
description: "Merge into existing database",
},
{
name: ["-h", "--help"],
description: "Print help",
},
{
name: ["-V", "--version"],
description: "Print version",
},
],
},
{
name: "z",
description: "Import from z",
options: [
{
name: "--merge",
description: "Merge into existing database",
},
{
name: ["-h", "--help"],
description: "Print help",
},
{
name: ["-V", "--version"],
description: "Print version",
},
],
},
{
name: "z.lua",
description: "Import from z.lua",
options: [
{
name: "--merge",
description: "Merge into existing database",
},
{
name: ["-h", "--help"],
description: "Print help",
},
{
name: ["-V", "--version"],
description: "Print version",
},
],
},
{
name: "zsh-z",
description: "Import from zsh-z",
options: [
{
name: "--merge",
description: "Merge into existing database",
},
{
name: ["-h", "--help"],
description: "Print help",
},
{
name: ["-V", "--version"],
description: "Print version",
},
],
},
],
options: [
{
name: "--merge",
description: "Merge into existing database",
@ -140,10 +238,6 @@ const completion: Fig.Spec = {
description: "Print version",
},
],
args: {
name: "path",
template: "filepaths",
},
},
{
name: "init",

View File

@ -4,7 +4,7 @@ let
overlays = [ rust ];
};
rust = import (builtins.fetchTarball
"https://github.com/oxalica/rust-overlay/archive/026e8fedefd6b167d92ed04b195c658d95ffc7a5.tar.gz");
"https://github.com/oxalica/rust-overlay/archive/4568a557ca325ff81fb354382d4a9968daa1001a.tar.gz");
rust-nightly =
pkgs.rust-bin.selectLatestNightlyWith (toolchain: toolchain.minimal);

View File

@ -95,23 +95,30 @@ pub enum EditCommand {
help_template = HelpTemplate,
)]
pub struct Import {
#[clap(value_hint = ValueHint::FilePath)]
pub path: PathBuf,
/// Application to import from
#[clap(value_enum, long)]
#[clap(subcommand)]
pub from: ImportFrom,
/// Merge into existing database
#[clap(long)]
#[clap(long, global = true)]
pub merge: bool,
}
#[derive(ValueEnum, Clone, Debug)]
#[derive(Subcommand, Clone, Debug)]
pub enum ImportFrom {
/// Import from atuin
Atuin,
/// Import from autojump
Autojump,
#[clap(alias = "fasd")]
/// Import from fasd
Fasd,
/// Import from z
Z,
/// Import from z.lua
#[clap(name = "z.lua")]
ZLua,
/// Import from zsh-z
#[clap(name = "zsh-z")]
ZshZ,
}
/// Generate shell configuration

View File

@ -1,166 +1,25 @@
use std::fs;
use anyhow::{Context, Result, bail};
use anyhow::{Result, bail};
use crate::cmd::{Import, ImportFrom, Run};
use crate::db::Database;
use crate::import;
impl Run for Import {
fn run(&self) -> Result<()> {
let buffer = fs::read_to_string(&self.path).with_context(|| {
format!("could not open database for importing: {}", &self.path.display())
})?;
let mut db = Database::open()?;
if !self.merge && !db.dirs().is_empty() {
bail!("current database is not empty, specify --merge to continue anyway");
}
match self.from {
ImportFrom::Autojump => import_autojump(&mut db, &buffer),
ImportFrom::Z => import_z(&mut db, &buffer),
ImportFrom::Atuin => import::run(&import::Atuin {}, &mut db)?,
ImportFrom::Autojump => import::run(&import::Autojump {}, &mut db)?,
ImportFrom::Fasd => import::run(&import::Fasd {}, &mut db)?,
ImportFrom::Z => import::run(&import::Z {}, &mut db)?,
ImportFrom::ZLua => import::run(&import::ZLua {}, &mut db)?,
ImportFrom::ZshZ => import::run(&import::ZshZ {}, &mut db)?,
}
.context("import error")?;
db.save()
}
}
fn import_autojump(db: &mut Database, buffer: &str) -> Result<()> {
for line in buffer.lines() {
if line.is_empty() {
continue;
}
let (rank, path) =
line.split_once('\t').with_context(|| format!("invalid entry: {line}"))?;
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 normalize.
rank = sigmoid(rank);
db.add_unchecked(path, rank, 0);
}
if db.dirty() {
db.dedup();
}
Ok(())
}
fn import_z(db: &mut Database, buffer: &str) -> Result<()> {
for line in buffer.lines() {
if line.is_empty() {
continue;
}
let mut split = line.rsplitn(3, '|');
let last_accessed = split.next().with_context(|| format!("invalid entry: {line}"))?;
let last_accessed =
last_accessed.parse().with_context(|| format!("invalid epoch: {last_accessed}"))?;
let rank = split.next().with_context(|| format!("invalid entry: {line}"))?;
let rank = rank.parse().with_context(|| format!("invalid rank: {rank}"))?;
let path = split.next().with_context(|| format!("invalid entry: {line}"))?;
db.add_unchecked(path, rank, last_accessed);
}
if db.dirty() {
db.dedup();
}
Ok(())
}
fn sigmoid(x: f64) -> f64 {
1.0 / (1.0 + (-x).exp())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::db::Dir;
#[test]
fn from_autojump() {
let data_dir = tempfile::tempdir().unwrap();
let mut db = Database::open_dir(data_dir.path()).unwrap();
for (path, rank, last_accessed) in [
("/quux/quuz", 1.0, 100),
("/corge/grault/garply", 6.0, 600),
("/waldo/fred/plugh", 3.0, 300),
("/xyzzy/thud", 8.0, 800),
("/foo/bar", 9.0, 900),
] {
db.add_unchecked(path, rank, last_accessed);
}
let buffer = "\
7.0 /baz
2.0 /foo/bar
5.0 /quux/quuz";
import_autojump(&mut db, buffer).unwrap();
db.sort_by_path();
println!("got: {:?}", &db.dirs());
let exp = [
Dir { path: "/baz".into(), rank: sigmoid(7.0), last_accessed: 0 },
Dir { path: "/corge/grault/garply".into(), rank: 6.0, last_accessed: 600 },
Dir { path: "/foo/bar".into(), rank: 9.0 + sigmoid(2.0), last_accessed: 900 },
Dir { path: "/quux/quuz".into(), rank: 1.0 + sigmoid(5.0), last_accessed: 100 },
Dir { path: "/waldo/fred/plugh".into(), rank: 3.0, last_accessed: 300 },
Dir { path: "/xyzzy/thud".into(), rank: 8.0, last_accessed: 800 },
];
println!("exp: {exp:?}");
for (dir1, dir2) in db.dirs().iter().zip(exp) {
assert_eq!(dir1.path, dir2.path);
assert!((dir1.rank - dir2.rank).abs() < 0.01);
assert_eq!(dir1.last_accessed, dir2.last_accessed);
}
}
#[test]
fn from_z() {
let data_dir = tempfile::tempdir().unwrap();
let mut db = Database::open_dir(data_dir.path()).unwrap();
for (path, rank, last_accessed) in [
("/quux/quuz", 1.0, 100),
("/corge/grault/garply", 6.0, 600),
("/waldo/fred/plugh", 3.0, 300),
("/xyzzy/thud", 8.0, 800),
("/foo/bar", 9.0, 900),
] {
db.add_unchecked(path, rank, last_accessed);
}
let buffer = "\
/baz|7|700
/quux/quuz|4|400
/foo/bar|2|200
/quux/quuz|5|500";
import_z(&mut db, buffer).unwrap();
db.sort_by_path();
println!("got: {:?}", &db.dirs());
let exp = [
Dir { path: "/baz".into(), rank: 7.0, last_accessed: 700 },
Dir { path: "/corge/grault/garply".into(), rank: 6.0, last_accessed: 600 },
Dir { path: "/foo/bar".into(), rank: 11.0, last_accessed: 900 },
Dir { path: "/quux/quuz".into(), rank: 10.0, last_accessed: 500 },
Dir { path: "/waldo/fred/plugh".into(), rank: 3.0, last_accessed: 300 },
Dir { path: "/xyzzy/thud".into(), rank: 8.0, last_accessed: 800 },
];
println!("exp: {exp:?}");
for (dir1, dir2) in db.dirs().iter().zip(exp) {
assert_eq!(dir1.path, dir2.path);
assert!((dir1.rank - dir2.rank).abs() < 0.01);
assert_eq!(dir1.last_accessed, dir2.last_accessed);
}
}
}

82
src/import.rs Normal file
View File

@ -0,0 +1,82 @@
pub(crate) use crate::import::atuin::Atuin;
pub(crate) use crate::import::autojump::Autojump;
pub(crate) use crate::import::fasd::Fasd;
pub(crate) use crate::import::z::Z;
pub(crate) use crate::import::z_lua::ZLua;
pub(crate) use crate::import::zsh_z::ZshZ;
mod atuin;
mod autojump;
mod fasd;
mod z;
mod z_lua;
mod zsh_z;
use std::io::{self, Write};
use std::path::PathBuf;
use anyhow::Result;
use crate::config;
use crate::db::{Database, Dir};
pub(crate) trait Importer {
/// Yields directory entries to be imported.
///
/// The outer `Result` reports failure to fetch the input (e.g. missing
/// file, subprocess errored). The per-item `Result` reports a malformed
/// row, which doesn't necessarily abort the whole import.
fn dirs(&self) -> Result<impl Iterator<Item = Result<Dir<'static>, ImportError>>>;
}
/// A single record that failed to import.
#[derive(Debug)]
pub(crate) struct ImportError {
/// Path of the source file containing the offending record. `None` if the
/// importer is not file-based (e.g. atuin streams from a subprocess).
pub path: Option<PathBuf>,
/// 1-indexed line number of the offending input.
pub line_num: usize,
/// Underlying reason the record could not be imported.
pub source: anyhow::Error,
}
/// Drives a single importer end-to-end: writes each `Ok` dir into the
/// database and prints each `Err` to stderr in `<path>:<line>: <reason>`
/// format. Doesn't abort on per-record errors — bad rows are skipped, the
/// rest of the import continues. After the iteration completes successfully,
/// the database is deduplicated and aged.
pub(crate) fn run(importer: &impl Importer, db: &mut Database) -> Result<()> {
let exclude_dirs = config::exclude_dirs()?;
let stderr = io::stderr();
let mut stderr = stderr.lock();
for entry in importer.dirs()? {
match entry {
Ok(dir) => {
if exclude_dirs.iter().any(|glob| glob.matches(&dir.path)) {
continue;
}
db.add_unchecked(dir.path, dir.rank, dir.last_accessed);
}
Err(e) => {
let location = match &e.path {
Some(path) => format!("{}:{}", path.display(), e.line_num),
None => format!("line {}", e.line_num),
};
_ = writeln!(stderr, "{location}: {:#}", e.source);
}
}
}
if db.dirty() {
db.dedup();
let max_age = config::maxage()?;
db.age(max_age);
}
Ok(())
}

116
src/import/atuin.rs Normal file
View File

@ -0,0 +1,116 @@
use std::borrow::Cow;
use std::io::{BufRead, BufReader};
use std::process::{Child, ChildStdout, Command, Stdio};
use std::str;
use anyhow::{Context, Result, anyhow};
use crate::db::{Dir, Epoch};
use crate::import::{ImportError, Importer};
#[derive(clap::Args, Clone, Debug)]
pub(crate) struct Atuin {}
impl Importer for Atuin {
fn dirs(&self) -> Result<impl Iterator<Item = Result<Dir<'static>, ImportError>>> {
// atuin renders `{time}` as `YYYY-MM-DD HH:MM:SS` in UTC.
let mut child = Command::new("atuin")
.args(["history", "list", "--format={time}\t{directory}", "--print0"])
.stdout(Stdio::piped())
.spawn()
.context("failed to run `atuin`; is it installed and on PATH?")?;
let stdout = child.stdout.take().expect("stdout piped");
let reader = BufReader::new(stdout);
Ok(Iter::new(reader, child))
}
}
/// Iterates atuin's NUL-separated `{time}\t{directory}` records, emitting one
/// `Dir` per directory transition (consecutive same-path records collapse).
/// Owns the `Child` handle so the subprocess is reaped on Drop.
struct Iter {
reader: BufReader<ChildStdout>,
buf: Vec<u8>,
line_num: usize,
child: Child,
prev_cwd: Option<String>,
}
impl Iter {
fn new(reader: BufReader<ChildStdout>, child: Child) -> Self {
Self { reader, buf: Vec::new(), line_num: 0, child, prev_cwd: None }
}
fn err(&self, source: anyhow::Error) -> ImportError {
ImportError { path: None, line_num: self.line_num, source }
}
fn parse_line(&self, line: &[u8]) -> Result<Dir<'static>, ImportError> {
let line =
str::from_utf8(line).map_err(|e| self.err(anyhow!(e).context("invalid utf-8")))?;
let (timestamp, path) =
line.split_once('\t').ok_or_else(|| self.err(anyhow!("invalid entry: {line}")))?;
let timestamp_format =
time::macros::format_description!("[year]-[month]-[day] [hour]:[minute]:[second]");
let timestamp = time::PrimitiveDateTime::parse(timestamp, timestamp_format)
.map_err(|e| self.err(anyhow!(e).context(format!("invalid timestamp: {timestamp:?}"))))?
.assume_utc()
.unix_timestamp();
let dir = Dir {
path: Cow::Owned(path.to_string()),
rank: 1.0,
last_accessed: timestamp as Epoch,
};
Ok(dir)
}
}
impl Iterator for Iter {
type Item = Result<Dir<'static>, ImportError>;
fn next(&mut self) -> Option<Self::Item> {
loop {
self.buf.clear();
self.line_num += 1;
match self.reader.read_until(b'\0', &mut self.buf) {
Ok(0) => return None,
Ok(_) => {
if self.buf.last() == Some(&b'\0') {
self.buf.pop();
}
if self.buf.is_empty() {
continue;
}
let result = self.parse_line(&self.buf);
match &result {
Ok(dir) => {
let path = dir.path.as_ref();
if self.prev_cwd.as_deref() == Some(path) {
continue; // dedup consecutive same-path entries
}
self.prev_cwd = Some(path.to_string());
return Some(result);
}
Err(_) => return Some(result),
}
}
Err(e) => {
return Some(Err(self.err(anyhow!(e).context("could not read from atuin"))));
}
}
}
}
}
impl Drop for Iter {
fn drop(&mut self) {
_ = self.child.kill();
_ = self.child.wait();
}
}

124
src/import/autojump.rs Normal file
View File

@ -0,0 +1,124 @@
use std::borrow::Cow;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::PathBuf;
use std::{env, str};
use anyhow::{Context, Result, anyhow};
use crate::db::Dir;
use crate::import::{ImportError, Importer};
#[derive(clap::Args, Clone, Debug)]
pub(crate) struct Autojump {}
impl Importer for Autojump {
fn dirs(&self) -> Result<impl Iterator<Item = Result<Dir<'static>, ImportError>>> {
let path = data_path()?;
let file = File::open(&path).with_context(|| format!("could not read {path:?}"))?;
let reader = BufReader::new(file);
Ok(Iter::new(reader, path))
}
}
struct Iter<R: BufRead> {
reader: R,
buf: Vec<u8>,
line_num: usize,
path: PathBuf,
}
impl<R: BufRead> Iter<R> {
fn new(reader: R, path: PathBuf) -> Self {
Self { reader, buf: Vec::new(), line_num: 0, path }
}
fn err(&self, source: anyhow::Error) -> ImportError {
ImportError { path: Some(self.path.clone()), line_num: self.line_num, source }
}
fn parse_line(&self, line: &[u8]) -> Result<Dir<'static>, ImportError> {
let line =
str::from_utf8(line).map_err(|e| self.err(anyhow!(e).context("invalid utf-8")))?;
let (rank, path) =
line.split_once('\t').ok_or_else(|| self.err(anyhow!("invalid entry: {line}")))?;
let rank = rank
.parse::<f64>()
.map_err(|e| self.err(anyhow!(e).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 normalize.
let rank = sigmoid(rank);
Ok(Dir { path: Cow::Owned(path.to_string()), rank, last_accessed: 0 })
}
}
impl<R: BufRead> Iterator for Iter<R> {
type Item = Result<Dir<'static>, ImportError>;
fn next(&mut self) -> Option<Self::Item> {
loop {
self.buf.clear();
self.line_num += 1;
match self.reader.read_until(b'\n', &mut self.buf) {
Ok(0) => return None,
Ok(_) => {
if self.buf.last() == Some(&b'\n') {
self.buf.pop();
}
if self.buf.last() == Some(&b'\r') {
self.buf.pop();
}
if self.buf.is_empty() {
continue;
}
return Some(self.parse_line(&self.buf));
}
Err(e) => return Some(Err(self.err(anyhow::Error::from(e)))),
}
}
}
}
/// Mirrors autojump's path logic:
///
/// ```python
/// if is_osx():
/// data_home = os.path.join(os.path.expanduser('~'), 'Library')
/// elif is_windows():
/// data_home = os.getenv('APPDATA')
/// else:
/// data_home = os.getenv(
/// 'XDG_DATA_HOME',
/// os.path.join(os.path.expanduser('~'), '.local', 'share'),
/// )
/// data_path = os.path.join(data_home, 'autojump', 'autojump.txt')
/// ```
fn data_path() -> Result<PathBuf> {
let mut path = if cfg!(target_os = "macos") {
let mut path = dirs::home_dir().context("could not find home directory")?;
path.push("Library");
path
} else if cfg!(target_os = "windows") {
let appdata = env::var_os("APPDATA").context("%APPDATA% is not set")?;
PathBuf::from(appdata)
} else if let Some(xdg) = env::var_os("XDG_DATA_HOME") {
PathBuf::from(xdg)
} else {
let mut path = dirs::home_dir().context("could not find home directory")?;
path.push(".local");
path.push("share");
path
};
path.push("autojump");
path.push("autojump.txt");
Ok(path)
}
fn sigmoid(x: f64) -> f64 {
1.0 / (1.0 + (-x).exp())
}

39
src/import/fasd.rs Normal file
View File

@ -0,0 +1,39 @@
use std::env;
use std::fs::File;
use std::io::BufReader;
use std::path::PathBuf;
use anyhow::{Context, Result};
use crate::db::Dir;
use crate::import::{ImportError, Importer, z};
#[derive(clap::Args, Clone, Debug)]
pub(crate) struct Fasd {}
impl Importer for Fasd {
fn dirs(&self) -> Result<impl Iterator<Item = Result<Dir<'static>, ImportError>>> {
let path = data_path()?;
let file = File::open(&path).with_context(|| format!("could not read {path:?}"))?;
let reader = BufReader::new(file);
// fasd uses the same `path|rank|last_accessed` line format as z, so reuse z's
// iterator.
Ok(z::Iter::new(reader, path))
}
}
/// Mirrors fasd's path logic:
///
/// ```sh
/// [ -z "$_FASD_DATA" ] && _FASD_DATA="$HOME/.fasd"
/// ```
fn data_path() -> Result<PathBuf> {
match env::var_os("_FASD_DATA") {
Some(path) => Ok(PathBuf::from(path)),
None => {
let mut path = dirs::home_dir().context("could not find home directory")?;
path.push(".fasd");
Ok(path)
}
}
}

103
src/import/z.rs Normal file
View File

@ -0,0 +1,103 @@
use std::borrow::Cow;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::PathBuf;
use std::{env, str};
use anyhow::{Context, Result, anyhow};
use crate::db::Dir;
use crate::import::{ImportError, Importer};
#[derive(clap::Args, Clone, Debug)]
pub(crate) struct Z {}
impl Importer for Z {
fn dirs(&self) -> Result<impl Iterator<Item = Result<Dir<'static>, ImportError>>> {
let path = data_path()?;
let file = File::open(&path).with_context(|| format!("could not read {path:?}"))?;
let reader = BufReader::new(file);
Ok(Iter::new(reader, path))
}
}
pub(crate) struct Iter<R: BufRead> {
reader: R,
buf: Vec<u8>,
line_num: usize,
path: PathBuf,
}
impl<R: BufRead> Iter<R> {
pub(crate) fn new(reader: R, path: PathBuf) -> Self {
Self { reader, buf: Vec::new(), line_num: 0, path }
}
fn err(&self, source: anyhow::Error) -> ImportError {
ImportError { path: Some(self.path.clone()), line_num: self.line_num, source }
}
fn parse_line(&self, line: &[u8]) -> Result<Dir<'static>, ImportError> {
let line =
str::from_utf8(line).map_err(|e| self.err(anyhow!(e).context("invalid utf-8")))?;
let err = || self.err(anyhow!("invalid entry: {line}"));
// z stores entries as `path|rank|last_accessed`. Use `rsplitn` so paths
// containing `|` are preserved.
let mut split = line.rsplitn(3, '|');
let last_accessed = split.next().ok_or_else(err)?;
let last_accessed = last_accessed.parse::<u64>().map_err(|_| err())?;
let rank = split.next().ok_or_else(err)?;
let rank = rank.parse::<f64>().map_err(|_| err())?;
let path = split.next().ok_or_else(err)?;
Ok(Dir { path: Cow::Owned(path.to_string()), rank, last_accessed })
}
}
impl<R: BufRead> Iterator for Iter<R> {
type Item = Result<Dir<'static>, ImportError>;
fn next(&mut self) -> Option<Self::Item> {
loop {
self.buf.clear();
self.line_num += 1;
match self.reader.read_until(b'\n', &mut self.buf) {
Ok(0) => return None,
Ok(_) => {
if self.buf.last() == Some(&b'\n') {
self.buf.pop();
}
if self.buf.last() == Some(&b'\r') {
self.buf.pop();
}
if self.buf.is_empty() {
continue;
}
return Some(self.parse_line(&self.buf));
}
Err(e) => return Some(Err(self.err(anyhow::Error::from(e)))),
}
}
}
}
/// Mirrors z's path logic:
///
/// ```sh
/// local datafile="${_Z_DATA:-$HOME/.z}"
/// ```
fn data_path() -> Result<PathBuf> {
match env::var_os("_Z_DATA") {
Some(path) => Ok(PathBuf::from(path)),
None => {
let mut path = dirs::home_dir().context("could not find home directory")?;
path.push(".z");
Ok(path)
}
}
}

108
src/import/z_lua.rs Normal file
View File

@ -0,0 +1,108 @@
use std::env;
use std::ffi::OsStr;
use std::fs::File;
use std::io::{self, BufReader};
use std::path::PathBuf;
use anyhow::{Context, Result};
use crate::db::Dir;
use crate::import::{ImportError, Importer, z};
#[derive(clap::Args, Clone, Debug)]
pub(crate) struct ZLua {}
impl Importer for ZLua {
fn dirs(&self) -> Result<impl Iterator<Item = Result<Dir<'static>, ImportError>>> {
let path = data_path()?;
let err = match File::open(&path) {
Ok(file) => return Ok(z::Iter::new(BufReader::new(file), path)),
Err(e) if e.kind() == io::ErrorKind::NotFound => e,
Err(e) => return Err(e).with_context(|| format!("could not read {path:?}")),
};
let fish_path = data_path_fish()?;
let file = match File::open(&fish_path) {
Ok(file) => file,
// Both paths missing - report the original path's error.
Err(e) if e.kind() == io::ErrorKind::NotFound => {
return Err(err).with_context(|| format!("could not read {path:?}"));
}
// Fish path failed for some other reason (permissions, etc.)
Err(e) => return Err(e).with_context(|| format!("could not read {fish_path:?}")),
};
// z.lua uses the same `path|rank|last_accessed` line format as z.
Ok(z::Iter::new(BufReader::new(file), fish_path))
}
}
/// Mirrors z.lua's path logic:
///
/// ```lua
/// DATA_FILE = '~/.zlua' -- default
///
/// -- in z_init():
/// local _zl_data = os.getenv('_ZL_DATA')
/// if _zl_data ~= nil and _zl_data ~= "" then
/// if windows then
/// DATA_FILE = _zl_data
/// else
/// -- avoid windows environments affect cygwin & msys
/// if not string.match(_zl_data, '^%a:[/\\]') then
/// DATA_FILE = _zl_data
/// end
/// end
/// end
/// ```
fn data_path() -> Result<PathBuf> {
if let Some(path) = env::var_os("_ZL_DATA")
// Skip empty paths.
.filter(|path| !path.is_empty())
// On non-Windows, skip values that look like a Windows path (`C:\...`)
// — guards against Cygwin/MSYS environments leaking through.
.filter(|path| cfg!(target_os = "windows") || !looks_like_windows_path(path))
{
return Ok(PathBuf::from(path));
}
let mut path = dirs::home_dir().context("could not find home directory")?;
path.push(".zlua");
Ok(path)
}
/// Mirrors z.lua's path logic on Fish:
///
/// ```fish
/// if test -z "$XDG_DATA_HOME"
/// set -U _ZL_DATA_DIR "$HOME/.local/share/zlua"
/// else
/// set -U _ZL_DATA_DIR "$XDG_DATA_HOME/zlua"
/// end
/// set -x _ZL_DATA "$_ZL_DATA_DIR/zlua.txt"
/// ```
fn data_path_fish() -> Result<PathBuf> {
let mut path = match env::var_os("XDG_DATA_HOME") {
Some(xdg) => PathBuf::from(xdg),
None => {
let mut path = dirs::home_dir().context("could not find home directory")?;
path.push(".local");
path.push("share");
path
}
};
path.push("zlua");
path.push("zlua.txt");
Ok(path)
}
/// Matches Lua's `^%a:[/\\]` — ASCII letter, colon, slash-or-backslash.
fn looks_like_windows_path(s: &OsStr) -> bool {
let bytes = s.as_encoded_bytes();
bytes.len() >= 3
&& bytes[0].is_ascii_alphabetic()
&& bytes[1] == b':'
&& (bytes[2] == b'/' || bytes[2] == b'\\')
}

41
src/import/zsh_z.rs Normal file
View File

@ -0,0 +1,41 @@
use std::env;
use std::fs::File;
use std::io::BufReader;
use std::path::PathBuf;
use anyhow::{Context, Result};
use crate::db::Dir;
use crate::import::{ImportError, Importer, z};
#[derive(clap::Args, Clone, Debug)]
pub(crate) struct ZshZ {}
impl Importer for ZshZ {
fn dirs(&self) -> Result<impl Iterator<Item = Result<Dir<'static>, ImportError>>> {
let path = data_path()?;
let file = File::open(&path).with_context(|| format!("could not read {path:?}"))?;
let reader = BufReader::new(file);
// zsh-z uses the same `path|rank|last_accessed` line format as z.
Ok(z::Iter::new(reader, path))
}
}
/// Mirrors zsh-z's path logic:
///
/// ```sh
/// # Allow the user to specify a custom datafile in $ZSHZ_DATA (or legacy $_Z_DATA)
/// local custom_datafile="${ZSHZ_DATA:-$_Z_DATA}"
/// # If the user specified a datafile, use that or default to ~/.z
/// local datafile=${${custom_datafile:-$HOME/.z}:A}
/// ```
fn data_path() -> Result<PathBuf> {
match env::var_os("ZSHZ_DATA").or_else(|| env::var_os("_Z_DATA")) {
Some(path) => Ok(PathBuf::from(path)),
None => {
let mut path = dirs::home_dir().context("could not find home directory")?;
path.push(".z");
Ok(path)
}
}
}

View File

@ -4,6 +4,7 @@ mod cmd;
mod config;
mod db;
mod error;
mod import;
mod shell;
mod util;

View File

@ -168,13 +168,9 @@ pub fn write(path: impl AsRef<Path>, contents: impl AsRef<[u8]>) -> Result<()> {
// Set the owner of the tmpfile (UNIX only).
#[cfg(unix)]
if let Ok(metadata) = path.metadata() {
use std::os::unix::fs::MetadataExt;
use std::os::unix::fs::{MetadataExt, fchown};
use nix::unistd::{self, Gid, Uid};
let uid = Uid::from_raw(metadata.uid());
let gid = Gid::from_raw(metadata.gid());
_ = unistd::fchown(&tmp_file, Some(uid), Some(gid));
_ = fchown(&tmp_file, Some(metadata.uid()), Some(metadata.gid()));
}
// Close and rename the tmpfile.

View File

@ -9,7 +9,7 @@
# pwd based on the value of _ZO_RESOLVE_SYMLINKS.
function __zoxide_pwd() {
{%- let pwd -%}
{%- decl pwd -%}
{%- if resolve_symlinks -%}
{%- let pwd = "\\builtin pwd -P" -%}
{%- else -%}
@ -178,7 +178,7 @@ if [[ ${BASH_VERSINFO[0]:-0} -eq 4 && ${BASH_VERSINFO[1]:-0} -ge 4 || ${BASH_VER
# If there is a space after the last word, use interactive selection.
elif [[ -z ${COMP_WORDS[-1]} ]]; then
# shellcheck disable=SC2312
__zoxide_result="$(\command zoxide query --exclude "$(__zoxide_pwd)" --interactive -- "{{ "${COMP_WORDS[@]:1:${#COMP_WORDS[@]}-2}" }}")" && {
if __zoxide_result="$(\command zoxide query --exclude "$(__zoxide_pwd)" --interactive -- "{{ "${COMP_WORDS[@]:1:${#COMP_WORDS[@]}-2}" }}" 2>/dev/null)"; then
# In case the terminal does not respond to \e[5n or another
# mechanism steals the response, it is still worth completing
# the directory in the command line.
@ -189,7 +189,12 @@ if [[ ${BASH_VERSINFO[0]:-0} -eq 4 && ${BASH_VERSINFO[1]:-0} -ge 4 || ${BASH_VER
# builtin "bind".
bind -x '"\e[0n": __zoxide_z_complete_helper'
\builtin printf '\e[5n' >/dev/tty
}
else
# The interactive selection was cancelled. fzf has drawn over
# the prompt, so redraw the current line.
bind '"\e[0n": redraw-current-line'
\builtin printf '\e[5n' >/dev/tty
fi
fi
}

View File

@ -7,7 +7,7 @@
# pwd based on the value of _ZO_RESOLVE_SYMLINKS.
function __zoxide_pwd
{%- let pwd -%}
{%- decl pwd -%}
{%- if resolve_symlinks -%}
{%- let pwd = "builtin pwd -P" -%}
{%- else -%}
@ -100,9 +100,9 @@ function __zoxide_z_complete
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
set -l result (command zoxide query --exclude (__zoxide_pwd) --interactive -- $query 2>/dev/null)
and builtin commandline --replace -- "{{ cmd.unwrap_or("cd") }} "(string escape -- $result)
and builtin commandline --function repaint execute
end
end
complete --command __zoxide_z --no-files --arguments '(__zoxide_z_complete)'

View File

@ -8,7 +8,7 @@
#
# pwd based on the value of _ZO_RESOLVE_SYMLINKS.
{%- let pwd -%}
{%- decl pwd -%}
{%- if resolve_symlinks -%}
{%- let pwd = "\\command pwd -P" -%}
{%- else -%}

View File

@ -19,7 +19,7 @@ function global:__zoxide_bin {
# pwd based on zoxide's format.
function global:__zoxide_pwd {
$cwd = Get-Location
$cwd = Microsoft.PowerShell.Management\Get-Location
if ($cwd.Provider.Name -eq "FileSystem") {
$cwd.ProviderPath
}
@ -29,23 +29,23 @@ function global:__zoxide_pwd {
function global:__zoxide_cd($dir, $literal) {
$dir = if ($literal) {
if ($null -eq $dir) {
Set-Location
Microsoft.PowerShell.Management\Set-Location
} else {
Set-Location -LiteralPath $dir -Passthru -ErrorAction Stop
Microsoft.PowerShell.Management\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."
Microsoft.PowerShell.Utility\Write-Error "cd - is not supported below PowerShell 6.1. Please upgrade your version of PowerShell."
}
elseif ($dir -eq '+' -and ($PSVersionTable.PSVersion -lt 6.2)) {
Write-Error "cd + is not supported below PowerShell 6.2. Please upgrade your version of PowerShell."
Microsoft.PowerShell.Utility\Write-Error "cd + is not supported below PowerShell 6.2. Please upgrade your version of PowerShell."
}
else {
Set-Location -Path $dir -Passthru -ErrorAction Stop
Microsoft.PowerShell.Management\Set-Location -Path $dir -Passthru -ErrorAction Stop
}
}
{%- if echo %}
Write-Output $dir.Path
Microsoft.PowerShell.Utility\Write-Output $dir.Path
{%- endif %}
}
@ -84,7 +84,7 @@ function global:__zoxide_hook {
{%- endif %}
# Initialize hook.
$global:__zoxide_hooked = (Get-Variable __zoxide_hooked -ErrorAction Ignore -ValueOnly)
$global:__zoxide_hooked = (Microsoft.PowerShell.Utility\Get-Variable __zoxide_hooked -ErrorAction Ignore -ValueOnly)
if ($global:__zoxide_hooked -ne 1) {
$global:__zoxide_hooked = 1
$global:__zoxide_prompt_old = $function:prompt
@ -110,10 +110,10 @@ function global:__zoxide_z {
elseif ($args.Length -eq 1 -and ($args[0] -eq '-' -or $args[0] -eq '+')) {
__zoxide_cd $args[0] $false
}
elseif ($args.Length -eq 1 -and (Test-Path -PathType Container -LiteralPath $args[0])) {
elseif ($args.Length -eq 1 -and (Microsoft.PowerShell.Management\Test-Path -PathType Container -LiteralPath $args[0])) {
__zoxide_cd $args[0] $true
}
elseif ($args.Length -eq 1 -and (Test-Path -PathType Container -Path $args[0] )) {
elseif ($args.Length -eq 1 -and (Microsoft.PowerShell.Management\Test-Path -PathType Container -Path $args[0] )) {
__zoxide_cd $args[0] $false
}
else {
@ -145,8 +145,8 @@ function global:__zoxide_zi {
{%- match cmd %}
{%- when Some with (cmd) %}
Set-Alias -Name {{cmd}} -Value __zoxide_z -Option AllScope -Scope Global -Force
Set-Alias -Name {{cmd}}i -Value __zoxide_zi -Option AllScope -Scope Global -Force
Microsoft.PowerShell.Utility\Set-Alias -Name {{cmd}} -Value __zoxide_z -Option AllScope -Scope Global -Force
Microsoft.PowerShell.Utility\Set-Alias -Name {{cmd}}i -Value __zoxide_zi -Option AllScope -Scope Global -Force
{%- when None %}

View File

@ -1,7 +1,7 @@
{%- let section = "# =============================================================================\n#" -%}
{%- let not_configured = "# -- not configured --" -%}
{%- let pwd_cmd -%}
{%- decl pwd_cmd -%}
{%- if resolve_symlinks -%}
{%- let pwd_cmd = "pwd -P" -%}
{%- else -%}
@ -23,7 +23,15 @@ alias __zoxide_hook 'set __zoxide_pwd_tmp = "`{{ pwd_cmd }}`"; test "$__zoxide_p
{%- endif %}
# Initialize hook.
alias precmd ';__zoxide_hook'
set __zoxide_precmd = "`alias precmd`"
if ( "$__zoxide_precmd" !~ *__zoxide_hook* ) then
if ( "$__zoxide_precmd" == "" ) then
alias precmd '__zoxide_hook'
else
alias precmd "$__zoxide_precmd"';__zoxide_hook'
endif
endif
unset __zoxide_precmd
{%- endif %}

View File

@ -9,7 +9,7 @@
# pwd based on the value of _ZO_RESOLVE_SYMLINKS.
function __zoxide_pwd() {
{%- let pwd -%}
{%- decl pwd -%}
{%- if resolve_symlinks -%}
{%- let pwd = "\\builtin pwd -P" -%}
{%- else -%}
@ -59,6 +59,7 @@ function __zoxide_doctor() {
{%- else %}
[[ ${_ZO_DOCTOR:-1} -ne 0 ]] || return 0
[[ $- == *i* ]] || return 0
{%- if hook == InitHook::Prompt %}
[[ ${precmd_functions[(Ie)__zoxide_hook]:-} -eq 0 ]] || return 0
@ -143,10 +144,10 @@ if [[ -o zle ]]; then
elif [[ "${words[-1]}" == '' ]]; then
# Show completions for Space-Tab.
# shellcheck disable=SC2086
__zoxide_result="$(\command zoxide query --exclude "$(__zoxide_pwd || \builtin true)" --interactive -- ${words[2,-1]})" || __zoxide_result=''
__zoxide_result="$(\command zoxide query --exclude "$(__zoxide_pwd || \builtin true)" --interactive -- ${words[2,-1]} 2>/dev/null)" || __zoxide_result=''
# Set a result to ensure completion doesn't re-run
compadd -Q ""
compadd -Q -S "" -- ""
# Bind '\e[0n' to helper function.
\builtin bindkey '\e[0n' '__zoxide_z_complete_helper'