Merge branch 'main' of github.com:ajeetdsouza/zoxide

This commit is contained in:
dracarys18 2026-06-07 01:38:56 +05:30
commit e3613ec49b
No known key found for this signature in database
36 changed files with 1824 additions and 671 deletions

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

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

View File

@ -21,7 +21,7 @@ jobs:
matrix:
os: [ubuntu-latest]
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
with:
fetch-depth: 0
- uses: actions-rs/toolchain@v1
@ -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.1
uses: Swatinem/rust-cache@v2.9.1
with:
key: ${{ matrix.os }}
- name: Install just

View File

@ -30,8 +30,13 @@ 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
target: armv7-linux-androideabi
- os: macos-latest
target: x86_64-apple-darwin
- os: macos-latest
@ -42,7 +47,7 @@ jobs:
target: aarch64-pc-windows-msvc
steps:
- name: Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Get version
@ -59,7 +64,7 @@ jobs:
override: true
target: ${{ matrix.target }}
- name: Setup cache
uses: Swatinem/rust-cache@v2.8.1
uses: Swatinem/rust-cache@v2.9.1
with:
key: ${{ matrix.target }}
- name: Install cross
@ -100,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@v5
uses: actions/upload-artifact@v7
with:
name: ${{ matrix.target }}
path: |
@ -110,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

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

691
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -9,15 +9,15 @@ license = "MIT"
name = "zoxide"
readme = "README.md"
repository = "https://github.com/ajeetdsouza/zoxide"
rust-version = "1.85.0"
version = "0.9.8"
rust-version = "1.88.0"
version = "0.9.9"
[badges]
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"] }

110
README.md
View File

@ -11,15 +11,28 @@
<sup>Special thanks to:</sup>
<!-- markdownlint-disable-next-line MD013 -->
<div><a href="https://go.warp.dev/zoxide"><img alt="Sponsored by Warp" width="230" src="https://raw.githubusercontent.com/warpdotdev/brand-assets/refs/heads/main/Github/Sponsor/Warp-Github-LG-03.png" /></a></div>
<div><sup><b>Warp, built for coding with multiple AI agents.</b></sup></div>
<div><sup>Available for macOS, Linux, and Windows.</sup></div>
<div><sup>
Visit
<a href="https://go.warp.dev/zoxide"><u>warp.dev</u></a>
to learn more.
</sup></div>
<table>
<tr>
<td align="center">
<!-- markdownlint-disable-next-line MD013 -->
<a href="https://go.warp.dev/zoxide"><img alt="Sponsored by Warp" width="230" src="https://raw.githubusercontent.com/warpdotdev/brand-assets/refs/heads/main/Github/Sponsor/Warp-Github-LG-03.png" /></a>
<div><sup><b>Warp, built for coding with multiple AI agents.</b></sup></div>
<div><sup>Available for macOS, Linux, and Windows.</sup></div>
<div><sup>
Visit
<a href="https://go.warp.dev/zoxide"><u>warp.dev</u></a>
to learn more.
</sup></div>
</td>
<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>
<hr />
@ -58,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].
@ -222,7 +235,7 @@ zoxide can be installed in 4 easy steps:
> eval (zoxide init elvish | slurp)
> ```
>
> **Note**
> **Note:**
> zoxide only supports elvish v0.18.0 and above.
</details>
@ -256,7 +269,7 @@ zoxide can be installed in 4 easy steps:
> source ~/.zoxide.nu
> ```
>
> **Note**
> **Note:**
> zoxide only supports Nushell v0.89.0+.
</details>
@ -322,72 +335,36 @@ zoxide can be installed in 4 easy steps:
</details>
> **Note:**
> [Warp] provides its own completions, so `Space+Tab` completions are not
> supported there.
3. **Install fzf** <sup>(optional)</sup>
[fzf] is a command-line fuzzy finder, used by zoxide for completions /
interactive selection. It can be installed from [here][fzf-installation].
> **Note**
> **Note:**
> The minimum supported fzf version is v0.51.0.
4. **Import your data** <sup>(optional)</sup>
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
@ -554,6 +531,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]: 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

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

View File

@ -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 get normalized.
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);
}
}
}

View File

@ -76,8 +76,9 @@ impl Database {
}
/// Creates a new directory. This will create a duplicate entry if this
/// directory is always in the database, it is expected that the user either
/// does a check before calling this, or calls `dedup()` afterward.
/// directory is already in the database, it is expected that the user
/// either does a check before calling this, or calls `dedup()`
/// afterward.
pub fn add_unchecked(&mut self, path: impl AsRef<str> + Into<String>, rank: Rank, now: Epoch) {
self.with_dirs_mut(|dirs| {
dirs.push(Dir { path: path.into().into(), rank, last_accessed: now })

View File

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

75
src/import.rs Normal file
View File

@ -0,0 +1,75 @@
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 stderr = io::stderr();
let mut stderr = stderr.lock();
for entry in importer.dirs()? {
match entry {
Ok(dir) => 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

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

View File

@ -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,12 +9,16 @@
# pwd based on the value of _ZO_RESOLVE_SYMLINKS.
function __zoxide_pwd() {
{%- decl pwd -%}
{%- if resolve_symlinks -%}
{%- let pwd = "\\builtin pwd -P" -%}
{%- else -%}
{%- let pwd = "\\builtin pwd -L" -%}
{%- endif -%}
{%- if cfg!(windows) %}
\command cygpath -w "$(\builtin pwd -P)"
{%- else if resolve_symlinks %}
\builtin pwd -P
\command cygpath -w "{{ pwd }}"
{%- else %}
\builtin pwd -L
{{ pwd }}
{%- endif %}
}
@ -59,7 +63,7 @@ if [[ ${PROMPT_COMMAND:=} != *'__zoxide_hook'* ]]; then
PROMPT_COMMAND=("${PROMPT_COMMAND[@]}" __zoxide_hook)
else
# shellcheck disable=SC2128,SC2178
PROMPT_COMMAND="${PROMPT_COMMAND%"${PROMPT_COMMAND##*[![:space:]]}"}"
PROMPT_COMMAND="${PROMPT_COMMAND%"${PROMPT_COMMAND##*[![:space:];]}"}"
# shellcheck disable=SC2128,SC2178
PROMPT_COMMAND="${PROMPT_COMMAND:+${PROMPT_COMMAND};}__zoxide_hook"
fi

View File

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

View File

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

View File

@ -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
}
@ -28,20 +28,24 @@ function global:__zoxide_pwd {
# cd + custom logic based on the value of _ZO_ECHO.
function global:__zoxide_cd($dir, $literal) {
$dir = if ($literal) {
Set-Location -LiteralPath $dir -Passthru -ErrorAction Stop
if ($null -eq $dir) {
Microsoft.PowerShell.Management\Set-Location
} else {
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 %}
}
@ -80,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
@ -101,15 +105,15 @@ if ($global:__zoxide_hooked -ne 1) {
# Jump to a directory using only keywords.
function global:__zoxide_z {
if ($args.Length -eq 0) {
__zoxide_cd ~ $true
__zoxide_cd $null $true
}
elseif ($args.Length -eq 1 -and ($args[0] -eq '-' -or $args[0] -eq '+')) {
__zoxide_cd $args[0] $false
}
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 {
@ -141,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 -%}

View File

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