diff --git a/.cargo/audit.toml b/.cargo/audit.toml
deleted file mode 100644
index 69f3b0f..0000000
--- a/.cargo/audit.toml
+++ /dev/null
@@ -1,2 +0,0 @@
-[advisories]
-ignore = ["RUSTSEC-2020-0095"]
diff --git a/.cargo/config.toml b/.cargo/config.toml
index 35049cb..4b40e31 100644
--- a/.cargo/config.toml
+++ b/.cargo/config.toml
@@ -1,2 +1,7 @@
[alias]
xtask = "run --package xtask --"
+
+# On Windows MSVC, statically link the C runtime so that the resulting EXE does
+# not depend on the vcruntime DLL.
+[target.'cfg(all(windows, target_env = "msvc"))']
+rustflags = ["-C", "target-feature=+crt-static"]
diff --git a/.deepsource.toml b/.deepsource.toml
index cf6e317..2895393 100644
--- a/.deepsource.toml
+++ b/.deepsource.toml
@@ -2,4 +2,9 @@ version = 1
[[analyzers]]
name = "rust"
-enabled = true
+
+ [analyzers.meta]
+ msrv = "stable"
+
+[[analyzers]]
+name = "shell"
\ No newline at end of file
diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md
index a2a6a67..609eb60 100644
--- a/.github/CODE_OF_CONDUCT.md
+++ b/.github/CODE_OF_CONDUCT.md
@@ -17,23 +17,23 @@ diverse, inclusive, and healthy community.
Examples of behavior that contributes to a positive environment for our
community include:
-* Demonstrating empathy and kindness toward other people
-* Being respectful of differing opinions, viewpoints, and experiences
-* Giving and gracefully accepting constructive feedback
-* Accepting responsibility and apologizing to those affected by our mistakes,
+- Demonstrating empathy and kindness toward other people
+- Being respectful of differing opinions, viewpoints, and experiences
+- Giving and gracefully accepting constructive feedback
+- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
-* Focusing on what is best not just for us as individuals, but for the
+- Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
-* The use of sexualized language or imagery, and sexual attention or
+- The use of sexualized language or imagery, and sexual attention or
advances of any kind
-* Trolling, insulting or derogatory comments, and personal or political attacks
-* Public or private harassment
-* Publishing others' private information, such as a physical or email
+- Trolling, insulting or derogatory comments, and personal or political attacks
+- Public or private harassment
+- Publishing others' private information, such as a physical or email
address, without their explicit permission
-* Other conduct which could reasonably be considered inappropriate in a
+- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
@@ -106,7 +106,7 @@ Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
-standards, including sustained inappropriate behavior, harassment of an
+standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..ca79ca5
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,6 @@
+version: 2
+updates:
+ - package-ecosystem: github-actions
+ directory: /
+ schedule:
+ interval: weekly
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 8500f7c..78290d9 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -4,31 +4,51 @@ on:
branches: [main]
pull_request:
workflow_dispatch:
+env:
+ CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
+ CARGO_INCREMENTAL: 0
+ CARGO_TERM_COLOR: always
+permissions:
+ contents: read
jobs:
ci:
name: ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
- os: [ubuntu-latest, macos-latest, windows-latest]
+ os: [ubuntu-latest, windows-latest]
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
with:
fetch-depth: 0
-
- uses: actions-rs/toolchain@v1
if: ${{ matrix.os == 'windows-latest' }}
with:
- toolchain: stable
- components: rustfmt, clippy
+ components: clippy
profile: minimal
- override: true
- - uses: cachix/install-nix-action@v15
+ toolchain: stable
+ - uses: actions-rs/toolchain@v1
+ if: ${{ matrix.os == 'windows-latest' }}
+ with:
+ components: rustfmt
+ profile: minimal
+ toolchain: nightly
+ - uses: cachix/install-nix-action@v23
if: ${{ matrix.os != 'windows-latest' }}
with:
- nix_path: nixpkgs=channel:nixpkgs-unstable
-
- - run: cargo xtask ci
- if: ${{ matrix.os == 'windows-latest' }}
- - run: nix-shell --cores 0 --pure --run 'rm -rf ~/.cargo/bin; cargo xtask ci'
- if: ${{ matrix.os != 'windows-latest' }}
+ nix_path: nixpkgs=channel:nixos-unstable
+ - uses: cachix/cachix-action@v12
+ 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.7.0
+ with:
+ key: ${{ matrix.os }}
+ - name: Install just
+ uses: taiki-e/install-action@v2
+ with:
+ tool: just
+ - name: Run lints + tests
+ run: just lint test
diff --git a/.github/workflows/no-response.yml b/.github/workflows/no-response.yml
new file mode 100644
index 0000000..dd40342
--- /dev/null
+++ b/.github/workflows/no-response.yml
@@ -0,0 +1,22 @@
+name: no-response
+on:
+ issue_comment:
+ types: [created]
+ schedule:
+ - cron: "0 0 * * *" # daily at 00:00
+jobs:
+ no-response:
+ if: github.repository == 'ajeetdsouza/zoxide'
+ permissions:
+ issues: write
+ runs-on: ubuntu-latest
+ steps:
+ - uses: lee-dohm/no-response@v0.5.0
+ with:
+ token: ${{ github.token }}
+ daysUntilClose: 30
+ responseRequiredLabel: waiting-for-response
+ closeComment: >
+ This issue has been automatically closed due to inactivity. If you feel
+ this is still relevant, please comment here or create a fresh issue.
+
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 2947bfc..73b9ea1 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -1,11 +1,11 @@
name: release
on:
push:
- branches: [main]
- tags: ["v[0-9]+.[0-9]+.[0-9]+"]
- pull_request:
workflow_dispatch:
-
+env:
+ CARGO_INCREMENTAL: 0
+permissions:
+ contents: write
jobs:
release:
name: ${{ matrix.target }}
@@ -16,38 +16,33 @@ jobs:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-musl
+ deb: true
- os: ubuntu-latest
target: arm-unknown-linux-musleabihf
- os: ubuntu-latest
target: armv7-unknown-linux-musleabihf
- os: ubuntu-latest
target: aarch64-unknown-linux-musl
-
+ deb: true
- os: macos-11
target: x86_64-apple-darwin
- os: macos-11
target: aarch64-apple-darwin
-
- os: windows-latest
target: x86_64-pc-windows-msvc
- os: windows-latest
target: aarch64-pc-windows-msvc
steps:
- name: Checkout repository
- uses: actions/checkout@v2
+ uses: actions/checkout@v4
with:
fetch-depth: 0
-
- - name: Set artifact name
- shell: bash
- run: |
- version="$(git describe --tags --match='v*.*.*' --always)"
- name="zoxide-$version-${{ matrix.target }}"
- echo "ARTIFACT_NAME=$name" >> $GITHUB_ENV
-
- echo "version: $version"
- echo "artifact: $name"
-
+ - name: Get version
+ id: get_version
+ uses: SebRollen/toml-action@v1.0.2
+ with:
+ file: Cargo.toml
+ field: package.version
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
@@ -55,45 +50,58 @@ jobs:
profile: minimal
override: true
target: ${{ matrix.target }}
-
+ - name: Setup cache
+ uses: Swatinem/rust-cache@v2.7.0
+ with:
+ key: ${{ matrix.target }}
- name: Build binary
uses: actions-rs/cargo@v1
with:
command: build
args: --release --locked --target=${{ matrix.target }} --color=always --verbose
use-cross: ${{ runner.os == 'Linux' }}
-
+ - name: Install cargo-deb
+ if: ${{ matrix.deb == true }}
+ uses: actions-rs/install@v0.1
+ with:
+ crate: cargo-deb
+ - name: Build deb
+ if: ${{ matrix.deb == true }}
+ uses: actions-rs/cargo@v1
+ with:
+ command: deb
+ args: --no-build --no-strip --output=. --target=${{ matrix.target }}
- name: Package (*nix)
if: runner.os != 'Windows'
- run: >
- tar -cv
- CHANGELOG.md LICENSE README.md
- man/
- -C contrib/ completions/ -C ../
- -C target/${{ matrix.target }}/release/ zoxide
- | gzip --best > '${{ env.ARTIFACT_NAME }}.tar.gz'
+ run: |
+ tar -cv CHANGELOG.md LICENSE README.md man/ \
+ -C contrib/ completions/ -C ../ \
+ -C target/${{ matrix.target }}/release/ zoxide |
+ gzip --best > \
+ zoxide-${{ steps.get_version.outputs.value }}-${{ matrix.target }}.tar.gz
- name: Package (Windows)
if: runner.os == 'Windows'
- run: >
- 7z a ${{ env.ARTIFACT_NAME }}.zip
- CHANGELOG.md LICENSE README.md
- ./man/
- ./contrib/completions/
- ./target/${{ matrix.target }}/release/zoxide.exe
-
+ run: |
+ 7z a zoxide-${{ steps.get_version.outputs.value }}-${{ matrix.target }}.zip `
+ CHANGELOG.md LICENSE README.md ./man/ ./contrib/completions/ `
+ ./target/${{ matrix.target }}/release/zoxide.exe
- name: Upload artifact
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
with:
name: ${{ matrix.target }}
path: |
- *.zip
+ *.deb
*.tar.gz
-
+ *.zip
- name: Create release
- if: startsWith(github.ref, 'refs/tags/v')
+ if: |
+ github.ref == 'refs/heads/main' && startsWith(github.event.head_commit.message, 'chore(release)')
uses: softprops/action-gh-release@v1
with:
draft: true
files: |
- *.zip
+ *.deb
*.tar.gz
+ *.zip
+ name: ${{ steps.get_version.outputs.value }}
+ tag_name: ""
diff --git a/.github/workflows/winget.yml b/.github/workflows/winget.yml
new file mode 100644
index 0000000..307ebe2
--- /dev/null
+++ b/.github/workflows/winget.yml
@@ -0,0 +1,13 @@
+name: winget
+on:
+ release:
+ types: [released]
+jobs:
+ publish:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: vedantmgoyal2009/winget-releaser@v2
+ with:
+ identifier: ajeetdsouza.zoxide
+ installers-regex: '-pc-windows-msvc\.zip$'
+ token: ${{ secrets.WINGET_TOKEN }}
diff --git a/.gitignore b/.gitignore
index 89b1201..887d2ae 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,7 @@
# Compiled files and executables
debug/
target/
+target_nix/
# Backup files generated by rustfmt
**/*.rs.bk
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 097eb03..93cf442 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,22 +7,101 @@ 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
+## [0.9.2] - 2023-08-04
+
+### Added
+
+- Short option `-a` for `zoxide query --all`.
+
+### Fixed
+
+- PowerShell: use `global` scope for variables / functions.
+
+## [0.9.1] - 2023-05-07
+
+### Added
+
+- Fish/Zsh: aliases on `__zoxide_z` will now use completions.
+- Nushell: add support for v0.78.0.
+- Fish: plugin now works on older versions.
+- PowerShell: warn when PowerShell version is too old for `z -` and `z +`.
+- PowerShell: support for PWD hooks on all versions.
+
+### Fixed
+
+- Fish: not providing `cd` completions when there is a space in the path.
+- Bash/Fish/Zsh: providing `z` completions when the last argument starts with `z!`.
+- Bash/Fish/Zsh: attempting to `cd` when the last argument is `z!`.
+
+## [0.9.0] - 2023-01-08
+
+### Added
+
+- `edit` subcommand to adjust the scores of entries.
+
+### Fixed
+
+- Zsh: completions clashing with `zsh-autocomplete`.
+- Fzf: 'invalid option' on macOS.
+- PowerShell: handle UTF-8 encoding correctly.
+- Zsh: don't hide output from `chpwd` hooks.
+- Nushell: upgrade minimum supported version to v0.73.0.
+- Zsh: fix extra space in interactive completions when no match is found.
+- Fzf: various improvements, upgrade minimum supported version to v0.33.0.
+- Nushell: accidental redefinition of hooks when initialized twice.
+
+### Removed
+
+- `remove -i` subcommand: use `edit` instead.
+
+## [0.8.3] - 2022-09-02
+
+### Added
+
+- Nushell: support for `z -`.
+- Nushell: support for PWD hooks.
+
+### Changed
+
+- Fish: change fuzzy completion prefix to `z!`.
+- Zsh: allow `z` to navigate dirstack via `+n` and `-n`.
+- Fzf: improved preview window.
+
+### Fixed
+
+- Bash: double forward slash in completions.
+
+## [0.8.2] - 2022-06-26
+
+### Changed
+
+- Fzf: show preview window below results.
+
+### Fixed
+
+- Bash/Fish/POSIX/Zsh: paths on Cygwin.
+- Fish: completions not working on certain systems.
+- Bash: completions not escaping spaces correctly.
+
+## [0.8.1] - 2021-04-23
### Changed
- Manpages: moved to `man/man1/*.1`.
+- Replace `--no-aliases` with `--no-cmd`.
+- Elvish: upgrade minimum supported version to v0.18.0.
+- Nushell: upgrade minimum supported version to v0.61.0.
### Fixed
- Bash/Zsh: rename `_z` completion function to avoid conflicts with other shell
plugins.
-- Elvish: upgrade to new lambda syntax.
-- Fzf: added `--keep-right` option by default, upgraded minimum version to
- v0.21.0.
+- Fzf: added `--keep-right` option by default, upgrade minimum supported version
+ to v0.21.0.
- Bash: only enable completions on 4.4+.
- Fzf: bypass `ls` alias in preview window.
- Retain ownership of database file.
+- `zoxide query --interactive` should not conflict with `--score`.
## [0.8.0] - 2021-12-25
@@ -73,7 +152,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
-- PowerShell: Hook not initializing correctly.
+- PowerShell: hook not initializing correctly.
## [0.7.6] - 2021-10-13
@@ -371,6 +450,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- GitHub Actions pipeline to build and upload releases.
- Support for zsh.
+[0.9.2]: https://github.com/ajeetdsouza/zoxide/compare/v0.9.1...v0.9.2
+[0.9.1]: https://github.com/ajeetdsouza/zoxide/compare/v0.9.0...v0.9.1
+[0.9.0]: https://github.com/ajeetdsouza/zoxide/compare/v0.8.3...v0.9.0
+[0.8.3]: https://github.com/ajeetdsouza/zoxide/compare/v0.8.2...v0.8.3
+[0.8.2]: https://github.com/ajeetdsouza/zoxide/compare/v0.8.1...v0.8.2
+[0.8.1]: https://github.com/ajeetdsouza/zoxide/compare/v0.8.0...v0.8.1
[0.8.0]: https://github.com/ajeetdsouza/zoxide/compare/v0.7.9...v0.8.0
[0.7.9]: https://github.com/ajeetdsouza/zoxide/compare/v0.7.8...v0.7.9
[0.7.8]: https://github.com/ajeetdsouza/zoxide/compare/v0.7.7...v0.7.8
diff --git a/Cargo.lock b/Cargo.lock
index b125936..975dba3 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4,39 +4,96 @@ version = 3
[[package]]
name = "aho-corasick"
-version = "0.7.18"
+version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
+checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41"
dependencies = [
"memchr",
]
[[package]]
-name = "anyhow"
-version = "1.0.56"
+name = "aliasable"
+version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4361135be9122e0870de935d7c439aef945b9f9ddd4199a553b5270b49c82a27"
+checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd"
+
+[[package]]
+name = "anstream"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "is-terminal",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd"
+
+[[package]]
+name = "anstyle-parse"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
+dependencies = [
+ "windows-sys",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188"
+dependencies = [
+ "anstyle",
+ "windows-sys",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.72"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854"
[[package]]
name = "askama"
-version = "0.11.1"
+version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fb98f10f371286b177db5eeb9a6e5396609555686a35e1d4f7b9a9c6d8af0139"
+checksum = "47cbc3cf73fa8d9833727bbee4835ba5c421a0d65b72daf9a7b5d0e0f9cfb57e"
dependencies = [
"askama_derive",
"askama_escape",
- "askama_shared",
]
[[package]]
name = "askama_derive"
-version = "0.11.2"
+version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "87bf87e6e8b47264efa9bde63d6225c6276a52e05e91bf37eaa8afd0032d6b71"
+checksum = "c22fbe0413545c098358e56966ff22cdd039e10215ae213cfbd65032b119fc94"
dependencies = [
- "askama_shared",
+ "mime",
+ "mime_guess",
+ "nom",
"proc-macro2",
- "syn",
+ "quote",
+ "syn 2.0.28",
]
[[package]]
@@ -45,27 +102,13 @@ version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341"
-[[package]]
-name = "askama_shared"
-version = "0.12.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bf722b94118a07fcbc6640190f247334027685d4e218b794dbfe17c32bf38ed0"
-dependencies = [
- "askama_escape",
- "mime",
- "mime_guess",
- "nom",
- "proc-macro2",
- "quote",
- "syn",
-]
-
[[package]]
name = "assert_cmd"
-version = "2.0.4"
+version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "93ae1ddd39efd67689deb1979d80bad3bf7f2b09c6e6117c8d1f2443b5e2f83e"
+checksum = "88903cb14723e4d4003335bb7f8a14f27691649105346a0f0957466c096adfe6"
dependencies = [
+ "anstyle",
"bstr",
"doc-comment",
"predicates",
@@ -74,23 +117,6 @@ dependencies = [
"wait-timeout",
]
-[[package]]
-name = "atty"
-version = "0.2.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
-dependencies = [
- "hermit-abi",
- "libc",
- "winapi",
-]
-
-[[package]]
-name = "autocfg"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
-
[[package]]
name = "bincode"
version = "1.3.3"
@@ -107,21 +133,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
-name = "bstr"
-version = "0.2.17"
+name = "bitflags"
+version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
+checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42"
+
+[[package]]
+name = "bstr"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05"
dependencies = [
- "lazy_static",
"memchr",
"regex-automata",
+ "serde",
]
[[package]]
name = "cc"
-version = "1.0.73"
+version = "1.0.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
+checksum = "6c6b2562119bf28c3439f7f02db99faf0aa1a8cdfe5772a2ee155d32227239f0"
+dependencies = [
+ "libc",
+]
[[package]]
name = "cfg-if"
@@ -131,35 +166,41 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
-version = "3.1.6"
+version = "4.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d8c93436c21e4698bacadf42917db28b23017027a4deccb35dbe47a7e7840123"
+checksum = "5fd304a20bff958a57f04c4e96a2e7594cc4490a0e809cbd48bb6437edaa452d"
dependencies = [
- "atty",
- "bitflags",
+ "clap_builder",
"clap_derive",
- "indexmap",
- "lazy_static",
- "os_str_bytes",
+ "once_cell",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.3.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01c6a3f08f1fe5662a35cfe393aec09c4df95f60ee93b7556505260f75eee9e1"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
"strsim",
- "termcolor",
- "textwrap",
]
[[package]]
name = "clap_complete"
-version = "3.1.1"
+version = "4.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "df6f3613c0a3cddfd78b41b10203eb322cb29b600cbdf808a7d3db95691b8e25"
+checksum = "5fc443334c81a804575546c5a8a79b4913b50e28d69232903604cada1de817ce"
dependencies = [
"clap",
]
[[package]]
name = "clap_complete_fig"
-version = "3.1.0"
+version = "4.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c11f6f44afea4aee21bb57a5297879c88ac8dc97224fbbbe796edd60a098f0e"
+checksum = "99fee1d30a51305a6c2ed3fc5709be3c8af626c9c958e04dd9ae94e27bcbce9f"
dependencies = [
"clap",
"clap_complete",
@@ -167,27 +208,49 @@ dependencies = [
[[package]]
name = "clap_derive"
-version = "3.1.4"
+version = "4.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da95d038ede1a964ce99f49cbe27a7fb538d1da595e4b4f70b8c8f338d17bf16"
+checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050"
dependencies = [
"heck",
- "proc-macro-error",
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.28",
]
[[package]]
-name = "crossbeam-utils"
-version = "0.8.7"
+name = "clap_lex"
+version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5e5bed1f1c269533fa816a0a5492b3545209a205ca1a54842be180eb63a16a6"
+checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b"
+
+[[package]]
+name = "color-print"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2a5e6504ed8648554968650feecea00557a3476bc040d0ffc33080e66b646d0"
dependencies = [
- "cfg-if",
- "lazy_static",
+ "color-print-proc-macro",
]
+[[package]]
+name = "color-print-proc-macro"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d51beaa537d73d2d1ff34ee70bc095f170420ab2ec5d687ecd3ec2b0d092514b"
+dependencies = [
+ "nom",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "colorchoice"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
+
[[package]]
name = "difflib"
version = "0.4.0"
@@ -196,22 +259,23 @@ checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
[[package]]
name = "dirs"
-version = "4.0.0"
+version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059"
+checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-sys"
-version = "0.3.6"
+version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780"
+checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
dependencies = [
"libc",
+ "option-ext",
"redox_users",
- "winapi",
+ "windows-sys",
]
[[package]]
@@ -222,36 +286,48 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
[[package]]
name = "dunce"
-version = "1.0.2"
+version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "453440c271cf5577fd2a40e4942540cb7d0d2f85e27c8d07dd0023c925a67541"
+checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b"
[[package]]
name = "either"
-version = "1.6.1"
+version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
+checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
[[package]]
-name = "fastrand"
-version = "1.7.0"
+name = "errno"
+version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf"
+checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f"
dependencies = [
- "instant",
+ "errno-dragonfly",
+ "libc",
+ "windows-sys",
]
[[package]]
-name = "fnv"
-version = "1.0.7"
+name = "errno-dragonfly"
+version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
+dependencies = [
+ "cc",
+ "libc",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764"
[[package]]
name = "getrandom"
-version = "0.2.5"
+version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77"
+checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
dependencies = [
"cfg-if",
"libc",
@@ -260,131 +336,65 @@ dependencies = [
[[package]]
name = "glob"
-version = "0.3.0"
+version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
-
-[[package]]
-name = "globset"
-version = "0.4.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "10463d9ff00a2a068db14231982f5132edebad0d7660cd956a1c30292dbcbfbd"
-dependencies = [
- "aho-corasick",
- "bstr",
- "fnv",
- "log",
- "regex",
-]
-
-[[package]]
-name = "hashbrown"
-version = "0.11.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
+checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "heck"
-version = "0.4.0"
+version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "hermit-abi"
-version = "0.1.19"
+version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
-dependencies = [
- "libc",
-]
+checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
[[package]]
-name = "ignore"
-version = "0.4.18"
+name = "is-terminal"
+version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d"
+checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
dependencies = [
- "crossbeam-utils",
- "globset",
- "lazy_static",
- "log",
- "memchr",
- "regex",
- "same-file",
- "thread_local",
- "walkdir",
- "winapi-util",
-]
-
-[[package]]
-name = "indexmap"
-version = "1.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223"
-dependencies = [
- "autocfg",
- "hashbrown",
-]
-
-[[package]]
-name = "instant"
-version = "0.1.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
-dependencies = [
- "cfg-if",
+ "hermit-abi",
+ "rustix",
+ "windows-sys",
]
[[package]]
name = "itertools"
-version = "0.10.3"
+version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
dependencies = [
"either",
]
-[[package]]
-name = "lazy_static"
-version = "1.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
-
[[package]]
name = "libc"
-version = "0.2.119"
+version = "0.2.147"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4"
+checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
[[package]]
-name = "log"
-version = "0.4.14"
+name = "linux-raw-sys"
+version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
-dependencies = [
- "cfg-if",
-]
+checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503"
[[package]]
name = "memchr"
-version = "2.4.1"
+version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
-
-[[package]]
-name = "memoffset"
-version = "0.6.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
-dependencies = [
- "autocfg",
-]
+checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "mime"
-version = "0.3.16"
+version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
+checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "mime_guess"
@@ -404,67 +414,75 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "nix"
-version = "0.23.1"
+version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6"
+checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a"
dependencies = [
- "bitflags",
- "cc",
+ "bitflags 1.3.2",
"cfg-if",
"libc",
- "memoffset",
+ "static_assertions",
]
[[package]]
name = "nom"
-version = "7.1.0"
+version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
- "version_check",
-]
-
-[[package]]
-name = "num-traits"
-version = "0.2.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
-dependencies = [
- "autocfg",
]
[[package]]
name = "once_cell"
-version = "1.10.0"
+version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9"
+checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
[[package]]
-name = "ordered-float"
-version = "2.10.0"
+name = "option-ext"
+version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87"
+checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
+
+[[package]]
+name = "ouroboros"
+version = "0.17.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2ba07320d39dfea882faa70554b4bd342a5f273ed59ba7c1c6b4c840492c954"
dependencies = [
- "num-traits",
+ "aliasable",
+ "ouroboros_macro",
+ "static_assertions",
]
[[package]]
-name = "os_str_bytes"
-version = "6.0.0"
+name = "ouroboros_macro"
+version = "0.17.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
+checksum = "ec4c6225c69b4ca778c0aea097321a64c421cf4577b331c61b229267edabb6f8"
dependencies = [
- "memchr",
+ "heck",
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.28",
]
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
[[package]]
name = "predicates"
-version = "2.1.1"
+version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a5aab5be6e4732b473071984b3164dbbfb7a3674d30ea5ff44410b6bcd960c3c"
+checksum = "09963355b9f467184c04017ced4a2ba2d75cbcb4e7462690d388233253d4b1a9"
dependencies = [
+ "anstyle",
"difflib",
"itertools",
"predicates-core",
@@ -472,15 +490,15 @@ dependencies = [
[[package]]
name = "predicates-core"
-version = "1.0.3"
+version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da1c2388b1513e1b605fcec39a95e0a9e8ef088f71443ef37099fa9ae6673fcb"
+checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174"
[[package]]
name = "predicates-tree"
-version = "1.0.5"
+version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4d86de6de25020a36c6d3643a86d9a6a9f552107c0559c60ea03551b5e16c032"
+checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf"
dependencies = [
"predicates-core",
"termtree",
@@ -495,7 +513,7 @@ dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
- "syn",
+ "syn 1.0.109",
"version_check",
]
@@ -512,95 +530,153 @@ dependencies = [
[[package]]
name = "proc-macro2"
-version = "1.0.36"
+version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
+checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
dependencies = [
- "unicode-xid",
+ "unicode-ident",
]
[[package]]
name = "quote"
-version = "1.0.15"
+version = "1.0.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
+checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965"
dependencies = [
"proc-macro2",
]
[[package]]
-name = "redox_syscall"
-version = "0.2.11"
+name = "rand"
+version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8380fe0152551244f0747b1bf41737e0f8a74f97a14ccefd1148187271634f3c"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
- "bitflags",
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
+dependencies = [
+ "bitflags 1.3.2",
]
[[package]]
name = "redox_users"
-version = "0.4.0"
+version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64"
+checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
dependencies = [
"getrandom",
- "redox_syscall",
+ "redox_syscall 0.2.16",
+ "thiserror",
]
[[package]]
name = "regex"
-version = "1.5.5"
+version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286"
+checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7b6d6190b7594385f61bd3911cd1be99dfddcfc365a4160cc2ab5bff4aed294"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
-[[package]]
-name = "regex-automata"
-version = "0.1.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
-
[[package]]
name = "regex-syntax"
-version = "0.6.25"
+version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
+checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2"
[[package]]
-name = "remove_dir_all"
-version = "0.5.3"
+name = "relative-path"
+version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
-dependencies = [
- "winapi",
-]
+checksum = "4bf2521270932c3c7bed1a59151222bd7643c79310f2916f01925e1e16255698"
[[package]]
name = "rstest"
-version = "0.12.0"
+version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d912f35156a3f99a66ee3e11ac2e0b3f34ac85a07e05263d05a7e2c8810d616f"
+checksum = "2b96577ca10cb3eade7b337eb46520108a67ca2818a24d0b63f41fd62bc9651c"
+dependencies = [
+ "rstest_macros",
+ "rustc_version",
+]
+
+[[package]]
+name = "rstest_macros"
+version = "0.18.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "225e674cf31712b8bb15fdbca3ec0c1b9d825c5a24407ff2b7e005fb6a29ba03"
dependencies = [
"cfg-if",
+ "glob",
"proc-macro2",
"quote",
+ "regex",
+ "relative-path",
"rustc_version",
- "syn",
+ "syn 2.0.28",
+ "unicode-ident",
]
[[package]]
name = "rstest_reuse"
-version = "0.3.0"
+version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b29d3117bce27ea307d1fb7ce12c64ba11b3fd04311a42d32bc5f0072e6e3d4d"
+checksum = "88530b681abe67924d42cca181d070e3ac20e0740569441a9e35a7cedd2b34a4"
dependencies = [
"quote",
+ "rand",
"rustc_version",
- "syn",
+ "syn 2.0.28",
]
[[package]]
@@ -613,45 +689,49 @@ dependencies = [
]
[[package]]
-name = "same-file"
-version = "1.0.6"
+name = "rustix"
+version = "0.38.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+checksum = "1ee020b1716f0a80e2ace9b03441a749e402e86712f15f16fe8a8f75afac732f"
dependencies = [
- "winapi-util",
+ "bitflags 2.3.3",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys",
]
[[package]]
name = "semver"
-version = "1.0.6"
+version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4a3381e03edd24287172047536f20cabde766e2cd3e65e6b00fb3af51c4f38d"
+checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918"
[[package]]
name = "serde"
-version = "1.0.136"
+version = "1.0.181"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
+checksum = "6d3e73c93c3240c0bda063c239298e633114c69a888c3e37ca8bb33f343e9890"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
-version = "1.0.136"
+version = "1.0.181"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
+checksum = "be02f6cb0cd3a5ec20bbcfbcbd749f57daddb1a0882dc2e46a6c236c90b977ed"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.28",
]
[[package]]
-name = "shell-words"
+name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "strsim"
@@ -661,57 +741,63 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
-version = "1.0.86"
+version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
- "unicode-xid",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
]
[[package]]
name = "tempfile"
-version = "3.3.0"
+version = "3.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
+checksum = "5486094ee78b2e5038a6382ed7645bc084dc2ec433426ca4c3cb61e2007b8998"
dependencies = [
"cfg-if",
"fastrand",
- "libc",
- "redox_syscall",
- "remove_dir_all",
- "winapi",
-]
-
-[[package]]
-name = "termcolor"
-version = "1.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
-dependencies = [
- "winapi-util",
+ "redox_syscall 0.3.5",
+ "rustix",
+ "windows-sys",
]
[[package]]
name = "termtree"
-version = "0.2.4"
+version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "507e9898683b6c43a9aa55b64259b721b52ba226e0f3779137e50ad114a4c90b"
+checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76"
[[package]]
-name = "textwrap"
-version = "0.15.0"
+name = "thiserror"
+version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
-
-[[package]]
-name = "thread_local"
-version = "1.1.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
+checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90"
dependencies = [
- "once_cell",
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.28",
]
[[package]]
@@ -724,10 +810,16 @@ dependencies = [
]
[[package]]
-name = "unicode-xid"
-version = "0.2.2"
+name = "unicode-ident"
+version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
+checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
+
+[[package]]
+name = "utf8parse"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "version_check"
@@ -744,67 +836,92 @@ dependencies = [
"libc",
]
-[[package]]
-name = "walkdir"
-version = "2.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
-dependencies = [
- "same-file",
- "winapi",
- "winapi-util",
-]
-
[[package]]
name = "wasi"
-version = "0.10.2+wasi-snapshot-preview1"
+version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
-name = "winapi"
-version = "0.3.9"
+name = "which"
+version = "4.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269"
dependencies = [
- "winapi-i686-pc-windows-gnu",
- "winapi-x86_64-pc-windows-gnu",
+ "either",
+ "libc",
+ "once_cell",
]
[[package]]
-name = "winapi-i686-pc-windows-gnu"
-version = "0.4.0"
+name = "windows-sys"
+version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
-
-[[package]]
-name = "winapi-util"
-version = "0.1.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
- "winapi",
+ "windows-targets",
]
[[package]]
-name = "winapi-x86_64-pc-windows-gnu"
-version = "0.4.0"
+name = "windows-targets"
+version = "0.48.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
[[package]]
-name = "xtask"
-version = "0.1.0"
-dependencies = [
- "anyhow",
- "clap",
- "ignore",
- "shell-words",
-]
+name = "windows_aarch64_gnullvm"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
[[package]]
name = "zoxide"
-version = "0.8.0"
+version = "0.9.2"
dependencies = [
"anyhow",
"askama",
@@ -813,14 +930,16 @@ dependencies = [
"clap",
"clap_complete",
"clap_complete_fig",
+ "color-print",
"dirs",
"dunce",
"fastrand",
"glob",
"nix",
- "ordered-float",
+ "ouroboros",
"rstest",
"rstest_reuse",
"serde",
"tempfile",
+ "which",
]
diff --git a/Cargo.toml b/Cargo.toml
index 27d729f..b65ac87 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -3,43 +3,50 @@ authors = ["Ajeet D'Souza <98ajeet@gmail.com>"]
categories = ["command-line-utilities", "filesystem"]
description = "A smarter cd command for your terminal"
edition = "2021"
-keywords = ["cli"]
+homepage = "https://github.com/ajeetdsouza/zoxide"
+keywords = ["cli", "filesystem", "shell", "tool", "utility"]
license = "MIT"
name = "zoxide"
+readme = "README.md"
repository = "https://github.com/ajeetdsouza/zoxide"
-rust-version = "1.59"
-version = "0.8.0"
+rust-version = "1.65"
+version = "0.9.2"
[badges]
maintenance = { status = "actively-developed" }
-[workspace]
-members = ["xtask/"]
-
[dependencies]
anyhow = "1.0.32"
-askama = { version = "0.11.0", default-features = false }
+askama = { version = "0.12.0", default-features = false }
bincode = "1.3.1"
-clap = { version = "3.1.0", features = ["derive"] }
-dirs = "4.0.0"
+clap = { version = "4.3.0", features = ["derive"] }
+color-print = "0.3.4"
+dirs = "5.0.0"
dunce = "1.0.1"
-fastrand = "1.7.0"
+fastrand = "2.0.0"
glob = "0.3.0"
-ordered-float = "2.0.0"
+ouroboros = "0.17.2"
serde = { version = "1.0.116", features = ["derive"] }
[target.'cfg(unix)'.dependencies]
-nix = "0.23.1"
+nix = { version = "0.26.1", default-features = false, features = [
+ "fs",
+ "user",
+] }
+
+[target.'cfg(windows)'.dependencies]
+which = "4.2.5"
[build-dependencies]
-clap = { version = "3.1.0", features = ["derive"] }
-clap_complete = "3.1.0"
-clap_complete_fig = "3.1.0"
+clap = { version = "4.3.0", features = ["derive"] }
+clap_complete = "4.3.0"
+clap_complete_fig = "4.3.0"
+color-print = "0.3.4"
[dev-dependencies]
assert_cmd = "2.0.0"
-rstest = "0.12.0"
-rstest_reuse = "0.3.0"
+rstest = { version = "0.18.0", default-features = false }
+rstest_reuse = "0.6.0"
tempfile = "3.1.0"
[features]
@@ -48,5 +55,56 @@ nix-dev = []
[profile.release]
codegen-units = 1
+debug = 0
lto = true
strip = true
+
+[package.metadata.deb]
+assets = [
+ [
+ "target/release/zoxide",
+ "usr/bin/",
+ "755",
+ ],
+ [
+ "contrib/completions/zoxide.bash",
+ "usr/share/bash-completion/completions/zoxide",
+ "644",
+ ],
+ [
+ "contrib/completions/zoxide.fish",
+ "usr/share/fish/vendor_completions.d/",
+ "664",
+ ],
+ [
+ "contrib/completions/_zoxide",
+ "usr/share/zsh/vendor-completions/",
+ "644",
+ ],
+ [
+ "man/man1/*",
+ "usr/share/man/man1/",
+ "644",
+ ],
+ [
+ "README.md",
+ "usr/share/doc/zoxide/",
+ "644",
+ ],
+ [
+ "CHANGELOG.md",
+ "usr/share/doc/zoxide/",
+ "644",
+ ],
+ [
+ "LICENSE",
+ "usr/share/doc/zoxide/",
+ "644",
+ ],
+]
+extended-description = """\
+zoxide is a smarter cd command, inspired by z and autojump. It remembers which \
+directories you use most frequently, so you can "jump" to them in just a few \
+keystrokes."""
+priority = "optional"
+section = "utils"
diff --git a/Cross.toml b/Cross.toml
new file mode 100644
index 0000000..4e38b0f
--- /dev/null
+++ b/Cross.toml
@@ -0,0 +1,2 @@
+[build.env]
+passthrough = ["CARGO_INCREMENTAL"]
diff --git a/README.md b/README.md
index 94e67e4..8600ef1 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,20 @@
+
Special thanks to:
+
+
+
+
+
+ Warp is a modern, Rust-based terminal with AI built in so you and your team can build great software, faster.
+
+ Visit warp.dev to learn more.
+
+
+
+
+
# zoxide
[![crates.io][crates.io-badge]][crates.io]
@@ -51,240 +65,300 @@ Read more about the matching algorithm [here][algorithm-matching].
## Installation
-### *Step 1: Install zoxide*
-
-zoxide runs on most major platforms. If your platform isn't listed below,
-please [open an issue][issues].
-
-
-Linux
-
-To install zoxide, run this command in your terminal:
-
-```sh
-curl -sS https://webinstall.dev/zoxide | bash
-```
-
-Alternatively, you can use a package manager:
-
-| Distribution | Repository | Instructions |
-| ------------------ | ----------------------- | ---------------------------------------------------------------------------------------------- |
-| ***Any*** | **[crates.io]** | `cargo install zoxide --locked` |
-| *Any* | [conda-forge] | `conda install -c conda-forge zoxide` |
-| *Any* | [Linuxbrew] | `brew install zoxide` |
-| Alpine Linux 3.13+ | [Alpine Linux Packages] | `apk add zoxide` |
-| Arch Linux | [Arch Linux Community] | `pacman -S zoxide` |
-| CentOS 7+ | [Copr] | `dnf copr enable atim/zoxide`
`dnf install zoxide` |
-| Debian 11+ | [Debian Packages] | `apt install zoxide` |
-| Devuan 4.0+ | [Devuan Packages] | `apt install zoxide` |
-| Fedora 32+ | [Fedora Packages] | `dnf install zoxide` |
-| Gentoo | [GURU Overlay] | `eselect repository enable guru`
`emerge --sync guru`
`emerge app-shells/zoxide` |
-| Manjaro | | `pacman -S zoxide` |
-| NixOS | [nixpkgs] | `nix-env -iA nixpkgs.zoxide` |
-| Parrot OS | | `apt install zoxide` |
-| Raspbian 11+ | [Raspbian Packages] | `apt install zoxide` |
-| Ubuntu 21.04+ | [Ubuntu Packages] | `apt install zoxide` |
-| Void Linux | [Void Linux Packages] | `xbps-install -S zoxide` |
-
-
-
-
-macOS
-
-To install zoxide, use a package manager:
-
-| Repository | Instructions |
-| --------------- | ------------------------------------- |
-| **[crates.io]** | `cargo install zoxide --locked` |
-| [conda-forge] | `conda install -c conda-forge zoxide` |
-| [Homebrew] | `brew install zoxide` |
-| [MacPorts] | `port install zoxide` |
-
-
-
-
-Windows
-
-To install zoxide, run this command in your command prompt:
-
-```sh
-curl.exe -A "MS" https://webinstall.dev/zoxide | powershell
-```
-
-Alternatively, you can use a package manager:
-
-| Repository | Instructions |
-| --------------- | ------------------------------------- |
-| **[crates.io]** | `cargo install zoxide --locked` |
-| [Chocolatey] | `choco install zoxide` |
-| [conda-forge] | `conda install -c conda-forge zoxide` |
-| [Scoop] | `scoop install zoxide` |
-
-
-
-
-BSD
-
-To install zoxide, use a package manager:
-
-| Distribution | Repository | Instructions |
-| ------------- | --------------- | ------------------------------- |
-| ***Any*** | **[crates.io]** | `cargo install zoxide --locked` |
-| DragonFly BSD | [DPorts] | `pkg install zoxide` |
-| FreeBSD | [FreshPorts] | `pkg install zoxide` |
-| NetBSD | [pkgsrc] | `pkgin install zoxide` |
-
-
-
-
-Android
-
-To install zoxide, use a package manager:
-
-| Repository | Instructions |
-| ---------- | -------------------- |
-| [Termux] | `pkg install zoxide` |
-
-
-
-### *Step 2: Add zoxide to your shell*
-
-To start using zoxide, add it to your shell.
-
-
-Bash
-
-Add this to your configuration (usually `~/.bashrc`):
-
-```sh
-eval "$(zoxide init bash)"
-```
-
-
-
-
-Elvish
-
-Add this to your configuration (usually `~/.elvish/rc.elv`):
-
-```sh
-eval (zoxide init elvish | slurp)
-```
-
-Note: zoxide only supports elvish v0.16.0 and above.
-
-
-
-
-Fish
-
-Add this to your configuration (usually `~/.config/fish/config.fish`):
-
-```fish
-zoxide init fish | source
-```
-
-
-
-
-Nushell
-
-Add this to your configuration (find it by running `config path` in Nushell):
-
-```toml
-startup = ["zoxide init nushell --hook prompt | save ~/.zoxide.nu", "source ~/.zoxide.nu"]
-```
-
-Note: zoxide only supports Nushell v0.37.0 and above.
-
-
-
-
-PowerShell
-
-Add this to your configuration (find it by running `echo $profile` in
-PowerShell):
-
-```powershell
-# For zoxide v0.8.0+
-Invoke-Expression (& {
- $hook = if ($PSVersionTable.PSVersion.Major -lt 6) { 'prompt' } else { 'pwd' }
- (zoxide init --hook $hook powershell | Out-String)
-})
-
-# For older versions of zoxide
-Invoke-Expression (& {
- $hook = if ($PSVersionTable.PSVersion.Major -lt 6) { 'prompt' } else { 'pwd' }
- (zoxide init --hook $hook powershell) -join "`n"
-})
-```
-
-
-
-
-Xonsh
-
-Add this to your configuration (usually `~/.xonshrc`):
-
-```python
-execx($(zoxide init xonsh), 'exec', __xonsh__.ctx, filename='zoxide')
-```
-
-
-
-
-Zsh
-
-Add this to your configuration (usually `~/.zshrc`):
-
-```sh
-eval "$(zoxide init zsh)"
-```
-
-For completions to work, the above line must be added _after_ `compinit` is
-called. You may have to rebuild your cache by running `rm ~/.zcompdump*; compinit`.
-
-
-
-
-Any POSIX shell
-
-Add this to your configuration:
-
-```sh
-eval "$(zoxide init posix --hook prompt)"
-```
-
-
-
-### *Step 3: Install fzf (optional)*
-
-[fzf] is a command-line fuzzy finder, used by zoxide for interactive
-selection. It can be installed from [here][fzf-installation]. zoxide supports
-fzf v0.21.0+.
-
-### *Step 4: Import your data (optional)*
-
-If you currently use any of the following utilities, you may want to import
-your data into zoxide:
-
-
-autojump
-
-```sh
-zoxide import --from autojump path/to/db
-```
-
-
-
-
-z, z.lua, or zsh-z
-
-```sh
-zoxide import --from z path/to/db
-```
-
-
+zoxide can be installed in 4 easy steps:
+
+1. **Install binary**
+
+ zoxide runs on most major platforms. If your platform isn't listed below,
+ please [open an issue][issues].
+
+
+ Linux
+
+ > The recommended way to install zoxide is via the install script:
+ >
+ > ```sh
+ > curl -sS https://raw.githubusercontent.com/ajeetdsouza/zoxide/main/install.sh | bash
+ > ```
+ >
+ > 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`
`asdf install zoxide latest` |
+ > | _Any_ | [conda-forge] | `conda install -c conda-forge 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` |
+ > | CentOS 7+ | [Copr] | `dnf copr enable atim/zoxide`
`dnf install zoxide` |
+ > | Debian 11+[^1] | [Debian Packages] | `apt install zoxide` |
+ > | Devuan 4.0+[^1] | [Devuan Packages] | `apt install zoxide` |
+ > | Fedora 32+ | [Fedora Packages] | `dnf install zoxide` |
+ > | Gentoo | [GURU Overlay] | `eselect repository enable guru`
`emerge --sync guru`
`emerge app-shells/zoxide` |
+ > | Manjaro | | `pacman -S zoxide` |
+ > | openSUSE Tumbleweed | [openSUSE Factory] | `zypper install zoxide` |
+ > | Parrot OS[^1] | | `apt install zoxide` |
+ > | Raspbian 11+[^1] | [Raspbian Packages] | `apt install zoxide` |
+ > | Rhino Linux | [Pacstall Packages] | `pacstall -I zoxide-deb` |
+ > | Slackware 15.0+ | [SlackBuilds] | [Instructions][slackbuilds-howto] |
+ > | Ubuntu 21.04+[^1] | [Ubuntu Packages] | `apt install zoxide` |
+ > | Void Linux | [Void Linux Packages] | `xbps-install -S zoxide` |
+
+
+
+
+ macOS
+
+ > 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`
`asdf install zoxide latest` |
+ > | [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:
+ >
+ > ```sh
+ > curl -sS https://raw.githubusercontent.com/ajeetdsouza/zoxide/main/install.sh | bash
+ > ```
+
+
+
+
+ Windows
+
+ > The recommended way to install zoxide is via `winget`:
+ >
+ > ```sh
+ > winget install ajeetdsouza.zoxide
+ > ```
+ >
+ > Or, you can use an alternative package manager:
+ >
+ > | Repository | Instructions |
+ > | --------------- | ------------------------------------- |
+ > | **[crates.io]** | `cargo install zoxide --locked` |
+ > | [Chocolatey] | `choco install zoxide` |
+ > | [conda-forge] | `conda install -c conda-forge zoxide` |
+ > | [Scoop] | `scoop install zoxide` |
+ >
+ > If you're using Cygwin, Git Bash, or MSYS2, use the install script instead:
+ >
+ > ```sh
+ > curl -sS https://raw.githubusercontent.com/ajeetdsouza/zoxide/main/install.sh | bash
+ > ```
+
+
+
+
+ BSD
+
+ > To install zoxide, use a package manager:
+ >
+ > | Distribution | Repository | Instructions |
+ > | ------------- | --------------- | ------------------------------- |
+ > | **_Any_** | **[crates.io]** | `cargo install zoxide --locked` |
+ > | DragonFly BSD | [DPorts] | `pkg install zoxide` |
+ > | FreeBSD | [FreshPorts] | `pkg install zoxide` |
+ > | NetBSD | [pkgsrc] | `pkgin install zoxide` |
+
+
+
+
+ Android
+
+ > To install zoxide, use a package manager:
+ >
+ > | Repository | Instructions |
+ > | ---------- | -------------------- |
+ > | [Termux] | `pkg install zoxide` |
+
+
+
+2. **Setup zoxide on your shell**
+
+ To start using zoxide, add it to your shell.
+
+
+ Bash
+
+ > Add this to the **end** of your config file (usually `~/.bashrc`):
+ >
+ > ```sh
+ > eval "$(zoxide init bash)"
+ > ```
+
+
+
+
+ Elvish
+
+ > Add this to the **end** of your config file (usually `~/.elvish/rc.elv`):
+ >
+ > ```sh
+ > eval (zoxide init elvish | slurp)
+ > ```
+ >
+ > **Note**
+ > zoxide only supports elvish v0.18.0 and above.
+
+
+
+
+ Fish
+
+ > Add this to the **end** of your config file (usually
+ > `~/.config/fish/config.fish`):
+ >
+ > ```fish
+ > zoxide init fish | source
+ > ```
+
+
+
+
+ Nushell
+
+ > Add this to the **end** of your env file (find it by running `$nu.env-path`
+ > in Nushell):
+ >
+ > ```sh
+ > zoxide init nushell | save -f ~/.zoxide.nu
+ > ```
+ >
+ > Now, add this to the **end** of your config file (find it by running
+ > `$nu.config-path` in Nushell):
+ >
+ > ```sh
+ > source ~/.zoxide.nu
+ > ```
+ >
+ > **Note**
+ > zoxide only supports Nushell v0.73.0 and above.
+
+
+
+
+ PowerShell
+
+ > Add this to the **end** of your config file (find it by running
+ > `echo $profile` in PowerShell):
+ >
+ > ```powershell
+ > Invoke-Expression (& { (zoxide init powershell | Out-String) })
+ > ```
+
+
+
+
+ Xonsh
+
+ > Add this to the **end** of your config file (usually `~/.xonshrc`):
+ >
+ > ```python
+ > execx($(zoxide init xonsh), 'exec', __xonsh__.ctx, filename='zoxide')
+ > ```
+
+
+
+
+ Zsh
+
+ > Add this to the **end** of your config file (usually `~/.zshrc`):
+ >
+ > ```sh
+ > eval "$(zoxide init zsh)"
+ > ```
+ >
+ > For completions to work, the above line must be added _after_ `compinit` is
+ > called. You may have to rebuild your completions cache by running
+ > `rm ~/.zcompdump*; compinit`.
+
+
+
+
+ Any POSIX shell
+
+ > Add this to the **end** of your config file:
+ >
+ > ```sh
+ > eval "$(zoxide init posix --hook prompt)"
+ > ```
+
+
+
+3. **Install fzf**
(optional)
+
+ [fzf] is a command-line fuzzy finder, used by zoxide for completions /
+ interactive selection. It can be installed from [here][fzf-installation].
+
+ > **Note**
+ > zoxide only supports fzf v0.33.0 and above.
+
+4. **Import your data**
(optional)
+
+ If you currently use any of these plugins, you may want to import your data
+ into zoxide:
+
+
+ autojump
+
+ > 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` |
+
+
+
+
+ fasd, z, z.lua, zsh-z
+
+ > 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` |
+
+
+
+
+ ZLocation
+
+ > 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
+ > ```
+
+
## Configuration
@@ -293,8 +367,8 @@ zoxide import --from z path/to/db
When calling `zoxide init`, the following flags are available:
- `--cmd`
- - Changes the prefix of predefined aliases (`z`, `zi`).
- - `--cmd j` would change the aliases to (`j`, `ji`).
+ - Changes the prefix of the `z` and `zi` commands.
+ - `--cmd j` would change the commands to (`j`, `ji`).
- `--cmd cd` would replace the `cd` command (doesn't work on Nushell / POSIX shells).
- `--hook
`
- Changes how often zoxide increments a directory's score:
@@ -303,15 +377,15 @@ When calling `zoxide init`, the following flags are available:
| `none` | Never |
| `prompt` | At every shell prompt |
| `pwd` | Whenever the directory is changed |
-- `--no-aliases`
- - Don't define aliases (`z`, `zi`).
+- `--no-cmd`
+ - Prevents zoxide from defining the `z` and `zi` commands.
- These functions will still be available in your shell as `__zoxide_z` and
`__zoxide_zi`, should you choose to redefine them.
### Environment variables
-Environment variables[?][wiki-env] can be used for configuration.
-They must be set before `zoxide init` is called.
+Environment variables[^2] can be used for configuration. They must be set before
+`zoxide init` is called.
- `_ZO_DATA_DIR`
- Specifies the directory in which the database is stored.
@@ -346,38 +420,55 @@ They must be set before `zoxide init` is called.
## Third-party integrations
-| Application | Description | Plugin |
-| ------------------ | -------------------------------------------- | -------------------------- |
-| [clink] | Improved cmd.exe for Windows | [clink-zoxide] |
-| [emacs] | Text editor | [zoxide.el] |
-| [nnn] | File manager | [nnn-autojump] |
-| [ranger] | File manager | [ranger-zoxide] |
-| [telescope.nvim] | Fuzzy finder for Neovim | [telescope-zoxide] |
-| [vim] | Text editor | [zoxide.vim] |
-| [xplr] | File manager | [zoxide.xplr] |
-| [xxh] | Transports shell configuration over SSH | [xxh-plugin-prerun-zoxide] |
-| [zabb] | Finds the shortest possible query for a path | Natively supported |
-| [zsh-autocomplete] | Realtime completions for zsh | Natively supported |
+| Application | Description | Plugin |
+| --------------------- | -------------------------------------------- | -------------------------- |
+| [aerc] | Email client | Natively supported |
+| [clink] | Improved cmd.exe for Windows | [clink-zoxide] |
+| [emacs] | Text editor | [zoxide.el] |
+| [felix] | File manager | Natively supported |
+| [joshuto] | File manager | Natively supported |
+| [lf] | File manager | See the [wiki][lf-wiki] |
+| [nnn] | File manager | [nnn-autojump] |
+| [ranger] | File manager | [ranger-zoxide] |
+| [telescope.nvim] | Fuzzy finder for Neovim | [telescope-zoxide] |
+| [t] | `tmux` session manager | Natively supported |
+| [tmux-session-wizard] | `tmux` session manager | Natively supported |
+| [vim] / [neovim] | Text editor | [zoxide.vim] |
+| [xplr] | File manager | [zoxide.xplr] |
+| [xxh] | Transports shell configuration over SSH | [xxh-plugin-prerun-zoxide] |
+| [zabb] | Finds the shortest possible query for a path | Natively supported |
+| [zsh-autocomplete] | Realtime completions for zsh | Natively supported |
+[^1]:
+ Debian / Ubuntu derivatives update their packages very slowly. If you're
+ using one of these distributions, consider using the install script instead.
+
+[^2]:
+ If you're not sure how to set an environment variable on your shell, check
+ out the [wiki][wiki-env].
+
+[aerc]: https://github.com/rjarry/aerc
[algorithm-aging]: https://github.com/ajeetdsouza/zoxide/wiki/Algorithm#aging
[algorithm-matching]: https://github.com/ajeetdsouza/zoxide/wiki/Algorithm#matching
[alpine linux packages]: https://pkgs.alpinelinux.org/packages?name=zoxide
-[arch linux community]: https://archlinux.org/packages/community/x86_64/zoxide/
-[builtwithnix-badge]: https://img.shields.io/badge/builtwith-nix-7d81f7?style=flat-square
+[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
[clink-zoxide]: https://github.com/shunsambongi/clink-zoxide
[clink]: https://github.com/mridgers/clink
[conda-forge]: https://anaconda.org/conda-forge/zoxide
[copr]: https://copr.fedorainfracloud.org/coprs/atim/zoxide/
-[crates.io-badge]: https://img.shields.io/crates/v/zoxide?style=flat-square
+[crates.io-badge]: https://img.shields.io/crates/v/zoxide?logo=rust&logoColor=white&style=flat-square
[crates.io]: https://crates.io/crates/zoxide
[debian packages]: https://packages.debian.org/stable/admin/zoxide
[devuan packages]: https://pkginfo.devuan.org/cgi-bin/package-query.html?c=package&q=zoxide
-[downloads-badge]: https://img.shields.io/github/downloads/ajeetdsouza/zoxide/total?style=flat-square
+[downloads-badge]: https://img.shields.io/github/downloads/ajeetdsouza/zoxide/total?logo=github&logoColor=white&style=flat-square
[dports]: https://github.com/DragonFlyBSD/DPorts/tree/master/sysutils/zoxide
[emacs]: https://www.gnu.org/software/emacs/
[fedora packages]: https://src.fedoraproject.org/rpms/rust-zoxide
+[felix]: https://github.com/kyoheiu/felix
[freshports]: https://www.freshports.org/sysutils/zoxide/
[fzf-installation]: https://github.com/junegunn/fzf#installation
[fzf-man]: https://manpages.ubuntu.com/manpages/en/man1/fzf.1.html
@@ -386,23 +477,31 @@ They must be set before `zoxide init` is called.
[guru overlay]: https://github.com/gentoo-mirror/guru
[homebrew]: https://formulae.brew.sh/formula/zoxide
[issues]: https://github.com/ajeetdsouza/zoxide/issues/new
+[joshuto]: https://github.com/kamiyaa/joshuto
+[lf]: https://github.com/gokcehan/lf
+[lf-wiki]: https://github.com/gokcehan/lf/wiki/Integrations#zoxide
[linuxbrew]: https://formulae.brew.sh/formula-linux/zoxide
[macports]: https://ports.macports.org/port/zoxide/summary
[neovim]: https://github.com/neovim/neovim
[nixpkgs]: https://github.com/NixOS/nixpkgs/blob/master/pkgs/tools/misc/zoxide/default.nix
[nnn-autojump]: https://github.com/jarun/nnn/blob/master/plugins/autojump
[nnn]: https://github.com/jarun/nnn
+[opensuse factory]: https://build.opensuse.org/package/show/openSUSE:Factory/zoxide
+[pacstall packages]: https://pacstall.dev/packages/zoxide-deb
[pkgsrc]: https://pkgsrc.se/sysutils/zoxide
[ranger-zoxide]: https://github.com/jchook/ranger-zoxide
[ranger]: https://github.com/ranger/ranger
[raspbian packages]: https://archive.raspbian.org/raspbian/pool/main/r/rust-zoxide/
[releases]: https://github.com/ajeetdsouza/zoxide/releases
[scoop]: https://github.com/ScoopInstaller/Main/tree/master/bucket/zoxide.json
+[slackbuilds-howto]: https://slackbuilds.org/howto/
+[t]: https://github.com/joshmedeski/t-smart-tmux-session-manager
[telescope-zoxide]: https://github.com/jvgrootveld/telescope-zoxide
[telescope.nvim]: https://github.com/nvim-telescope/telescope.nvim
[termux]: https://github.com/termux/termux-packages/tree/master/packages/zoxide
+[tmux-session-wizard]: https://github.com/27medkamal/tmux-session-wizard
[tutorial]: contrib/tutorial.webp
-[ubuntu packages]: https://packages.ubuntu.com/hirsute/zoxide
+[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
[wiki-env]: https://github.com/ajeetdsouza/zoxide/wiki/HOWTO:-set-environment-variables "HOWTO: set environment variables"
diff --git a/build.rs b/build.rs
index 2c1df03..5831b2d 100644
--- a/build.rs
+++ b/build.rs
@@ -1,56 +1,34 @@
-use std::process::Command;
+#[path = "src/cmd/cmd.rs"]
+mod cmd;
+
use std::{env, io};
-fn main() {
- let pkg_version = env!("CARGO_PKG_VERSION");
- let version = match env::var_os("PROFILE") {
- Some(profile) if profile == "release" => format!("v{}", pkg_version),
- _ => git_version().unwrap_or_else(|| format!("v{}-unknown", pkg_version)),
- };
- println!("cargo:rustc-env=ZOXIDE_VERSION={}", version);
+use clap::CommandFactory;
+use clap_complete::shells::{Bash, Elvish, Fish, PowerShell, Zsh};
+use clap_complete_fig::Fig;
+use cmd::Cmd;
- // Since we are generating completions in the package directory, we need to set this so that
- // Cargo doesn't rebuild every time.
+fn main() -> io::Result<()> {
+ // Since we are generating completions in the package directory, we need to
+ // set this so that Cargo doesn't rebuild every time.
println!("cargo:rerun-if-changed=build.rs");
- println!("cargo:rerun-if-changed=src");
- println!("cargo:rerun-if-changed=templates");
- println!("cargo:rerun-if-changed=tests");
-
- generate_completions().unwrap();
-}
-
-fn git_version() -> Option {
- let dir = env!("CARGO_MANIFEST_DIR");
- let mut git = Command::new("git");
- git.args(&["-C", dir, "describe", "--tags", "--match=v*.*.*", "--always", "--broken"]);
-
- let output = git.output().ok()?;
- if !output.status.success() || output.stdout.is_empty() || !output.stderr.is_empty() {
- return None;
- }
- String::from_utf8(output.stdout).ok()
+ println!("cargo:rerun-if-changed=src/");
+ println!("cargo:rerun-if-changed=templates/");
+ println!("cargo:rerun-if-changed=tests/");
+ generate_completions()
}
fn generate_completions() -> io::Result<()> {
- #[path = "src/cmd/_cmd.rs"]
- mod cmd;
-
- use clap::CommandFactory;
- use clap_complete::generate_to;
- use clap_complete::shells::{Bash, Elvish, Fish, PowerShell, Zsh};
- use clap_complete_fig::Fig;
- use cmd::Cmd;
-
+ const BIN_NAME: &str = env!("CARGO_PKG_NAME");
+ const OUT_DIR: &str = "contrib/completions";
let cmd = &mut Cmd::command();
- let bin_name = env!("CARGO_PKG_NAME");
- let out_dir = "contrib/completions";
- generate_to(Bash, cmd, bin_name, out_dir)?;
- generate_to(Elvish, cmd, bin_name, out_dir)?;
- generate_to(Fig, cmd, bin_name, out_dir)?;
- generate_to(Fish, cmd, bin_name, out_dir)?;
- generate_to(PowerShell, cmd, bin_name, out_dir)?;
- generate_to(Zsh, cmd, bin_name, out_dir)?;
+ clap_complete::generate_to(Bash, cmd, BIN_NAME, OUT_DIR)?;
+ clap_complete::generate_to(Elvish, cmd, BIN_NAME, OUT_DIR)?;
+ clap_complete::generate_to(Fig, cmd, BIN_NAME, OUT_DIR)?;
+ clap_complete::generate_to(Fish, cmd, BIN_NAME, OUT_DIR)?;
+ clap_complete::generate_to(PowerShell, cmd, BIN_NAME, OUT_DIR)?;
+ clap_complete::generate_to(Zsh, cmd, BIN_NAME, OUT_DIR)?;
Ok(())
}
diff --git a/contrib/completions/_zoxide b/contrib/completions/_zoxide
index 9fb15d6..fd898e6 100644
--- a/contrib/completions/_zoxide
+++ b/contrib/completions/_zoxide
@@ -15,10 +15,10 @@ _zoxide() {
local context curcontext="$curcontext" state line
_arguments "${_arguments_options[@]}" \
-'-h[Print help information]' \
-'--help[Print help information]' \
-'-V[Print version information]' \
-'--version[Print version information]' \
+'-h[Print help]' \
+'--help[Print help]' \
+'-V[Print version]' \
+'--version[Print version]' \
":: :_zoxide_commands" \
"*::: :->zoxide" \
&& ret=0
@@ -30,61 +30,115 @@ _zoxide() {
case $line[1] in
(add)
_arguments "${_arguments_options[@]}" \
-'-h[Print help information]' \
-'--help[Print help information]' \
-'-V[Print version information]' \
-'--version[Print version information]' \
+'-h[Print help]' \
+'--help[Print help]' \
+'-V[Print version]' \
+'--version[Print version]' \
'*::paths:_files -/' \
&& ret=0
;;
+(edit)
+_arguments "${_arguments_options[@]}" \
+'-h[Print help]' \
+'--help[Print help]' \
+'-V[Print version]' \
+'--version[Print version]' \
+":: :_zoxide__edit_commands" \
+"*::: :->edit" \
+&& ret=0
+
+ case $state in
+ (edit)
+ words=($line[1] "${words[@]}")
+ (( CURRENT += 1 ))
+ curcontext="${curcontext%:*:*}:zoxide-edit-command-$line[1]:"
+ case $line[1] in
+ (decrement)
+_arguments "${_arguments_options[@]}" \
+'-h[Print help]' \
+'--help[Print help]' \
+'-V[Print version]' \
+'--version[Print version]' \
+':path:' \
+&& ret=0
+;;
+(delete)
+_arguments "${_arguments_options[@]}" \
+'-h[Print help]' \
+'--help[Print help]' \
+'-V[Print version]' \
+'--version[Print version]' \
+':path:' \
+&& ret=0
+;;
+(increment)
+_arguments "${_arguments_options[@]}" \
+'-h[Print help]' \
+'--help[Print help]' \
+'-V[Print version]' \
+'--version[Print version]' \
+':path:' \
+&& ret=0
+;;
+(reload)
+_arguments "${_arguments_options[@]}" \
+'-h[Print help]' \
+'--help[Print help]' \
+'-V[Print version]' \
+'--version[Print version]' \
+&& ret=0
+;;
+ esac
+ ;;
+esac
+;;
(import)
_arguments "${_arguments_options[@]}" \
'--from=[Application to import from]:FROM:(autojump z)' \
'--merge[Merge into existing database]' \
-'-h[Print help information]' \
-'--help[Print help information]' \
-'-V[Print version information]' \
-'--version[Print version information]' \
+'-h[Print help]' \
+'--help[Print help]' \
+'-V[Print version]' \
+'--version[Print version]' \
':path:_files' \
&& ret=0
;;
(init)
_arguments "${_arguments_options[@]}" \
-'--cmd=[Renames the '\''z'\'' command and corresponding aliases]:CMD: ' \
-'--hook=[Chooses event upon which an entry is added to the database]:HOOK:(none prompt pwd)' \
-'--no-aliases[Prevents zoxide from defining any commands]' \
-'-h[Print help information]' \
-'--help[Print help information]' \
-'-V[Print version information]' \
-'--version[Print version information]' \
+'--cmd=[Changes the prefix of the \`z\` and \`zi\` commands]:CMD: ' \
+'--hook=[Changes how often zoxide increments a directory'\''s score]:HOOK:(none prompt pwd)' \
+'--no-cmd[Prevents zoxide from defining the \`z\` and \`zi\` commands]' \
+'-h[Print help]' \
+'--help[Print help]' \
+'-V[Print version]' \
+'--version[Print version]' \
':shell:(bash elvish fish nushell posix powershell xonsh zsh)' \
&& ret=0
;;
(query)
_arguments "${_arguments_options[@]}" \
-'--exclude=[Exclude a path from results]:path:_files -/' \
-'--all[Show deleted directories]' \
+'--exclude=[Exclude the current directory]:path:_files -/' \
+'-a[Show unavailable directories]' \
+'--all[Show unavailable directories]' \
'(-l --list)-i[Use interactive selection]' \
'(-l --list)--interactive[Use interactive selection]' \
'(-i --interactive)-l[List all matching directories]' \
'(-i --interactive)--list[List all matching directories]' \
-'(-i --interactive)-s[Print score with results]' \
-'(-i --interactive)--score[Print score with results]' \
-'-h[Print help information]' \
-'--help[Print help information]' \
-'-V[Print version information]' \
-'--version[Print version information]' \
+'-s[Print score with results]' \
+'--score[Print score with results]' \
+'-h[Print help]' \
+'--help[Print help]' \
+'-V[Print version]' \
+'--version[Print version]' \
'*::keywords:' \
&& ret=0
;;
(remove)
_arguments "${_arguments_options[@]}" \
-'-i[Use interactive selection]' \
-'--interactive[Use interactive selection]' \
-'-h[Print help information]' \
-'--help[Print help information]' \
-'-V[Print version information]' \
-'--version[Print version information]' \
+'-h[Print help]' \
+'--help[Print help]' \
+'-V[Print version]' \
+'--version[Print version]' \
'*::paths:_files -/' \
&& ret=0
;;
@@ -97,6 +151,7 @@ esac
_zoxide_commands() {
local commands; commands=(
'add:Add a new directory or increment its rank' \
+'edit:Edit the database' \
'import:Import entries from another application' \
'init:Generate shell configuration' \
'query:Search for a directory in the database' \
@@ -109,11 +164,36 @@ _zoxide__add_commands() {
local commands; commands=()
_describe -t commands 'zoxide add commands' commands "$@"
}
+(( $+functions[_zoxide__edit__decrement_commands] )) ||
+_zoxide__edit__decrement_commands() {
+ local commands; commands=()
+ _describe -t commands 'zoxide edit decrement commands' commands "$@"
+}
+(( $+functions[_zoxide__edit__delete_commands] )) ||
+_zoxide__edit__delete_commands() {
+ local commands; commands=()
+ _describe -t commands 'zoxide edit delete commands' commands "$@"
+}
+(( $+functions[_zoxide__edit_commands] )) ||
+_zoxide__edit_commands() {
+ local commands; commands=(
+'decrement:' \
+'delete:' \
+'increment:' \
+'reload:' \
+ )
+ _describe -t commands 'zoxide edit commands' commands "$@"
+}
(( $+functions[_zoxide__import_commands] )) ||
_zoxide__import_commands() {
local commands; commands=()
_describe -t commands 'zoxide import commands' commands "$@"
}
+(( $+functions[_zoxide__edit__increment_commands] )) ||
+_zoxide__edit__increment_commands() {
+ local commands; commands=()
+ _describe -t commands 'zoxide edit increment commands' commands "$@"
+}
(( $+functions[_zoxide__init_commands] )) ||
_zoxide__init_commands() {
local commands; commands=()
@@ -124,10 +204,19 @@ _zoxide__query_commands() {
local commands; commands=()
_describe -t commands 'zoxide query commands' commands "$@"
}
+(( $+functions[_zoxide__edit__reload_commands] )) ||
+_zoxide__edit__reload_commands() {
+ local commands; commands=()
+ _describe -t commands 'zoxide edit reload commands' commands "$@"
+}
(( $+functions[_zoxide__remove_commands] )) ||
_zoxide__remove_commands() {
local commands; commands=()
_describe -t commands 'zoxide remove commands' commands "$@"
}
-_zoxide "$@"
+if [ "$funcstack[1]" = "_zoxide" ]; then
+ _zoxide "$@"
+else
+ compdef _zoxide zoxide
+fi
diff --git a/contrib/completions/_zoxide.ps1 b/contrib/completions/_zoxide.ps1
index ebe83de..ff0e6b0 100644
--- a/contrib/completions/_zoxide.ps1
+++ b/contrib/completions/_zoxide.ps1
@@ -21,11 +21,12 @@ Register-ArgumentCompleter -Native -CommandName 'zoxide' -ScriptBlock {
$completions = @(switch ($command) {
'zoxide' {
- [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help information')
- [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help information')
- [CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Print version information')
- [CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Print version information')
+ [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('add', 'add', [CompletionResultType]::ParameterValue, 'Add a new directory or increment its rank')
+ [CompletionResult]::new('edit', 'edit', [CompletionResultType]::ParameterValue, 'Edit the database')
[CompletionResult]::new('import', 'import', [CompletionResultType]::ParameterValue, 'Import entries from another application')
[CompletionResult]::new('init', 'init', [CompletionResultType]::ParameterValue, 'Generate shell configuration')
[CompletionResult]::new('query', 'query', [CompletionResultType]::ParameterValue, 'Search for a directory in the database')
@@ -33,53 +34,91 @@ Register-ArgumentCompleter -Native -CommandName 'zoxide' -ScriptBlock {
break
}
'zoxide;add' {
- [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help information')
- [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help information')
- [CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Print version information')
- [CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Print version information')
+ [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;edit' {
+ [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('decrement', 'decrement', [CompletionResultType]::ParameterValue, 'decrement')
+ [CompletionResult]::new('delete', 'delete', [CompletionResultType]::ParameterValue, 'delete')
+ [CompletionResult]::new('increment', 'increment', [CompletionResultType]::ParameterValue, 'increment')
+ [CompletionResult]::new('reload', 'reload', [CompletionResultType]::ParameterValue, 'reload')
+ break
+ }
+ 'zoxide;edit;decrement' {
+ [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;edit;delete' {
+ [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;edit;increment' {
+ [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;edit;reload' {
+ [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' {
[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 information')
- [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help information')
- [CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Print version information')
- [CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Print version information')
+ [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;init' {
- [CompletionResult]::new('--cmd', 'cmd', [CompletionResultType]::ParameterName, 'Renames the ''z'' command and corresponding aliases')
- [CompletionResult]::new('--hook', 'hook', [CompletionResultType]::ParameterName, 'Chooses event upon which an entry is added to the database')
- [CompletionResult]::new('--no-aliases', 'no-aliases', [CompletionResultType]::ParameterName, 'Prevents zoxide from defining any commands')
- [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help information')
- [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help information')
- [CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Print version information')
- [CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Print version information')
+ [CompletionResult]::new('--cmd', 'cmd', [CompletionResultType]::ParameterName, 'Changes the prefix of the `z` and `zi` commands')
+ [CompletionResult]::new('--hook', 'hook', [CompletionResultType]::ParameterName, 'Changes how often zoxide increments a directory''s score')
+ [CompletionResult]::new('--no-cmd', 'no-cmd', [CompletionResultType]::ParameterName, 'Prevents zoxide from defining the `z` and `zi` commands')
+ [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;query' {
- [CompletionResult]::new('--exclude', 'exclude', [CompletionResultType]::ParameterName, 'Exclude a path from results')
- [CompletionResult]::new('--all', 'all', [CompletionResultType]::ParameterName, 'Show deleted directories')
+ [CompletionResult]::new('--exclude', 'exclude', [CompletionResultType]::ParameterName, 'Exclude the current directory')
+ [CompletionResult]::new('-a', 'a', [CompletionResultType]::ParameterName, 'Show unavailable directories')
+ [CompletionResult]::new('--all', 'all', [CompletionResultType]::ParameterName, 'Show unavailable directories')
[CompletionResult]::new('-i', 'i', [CompletionResultType]::ParameterName, 'Use interactive selection')
[CompletionResult]::new('--interactive', 'interactive', [CompletionResultType]::ParameterName, 'Use interactive selection')
[CompletionResult]::new('-l', 'l', [CompletionResultType]::ParameterName, 'List all matching directories')
[CompletionResult]::new('--list', 'list', [CompletionResultType]::ParameterName, 'List all matching directories')
[CompletionResult]::new('-s', 's', [CompletionResultType]::ParameterName, 'Print score with results')
[CompletionResult]::new('--score', 'score', [CompletionResultType]::ParameterName, 'Print score with results')
- [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help information')
- [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help information')
- [CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Print version information')
- [CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Print version information')
+ [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;remove' {
- [CompletionResult]::new('-i', 'i', [CompletionResultType]::ParameterName, 'Use interactive selection')
- [CompletionResult]::new('--interactive', 'interactive', [CompletionResultType]::ParameterName, 'Use interactive selection')
- [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help information')
- [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help information')
- [CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Print version information')
- [CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Print version information')
+ [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
}
})
diff --git a/contrib/completions/zoxide.bash b/contrib/completions/zoxide.bash
index e4ca62d..93c19d9 100644
--- a/contrib/completions/zoxide.bash
+++ b/contrib/completions/zoxide.bash
@@ -1,5 +1,5 @@
_zoxide() {
- local i cur prev opts cmds
+ local i cur prev opts cmd
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
@@ -8,24 +8,39 @@ _zoxide() {
for i in ${COMP_WORDS[@]}
do
- case "${i}" in
- "$1")
+ case "${cmd},${i}" in
+ ",$1")
cmd="zoxide"
;;
- add)
- cmd+="__add"
+ zoxide,add)
+ cmd="zoxide__add"
;;
- import)
- cmd+="__import"
+ zoxide,edit)
+ cmd="zoxide__edit"
;;
- init)
- cmd+="__init"
+ zoxide,import)
+ cmd="zoxide__import"
;;
- query)
- cmd+="__query"
+ zoxide,init)
+ cmd="zoxide__init"
;;
- remove)
- cmd+="__remove"
+ zoxide,query)
+ cmd="zoxide__query"
+ ;;
+ zoxide,remove)
+ cmd="zoxide__remove"
+ ;;
+ zoxide__edit,decrement)
+ cmd="zoxide__edit__decrement"
+ ;;
+ zoxide__edit,delete)
+ cmd="zoxide__edit__delete"
+ ;;
+ zoxide__edit,increment)
+ cmd="zoxide__edit__increment"
+ ;;
+ zoxide__edit,reload)
+ cmd="zoxide__edit__reload"
;;
*)
;;
@@ -34,7 +49,7 @@ _zoxide() {
case "${cmd}" in
zoxide)
- opts="-h -V --help --version add import init query remove"
+ opts="-h -V --help --version add edit import init query remove"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
@@ -61,6 +76,76 @@ _zoxide() {
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
+ zoxide__edit)
+ opts="-h -V --help --version decrement delete increment reload"
+ if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
+ COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
+ return 0
+ fi
+ case "${prev}" in
+ *)
+ COMPREPLY=()
+ ;;
+ esac
+ COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
+ return 0
+ ;;
+ zoxide__edit__decrement)
+ opts="-h -V --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__edit__delete)
+ opts="-h -V --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__edit__increment)
+ opts="-h -V --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__edit__reload)
+ opts="-h -V --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__import)
opts="-h -V --from --merge --help --version "
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
@@ -69,7 +154,7 @@ _zoxide() {
fi
case "${prev}" in
--from)
- COMPREPLY=($(compgen -W "" -- "${cur}"))
+ COMPREPLY=($(compgen -W "autojump z" -- "${cur}"))
return 0
;;
*)
@@ -80,7 +165,7 @@ _zoxide() {
return 0
;;
zoxide__init)
- opts="-h -V --no-aliases --cmd --hook --help --version bash elvish fish nushell posix powershell xonsh zsh"
+ opts="-h -V --no-cmd --cmd --hook --help --version bash elvish fish nushell posix powershell xonsh zsh"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
@@ -91,7 +176,7 @@ _zoxide() {
return 0
;;
--hook)
- COMPREPLY=($(compgen -W "" -- "${cur}"))
+ COMPREPLY=($(compgen -W "none prompt pwd" -- "${cur}"))
return 0
;;
*)
@@ -102,7 +187,7 @@ _zoxide() {
return 0
;;
zoxide__query)
- opts="-i -l -s -h -V --all --interactive --list --score --exclude --help --version ..."
+ opts="-a -i -l -s -h -V --all --interactive --list --score --exclude --help --version [KEYWORDS]..."
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
@@ -120,7 +205,7 @@ _zoxide() {
return 0
;;
zoxide__remove)
- opts="-i -h -V --interactive --help --version ..."
+ opts="-h -V --help --version [PATHS]..."
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
diff --git a/contrib/completions/zoxide.elv b/contrib/completions/zoxide.elv
index 607c208..6183d37 100644
--- a/contrib/completions/zoxide.elv
+++ b/contrib/completions/zoxide.elv
@@ -18,60 +18,94 @@ set edit:completion:arg-completer[zoxide] = {|@words|
}
var completions = [
&'zoxide'= {
- cand -h 'Print help information'
- cand --help 'Print help information'
- cand -V 'Print version information'
- cand --version 'Print version information'
+ cand -h 'Print help'
+ cand --help 'Print help'
+ cand -V 'Print version'
+ cand --version 'Print version'
cand add 'Add a new directory or increment its rank'
+ cand edit 'Edit the database'
cand import 'Import entries from another application'
cand init 'Generate shell configuration'
cand query 'Search for a directory in the database'
cand remove 'Remove a directory from the database'
}
&'zoxide;add'= {
- cand -h 'Print help information'
- cand --help 'Print help information'
- cand -V 'Print version information'
- cand --version 'Print version information'
+ cand -h 'Print help'
+ cand --help 'Print help'
+ cand -V 'Print version'
+ cand --version 'Print version'
+ }
+ &'zoxide;edit'= {
+ cand -h 'Print help'
+ cand --help 'Print help'
+ cand -V 'Print version'
+ cand --version 'Print version'
+ cand decrement 'decrement'
+ cand delete 'delete'
+ cand increment 'increment'
+ cand reload 'reload'
+ }
+ &'zoxide;edit;decrement'= {
+ cand -h 'Print help'
+ cand --help 'Print help'
+ cand -V 'Print version'
+ cand --version 'Print version'
+ }
+ &'zoxide;edit;delete'= {
+ cand -h 'Print help'
+ cand --help 'Print help'
+ cand -V 'Print version'
+ cand --version 'Print version'
+ }
+ &'zoxide;edit;increment'= {
+ cand -h 'Print help'
+ cand --help 'Print help'
+ cand -V 'Print version'
+ cand --version 'Print version'
+ }
+ &'zoxide;edit;reload'= {
+ cand -h 'Print help'
+ cand --help 'Print help'
+ cand -V 'Print version'
+ cand --version 'Print version'
}
&'zoxide;import'= {
cand --from 'Application to import from'
cand --merge 'Merge into existing database'
- cand -h 'Print help information'
- cand --help 'Print help information'
- cand -V 'Print version information'
- cand --version 'Print version information'
+ cand -h 'Print help'
+ cand --help 'Print help'
+ cand -V 'Print version'
+ cand --version 'Print version'
}
&'zoxide;init'= {
- cand --cmd 'Renames the ''z'' command and corresponding aliases'
- cand --hook 'Chooses event upon which an entry is added to the database'
- cand --no-aliases 'Prevents zoxide from defining any commands'
- cand -h 'Print help information'
- cand --help 'Print help information'
- cand -V 'Print version information'
- cand --version 'Print version information'
+ cand --cmd 'Changes the prefix of the `z` and `zi` commands'
+ cand --hook 'Changes how often zoxide increments a directory''s score'
+ cand --no-cmd 'Prevents zoxide from defining the `z` and `zi` commands'
+ cand -h 'Print help'
+ cand --help 'Print help'
+ cand -V 'Print version'
+ cand --version 'Print version'
}
&'zoxide;query'= {
- cand --exclude 'Exclude a path from results'
- cand --all 'Show deleted directories'
+ cand --exclude 'Exclude the current directory'
+ cand -a 'Show unavailable directories'
+ cand --all 'Show unavailable directories'
cand -i 'Use interactive selection'
cand --interactive 'Use interactive selection'
cand -l 'List all matching directories'
cand --list 'List all matching directories'
cand -s 'Print score with results'
cand --score 'Print score with results'
- cand -h 'Print help information'
- cand --help 'Print help information'
- cand -V 'Print version information'
- cand --version 'Print version information'
+ cand -h 'Print help'
+ cand --help 'Print help'
+ cand -V 'Print version'
+ cand --version 'Print version'
}
&'zoxide;remove'= {
- cand -i 'Use interactive selection'
- cand --interactive 'Use interactive selection'
- cand -h 'Print help information'
- cand --help 'Print help information'
- cand -V 'Print version information'
- cand --version 'Print version information'
+ cand -h 'Print help'
+ cand --help 'Print help'
+ cand -V 'Print version'
+ cand --version 'Print version'
}
]
$completions[$command]
diff --git a/contrib/completions/zoxide.fish b/contrib/completions/zoxide.fish
index 9945b3a..276cbaf 100644
--- a/contrib/completions/zoxide.fish
+++ b/contrib/completions/zoxide.fish
@@ -1,28 +1,42 @@
-complete -c zoxide -n "__fish_use_subcommand" -s h -l help -d 'Print help information'
-complete -c zoxide -n "__fish_use_subcommand" -s V -l version -d 'Print version information'
+complete -c zoxide -n "__fish_use_subcommand" -s h -l help -d 'Print help'
+complete -c zoxide -n "__fish_use_subcommand" -s V -l version -d 'Print version'
complete -c zoxide -n "__fish_use_subcommand" -f -a "add" -d 'Add a new directory or increment its rank'
+complete -c zoxide -n "__fish_use_subcommand" -f -a "edit" -d 'Edit the database'
complete -c zoxide -n "__fish_use_subcommand" -f -a "import" -d 'Import entries from another application'
complete -c zoxide -n "__fish_use_subcommand" -f -a "init" -d 'Generate shell configuration'
complete -c zoxide -n "__fish_use_subcommand" -f -a "query" -d 'Search for a directory in the database'
complete -c zoxide -n "__fish_use_subcommand" -f -a "remove" -d 'Remove a directory from the database'
-complete -c zoxide -n "__fish_seen_subcommand_from add" -s h -l help -d 'Print help information'
-complete -c zoxide -n "__fish_seen_subcommand_from add" -s V -l version -d 'Print version information'
+complete -c zoxide -n "__fish_seen_subcommand_from add" -s h -l help -d 'Print help'
+complete -c zoxide -n "__fish_seen_subcommand_from add" -s V -l version -d 'Print version'
+complete -c zoxide -n "__fish_seen_subcommand_from edit; and not __fish_seen_subcommand_from decrement; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from increment; and not __fish_seen_subcommand_from reload" -s h -l help -d 'Print help'
+complete -c zoxide -n "__fish_seen_subcommand_from edit; and not __fish_seen_subcommand_from decrement; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from increment; and not __fish_seen_subcommand_from reload" -s V -l version -d 'Print version'
+complete -c zoxide -n "__fish_seen_subcommand_from edit; and not __fish_seen_subcommand_from decrement; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from increment; and not __fish_seen_subcommand_from reload" -f -a "decrement"
+complete -c zoxide -n "__fish_seen_subcommand_from edit; and not __fish_seen_subcommand_from decrement; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from increment; and not __fish_seen_subcommand_from reload" -f -a "delete"
+complete -c zoxide -n "__fish_seen_subcommand_from edit; and not __fish_seen_subcommand_from decrement; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from increment; and not __fish_seen_subcommand_from reload" -f -a "increment"
+complete -c zoxide -n "__fish_seen_subcommand_from edit; and not __fish_seen_subcommand_from decrement; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from increment; and not __fish_seen_subcommand_from reload" -f -a "reload"
+complete -c zoxide -n "__fish_seen_subcommand_from edit; and __fish_seen_subcommand_from decrement" -s h -l help -d 'Print help'
+complete -c zoxide -n "__fish_seen_subcommand_from edit; and __fish_seen_subcommand_from decrement" -s V -l version -d 'Print version'
+complete -c zoxide -n "__fish_seen_subcommand_from edit; and __fish_seen_subcommand_from delete" -s h -l help -d 'Print help'
+complete -c zoxide -n "__fish_seen_subcommand_from edit; and __fish_seen_subcommand_from delete" -s V -l version -d 'Print version'
+complete -c zoxide -n "__fish_seen_subcommand_from edit; and __fish_seen_subcommand_from increment" -s h -l help -d 'Print help'
+complete -c zoxide -n "__fish_seen_subcommand_from edit; and __fish_seen_subcommand_from increment" -s V -l version -d 'Print version'
+complete -c zoxide -n "__fish_seen_subcommand_from edit; and __fish_seen_subcommand_from reload" -s h -l help -d 'Print help'
+complete -c zoxide -n "__fish_seen_subcommand_from edit; and __fish_seen_subcommand_from reload" -s V -l version -d 'Print version'
complete -c zoxide -n "__fish_seen_subcommand_from import" -l from -d 'Application to import from' -r -f -a "{autojump ,z }"
complete -c zoxide -n "__fish_seen_subcommand_from import" -l merge -d 'Merge into existing database'
-complete -c zoxide -n "__fish_seen_subcommand_from import" -s h -l help -d 'Print help information'
-complete -c zoxide -n "__fish_seen_subcommand_from import" -s V -l version -d 'Print version information'
-complete -c zoxide -n "__fish_seen_subcommand_from init" -l cmd -d 'Renames the \'z\' command and corresponding aliases' -r
-complete -c zoxide -n "__fish_seen_subcommand_from init" -l hook -d 'Chooses event upon which an entry is added to the database' -r -f -a "{none ,prompt ,pwd }"
-complete -c zoxide -n "__fish_seen_subcommand_from init" -l no-aliases -d 'Prevents zoxide from defining any commands'
-complete -c zoxide -n "__fish_seen_subcommand_from init" -s h -l help -d 'Print help information'
-complete -c zoxide -n "__fish_seen_subcommand_from init" -s V -l version -d 'Print version information'
-complete -c zoxide -n "__fish_seen_subcommand_from query" -l exclude -d 'Exclude a path from results' -r -f -a "(__fish_complete_directories)"
-complete -c zoxide -n "__fish_seen_subcommand_from query" -l all -d 'Show deleted directories'
+complete -c zoxide -n "__fish_seen_subcommand_from import" -s h -l help -d 'Print help'
+complete -c zoxide -n "__fish_seen_subcommand_from import" -s V -l version -d 'Print version'
+complete -c zoxide -n "__fish_seen_subcommand_from init" -l cmd -d 'Changes the prefix of the `z` and `zi` commands' -r
+complete -c zoxide -n "__fish_seen_subcommand_from init" -l hook -d 'Changes how often zoxide increments a directory\'s score' -r -f -a "{none ,prompt ,pwd }"
+complete -c zoxide -n "__fish_seen_subcommand_from init" -l no-cmd -d 'Prevents zoxide from defining the `z` and `zi` commands'
+complete -c zoxide -n "__fish_seen_subcommand_from init" -s h -l help -d 'Print help'
+complete -c zoxide -n "__fish_seen_subcommand_from init" -s V -l version -d 'Print version'
+complete -c zoxide -n "__fish_seen_subcommand_from query" -l exclude -d 'Exclude the current directory' -r -f -a "(__fish_complete_directories)"
+complete -c zoxide -n "__fish_seen_subcommand_from query" -s a -l all -d 'Show unavailable directories'
complete -c zoxide -n "__fish_seen_subcommand_from query" -s i -l interactive -d 'Use interactive selection'
complete -c zoxide -n "__fish_seen_subcommand_from query" -s l -l list -d 'List all matching directories'
complete -c zoxide -n "__fish_seen_subcommand_from query" -s s -l score -d 'Print score with results'
-complete -c zoxide -n "__fish_seen_subcommand_from query" -s h -l help -d 'Print help information'
-complete -c zoxide -n "__fish_seen_subcommand_from query" -s V -l version -d 'Print version information'
-complete -c zoxide -n "__fish_seen_subcommand_from remove" -s i -l interactive -d 'Use interactive selection'
-complete -c zoxide -n "__fish_seen_subcommand_from remove" -s h -l help -d 'Print help information'
-complete -c zoxide -n "__fish_seen_subcommand_from remove" -s V -l version -d 'Print version information'
+complete -c zoxide -n "__fish_seen_subcommand_from query" -s h -l help -d 'Print help'
+complete -c zoxide -n "__fish_seen_subcommand_from query" -s V -l version -d 'Print version'
+complete -c zoxide -n "__fish_seen_subcommand_from remove" -s h -l help -d 'Print help'
+complete -c zoxide -n "__fish_seen_subcommand_from remove" -s V -l version -d 'Print version'
diff --git a/contrib/completions/zoxide.ts b/contrib/completions/zoxide.ts
index c3c6fb7..0200591 100644
--- a/contrib/completions/zoxide.ts
+++ b/contrib/completions/zoxide.ts
@@ -8,11 +8,11 @@ const completion: Fig.Spec = {
options: [
{
name: ["-h", "--help"],
- description: "Print help information",
+ description: "Print help",
},
{
name: ["-V", "--version"],
- description: "Print version information",
+ description: "Print version",
},
],
args: {
@@ -21,6 +21,87 @@ const completion: Fig.Spec = {
template: "folders",
},
},
+ {
+ name: "edit",
+ description: "Edit the database",
+ subcommands: [
+ {
+ name: "decrement",
+ hidden: true,
+ options: [
+ {
+ name: ["-h", "--help"],
+ description: "Print help",
+ },
+ {
+ name: ["-V", "--version"],
+ description: "Print version",
+ },
+ ],
+ args: {
+ name: "path",
+ },
+ },
+ {
+ name: "delete",
+ hidden: true,
+ options: [
+ {
+ name: ["-h", "--help"],
+ description: "Print help",
+ },
+ {
+ name: ["-V", "--version"],
+ description: "Print version",
+ },
+ ],
+ args: {
+ name: "path",
+ },
+ },
+ {
+ name: "increment",
+ hidden: true,
+ options: [
+ {
+ name: ["-h", "--help"],
+ description: "Print help",
+ },
+ {
+ name: ["-V", "--version"],
+ description: "Print version",
+ },
+ ],
+ args: {
+ name: "path",
+ },
+ },
+ {
+ name: "reload",
+ hidden: true,
+ options: [
+ {
+ name: ["-h", "--help"],
+ description: "Print help",
+ },
+ {
+ name: ["-V", "--version"],
+ description: "Print version",
+ },
+ ],
+ },
+ ],
+ options: [
+ {
+ name: ["-h", "--help"],
+ description: "Print help",
+ },
+ {
+ name: ["-V", "--version"],
+ description: "Print version",
+ },
+ ],
+ },
{
name: "import",
description: "Import entries from another application",
@@ -28,16 +109,13 @@ const completion: Fig.Spec = {
{
name: "--from",
description: "Application to import from",
+ isRepeatable: true,
args: {
name: "from",
suggestions: [
- {
- name: "autojump",
- },
- {
- name: "z",
- },
- ]
+ "autojump",
+ "z",
+ ],
},
},
{
@@ -46,11 +124,11 @@ const completion: Fig.Spec = {
},
{
name: ["-h", "--help"],
- description: "Print help information",
+ description: "Print help",
},
{
name: ["-V", "--version"],
- description: "Print version information",
+ description: "Print version",
},
],
args: {
@@ -64,7 +142,8 @@ const completion: Fig.Spec = {
options: [
{
name: "--cmd",
- description: "Renames the 'z' command and corresponding aliases",
+ description: "Changes the prefix of the `z` and `zi` commands",
+ isRepeatable: true,
args: {
name: "cmd",
isOptional: true,
@@ -72,64 +151,43 @@ const completion: Fig.Spec = {
},
{
name: "--hook",
- description: "Chooses event upon which an entry is added to the database",
+ description: "Changes how often zoxide increments a directory's score",
+ isRepeatable: true,
args: {
name: "hook",
isOptional: true,
suggestions: [
- {
- name: "none",
- },
- {
- name: "prompt",
- },
- {
- name: "pwd",
- },
- ]
+ "none",
+ "prompt",
+ "pwd",
+ ],
},
},
{
- name: "--no-aliases",
- description: "Prevents zoxide from defining any commands",
+ name: "--no-cmd",
+ description: "Prevents zoxide from defining the `z` and `zi` commands",
},
{
name: ["-h", "--help"],
- description: "Print help information",
+ description: "Print help",
},
{
name: ["-V", "--version"],
- description: "Print version information",
+ description: "Print version",
},
],
args: {
name: "shell",
suggestions: [
- {
- name: "bash",
- },
- {
- name: "elvish",
- },
- {
- name: "fish",
- },
- {
- name: "nushell",
- },
- {
- name: "posix",
- },
- {
- name: "powershell",
- },
- {
- name: "xonsh",
- },
- {
- name: "zsh",
- },
- ]
+ "bash",
+ "elvish",
+ "fish",
+ "nushell",
+ "posix",
+ "powershell",
+ "xonsh",
+ "zsh",
+ ],
},
},
{
@@ -138,7 +196,8 @@ const completion: Fig.Spec = {
options: [
{
name: "--exclude",
- description: "Exclude a path from results",
+ description: "Exclude the current directory",
+ isRepeatable: true,
args: {
name: "exclude",
isOptional: true,
@@ -146,16 +205,24 @@ const completion: Fig.Spec = {
},
},
{
- name: "--all",
- description: "Show deleted directories",
+ name: ["-a", "--all"],
+ description: "Show unavailable directories",
},
{
name: ["-i", "--interactive"],
description: "Use interactive selection",
+ exclusiveOn: [
+ "-l",
+ "--list",
+ ],
},
{
name: ["-l", "--list"],
description: "List all matching directories",
+ exclusiveOn: [
+ "-i",
+ "--interactive",
+ ],
},
{
name: ["-s", "--score"],
@@ -163,15 +230,16 @@ const completion: Fig.Spec = {
},
{
name: ["-h", "--help"],
- description: "Print help information",
+ description: "Print help",
},
{
name: ["-V", "--version"],
- description: "Print version information",
+ description: "Print version",
},
],
args: {
name: "keywords",
+ isVariadic: true,
isOptional: true,
},
},
@@ -179,21 +247,18 @@ const completion: Fig.Spec = {
name: "remove",
description: "Remove a directory from the database",
options: [
- {
- name: ["-i", "--interactive"],
- description: "Use interactive selection",
- },
{
name: ["-h", "--help"],
- description: "Print help information",
+ description: "Print help",
},
{
name: ["-V", "--version"],
- description: "Print version information",
+ description: "Print version",
},
],
args: {
name: "paths",
+ isVariadic: true,
isOptional: true,
template: "folders",
},
@@ -202,11 +267,11 @@ const completion: Fig.Spec = {
options: [
{
name: ["-h", "--help"],
- description: "Print help information",
+ description: "Print help",
},
{
name: ["-V", "--version"],
- description: "Print version information",
+ description: "Print version",
},
],
};
diff --git a/contrib/warp.png b/contrib/warp.png
new file mode 100644
index 0000000..28b35f1
Binary files /dev/null and b/contrib/warp.png differ
diff --git a/install.sh b/install.sh
new file mode 100755
index 0000000..796c4d9
--- /dev/null
+++ b/install.sh
@@ -0,0 +1,391 @@
+#!/bin/sh
+# shellcheck shell=dash
+
+# The official zoxide installer.
+#
+# It runs on Unix shells like {a,ba,da,k,z}sh. It uses the common `local`
+# extension. Note: Most shells limit `local` to 1 var per line, contra bash.
+
+main() {
+ if [ "${KSH_VERSION-}" = 'Version JM 93t+ 2010-03-05' ]; then
+ # The version of ksh93 that ships with many illumos systems does not
+ # support the "local" extension. Print a message rather than fail in
+ # subtle ways later on:
+ err 'the installer does not work with this ksh93 version; please try bash'
+ fi
+
+ set -u
+
+ # Detect and print host target triple.
+ ensure get_architecture
+ local _arch="${RETVAL}"
+ assert_nz "${_arch}" "arch"
+ echo "Detected architecture: ${_arch}"
+
+ # Create and enter a temporary directory.
+ local _tmp_dir
+ _tmp_dir="$(mktemp -d)" || err "mktemp: could not create temporary directory"
+ cd "${_tmp_dir}" || err "cd: failed to enter directory: ${_tmp_dir}"
+
+ # Download and extract zoxide.
+ ensure download_zoxide "${_arch}"
+ local _package="${RETVAL}"
+ assert_nz "${_package}" "package"
+ echo "Downloaded package: ${_package}"
+ case "${_package}" in
+ *.tar.gz)
+ need_cmd tar
+ ensure tar -xf "${_package}"
+ ;;
+ *.zip)
+ need_cmd unzip
+ ensure unzip -oq "${_package}"
+ ;;
+ *)
+ err "unsupported package format: ${_package}"
+ ;;
+ esac
+
+ # Install binary.
+ local _bin_dir="${HOME}/.local/bin"
+ local _bin_name
+ case "${_arch}" in
+ *windows*) _bin_name="zoxide.exe" ;;
+ *) _bin_name="zoxide" ;;
+ esac
+ ensure mkdir -p "${_bin_dir}"
+ ensure cp "${_bin_name}" "${_bin_dir}"
+ ensure chmod +x "${_bin_dir}/${_bin_name}"
+ echo "Installed zoxide to ${_bin_dir}"
+
+ # Install manpages.
+ local _man_dir="${HOME}/.local/share/man"
+ ensure mkdir -p "${_man_dir}/man1"
+ ensure cp "man/man1/"* "${_man_dir}/man1/"
+ echo "Installed manpages to ${_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."
+ fi
+}
+
+download_zoxide() {
+ local _arch="$1"
+
+ if check_cmd curl; then
+ _dld=curl
+ elif check_cmd wget; then
+ _dld=wget
+ else
+ need_cmd 'curl or wget'
+ fi
+ need_cmd grep
+
+ local _releases_url="https://api.github.com/repos/ajeetdsouza/zoxide/releases/latest"
+ local _releases
+ case "${_dld}" in
+ curl) _releases="$(curl -sL "${_releases_url}")" ||
+ err "curl: failed to download ${_releases_url}" ;;
+ wget) _releases="$(wget -qO- "${_releases_url}")" ||
+ err "wget: failed to download ${_releases_url}" ;;
+ *) err "unsupported downloader: ${_dld}" ;;
+ esac
+ (echo "${_releases}" | grep -q 'API rate limit exceeded') &&
+ err "you have exceeded GitHub's API rate limit. Please try again later, or use a different installation method: https://github.com/ajeetdsouza/zoxide/#installation"
+
+ local _package_url
+ _package_url="$(echo "${_releases}" | grep "browser_download_url" | cut -d '"' -f 4 | grep "${_arch}")" ||
+ err "zoxide has not yet been packaged for your architecture (${_arch}), please file an issue: https://github.com/ajeetdsouza/zoxide/issues"
+
+ local _ext
+ case "${_package_url}" in
+ *.tar.gz) _ext="tar.gz" ;;
+ *.zip) _ext="zip" ;;
+ *) err "unsupported package format: ${_package_url}" ;;
+ esac
+
+ local _package="zoxide.${_ext}"
+ case "${_dld}" in
+ curl) _releases="$(curl -sLo "${_package}" "${_package_url}")" || err "curl: failed to download ${_package_url}" ;;
+ wget) _releases="$(wget -qO "${_package}" "${_package_url}")" || err "wget: failed to download ${_package_url}" ;;
+ *) err "unsupported downloader: ${_dld}" ;;
+ esac
+
+ RETVAL="${_package}"
+}
+
+# The below functions have been extracted with minor modifications from the
+# Rustup install script:
+#
+# https://github.com/rust-lang/rustup/blob/4c1289b2c3f3702783900934a38d7c5f912af787/rustup-init.sh
+
+get_architecture() {
+ local _ostype _cputype _bitness _arch _clibtype
+ _ostype="$(uname -s)"
+ _cputype="$(uname -m)"
+ _clibtype="musl"
+
+ if [ "${_ostype}" = Linux ]; then
+ if [ "$(uname -o || true)" = Android ]; then
+ _ostype=Android
+ fi
+ fi
+
+ if [ "${_ostype}" = Darwin ] && [ "${_cputype}" = i386 ]; then
+ # Darwin `uname -m` lies
+ if sysctl hw.optional.x86_64 | grep -q ': 1'; then
+ _cputype=x86_64
+ fi
+ fi
+
+ if [ "${_ostype}" = SunOS ]; then
+ # Both Solaris and illumos presently announce as "SunOS" in "uname -s"
+ # so use "uname -o" to disambiguate. We use the full path to the
+ # system uname in case the user has coreutils uname first in PATH,
+ # which has historically sometimes printed the wrong value here.
+ if [ "$(/usr/bin/uname -o || true)" = illumos ]; then
+ _ostype=illumos
+ fi
+
+ # illumos systems have multi-arch userlands, and "uname -m" reports the
+ # machine hardware name; e.g., "i86pc" on both 32- and 64-bit x86
+ # systems. Check for the native (widest) instruction set on the
+ # running kernel:
+ if [ "${_cputype}" = i86pc ]; then
+ _cputype="$(isainfo -n)"
+ fi
+ fi
+
+ case "${_ostype}" in
+ Android)
+ _ostype=linux-android
+ ;;
+ Linux)
+ check_proc
+ _ostype=unknown-linux-${_clibtype}
+ _bitness=$(get_bitness)
+ ;;
+ FreeBSD)
+ _ostype=unknown-freebsd
+ ;;
+ NetBSD)
+ _ostype=unknown-netbsd
+ ;;
+ DragonFly)
+ _ostype=unknown-dragonfly
+ ;;
+ Darwin)
+ _ostype=apple-darwin
+ ;;
+ illumos)
+ _ostype=unknown-illumos
+ ;;
+ MINGW* | MSYS* | CYGWIN* | Windows_NT)
+ _ostype=pc-windows-msvc
+ ;;
+ *)
+ err "unrecognized OS type: ${_ostype}"
+ ;;
+ esac
+
+ case "${_cputype}" in
+ i386 | i486 | i686 | i786 | x86)
+ _cputype=i686
+ ;;
+ xscale | arm)
+ _cputype=arm
+ if [ "${_ostype}" = "linux-android" ]; then
+ _ostype=linux-androideabi
+ fi
+ ;;
+ armv6l)
+ _cputype=arm
+ if [ "${_ostype}" = "linux-android" ]; then
+ _ostype=linux-androideabi
+ else
+ _ostype="${_ostype}eabihf"
+ fi
+ ;;
+ armv7l | armv8l)
+ _cputype=armv7
+ if [ "${_ostype}" = "linux-android" ]; then
+ _ostype=linux-androideabi
+ else
+ _ostype="${_ostype}eabihf"
+ fi
+ ;;
+ aarch64 | arm64)
+ _cputype=aarch64
+ ;;
+ x86_64 | x86-64 | x64 | amd64)
+ _cputype=x86_64
+ ;;
+ mips)
+ _cputype=$(get_endianness mips '' el)
+ ;;
+ mips64)
+ if [ "${_bitness}" -eq 64 ]; then
+ # only n64 ABI is supported for now
+ _ostype="${_ostype}abi64"
+ _cputype=$(get_endianness mips64 '' el)
+ fi
+ ;;
+ ppc)
+ _cputype=powerpc
+ ;;
+ ppc64)
+ _cputype=powerpc64
+ ;;
+ ppc64le)
+ _cputype=powerpc64le
+ ;;
+ s390x)
+ _cputype=s390x
+ ;;
+ riscv64)
+ _cputype=riscv64gc
+ ;;
+ *)
+ err "unknown CPU type: ${_cputype}"
+ ;;
+ esac
+
+ # Detect 64-bit linux with 32-bit userland
+ if [ "${_ostype}" = unknown-linux-musl ] && [ "${_bitness}" -eq 32 ]; then
+ case ${_cputype} in
+ x86_64)
+ # 32-bit executable for amd64 = x32
+ if is_host_amd64_elf; then {
+ echo "x32 userland is unsupported" 1>&2
+ exit 1
+ }; else
+ _cputype=i686
+ fi
+ ;;
+ mips64)
+ _cputype=$(get_endianness mips '' el)
+ ;;
+ powerpc64)
+ _cputype=powerpc
+ ;;
+ aarch64)
+ _cputype=armv7
+ if [ "${_ostype}" = "linux-android" ]; then
+ _ostype=linux-androideabi
+ else
+ _ostype="${_ostype}eabihf"
+ fi
+ ;;
+ riscv64gc)
+ err "riscv64 with 32-bit userland unsupported"
+ ;;
+ *) ;;
+ esac
+ fi
+
+ # Detect armv7 but without the CPU features Rust needs in that build,
+ # and fall back to arm.
+ # See https://github.com/rust-lang/rustup.rs/issues/587.
+ if [ "${_ostype}" = "unknown-linux-musleabihf" ] && [ "${_cputype}" = armv7 ]; then
+ if ensure grep '^Features' /proc/cpuinfo | grep -q -v neon; then
+ # At least one processor does not have NEON.
+ _cputype=arm
+ fi
+ fi
+
+ _arch="${_cputype}-${_ostype}"
+ RETVAL="${_arch}"
+}
+
+get_bitness() {
+ need_cmd head
+ # Architecture detection without dependencies beyond coreutils.
+ # ELF files start out "\x7fELF", and the following byte is
+ # 0x01 for 32-bit and
+ # 0x02 for 64-bit.
+ # The printf builtin on some shells like dash only supports octal
+ # escape sequences, so we use those.
+ local _current_exe_head
+ _current_exe_head=$(head -c 5 /proc/self/exe)
+ if [ "${_current_exe_head}" = "$(printf '\177ELF\001')" ]; then
+ echo 32
+ elif [ "${_current_exe_head}" = "$(printf '\177ELF\002')" ]; then
+ echo 64
+ else
+ err "unknown platform bitness"
+ fi
+}
+
+get_endianness() {
+ local cputype=$1
+ local suffix_eb=$2
+ local suffix_el=$3
+
+ # detect endianness without od/hexdump, like get_bitness() does.
+ need_cmd head
+ need_cmd tail
+
+ local _current_exe_endianness
+ _current_exe_endianness="$(head -c 6 /proc/self/exe | tail -c 1)"
+ if [ "${_current_exe_endianness}" = "$(printf '\001')" ]; then
+ echo "${cputype}${suffix_el}"
+ elif [ "${_current_exe_endianness}" = "$(printf '\002')" ]; then
+ echo "${cputype}${suffix_eb}"
+ else
+ err "unknown platform endianness"
+ fi
+}
+
+is_host_amd64_elf() {
+ need_cmd head
+ need_cmd tail
+ # ELF e_machine detection without dependencies beyond coreutils.
+ # Two-byte field at offset 0x12 indicates the CPU,
+ # but we're interested in it being 0x3E to indicate amd64, or not that.
+ local _current_exe_machine
+ _current_exe_machine=$(head -c 19 /proc/self/exe | tail -c 1)
+ [ "${_current_exe_machine}" = "$(printf '\076')" ]
+}
+
+check_proc() {
+ # Check for /proc by looking for the /proc/self/exe link.
+ # This is only run on Linux.
+ if ! test -L /proc/self/exe; then
+ err "unable to find /proc/self/exe. Is /proc mounted? Installation cannot proceed without /proc."
+ fi
+}
+
+need_cmd() {
+ if ! check_cmd "$1"; then
+ err "need '$1' (command not found)"
+ fi
+}
+
+check_cmd() {
+ command -v "$1" >/dev/null 2>&1
+}
+
+# Run a command that should never fail. If the command fails execution
+# will immediately terminate with an error showing the failing
+# command.
+ensure() {
+ if ! "$@"; then err "command failed: $*"; fi
+}
+
+assert_nz() {
+ if [ -z "$1" ]; then err "found empty string: $2"; fi
+}
+
+err() {
+ echo "Error: $1" >&2
+ exit 1
+}
+
+# This is put in braces to ensure that the script does not run until it is
+# downloaded completely.
+{
+ main "$@" || exit 1
+}
diff --git a/justfile b/justfile
new file mode 100644
index 0000000..70f01eb
--- /dev/null
+++ b/justfile
@@ -0,0 +1,39 @@
+default:
+ @just --list
+
+[unix]
+fmt:
+ nix-shell --cores 0 --pure --run 'cargo-fmt --all'
+ nix-shell --cores 0 --pure --run 'nixfmt -- *.nix'
+ nix-shell --cores 0 --pure --run 'shfmt --indent=4 --language-dialect=posix --simplify --write *.sh'
+ nix-shell --cores 0 --pure --run 'yamlfmt -- .github/workflows/*.yml'
+
+[windows]
+fmt:
+ cargo +nightly fmt --all
+
+[unix]
+lint:
+ nix-shell --cores 0 --pure --run 'cargo-fmt --all --check'
+ nix-shell --cores 0 --pure --run 'cargo clippy --all-features --all-targets -- -Dwarnings'
+ nix-shell --cores 0 --pure --run 'cargo msrv verify'
+ nix-shell --cores 0 --pure --run 'cargo udeps --all-features --all-targets --workspace'
+ nix-shell --cores 0 --pure --run 'mandoc -man -Wall -Tlint -- man/man1/*.1'
+ nix-shell --cores 0 --pure --run 'markdownlint *.md'
+ nix-shell --cores 0 --pure --run 'nixfmt --check -- *.nix'
+ nix-shell --cores 0 --pure --run 'shellcheck --enable all *.sh'
+ nix-shell --cores 0 --pure --run 'shfmt --diff --indent=4 --language-dialect=posix --simplify *.sh'
+ nix-shell --cores 0 --pure --run 'yamlfmt -lint -- .github/workflows/*.yml'
+
+[windows]
+lint:
+ cargo +nightly fmt --all --check
+ cargo +stable clippy --all-features --all-targets -- -Dwarnings
+
+[unix]
+test *args:
+ nix-shell --cores 0 --pure --run 'cargo nextest run --all-features --no-fail-fast --workspace {{args}}'
+
+[windows]
+test *args:
+ cargo +stable test --no-fail-fast --workspace {{args}}
diff --git a/man/man1/zoxide-add.1 b/man/man1/zoxide-add.1
index 6e136bc..1ee9af3 100644
--- a/man/man1/zoxide-add.1
+++ b/man/man1/zoxide-add.1
@@ -19,6 +19,6 @@ Print help information.
.SH REPORTING BUGS
For any issues, feature requests, or questions, please visit:
.sp
-\fBhttps://github.com/ajeetdsouza/zoxide/issues\fR.
+\fBhttps://github.com/ajeetdsouza/zoxide/issues\fR
.SH AUTHOR
-Ajeet D'Souza <\fB98ajeet@gmail.com\fR>
+Ajeet D'Souza \fB<98ajeet@gmail.com>\fR
diff --git a/man/man1/zoxide-import.1 b/man/man1/zoxide-import.1
index 3d3b262..d008692 100644
--- a/man/man1/zoxide-import.1
+++ b/man/man1/zoxide-import.1
@@ -11,7 +11,7 @@ The format of the database being imported:
tab(|);
l l.
\fBautojump\fR
- \fBz\fR|(for \fBz\fR, \fBz.lua\fR, or \fBzsh-z\fR)
+ \fBz\fR|(for \fBfasd\fR, \fBz\fR, \fBz.lua\fR, or \fBzsh-z\fR)
.TE
.sp
Note: zoxide only imports paths from autojump, since its matching
@@ -26,6 +26,6 @@ option merges imported data into the existing database.
.SH REPORTING BUGS
For any issues, feature requests, or questions, please visit:
.sp
-\fBhttps://github.com/ajeetdsouza/zoxide/issues\fR.
+\fBhttps://github.com/ajeetdsouza/zoxide/issues\fR
.SH AUTHOR
-Ajeet D'Souza <\fB98ajeet@gmail.com\fR>
+Ajeet D'Souza \fB<98ajeet@gmail.com>\fR
diff --git a/man/man1/zoxide-init.1 b/man/man1/zoxide-init.1
index 69a3a9e..ee77216 100644
--- a/man/man1/zoxide-init.1
+++ b/man/man1/zoxide-init.1
@@ -7,58 +7,63 @@
To initialize zoxide on your shell:
.TP
.B bash
-Add this to your configuration (usually \fB~/.bashrc\fR):
+Add this to the \fBend\fR of your config file (usually \fB~/.bashrc\fR):
.sp
.nf
\fBeval "$(zoxide init bash)"\fR
.fi
.TP
.B elvish
-Add this to your configuration (usually \fB~/.elvish/rc.elv\fR):
+Add this to the \fBend\fR of your config file (usually \fB~/.elvish/rc.elv\fR):
.sp
.nf
\fBeval $(zoxide init elvish | slurp)\fR
.fi
.sp
-Note: zoxide only supports elvish v0.16.0 and above.
+Note: zoxide only supports elvish v0.18.0 and above.
.TP
.B fish
-Add this to your configuration (usually \fB~/.config/fish/config.fish\fR):
+Add this to the \fBend\fR of your config file (usually
+\fB~/.config/fish/config.fish\fR):
.sp
.nf
\fBzoxide init fish | source\fR
.fi
.TP
.B nushell
-Add this to your configuration (find it by running \fBconfig path\fR in
-Nushell):
+Add this to the \fBend\fR of your env file (find it by running
+\fB$nu.env-path\fR in Nushell):
.sp
.nf
- \fBstartup = ["zoxide init nushell --hook prompt | save ~/.zoxide.nu", "source ~/.zoxide.nu"]\fR
+ \fBzoxide init nushell | save -f ~/.zoxide.nu\fR
.fi
.sp
-Note: zoxide only supports Nushell v0.37.0 and above.
-.TP
-.B powershell
-Add this to your configuration (find it by running \fBecho $profile\fR in
-PowerShell):
+Now, add this to the \fBend\fR of your config file (find it by running
+\fB$nu.config-path\fR in Nushell):
.sp
.nf
- \fBInvoke-Expression (& {
- $hook = if ($PSVersionTable.PSVersion.Major -lt 6) { 'prompt' } else { 'pwd' }
- (zoxide init --hook $hook powershell | Out-String)
- })\fR
+ \fBsource ~/.zoxide.nu\fR
+.fi
+.sp
+Note: zoxide only supports Nushell v0.73.0 and above.
+.TP
+.B powershell
+Add this to the \fBend\fR of your config file (find it by running \fBecho
+$profile\fR in PowerShell):
+.sp
+.nf
+ \fBInvoke-Expression (& { (zoxide init powershell | Out-String) })\fR
.fi
.TP
.B xonsh
-Add this to your configuration (usually \fB~/.xonshrc\fR):
+Add this to the \fBend\fR of your config file (usually \fB~/.xonshrc\fR):
.sp
.nf
\fBexecx($(zoxide init xonsh), 'exec', __xonsh__.ctx, filename='zoxide')\fR
.fi
.TP
.B zsh
-Add this to your configuration (usually \fB~/.zshrc\fR):
+Add this to the \fBend\fR of your config file (usually \fB~/.zshrc\fR):
.sp
.nf
\fBeval "$(zoxide init zsh)"\fR
@@ -66,7 +71,7 @@ Add this to your configuration (usually \fB~/.zshrc\fR):
.TP
.B any POSIX shell
.sp
-Add this to your configuration:
+Add this to the \fBend\fR of your config file:
.sp
.nf
\fBeval "$(zoxide init posix --hook prompt)"\fR
@@ -74,9 +79,9 @@ Add this to your configuration:
.SH OPTIONS
.TP
.B --cmd
-Changes the prefix of predefined aliases (\fBz\fR, \fBzi\fR).
+Changes the prefix of the \fBz\fR and \fBzi\fR commands.
.br
-\fB--cmd j\fR would change the aliases to (\fBj\fR, \fBji\fR).
+\fB--cmd j\fR would change the commands to (\fBj\fR, \fBji\fR).
.br
\fB--cmd cd\fR would replace the \fBcd\fR command (doesn't work on Nushell /
POSIX shells).
@@ -94,13 +99,13 @@ l l.
\fBpwd\fR|Whenever the directory is changed
.TE
.TP
-.B --no-aliases
-Don't define extra aliases (\fBz\fR, \fBzi\fR). These functions will still be
-available in your shell as \fB__zoxide_z\fR and \fB__zoxide_zi\fR, should you
-choose to redefine them.
+.B --no-cmd
+Prevents zoxide from defining the \fBz\fR and \fBzi\fR commands. These functions
+will still be available in your shell as \fB__zoxide_z\fR and \fB__zoxide_zi\fR,
+should you choose to redefine them.
.SH REPORTING BUGS
For any issues, feature requests, or questions, please visit:
.sp
-\fBhttps://github.com/ajeetdsouza/zoxide/issues\fR.
+\fBhttps://github.com/ajeetdsouza/zoxide/issues\fR
.SH AUTHOR
-Ajeet D'Souza <\fB98ajeet@gmail.com\fR>
+Ajeet D'Souza \fB<98ajeet@gmail.com>\fR
diff --git a/man/man1/zoxide-query.1 b/man/man1/zoxide-query.1
index bf19a43..e1ccd1d 100644
--- a/man/man1/zoxide-query.1
+++ b/man/man1/zoxide-query.1
@@ -28,6 +28,6 @@ Print the calculated score as well as the matched path.
.SH REPORTING BUGS
For any issues, feature requests, or questions, please visit:
.sp
-\fBhttps://github.com/ajeetdsouza/zoxide/issues\fR.
+\fBhttps://github.com/ajeetdsouza/zoxide/issues\fR
.SH AUTHOR
-Ajeet D'Souza <\fB98ajeet@gmail.com\fR>
+Ajeet D'Souza \fB<98ajeet@gmail.com>\fR
diff --git a/man/man1/zoxide-remove.1 b/man/man1/zoxide-remove.1
index 35611b9..e174a6f 100644
--- a/man/man1/zoxide-remove.1
+++ b/man/man1/zoxide-remove.1
@@ -10,12 +10,9 @@ If you'd like to permanently exclude a directory from the database, see the
.TP
.B -h, --help
Print help information.
-.TP
-.B -i, --interactive [KEYWORDS]
-Use interactive selection. This option requires \fBfzf\fR(1).
.SH REPORTING BUGS
For any issues, feature requests, or questions, please visit:
.sp
-\fBhttps://github.com/ajeetdsouza/zoxide/issues\fR.
+\fBhttps://github.com/ajeetdsouza/zoxide/issues\fR
.SH AUTHOR
-Ajeet D'Souza <\fB98ajeet@gmail.com\fR>
+Ajeet D'Souza \fB<98ajeet@gmail.com>\fR
diff --git a/man/man1/zoxide.1 b/man/man1/zoxide.1
index 9ebf751..ef1792b 100644
--- a/man/man1/zoxide.1
+++ b/man/man1/zoxide.1
@@ -129,6 +129,6 @@ l l.
.SH REPORTING BUGS
For any issues, feature requests, or questions, please visit:
.sp
-\fBhttps://github.com/ajeetdsouza/zoxide/issues\fR.
+\fBhttps://github.com/ajeetdsouza/zoxide/issues\fR
.SH AUTHOR
-Ajeet D'Souza <\fB98ajeet@gmail.com\fR>
+Ajeet D'Souza \fB<98ajeet@gmail.com>\fR
diff --git a/rustfmt.toml b/rustfmt.toml
index 450ab8a..024f400 100644
--- a/rustfmt.toml
+++ b/rustfmt.toml
@@ -1,12 +1,8 @@
-# comment_width = 100
-# error_on_line_overflow = true
-# error_on_unformatted = true
-# group_imports = "StdExternalCrate"
-# imports_granularity = "Module"
-max_width = 120
+group_imports = "StdExternalCrate"
+imports_granularity = "Module"
newline_style = "Native"
use_field_init_shorthand = true
use_small_heuristics = "Max"
use_try_shorthand = true
-# wrap_comments = true
-# version = "Two"
+wrap_comments = true
+version = "Two"
diff --git a/shell.nix b/shell.nix
index 1a4bd42..2059f4f 100644
--- a/shell.nix
+++ b/shell.nix
@@ -1,13 +1,22 @@
let
- rust = import (builtins.fetchTarball
- "https://github.com/oxalica/rust-overlay/archive/46d8d20fce510c6a25fa66f36e31f207f6ea49e4.tar.gz");
pkgs = import (builtins.fetchTarball
- "https://github.com/NixOS/nixpkgs/archive/fae46e66a5df220327b45e0d7c27c6961cf922ce.tar.gz") {
+ "https://github.com/NixOS/nixpkgs/archive/22a6958f46fd8e14830d02856ff63b1d0e5cc3e4.tar.gz") {
overlays = [ rust ];
};
+ rust = import (builtins.fetchTarball
+ "https://github.com/oxalica/rust-overlay/archive/a61fcd9910229d097ffef92b5a2440065e3b64d5.tar.gz");
+
+ rust-nightly =
+ pkgs.rust-bin.selectLatestNightlyWith (toolchain: toolchain.minimal);
+ cargo-udeps = pkgs.writeShellScriptBin "cargo-udeps" ''
+ export RUSTC="${rust-nightly}/bin/rustc";
+ export CARGO="${rust-nightly}/bin/cargo";
+ exec "${pkgs.cargo-udeps}/bin/cargo-udeps" "$@"
+ '';
in pkgs.mkShell {
buildInputs = [
# Rust
+ (pkgs.rust-bin.selectLatestNightlyWith (toolchain: toolchain.rustfmt))
pkgs.rust-bin.stable.latest.default
# Shells
@@ -21,7 +30,11 @@ in pkgs.mkShell {
pkgs.zsh
# Tools
- pkgs.cargo-audit
+ cargo-udeps
+ pkgs.cargo-msrv
+ pkgs.cargo-nextest
+ pkgs.cargo-udeps
+ pkgs.just
pkgs.mandoc
pkgs.nixfmt
pkgs.nodePackages.markdownlint-cli
@@ -30,6 +43,7 @@ in pkgs.mkShell {
pkgs.python3Packages.pylint
pkgs.shellcheck
pkgs.shfmt
+ pkgs.yamlfmt
# Dependencies
pkgs.cacert
@@ -38,5 +52,5 @@ in pkgs.mkShell {
pkgs.libiconv
];
- RUST_BACKTRACE = 1;
+ CARGO_TARGET_DIR = "target_nix";
}
diff --git a/src/cmd/_cmd.rs b/src/cmd/_cmd.rs
deleted file mode 100644
index d75a86f..0000000
--- a/src/cmd/_cmd.rs
+++ /dev/null
@@ -1,131 +0,0 @@
-use std::path::PathBuf;
-
-use clap::{ArgEnum, Parser, ValueHint};
-
-const ENV_HELP: &str = "ENVIRONMENT VARIABLES:
- _ZO_DATA_DIR Path for zoxide data files
- _ZO_ECHO Print the matched directory before navigating to it when set to 1
- _ZO_EXCLUDE_DIRS List of directory globs to be excluded
- _ZO_FZF_OPTS Custom flags to pass to fzf
- _ZO_MAXAGE Maximum total age after which entries start getting deleted
- _ZO_RESOLVE_SYMLINKS Resolve symlinks when storing paths";
-
-#[derive(Debug, Parser)]
-#[clap(
- bin_name = env!("CARGO_PKG_NAME"),
- about,
- author,
- after_help = ENV_HELP,
- disable_help_subcommand = true,
- propagate_version = true,
- version = option_env!("ZOXIDE_VERSION").unwrap_or_default()
-)]
-pub enum Cmd {
- Add(Add),
- Import(Import),
- Init(Init),
- Query(Query),
- Remove(Remove),
-}
-
-/// Add a new directory or increment its rank
-#[derive(Debug, Parser)]
-pub struct Add {
- #[clap(min_values = 1, required = true, value_hint = ValueHint::DirPath)]
- pub paths: Vec,
-}
-
-/// Import entries from another application
-#[derive(Debug, Parser)]
-pub struct Import {
- #[clap(value_hint = ValueHint::FilePath)]
- pub path: PathBuf,
-
- /// Application to import from
- #[clap(arg_enum, long)]
- pub from: ImportFrom,
-
- /// Merge into existing database
- #[clap(long)]
- pub merge: bool,
-}
-
-#[derive(ArgEnum, Clone, Debug)]
-pub enum ImportFrom {
- Autojump,
- Z,
-}
-
-/// Generate shell configuration
-#[derive(Debug, Parser)]
-pub struct Init {
- #[clap(arg_enum)]
- pub shell: InitShell,
-
- /// Prevents zoxide from defining any commands
- #[clap(long)]
- pub no_aliases: bool,
-
- /// Renames the 'z' command and corresponding aliases
- #[clap(long, default_value = "z")]
- pub cmd: String,
-
- /// Chooses event upon which an entry is added to the database
- #[clap(arg_enum, long, default_value = "pwd")]
- pub hook: InitHook,
-}
-
-#[derive(ArgEnum, Clone, Copy, Debug, Eq, PartialEq)]
-pub enum InitHook {
- None,
- Prompt,
- Pwd,
-}
-
-#[derive(ArgEnum, Clone, Debug)]
-pub enum InitShell {
- Bash,
- Elvish,
- Fish,
- Nushell,
- Posix,
- Powershell,
- Xonsh,
- Zsh,
-}
-
-/// Search for a directory in the database
-#[derive(Debug, Parser)]
-pub struct Query {
- pub keywords: Vec,
-
- /// Show deleted directories
- #[clap(long)]
- pub all: bool,
-
- /// Use interactive selection
- #[clap(long, short, conflicts_with = "list")]
- pub interactive: bool,
-
- /// List all matching directories
- #[clap(long, short, conflicts_with = "interactive")]
- pub list: bool,
-
- /// Print score with results
- #[clap(long, short, conflicts_with = "interactive")]
- pub score: bool,
-
- /// Exclude a path from results
- #[clap(long, value_hint = ValueHint::DirPath, value_name = "path")]
- pub exclude: Option,
-}
-
-/// Remove a directory from the database
-#[derive(Debug, Parser)]
-pub struct Remove {
- /// Use interactive selection
- #[clap(long, short)]
- pub interactive: bool,
- #[clap(value_hint = ValueHint::DirPath)]
- pub paths: Vec,
-}
diff --git a/src/cmd/add.rs b/src/cmd/add.rs
index 1bbe697..945bbe5 100644
--- a/src/cmd/add.rs
+++ b/src/cmd/add.rs
@@ -3,42 +3,42 @@ use std::path::Path;
use anyhow::{bail, Result};
use crate::cmd::{Add, Run};
-use crate::db::DatabaseFile;
+use crate::db::Database;
use crate::{config, util};
impl Run for Add {
fn run(&self) -> Result<()> {
- // These characters can't be printed cleanly to a single line, so they can cause confusion
- // when writing to fzf / stdout.
+ // These characters can't be printed cleanly to a single line, so they can cause
+ // confusion when writing to stdout.
const EXCLUDE_CHARS: &[char] = &['\n', '\r'];
- let data_dir = config::data_dir()?;
let exclude_dirs = config::exclude_dirs()?;
let max_age = config::maxage()?;
let now = util::current_time()?;
- let mut db = DatabaseFile::new(data_dir);
- let mut db = db.open()?;
+ let mut db = Database::open()?;
for path in &self.paths {
- let path = if config::resolve_symlinks() { util::canonicalize } else { util::resolve_path }(path)?;
+ let path =
+ if config::resolve_symlinks() { util::canonicalize } else { util::resolve_path }(
+ path,
+ )?;
let path = util::path_to_str(&path)?;
- // Ignore path if it contains unsupported characters, or if it's in the exclude list.
+ // Ignore path if it contains unsupported characters, or if it's in the exclude
+ // list.
if path.contains(EXCLUDE_CHARS) || exclude_dirs.iter().any(|glob| glob.matches(path)) {
continue;
}
if !Path::new(path).is_dir() {
- bail!("not a directory: {}", path);
+ bail!("not a directory: {path}");
}
- db.add(path, now);
+ db.add_update(path, 1.0, now);
}
- if db.modified {
+ if db.dirty() {
db.age(max_age);
- db.save()?;
}
-
- Ok(())
+ db.save()
}
}
diff --git a/src/cmd/cmd.rs b/src/cmd/cmd.rs
new file mode 100644
index 0000000..0e5f6c4
--- /dev/null
+++ b/src/cmd/cmd.rs
@@ -0,0 +1,186 @@
+#![allow(clippy::module_inception)]
+
+use std::path::PathBuf;
+
+use clap::{Parser, Subcommand, ValueEnum, ValueHint};
+
+const HELP_TEMPLATE: &str = color_print::cstr!("\
+{before-help}{name} {version}
+{author}
+https://github.com/ajeetdsouza/zoxide
+
+{about}
+
+{usage-heading}
+{tab}{usage}
+
+{all-args}{after-help}
+
+Environment variables:
+{tab}_ZO_DATA_DIR {tab}Path for zoxide data files
+{tab}_ZO_ECHO {tab}Print the matched directory before navigating to it when set to 1
+{tab}_ZO_EXCLUDE_DIRS {tab}List of directory globs to be excluded
+{tab}_ZO_FZF_OPTS {tab}Custom flags to pass to fzf
+{tab}_ZO_MAXAGE {tab}Maximum total age after which entries start getting deleted
+{tab}_ZO_RESOLVE_SYMLINKS{tab}Resolve symlinks when storing paths");
+
+#[derive(Debug, Parser)]
+#[clap(
+ about,
+ author,
+ help_template = HELP_TEMPLATE,
+ disable_help_subcommand = true,
+ propagate_version = true,
+ version,
+)]
+pub enum Cmd {
+ Add(Add),
+ Edit(Edit),
+ Import(Import),
+ Init(Init),
+ Query(Query),
+ Remove(Remove),
+}
+
+/// Add a new directory or increment its rank
+#[derive(Debug, Parser)]
+#[clap(
+ author,
+ help_template = HELP_TEMPLATE,
+)]
+pub struct Add {
+ #[clap(num_args = 1.., required = true, value_hint = ValueHint::DirPath)]
+ pub paths: Vec,
+}
+
+/// Edit the database
+#[derive(Debug, Parser)]
+#[clap(
+ author,
+ help_template = HELP_TEMPLATE,
+)]
+pub struct Edit {
+ #[clap(subcommand)]
+ pub cmd: Option,
+}
+
+#[derive(Clone, Debug, Subcommand)]
+pub enum EditCommand {
+ #[clap(hide = true)]
+ Decrement { path: String },
+ #[clap(hide = true)]
+ Delete { path: String },
+ #[clap(hide = true)]
+ Increment { path: String },
+ #[clap(hide = true)]
+ Reload,
+}
+
+/// Import entries from another application
+#[derive(Debug, Parser)]
+#[clap(
+ author,
+ help_template = HELP_TEMPLATE,
+)]
+pub struct Import {
+ #[clap(value_hint = ValueHint::FilePath)]
+ pub path: PathBuf,
+
+ /// Application to import from
+ #[clap(value_enum, long)]
+ pub from: ImportFrom,
+
+ /// Merge into existing database
+ #[clap(long)]
+ pub merge: bool,
+}
+
+#[derive(ValueEnum, Clone, Debug)]
+pub enum ImportFrom {
+ Autojump,
+ #[clap(alias = "fasd")]
+ Z,
+}
+
+/// Generate shell configuration
+#[derive(Debug, Parser)]
+#[clap(
+ author,
+ help_template = HELP_TEMPLATE,
+)]
+pub struct Init {
+ #[clap(value_enum)]
+ pub shell: InitShell,
+
+ /// Prevents zoxide from defining the `z` and `zi` commands
+ #[clap(long, alias = "no-aliases")]
+ pub no_cmd: bool,
+
+ /// Changes the prefix of the `z` and `zi` commands
+ #[clap(long, default_value = "z")]
+ pub cmd: String,
+
+ /// Changes how often zoxide increments a directory's score
+ #[clap(value_enum, long, default_value = "pwd")]
+ pub hook: InitHook,
+}
+
+#[derive(ValueEnum, Clone, Copy, Debug, Eq, PartialEq)]
+pub enum InitHook {
+ None,
+ Prompt,
+ Pwd,
+}
+
+#[derive(ValueEnum, Clone, Debug)]
+pub enum InitShell {
+ Bash,
+ Elvish,
+ Fish,
+ Nushell,
+ Posix,
+ Powershell,
+ Xonsh,
+ Zsh,
+}
+
+/// Search for a directory in the database
+#[derive(Debug, Parser)]
+#[clap(
+ author,
+ help_template = HELP_TEMPLATE,
+)]
+pub struct Query {
+ pub keywords: Vec,
+
+ /// Show unavailable directories
+ #[clap(long, short)]
+ pub all: bool,
+
+ /// Use interactive selection
+ #[clap(long, short, conflicts_with = "list")]
+ pub interactive: bool,
+
+ /// List all matching directories
+ #[clap(long, short, conflicts_with = "interactive")]
+ pub list: bool,
+
+ /// Print score with results
+ #[clap(long, short)]
+ pub score: bool,
+
+ /// Exclude the current directory
+ #[clap(long, value_hint = ValueHint::DirPath, value_name = "path")]
+ pub exclude: Option,
+}
+
+/// Remove a directory from the database
+#[derive(Debug, Parser)]
+#[clap(
+ author,
+ help_template = HELP_TEMPLATE,
+)]
+pub struct Remove {
+ #[clap(value_hint = ValueHint::DirPath)]
+ pub paths: Vec,
+}
diff --git a/src/cmd/edit.rs b/src/cmd/edit.rs
new file mode 100644
index 0000000..0f37165
--- /dev/null
+++ b/src/cmd/edit.rs
@@ -0,0 +1,84 @@
+use std::io::{self, Write};
+
+use anyhow::Result;
+
+use crate::cmd::{Edit, EditCommand, Run};
+use crate::db::Database;
+use crate::error::BrokenPipeHandler;
+use crate::util::{self, Fzf, FzfChild};
+
+impl Run for Edit {
+ fn run(&self) -> Result<()> {
+ let now = util::current_time()?;
+ let db = &mut Database::open()?;
+
+ match &self.cmd {
+ Some(cmd) => {
+ match cmd {
+ EditCommand::Decrement { path } => db.add(path, -1.0, now),
+ EditCommand::Delete { path } => {
+ db.remove(path);
+ }
+ EditCommand::Increment { path } => db.add(path, 1.0, now),
+ EditCommand::Reload => {}
+ }
+ db.save()?;
+
+ let stdout = &mut io::stdout().lock();
+ for dir in db.dirs().iter().rev() {
+ write!(stdout, "{}\0", dir.display().with_score(now).with_separator('\t'))
+ .pipe_exit("fzf")?;
+ }
+ Ok(())
+ }
+ None => {
+ db.sort_by_score(now);
+ db.save()?;
+ Self::get_fzf()?.wait()?;
+ Ok(())
+ }
+ }
+ }
+}
+
+impl Edit {
+ fn get_fzf() -> Result {
+ Fzf::new()?
+ .args([
+ // Search mode
+ "--exact",
+ // Search result
+ "--no-sort",
+ // Interface
+ "--bind=\
+btab:up,\
+ctrl-r:reload(zoxide edit reload),\
+ctrl-d:reload(zoxide edit delete {2..}),\
+ctrl-w:reload(zoxide edit increment {2..}),\
+ctrl-s:reload(zoxide edit decrement {2..}),\
+ctrl-z:ignore,\
+double-click:ignore,\
+enter:abort,\
+start:reload(zoxide edit reload),\
+tab:down",
+ "--cycle",
+ "--keep-right",
+ // Layout
+ "--border=sharp",
+ "--border-label= zoxide-edit ",
+ "--header=\
+ctrl-r:reload \tctrl-d:delete
+ctrl-w:increment\tctrl-s:decrement
+
+ SCORE\tPATH",
+ "--info=inline",
+ "--layout=reverse",
+ "--padding=1,0,0,0",
+ // Display
+ "--color=label:bold",
+ "--tabstop=1",
+ ])
+ .enable_preview()
+ .spawn()
+ }
+}
diff --git a/src/cmd/import.rs b/src/cmd/import.rs
index 83c939a..182a25f 100644
--- a/src/cmd/import.rs
+++ b/src/cmd/import.rs
@@ -3,24 +3,22 @@ use std::fs;
use anyhow::{bail, Context, Result};
use crate::cmd::{Import, ImportFrom, Run};
-use crate::config;
-use crate::db::{Database, DatabaseFile, Dir};
+use crate::db::Database;
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 buffer = fs::read_to_string(&self.path).with_context(|| {
+ format!("could not open database for importing: {}", &self.path.display())
+ })?;
- let data_dir = config::data_dir()?;
- let mut db = DatabaseFile::new(data_dir);
- let db = &mut db.open()?;
- if !self.merge && !db.dirs.is_empty() {
+ 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 => from_autojump(db, &buffer),
- ImportFrom::Z => from_z(db, &buffer),
+ ImportFrom::Autojump => import_autojump(&mut db, &buffer),
+ ImportFrom::Z => import_z(&mut db, &buffer),
}
.context("import error")?;
@@ -28,55 +26,51 @@ impl Run for Import {
}
}
-fn from_autojump<'a>(db: &mut Database<'a>, buffer: &'a str) -> Result<()> {
+fn import_autojump(db: &mut Database, buffer: &str) -> Result<()> {
for line in buffer.lines() {
if line.is_empty() {
continue;
}
- let mut split = line.splitn(2, '\t');
+ let (rank, path) =
+ line.split_once('\t').with_context(|| format!("invalid entry: {line}"))?;
- let rank = split.next().with_context(|| format!("invalid entry: {}", line))?;
- let mut rank = rank.parse::().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.
+ let mut rank = rank.parse::().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);
- let path = split.next().with_context(|| format!("invalid entry: {}", line))?;
-
- db.dirs.push(Dir { path: path.into(), rank, last_accessed: 0 });
- db.modified = true;
+ db.add_unchecked(path, rank, 0);
}
- if db.modified {
+ if db.dirty() {
db.dedup();
}
-
Ok(())
}
-fn from_z<'a>(db: &mut Database<'a>, buffer: &'a str) -> Result<()> {
+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 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 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))?;
+ let path = split.next().with_context(|| format!("invalid entry: {line}"))?;
- db.dirs.push(Dir { path: path.into(), rank, last_accessed });
- db.modified = true;
+ db.add_unchecked(path, rank, last_accessed);
}
- if db.modified {
+ if db.dirty() {
db.dedup();
}
-
Ok(())
}
@@ -86,33 +80,33 @@ fn sigmoid(x: f64) -> f64 {
#[cfg(test)]
mod tests {
- use super::sigmoid;
- use crate::db::{Database, Dir};
+ use super::*;
+ use crate::db::Dir;
#[test]
fn from_autojump() {
- let buffer = r#"
+ 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
-"#;
+5.0 /quux/quuz";
+ import_autojump(&mut db, buffer).unwrap();
- let dirs = vec![
- Dir { path: "/quux/quuz".into(), rank: 1.0, last_accessed: 100 },
- Dir { path: "/corge/grault/garply".into(), rank: 6.0, last_accessed: 600 },
- Dir { path: "/waldo/fred/plugh".into(), rank: 3.0, last_accessed: 300 },
- Dir { path: "/xyzzy/thud".into(), rank: 8.0, last_accessed: 800 },
- Dir { path: "/foo/bar".into(), rank: 9.0, last_accessed: 900 },
- ];
- let data_dir = tempfile::tempdir().unwrap();
- let data_dir = &data_dir.path().to_path_buf();
- let mut db = Database { dirs: dirs.into(), modified: false, data_dir };
+ db.sort_by_path();
+ println!("got: {:?}", &db.dirs());
- super::from_autojump(&mut db, buffer).unwrap();
- db.dirs.sort_by(|dir1, dir2| dir1.path.cmp(&dir2.path));
- println!("got: {:?}", &db.dirs.as_slice());
-
- let exp = &[
+ 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 },
@@ -120,9 +114,9 @@ mod tests {
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);
+ println!("exp: {exp:?}");
- for (dir1, dir2) in db.dirs.iter().zip(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);
@@ -131,29 +125,29 @@ mod tests {
#[test]
fn from_z() {
- let buffer = r#"
+ 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
-"#;
+/quux/quuz|5|500";
+ import_z(&mut db, buffer).unwrap();
- let dirs = vec![
- Dir { path: "/quux/quuz".into(), rank: 1.0, last_accessed: 100 },
- Dir { path: "/corge/grault/garply".into(), rank: 6.0, last_accessed: 600 },
- Dir { path: "/waldo/fred/plugh".into(), rank: 3.0, last_accessed: 300 },
- Dir { path: "/xyzzy/thud".into(), rank: 8.0, last_accessed: 800 },
- Dir { path: "/foo/bar".into(), rank: 9.0, last_accessed: 900 },
- ];
- let data_dir = tempfile::tempdir().unwrap();
- let data_dir = &data_dir.path().to_path_buf();
- let mut db = Database { dirs: dirs.into(), modified: false, data_dir };
+ db.sort_by_path();
+ println!("got: {:?}", &db.dirs());
- super::from_z(&mut db, buffer).unwrap();
- db.dirs.sort_by(|dir1, dir2| dir1.path.cmp(&dir2.path));
- println!("got: {:?}", &db.dirs.as_slice());
-
- let exp = &[
+ 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 },
@@ -161,9 +155,9 @@ mod tests {
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);
+ println!("exp: {exp:?}");
- for (dir1, dir2) in db.dirs.iter().zip(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);
diff --git a/src/cmd/init.rs b/src/cmd/init.rs
index 639ec0f..60bad63 100644
--- a/src/cmd/init.rs
+++ b/src/cmd/init.rs
@@ -6,28 +6,26 @@ use askama::Template;
use crate::cmd::{Init, InitShell, Run};
use crate::config;
use crate::error::BrokenPipeHandler;
-use crate::shell::{self, Opts};
+use crate::shell::{Bash, Elvish, Fish, Nushell, Opts, Posix, Powershell, Xonsh, Zsh};
impl Run for Init {
fn run(&self) -> Result<()> {
- let cmd = if self.no_aliases { None } else { Some(self.cmd.as_str()) };
-
+ let cmd = if self.no_cmd { None } else { Some(self.cmd.as_str()) };
let echo = config::echo();
let resolve_symlinks = config::resolve_symlinks();
-
let opts = &Opts { cmd, hook: self.hook, echo, resolve_symlinks };
let source = match self.shell {
- InitShell::Bash => shell::Bash(opts).render(),
- InitShell::Elvish => shell::Elvish(opts).render(),
- InitShell::Fish => shell::Fish(opts).render(),
- InitShell::Nushell => shell::Nushell(opts).render(),
- InitShell::Posix => shell::Posix(opts).render(),
- InitShell::Powershell => shell::Powershell(opts).render(),
- InitShell::Xonsh => shell::Xonsh(opts).render(),
- InitShell::Zsh => shell::Zsh(opts).render(),
+ InitShell::Bash => Bash(opts).render(),
+ InitShell::Elvish => Elvish(opts).render(),
+ InitShell::Fish => Fish(opts).render(),
+ InitShell::Nushell => Nushell(opts).render(),
+ InitShell::Posix => Posix(opts).render(),
+ InitShell::Powershell => Powershell(opts).render(),
+ InitShell::Xonsh => Xonsh(opts).render(),
+ InitShell::Zsh => Zsh(opts).render(),
}
.context("could not render template")?;
- writeln!(io::stdout(), "{}", source).pipe_exit("stdout")
+ writeln!(io::stdout(), "{source}").pipe_exit("stdout")
}
}
diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs
index c9cab3c..5c17474 100644
--- a/src/cmd/mod.rs
+++ b/src/cmd/mod.rs
@@ -1,5 +1,6 @@
-mod _cmd;
mod add;
+mod cmd;
+mod edit;
mod import;
mod init;
mod query;
@@ -7,7 +8,7 @@ mod remove;
use anyhow::Result;
-pub use crate::cmd::_cmd::*;
+pub use crate::cmd::cmd::*;
pub trait Run {
fn run(&self) -> Result<()>;
@@ -17,6 +18,7 @@ impl Run for Cmd {
fn run(&self) -> Result<()> {
match self {
Cmd::Add(cmd) => cmd.run(),
+ Cmd::Edit(cmd) => cmd.run(),
Cmd::Import(cmd) => cmd.run(),
Cmd::Init(cmd) => cmd.run(),
Cmd::Query(cmd) => cmd.run(),
diff --git a/src/cmd/query.rs b/src/cmd/query.rs
index f095501..21ae197 100644
--- a/src/cmd/query.rs
+++ b/src/cmd/query.rs
@@ -1,18 +1,16 @@
use std::io::{self, Write};
-use anyhow::{Context, Result};
+use anyhow::{bail, Context, Result};
use crate::cmd::{Query, Run};
use crate::config;
-use crate::db::{Database, DatabaseFile};
+use crate::db::{Database, Epoch, Stream};
use crate::error::BrokenPipeHandler;
-use crate::util::{self, Fzf};
+use crate::util::{self, Fzf, FzfChild};
impl Run for Query {
fn run(&self) -> Result<()> {
- let data_dir = config::data_dir()?;
- let mut db = DatabaseFile::new(data_dir);
- let mut db = db.open()?;
+ let mut db = crate::db::Database::open()?;
self.query(&mut db).and(db.save())
}
}
@@ -20,7 +18,50 @@ impl Run for Query {
impl Query {
fn query(&self, db: &mut Database) -> Result<()> {
let now = util::current_time()?;
+ let mut stream = self.get_stream(db, now);
+ if self.interactive {
+ let mut fzf = Self::get_fzf()?;
+ let selection = loop {
+ match stream.next() {
+ Some(dir) => {
+ if let Some(selection) = fzf.write(dir, now)? {
+ break selection;
+ }
+ }
+ None => break fzf.wait()?,
+ }
+ };
+
+ if self.score {
+ print!("{selection}");
+ } else {
+ let path = selection.get(7..).context("could not read selection from fzf")?;
+ print!("{path}");
+ }
+ } else if self.list {
+ let handle = &mut io::stdout().lock();
+ while let Some(dir) = stream.next() {
+ let dir = if self.score { dir.display().with_score(now) } else { dir.display() };
+ writeln!(handle, "{dir}").pipe_exit("stdout")?;
+ }
+ } else {
+ let handle = &mut io::stdout();
+ let Some(dir) = stream.next() else {
+ bail!(if stream.did_exclude() {
+ "you are already in the only match"
+ } else {
+ "no match found"
+ });
+ };
+ let dir = if self.score { dir.display().with_score(now) } else { dir.display() };
+ writeln!(handle, "{dir}").pipe_exit("stdout")?;
+ }
+
+ Ok(())
+ }
+
+ fn get_stream<'a>(&self, db: &'a mut Database, now: Epoch) -> Stream<'a> {
let mut stream = db.stream(now).with_keywords(&self.keywords);
if !self.all {
let resolve_symlinks = config::resolve_symlinks();
@@ -29,51 +70,36 @@ impl Query {
if let Some(path) = &self.exclude {
stream = stream.with_exclude(path);
}
+ stream
+ }
- if self.interactive {
- let mut fzf = Fzf::new(false)?;
- let stdin = fzf.stdin();
-
- let selection = loop {
- let dir = match stream.next() {
- Some(dir) => dir,
- None => break fzf.select()?,
- };
-
- match writeln!(stdin, "{}", dir.display_score(now)) {
- Err(e) if e.kind() == io::ErrorKind::BrokenPipe => break fzf.select()?,
- result => result.context("could not write to fzf")?,
- }
- };
-
- if self.score {
- print!("{}", selection);
- } else {
- let path = selection.get(5..).context("could not read selection from fzf")?;
- print!("{}", path);
- }
- } else if self.list {
- let stdout = io::stdout();
- let handle = &mut stdout.lock();
- while let Some(dir) = stream.next() {
- if self.score {
- writeln!(handle, "{}", dir.display_score(now))
- } else {
- writeln!(handle, "{}", dir.display())
- }
- .pipe_exit("stdout")?;
- }
- handle.flush().pipe_exit("stdout")?;
+ fn get_fzf() -> Result {
+ let mut fzf = Fzf::new()?;
+ if let Some(fzf_opts) = config::fzf_opts() {
+ fzf.env("FZF_DEFAULT_OPTS", fzf_opts)
} else {
- let dir = stream.next().context("no match found")?;
- if self.score {
- writeln!(io::stdout(), "{}", dir.display_score(now))
- } else {
- writeln!(io::stdout(), "{}", dir.display())
- }
- .pipe_exit("stdout")?;
+ fzf.args([
+ // Search mode
+ "--exact",
+ // Search result
+ "--no-sort",
+ // Interface
+ "--bind=ctrl-z:ignore,btab:up,tab:down",
+ "--cycle",
+ "--keep-right",
+ // Layout
+ "--border=sharp", // rounded edges don't display correctly on some terminals
+ "--height=45%",
+ "--info=inline",
+ "--layout=reverse",
+ // Display
+ "--tabstop=1",
+ // Scripting
+ "--exit-0",
+ "--select-1",
+ ])
+ .enable_preview()
}
-
- Ok(())
+ .spawn()
}
}
diff --git a/src/cmd/remove.rs b/src/cmd/remove.rs
index 7805517..55c6989 100644
--- a/src/cmd/remove.rs
+++ b/src/cmd/remove.rs
@@ -1,52 +1,19 @@
-use std::io::{self, Write};
-
-use anyhow::{bail, Context, Result};
+use anyhow::{bail, Result};
use crate::cmd::{Remove, Run};
-use crate::config;
-use crate::db::DatabaseFile;
-use crate::util::{self, Fzf};
+use crate::db::Database;
+use crate::util;
impl Run for Remove {
fn run(&self) -> Result<()> {
- let data_dir = config::data_dir()?;
- let mut db = DatabaseFile::new(data_dir);
- let mut db = db.open()?;
+ let mut db = Database::open()?;
- if self.interactive {
- let keywords = &self.paths;
- let now = util::current_time()?;
- let mut stream = db.stream(now).with_keywords(keywords);
-
- let mut fzf = Fzf::new(true)?;
- let stdin = fzf.stdin();
-
- let selection = loop {
- let dir = match stream.next() {
- Some(dir) => dir,
- None => break fzf.select()?,
- };
-
- match writeln!(stdin, "{}", dir.display_score(now)) {
- Err(e) if e.kind() == io::ErrorKind::BrokenPipe => break fzf.select()?,
- result => result.context("could not write to fzf")?,
- }
- };
-
- let paths = selection.lines().filter_map(|line| line.get(5..));
- for path in paths {
- if !db.remove(path) {
- bail!("path not found in database: {}", path);
- }
- }
- } else {
- for path in &self.paths {
- if !db.remove(path) {
- let path_abs = util::resolve_path(path)?;
- let path_abs = util::path_to_str(&path_abs)?;
- if path_abs != path && !db.remove(path_abs) {
- bail!("path not found in database: {} ({})", path, path_abs)
- }
+ for path in &self.paths {
+ if !db.remove(path) {
+ let path_abs = util::resolve_path(path)?;
+ let path_abs = util::path_to_str(&path_abs)?;
+ if path_abs == path || !db.remove(path_abs) {
+ bail!("path not found in database: {path}")
}
}
}
diff --git a/src/config.rs b/src/config.rs
index 4ec7903..4a1b6b4 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -2,7 +2,7 @@ use std::env;
use std::ffi::OsString;
use std::path::PathBuf;
-use anyhow::{bail, Context, Result};
+use anyhow::{Context, Result};
use glob::Pattern;
use crate::db::Rank;
@@ -10,45 +10,35 @@ use crate::db::Rank;
pub fn data_dir() -> Result {
let path = match env::var_os("_ZO_DATA_DIR") {
Some(path) => PathBuf::from(path),
- None => match dirs::data_local_dir() {
- Some(mut path) => {
- path.push("zoxide");
- path
- }
- None => bail!("could not find data directory, please set _ZO_DATA_DIR manually"),
- },
+ None => dirs::data_local_dir()
+ .context("could not find data directory, please set _ZO_DATA_DIR manually")?
+ .join("zoxide"),
};
-
Ok(path)
}
pub fn echo() -> bool {
- match env::var_os("_ZO_ECHO") {
- Some(var) => var == "1",
- None => false,
- }
+ env::var_os("_ZO_ECHO").map_or(false, |var| var == "1")
}
pub fn exclude_dirs() -> Result> {
- env::var_os("_ZO_EXCLUDE_DIRS").map_or_else(
- || {
+ match env::var_os("_ZO_EXCLUDE_DIRS") {
+ Some(paths) => env::split_paths(&paths)
+ .map(|path| {
+ let pattern = path.to_str().context("invalid unicode in _ZO_EXCLUDE_DIRS")?;
+ Pattern::new(pattern)
+ .with_context(|| format!("invalid glob in _ZO_EXCLUDE_DIRS: {pattern}"))
+ })
+ .collect(),
+ None => {
let pattern = (|| {
let home = dirs::home_dir()?;
- let home = home.to_str()?;
- let home = Pattern::escape(home);
+ let home = Pattern::escape(home.to_str()?);
Pattern::new(&home).ok()
})();
Ok(pattern.into_iter().collect())
- },
- |paths| {
- env::split_paths(&paths)
- .map(|path| {
- let pattern = path.to_str().context("invalid unicode in _ZO_EXCLUDE_DIRS")?;
- Pattern::new(pattern).with_context(|| format!("invalid glob in _ZO_EXCLUDE_DIRS: {}", pattern))
- })
- .collect()
- },
- )
+ }
+ }
}
pub fn fzf_opts() -> Option {
@@ -56,20 +46,15 @@ pub fn fzf_opts() -> Option {
}
pub fn maxage() -> Result {
- match env::var_os("_ZO_MAXAGE") {
- Some(maxage) => {
- let maxage = maxage.to_str().context("invalid unicode in _ZO_MAXAGE")?;
- let maxage =
- maxage.parse::().with_context(|| format!("unable to parse _ZO_MAXAGE as integer: {}", maxage))?;
- Ok(maxage as Rank)
- }
- None => Ok(10000.0),
- }
+ env::var_os("_ZO_MAXAGE").map_or(Ok(10_000.0), |maxage| {
+ let maxage = maxage.to_str().context("invalid unicode in _ZO_MAXAGE")?;
+ let maxage = maxage
+ .parse::()
+ .with_context(|| format!("unable to parse _ZO_MAXAGE as integer: {maxage}"))?;
+ Ok(maxage as Rank)
+ })
}
pub fn resolve_symlinks() -> bool {
- match env::var_os("_ZO_RESOLVE_SYMLINKS") {
- Some(var) => var == "1",
- None => false,
- }
+ env::var_os("_ZO_RESOLVE_SYMLINKS").map_or(false, |var| var == "1")
}
diff --git a/src/db/dir.rs b/src/db/dir.rs
index 1661a1f..5d6d62c 100644
--- a/src/db/dir.rs
+++ b/src/db/dir.rs
@@ -1,83 +1,9 @@
use std::borrow::Cow;
use std::fmt::{self, Display, Formatter};
-use std::ops::{Deref, DerefMut};
-use anyhow::{bail, Context, Result};
-use bincode::Options as _;
use serde::{Deserialize, Serialize};
-#[derive(Debug, Deserialize, Serialize)]
-pub struct DirList<'a>(#[serde(borrow)] pub Vec>);
-
-impl DirList<'_> {
- const VERSION: u32 = 3;
-
- pub fn new() -> DirList<'static> {
- DirList(Vec::new())
- }
-
- pub fn from_bytes(bytes: &[u8]) -> Result {
- // Assume a maximum size for the database. This prevents bincode from throwing strange
- // errors when it encounters invalid data.
- const MAX_SIZE: u64 = 32 << 20; // 32 MiB
- let deserializer = &mut bincode::options().with_fixint_encoding().with_limit(MAX_SIZE);
-
- // Split bytes into sections.
- let version_size = deserializer.serialized_size(&Self::VERSION).unwrap() as _;
- if bytes.len() < version_size {
- bail!("could not deserialize database: corrupted data");
- }
- let (bytes_version, bytes_dirs) = bytes.split_at(version_size);
-
- // Deserialize sections.
- (|| {
- let version = deserializer.deserialize(bytes_version)?;
- match version {
- Self::VERSION => Ok(deserializer.deserialize(bytes_dirs)?),
- version => {
- bail!("unsupported version (got {}, supports {})", version, Self::VERSION,)
- }
- }
- })()
- .context("could not deserialize database")
- }
-
- pub fn to_bytes(&self) -> Result> {
- (|| -> bincode::Result<_> {
- // Preallocate buffer with combined size of sections.
- let version_size = bincode::serialized_size(&Self::VERSION)?;
- let dirs_size = bincode::serialized_size(&self)?;
- let buffer_size = version_size + dirs_size;
- let mut buffer = Vec::with_capacity(buffer_size as _);
-
- // Serialize sections into buffer.
- bincode::serialize_into(&mut buffer, &Self::VERSION)?;
- bincode::serialize_into(&mut buffer, &self)?;
- Ok(buffer)
- })()
- .context("could not serialize database")
- }
-}
-
-impl<'a> Deref for DirList<'a> {
- type Target = Vec>;
-
- fn deref(&self) -> &Self::Target {
- &self.0
- }
-}
-
-impl<'a> DerefMut for DirList<'a> {
- fn deref_mut(&mut self) -> &mut Self::Target {
- &mut self.0
- }
-}
-
-impl<'a> From>> for DirList<'a> {
- fn from(dirs: Vec>) -> Self {
- DirList(dirs)
- }
-}
+use crate::util::{DAY, HOUR, WEEK};
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Dir<'a> {
@@ -88,11 +14,11 @@ pub struct Dir<'a> {
}
impl Dir<'_> {
- pub fn score(&self, now: Epoch) -> Rank {
- const HOUR: Epoch = 60 * 60;
- const DAY: Epoch = 24 * HOUR;
- const WEEK: Epoch = 7 * DAY;
+ pub fn display(&self) -> DirDisplay<'_> {
+ DirDisplay::new(self)
+ }
+ pub fn score(&self, now: Epoch) -> Rank {
// The older the entry, the lesser its importance.
let duration = now.saturating_sub(self.last_accessed);
if duration < HOUR {
@@ -105,63 +31,39 @@ impl Dir<'_> {
self.rank * 0.25
}
}
-
- pub fn display(&self) -> DirDisplay {
- DirDisplay { dir: self }
- }
-
- pub fn display_score(&self, now: Epoch) -> DirDisplayScore {
- DirDisplayScore { dir: self, now }
- }
}
pub struct DirDisplay<'a> {
dir: &'a Dir<'a>,
+ now: Option,
+ separator: char,
+}
+
+impl<'a> DirDisplay<'a> {
+ fn new(dir: &'a Dir) -> Self {
+ Self { dir, separator: ' ', now: None }
+ }
+
+ pub fn with_score(mut self, now: Epoch) -> Self {
+ self.now = Some(now);
+ self
+ }
+
+ pub fn with_separator(mut self, separator: char) -> Self {
+ self.separator = separator;
+ self
+ }
}
impl Display for DirDisplay<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ if let Some(now) = self.now {
+ let score = self.dir.score(now).clamp(0.0, 9999.0);
+ write!(f, "{score:>6.1}{}", self.separator)?;
+ }
write!(f, "{}", self.dir.path)
}
}
-pub struct DirDisplayScore<'a> {
- dir: &'a Dir<'a>,
- now: Epoch,
-}
-
-impl Display for DirDisplayScore<'_> {
- fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
- let score = self.dir.score(self.now);
- let score = if score > 9999.0 {
- 9999
- } else if score > 0.0 {
- score as u32
- } else {
- 0
- };
- write!(f, "{:>4} {}", score, self.dir.path)
- }
-}
-
pub type Rank = f64;
pub type Epoch = u64;
-
-#[cfg(test)]
-mod tests {
- use std::borrow::Cow;
-
- use super::{Dir, DirList};
-
- #[test]
- fn zero_copy() {
- let dirs = DirList(vec![Dir { path: "/".into(), rank: 0.0, last_accessed: 0 }]);
-
- let bytes = dirs.to_bytes().unwrap();
- let dirs = DirList::from_bytes(&bytes).unwrap();
-
- for dir in dirs.iter() {
- assert!(matches!(dir.path, Cow::Borrowed(_)))
- }
- }
-}
diff --git a/src/db/mod.rs b/src/db/mod.rs
index f0e99d1..8eb9fc6 100644
--- a/src/db/mod.rs
+++ b/src/db/mod.rs
@@ -1,152 +1,237 @@
mod dir;
mod stream;
-use std::fs;
-use std::io;
use std::path::{Path, PathBuf};
+use std::{fs, io};
-use anyhow::{Context, Result};
-pub use dir::{Dir, DirList, Epoch, Rank};
-pub use stream::Stream;
+use anyhow::{bail, Context, Result};
+use bincode::Options;
+use ouroboros::self_referencing;
-use crate::util;
+pub use crate::db::dir::{Dir, Epoch, Rank};
+pub use crate::db::stream::Stream;
+use crate::{config, util};
-#[derive(Debug)]
-pub struct Database<'file> {
- pub dirs: DirList<'file>,
- pub modified: bool,
- pub data_dir: &'file Path,
+#[self_referencing]
+pub struct Database {
+ path: PathBuf,
+ bytes: Vec,
+ #[borrows(bytes)]
+ #[covariant]
+ pub dirs: Vec>,
+ dirty: bool,
}
-impl<'file> Database<'file> {
+impl Database {
+ const VERSION: u32 = 3;
+
+ pub fn open() -> Result {
+ let data_dir = config::data_dir()?;
+ Self::open_dir(data_dir)
+ }
+
+ pub fn open_dir(data_dir: impl AsRef) -> Result {
+ let data_dir = data_dir.as_ref();
+ let path = data_dir.join("db.zo");
+
+ match fs::read(&path) {
+ Ok(bytes) => Self::try_new(path, bytes, |bytes| Self::deserialize(bytes), false),
+ Err(e) if e.kind() == io::ErrorKind::NotFound => {
+ // Create data directory, but don't create any file yet. The file will be
+ // created later by [`Database::save`] if any data is modified.
+ fs::create_dir_all(data_dir).with_context(|| {
+ format!("unable to create data directory: {}", data_dir.display())
+ })?;
+ Ok(Self::new(path, Vec::new(), |_| Vec::new(), false))
+ }
+ Err(e) => {
+ Err(e).with_context(|| format!("could not read from database: {}", path.display()))
+ }
+ }
+ }
+
pub fn save(&mut self) -> Result<()> {
- if !self.modified {
+ // Only write to disk if the database is modified.
+ if !self.dirty() {
return Ok(());
}
- let buffer = self.dirs.to_bytes()?;
- let path = db_path(&self.data_dir);
- util::write(&path, &buffer).context("could not write to database")?;
- self.modified = false;
+ let bytes = Self::serialize(self.dirs())?;
+ util::write(self.borrow_path(), bytes).context("could not write to database")?;
+ self.with_dirty_mut(|dirty| *dirty = false);
+
Ok(())
}
- /// Adds a new directory or increments its rank. Also updates its last accessed time.
- pub fn add>(&mut self, path: S, now: Epoch) {
- let path = path.as_ref();
-
- match self.dirs.iter_mut().find(|dir| dir.path == path) {
+ /// Increments the rank of a directory, or creates it if it does not exist.
+ pub fn add(&mut self, path: impl AsRef + Into, by: Rank, now: Epoch) {
+ self.with_dirs_mut(|dirs| match dirs.iter_mut().find(|dir| dir.path == path.as_ref()) {
+ Some(dir) => dir.rank = (dir.rank + by).max(0.0),
None => {
- self.dirs.push(Dir { path: path.to_string().into(), last_accessed: now, rank: 1.0 });
+ dirs.push(Dir { path: path.into().into(), rank: by.max(0.0), last_accessed: now })
}
- Some(dir) => {
- dir.last_accessed = now;
- dir.rank += 1.0;
- }
- };
+ });
+ self.with_dirty_mut(|dirty| *dirty = true);
+ }
- self.modified = true;
+ /// 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.
+ pub fn add_unchecked(&mut self, path: impl AsRef + Into, rank: Rank, now: Epoch) {
+ self.with_dirs_mut(|dirs| {
+ dirs.push(Dir { path: path.into().into(), rank, last_accessed: now })
+ });
+ self.with_dirty_mut(|dirty| *dirty = true);
+ }
+
+ /// Increments the rank and updates the last_accessed of a directory, or
+ /// creates it if it does not exist.
+ pub fn add_update(&mut self, path: impl AsRef + Into, by: Rank, now: Epoch) {
+ self.with_dirs_mut(|dirs| match dirs.iter_mut().find(|dir| dir.path == path.as_ref()) {
+ Some(dir) => {
+ dir.rank = (dir.rank + by).max(0.0);
+ dir.last_accessed = now;
+ }
+ None => {
+ dirs.push(Dir { path: path.into().into(), rank: by.max(0.0), last_accessed: now })
+ }
+ });
+ self.with_dirty_mut(|dirty| *dirty = true);
+ }
+
+ /// Removes the directory with `path` from the store. This does not preserve
+ /// ordering, but is O(1).
+ pub fn remove(&mut self, path: impl AsRef) -> bool {
+ match self.dirs().iter().position(|dir| dir.path == path.as_ref()) {
+ Some(idx) => {
+ self.swap_remove(idx);
+ true
+ }
+ None => false,
+ }
+ }
+
+ pub fn swap_remove(&mut self, idx: usize) {
+ self.with_dirs_mut(|dirs| dirs.swap_remove(idx));
+ self.with_dirty_mut(|dirty| *dirty = true);
+ }
+
+ pub fn age(&mut self, max_age: Rank) {
+ let mut dirty = false;
+ self.with_dirs_mut(|dirs| {
+ let total_age = dirs.iter().map(|dir| dir.rank).sum::();
+ if total_age > max_age {
+ let factor = 0.9 * max_age / total_age;
+ for idx in (0..dirs.len()).rev() {
+ let dir = &mut dirs[idx];
+ dir.rank *= factor;
+ if dir.rank < 1.0 {
+ dirs.swap_remove(idx);
+ }
+ }
+ dirty = true;
+ }
+ });
+ self.with_dirty_mut(|dirty_prev| *dirty_prev |= dirty);
+ }
+
+ pub fn stream(&mut self, now: Epoch) -> Stream {
+ Stream::new(self, now)
}
pub fn dedup(&mut self) {
// Sort by path, so that equal paths are next to each other.
- self.dirs.sort_by(|dir1, dir2| dir1.path.cmp(&dir2.path));
+ self.sort_by_path();
- for idx in (1..self.dirs.len()).rev() {
- // Check if curr_dir and next_dir have equal paths.
- let curr_dir = &self.dirs[idx];
- let next_dir = &self.dirs[idx - 1];
- if next_dir.path != curr_dir.path {
- continue;
- }
-
- // Merge curr_dir's rank and last_accessed into next_dir.
- let rank = curr_dir.rank;
- let last_accessed = curr_dir.last_accessed;
- let next_dir = &mut self.dirs[idx - 1];
- next_dir.last_accessed = next_dir.last_accessed.max(last_accessed);
- next_dir.rank += rank;
-
- // Delete curr_dir.
- self.dirs.swap_remove(idx);
- self.modified = true;
- }
- }
-
- // Streaming iterator for directories.
- pub fn stream(&mut self, now: Epoch) -> Stream<'_, 'file> {
- Stream::new(self, now)
- }
-
- /// Removes the directory with `path` from the store. This does not preserve ordering, but is
- /// O(1).
- pub fn remove>(&mut self, path: S) -> bool {
- let path = path.as_ref();
-
- if let Some(idx) = self.dirs.iter().position(|dir| dir.path == path) {
- self.dirs.swap_remove(idx);
- self.modified = true;
- return true;
- }
-
- false
- }
-
- pub fn age(&mut self, max_age: Rank) {
- let sum_age = self.dirs.iter().map(|dir| dir.rank).sum::();
-
- if sum_age > max_age {
- let factor = 0.9 * max_age / sum_age;
-
- for idx in (0..self.dirs.len()).rev() {
- let dir = &mut self.dirs[idx];
- dir.rank *= factor;
- if dir.rank < 1.0 {
- self.dirs.swap_remove(idx);
+ let mut dirty = false;
+ self.with_dirs_mut(|dirs| {
+ for idx in (1..dirs.len()).rev() {
+ // Check if curr_dir and next_dir have equal paths.
+ let curr_dir = &dirs[idx];
+ let next_dir = &dirs[idx - 1];
+ if next_dir.path != curr_dir.path {
+ continue;
}
- }
- self.modified = true;
+ // Merge curr_dir's rank and last_accessed into next_dir.
+ let rank = curr_dir.rank;
+ let last_accessed = curr_dir.last_accessed;
+ let next_dir = &mut dirs[idx - 1];
+ next_dir.last_accessed = next_dir.last_accessed.max(last_accessed);
+ next_dir.rank += rank;
+
+ // Delete curr_dir.
+ dirs.swap_remove(idx);
+ dirty = true;
+ }
+ });
+ self.with_dirty_mut(|dirty_prev| *dirty_prev |= dirty);
+ }
+
+ pub fn sort_by_path(&mut self) {
+ self.with_dirs_mut(|dirs| dirs.sort_unstable_by(|dir1, dir2| dir1.path.cmp(&dir2.path)));
+ self.with_dirty_mut(|dirty| *dirty = true);
+ }
+
+ pub fn sort_by_score(&mut self, now: Epoch) {
+ self.with_dirs_mut(|dirs| {
+ dirs.sort_unstable_by(|dir1: &Dir, dir2: &Dir| {
+ dir1.score(now).total_cmp(&dir2.score(now))
+ })
+ });
+ self.with_dirty_mut(|dirty| *dirty = true);
+ }
+
+ pub fn dirty(&self) -> bool {
+ *self.borrow_dirty()
+ }
+
+ pub fn dirs(&self) -> &[Dir] {
+ self.borrow_dirs()
+ }
+
+ fn serialize(dirs: &[Dir<'_>]) -> Result> {
+ (|| -> bincode::Result<_> {
+ // Preallocate buffer with combined size of sections.
+ let buffer_size =
+ bincode::serialized_size(&Self::VERSION)? + bincode::serialized_size(&dirs)?;
+ let mut buffer = Vec::with_capacity(buffer_size as usize);
+
+ // Serialize sections into buffer.
+ bincode::serialize_into(&mut buffer, &Self::VERSION)?;
+ bincode::serialize_into(&mut buffer, &dirs)?;
+
+ Ok(buffer)
+ })()
+ .context("could not serialize database")
+ }
+
+ fn deserialize(bytes: &[u8]) -> Result> {
+ // Assume a maximum size for the database. This prevents bincode from throwing
+ // strange errors when it encounters invalid data.
+ const MAX_SIZE: u64 = 32 << 20; // 32 MiB
+ let deserializer = &mut bincode::options().with_fixint_encoding().with_limit(MAX_SIZE);
+
+ // Split bytes into sections.
+ let version_size = deserializer.serialized_size(&Self::VERSION).unwrap() as _;
+ if bytes.len() < version_size {
+ bail!("could not deserialize database: corrupted data");
}
- }
-}
+ let (bytes_version, bytes_dirs) = bytes.split_at(version_size);
-pub struct DatabaseFile {
- buffer: Vec,
- data_dir: PathBuf,
-}
-
-impl DatabaseFile {
- pub fn new>(data_dir: P) -> Self {
- DatabaseFile { buffer: Vec::new(), data_dir: data_dir.into() }
- }
-
- pub fn open(&mut self) -> Result {
- // Read the entire database to memory. For smaller files, this is faster than
- // mmap / streaming, and allows for zero-copy deserialization.
- let path = db_path(&self.data_dir);
- match fs::read(&path) {
- Ok(buffer) => {
- self.buffer = buffer;
- let dirs = DirList::from_bytes(&self.buffer)
- .with_context(|| format!("could not deserialize database: {}", path.display()))?;
- Ok(Database { dirs, modified: false, data_dir: &self.data_dir })
+ // Deserialize sections.
+ let version = deserializer.deserialize(bytes_version)?;
+ let dirs = match version {
+ Self::VERSION => {
+ deserializer.deserialize(bytes_dirs).context("could not deserialize database")?
}
- Err(e) if e.kind() == io::ErrorKind::NotFound => {
- // Create data directory, but don't create any file yet. The file will be created
- // later by [`Database::save`] if any data is modified.
- fs::create_dir_all(&self.data_dir)
- .with_context(|| format!("unable to create data directory: {}", self.data_dir.display()))?;
- Ok(Database { dirs: DirList::new(), modified: false, data_dir: &self.data_dir })
+ version => {
+ bail!("unsupported version (got {version}, supports {})", Self::VERSION)
}
- Err(e) => Err(e).with_context(|| format!("could not read from database: {}", path.display())),
- }
- }
-}
+ };
-fn db_path>(data_dir: P) -> PathBuf {
- const DB_FILENAME: &str = "db.zo";
- data_dir.as_ref().join(DB_FILENAME)
+ Ok(dirs)
+ }
}
#[cfg(test)]
@@ -155,50 +240,49 @@ mod tests {
#[test]
fn add() {
+ let data_dir = tempfile::tempdir().unwrap();
let path = if cfg!(windows) { r"C:\foo\bar" } else { "/foo/bar" };
let now = 946684800;
- let data_dir = tempfile::tempdir().unwrap();
{
- let mut db = DatabaseFile::new(data_dir.path());
- let mut db = db.open().unwrap();
- db.add(path, now);
- db.add(path, now);
+ let mut db = Database::open_dir(data_dir.path()).unwrap();
+ db.add(path, 1.0, now);
+ db.add(path, 1.0, now);
db.save().unwrap();
}
- {
- let mut db = DatabaseFile::new(data_dir.path());
- let db = db.open().unwrap();
- assert_eq!(db.dirs.len(), 1);
- let dir = &db.dirs[0];
+ {
+ let db = Database::open_dir(data_dir.path()).unwrap();
+ assert_eq!(db.dirs().len(), 1);
+
+ let dir = &db.dirs()[0];
assert_eq!(dir.path, path);
+ assert!((dir.rank - 2.0).abs() < 0.01);
assert_eq!(dir.last_accessed, now);
}
}
#[test]
fn remove() {
+ let data_dir = tempfile::tempdir().unwrap();
let path = if cfg!(windows) { r"C:\foo\bar" } else { "/foo/bar" };
let now = 946684800;
- let data_dir = tempfile::tempdir().unwrap();
{
- let mut db = DatabaseFile::new(data_dir.path());
- let mut db = db.open().unwrap();
- db.add(path, now);
+ let mut db = Database::open_dir(data_dir.path()).unwrap();
+ db.add(path, 1.0, now);
db.save().unwrap();
}
+
{
- let mut db = DatabaseFile::new(data_dir.path());
- let mut db = db.open().unwrap();
+ let mut db = Database::open_dir(data_dir.path()).unwrap();
assert!(db.remove(path));
db.save().unwrap();
}
+
{
- let mut db = DatabaseFile::new(data_dir.path());
- let mut db = db.open().unwrap();
- assert!(db.dirs.is_empty());
+ let mut db = Database::open_dir(data_dir.path()).unwrap();
+ assert!(db.dirs().is_empty());
assert!(!db.remove(path));
db.save().unwrap();
}
diff --git a/src/db/stream.rs b/src/db/stream.rs
index e5d3eb9..44b59d9 100644
--- a/src/db/stream.rs
+++ b/src/db/stream.rs
@@ -2,36 +2,36 @@ use std::iter::Rev;
use std::ops::Range;
use std::{fs, path};
-use ordered_float::OrderedFloat;
+use crate::db::{Database, Dir, Epoch};
+use crate::util::{self, MONTH};
-use super::{Database, Dir, Epoch};
-use crate::util;
-
-pub struct Stream<'db, 'file> {
- db: &'db mut Database<'file>,
+pub struct Stream<'a> {
+ // State
+ db: &'a mut Database,
idxs: Rev>,
+ did_exclude: bool,
+ // Configuration
keywords: Vec,
-
check_exists: bool,
expire_below: Epoch,
resolve_symlinks: bool,
-
exclude_path: Option,
}
-impl<'db, 'file> Stream<'db, 'file> {
- pub fn new(db: &'db mut Database<'file>, now: Epoch) -> Self {
- // Iterate in descending order of score.
- db.dirs.sort_unstable_by_key(|dir| OrderedFloat(dir.score(now)));
- let idxs = (0..db.dirs.len()).rev();
+impl<'a> Stream<'a> {
+ pub fn new(db: &'a mut Database, now: Epoch) -> Self {
+ db.sort_by_score(now);
+ let idxs = (0..db.dirs().len()).rev();
- // If a directory is deleted and hasn't been used for 90 days, delete it from the database.
- let expire_below = now.saturating_sub(90 * 24 * 60 * 60);
+ // If a directory is deleted and hasn't been used for 3 months, delete
+ // it from the database.
+ let expire_below = now.saturating_sub(3 * MONTH);
Stream {
db,
idxs,
+ did_exclude: false,
keywords: Vec::new(),
check_exists: false,
expire_below,
@@ -40,7 +40,7 @@ impl<'db, 'file> Stream<'db, 'file> {
}
}
- pub fn with_exclude>(mut self, path: S) -> Self {
+ pub fn with_exclude(mut self, path: impl Into) -> Self {
self.exclude_path = Some(path.into());
self
}
@@ -51,14 +51,14 @@ impl<'db, 'file> Stream<'db, 'file> {
self
}
- pub fn with_keywords>(mut self, keywords: &[S]) -> Self {
+ pub fn with_keywords(mut self, keywords: &[impl AsRef]) -> Self {
self.keywords = keywords.iter().map(util::to_lowercase).collect();
self
}
- pub fn next(&mut self) -> Option<&Dir<'file>> {
+ pub fn next(&mut self) -> Option<&Dir> {
while let Some(idx) = self.idxs.next() {
- let dir = &self.db.dirs[idx];
+ let dir = &self.db.dirs()[idx];
if !self.matches_keywords(&dir.path) {
continue;
@@ -66,32 +66,36 @@ impl<'db, 'file> Stream<'db, 'file> {
if !self.matches_exists(&dir.path) {
if dir.last_accessed < self.expire_below {
- self.db.dirs.swap_remove(idx);
- self.db.modified = true;
+ self.db.swap_remove(idx);
}
continue;
}
if Some(dir.path.as_ref()) == self.exclude_path.as_deref() {
+ self.did_exclude = true;
continue;
}
- let dir = &self.db.dirs[idx];
+ let dir = &self.db.dirs()[idx];
return Some(dir);
}
None
}
- fn matches_exists>(&self, path: S) -> bool {
+ pub fn did_exclude(&self) -> bool {
+ self.did_exclude
+ }
+
+ fn matches_exists(&self, path: &str) -> bool {
if !self.check_exists {
return true;
}
let resolver = if self.resolve_symlinks { fs::symlink_metadata } else { fs::metadata };
- resolver(path.as_ref()).map(|m| m.is_dir()).unwrap_or_default()
+ resolver(path).map(|m| m.is_dir()).unwrap_or_default()
}
- fn matches_keywords>(&self, path: S) -> bool {
+ fn matches_keywords(&self, path: &str) -> bool {
let (keywords_last, keywords) = match self.keywords.split_last() {
Some(split) => split,
None => return true,
@@ -126,7 +130,7 @@ mod tests {
use rstest::rstest;
- use super::Database;
+ use super::*;
#[rstest]
// Case normalization
@@ -149,8 +153,8 @@ mod tests {
#[case(&["/foo/", "/bar"], "/foo/bar", false)]
#[case(&["/foo/", "/bar"], "/foo/baz/bar", true)]
fn query(#[case] keywords: &[&str], #[case] path: &str, #[case] is_match: bool) {
- let mut db = Database { dirs: Vec::new().into(), modified: false, data_dir: &PathBuf::new() };
- let stream = db.stream(0).with_keywords(keywords);
+ let db = &mut Database::new(PathBuf::new(), Vec::new(), |_| Vec::new(), false);
+ let stream = Stream::new(db, 0).with_keywords(keywords);
assert_eq!(is_match, stream.matches_keywords(path));
}
}
diff --git a/src/error.rs b/src/error.rs
index 4a482b8..d2baa7f 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -6,7 +6,7 @@ use anyhow::{bail, Context, Result};
/// Custom error type for early exit.
#[derive(Debug)]
pub struct SilentExit {
- pub code: i32,
+ pub code: u8,
}
impl Display for SilentExit {
@@ -23,7 +23,7 @@ impl BrokenPipeHandler for io::Result<()> {
fn pipe_exit(self, device: &str) -> Result<()> {
match self {
Err(e) if e.kind() == io::ErrorKind::BrokenPipe => bail!(SilentExit { code: 0 }),
- result => result.with_context(|| format!("could not write to {}", device)),
+ result => result.with_context(|| format!("could not write to {device}")),
}
}
}
diff --git a/src/main.rs b/src/main.rs
index b22b216..18ac3a6 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,7 +1,7 @@
#![allow(clippy::single_component_path_imports)]
// rstest_reuse must be imported at the top of the crate.
-#[cfg(test)]
+#[cfg(all(test, feature = "nix-dev"))]
use rstest_reuse;
mod cmd;
@@ -11,26 +11,28 @@ mod error;
mod shell;
mod util;
+use std::env;
use std::io::{self, Write};
-use std::{env, process};
+use std::process::ExitCode;
use clap::Parser;
use crate::cmd::{Cmd, Run};
use crate::error::SilentExit;
-pub fn main() {
+pub fn main() -> ExitCode {
// Forcibly disable backtraces.
env::remove_var("RUST_LIB_BACKTRACE");
env::remove_var("RUST_BACKTRACE");
- if let Err(e) = Cmd::parse().run() {
- match e.downcast::() {
- Ok(SilentExit { code }) => process::exit(code),
+ match Cmd::parse().run() {
+ Ok(()) => ExitCode::SUCCESS,
+ Err(e) => match e.downcast::() {
+ Ok(SilentExit { code }) => code.into(),
Err(e) => {
- let _ = writeln!(io::stderr(), "zoxide: {:?}", e);
- process::exit(1);
+ _ = writeln!(io::stderr(), "zoxide: {e:?}");
+ ExitCode::FAILURE
}
- }
+ },
}
}
diff --git a/src/shell.rs b/src/shell.rs
index 79554f4..a50c184 100644
--- a/src/shell.rs
+++ b/src/shell.rs
@@ -57,7 +57,7 @@ mod tests {
let opts = Opts { cmd, hook, echo, resolve_symlinks };
let source = Bash(&opts).render().unwrap();
Command::new("bash")
- .args(&["--noprofile", "--norc", "-e", "-u", "-o", "pipefail", "-c", &source])
+ .args(["--noprofile", "--norc", "-e", "-u", "-o", "pipefail", "-c", &source])
.assert()
.success()
.stdout("")
@@ -70,7 +70,7 @@ mod tests {
let source = Bash(&opts).render().unwrap();
Command::new("shellcheck")
- .args(&["--enable", "all", "--shell", "bash", "-"])
+ .args(["--enable", "all", "--shell", "bash", "-"])
.write_stdin(source)
.assert()
.success()
@@ -85,7 +85,7 @@ mod tests {
source.push('\n');
Command::new("shfmt")
- .args(&["-d", "-s", "-ln", "bash", "-i", "4", "-ci", "-"])
+ .args(["--diff", "--indent=4", "--language-dialect=bash", "--simplify", "-"])
.write_stdin(source)
.assert()
.success()
@@ -96,16 +96,21 @@ mod tests {
#[apply(opts)]
fn elvish_elvish(cmd: Option<&str>, hook: InitHook, echo: bool, resolve_symlinks: bool) {
let opts = Opts { cmd, hook, echo, resolve_symlinks };
- let mut source = String::new();
+ let mut source = String::default();
- // Filter out lines using edit:*, since those functions are only available in the
- // interactive editor.
- for line in Elvish(&opts).render().unwrap().split('\n').filter(|line| !line.contains("edit:")) {
+ // Filter out lines using edit:*, since those functions are only available in
+ // the interactive editor.
+ for line in Elvish(&opts).render().unwrap().lines().filter(|line| !line.contains("edit:")) {
source.push_str(line);
source.push('\n');
}
- Command::new("elvish").args(&["-c", &source, "-norc"]).assert().success().stdout("").stderr("");
+ Command::new("elvish")
+ .args(["-c", &source, "-norc"])
+ .assert()
+ .success()
+ .stdout("")
+ .stderr("");
}
#[apply(opts)]
@@ -118,7 +123,7 @@ mod tests {
Command::new("fish")
.env("HOME", tempdir)
- .args(&["--command", &source, "--private"])
+ .args(["--command", &source, "--no-config", "--private"])
.assert()
.success()
.stdout("")
@@ -149,10 +154,14 @@ mod tests {
let source = Nushell(&opts).render().unwrap();
let tempdir = tempfile::tempdir().unwrap();
- let tempdir = tempdir.path().to_str().unwrap();
+ let tempdir = tempdir.path();
- let assert =
- Command::new("nu").env("HOME", tempdir).args(&["--commands", &source]).assert().success().stderr("");
+ let assert = Command::new("nu")
+ .env("HOME", tempdir)
+ .args(["--commands", &source])
+ .assert()
+ .success()
+ .stderr("");
if opts.hook != InitHook::Pwd {
assert.stdout("");
@@ -165,7 +174,7 @@ mod tests {
let source = Posix(&opts).render().unwrap();
let assert = Command::new("bash")
- .args(&["--posix", "--noprofile", "--norc", "-e", "-u", "-o", "pipefail", "-c", &source])
+ .args(["--posix", "--noprofile", "--norc", "-e", "-u", "-o", "pipefail", "-c", &source])
.assert()
.success()
.stderr("");
@@ -179,7 +188,8 @@ mod tests {
let opts = Opts { cmd, hook, echo, resolve_symlinks };
let source = Posix(&opts).render().unwrap();
- let assert = Command::new("dash").args(&["-e", "-u", "-c", &source]).assert().success().stderr("");
+ let assert =
+ Command::new("dash").args(["-e", "-u", "-c", &source]).assert().success().stderr("");
if opts.hook != InitHook::Pwd {
assert.stdout("");
}
@@ -191,7 +201,7 @@ mod tests {
let source = Posix(&opts).render().unwrap();
Command::new("shellcheck")
- .args(&["--enable", "all", "--shell", "sh", "-"])
+ .args(["--enable", "all", "--shell", "sh", "-"])
.write_stdin(source)
.assert()
.success()
@@ -206,7 +216,7 @@ mod tests {
source.push('\n');
Command::new("shfmt")
- .args(&["-d", "-s", "-ln", "posix", "-i", "4", "-ci", "-"])
+ .args(["--diff", "--indent=4", "--language-dialect=posix", "--simplify", "-"])
.write_stdin(source)
.assert()
.success()
@@ -221,7 +231,7 @@ mod tests {
Powershell(&opts).render_into(&mut source).unwrap();
Command::new("pwsh")
- .args(&["-NoLogo", "-NonInteractive", "-NoProfile", "-Command", &source])
+ .args(["-NoLogo", "-NonInteractive", "-NoProfile", "-Command", &source])
.assert()
.success()
.stdout("")
@@ -234,7 +244,12 @@ mod tests {
let mut source = Xonsh(&opts).render().unwrap();
source.push('\n');
- Command::new("black").args(&["--check", "--diff", "-"]).write_stdin(source).assert().success().stdout("");
+ Command::new("black")
+ .args(["--check", "--diff", "-"])
+ .write_stdin(source)
+ .assert()
+ .success()
+ .stdout("");
}
#[apply(opts)]
@@ -242,7 +257,7 @@ mod tests {
let opts = Opts { cmd, hook, echo, resolve_symlinks };
let source = Xonsh(&opts).render().unwrap();
- Command::new("mypy").args(&["--command", &source, "--strict"]).assert().success().stderr("");
+ Command::new("mypy").args(["--command", &source, "--strict"]).assert().success().stderr("");
}
#[apply(opts)]
@@ -252,7 +267,7 @@ mod tests {
source.push('\n');
Command::new("pylint")
- .args(&["--from-stdin", "--persistent=n", "zoxide"])
+ .args(["--from-stdin", "--persistent=n", "zoxide"])
.write_stdin(source)
.assert()
.success()
@@ -268,7 +283,7 @@ mod tests {
let tempdir = tempdir.path().to_str().unwrap();
Command::new("xonsh")
- .args(&["-c", &source, "--no-rc"])
+ .args(["-c", &source, "--no-rc"])
.env("HOME", tempdir)
.assert()
.success()
@@ -283,7 +298,7 @@ mod tests {
// ShellCheck doesn't support zsh yet: https://github.com/koalaman/shellcheck/issues/809
Command::new("shellcheck")
- .args(&["--enable", "all", "--shell", "bash", "-"])
+ .args(["--enable", "all", "--shell", "bash", "-"])
.write_stdin(source)
.assert()
.success()
@@ -297,7 +312,7 @@ mod tests {
let source = Zsh(&opts).render().unwrap();
Command::new("zsh")
- .args(&["-e", "-u", "-o", "pipefail", "--no-globalrcs", "--no-rcs", "-c", &source])
+ .args(["-e", "-u", "-o", "pipefail", "--no-globalrcs", "--no-rcs", "-c", &source])
.assert()
.success()
.stdout("")
diff --git a/src/util.rs b/src/util.rs
index 2ad0b16..47e7e93 100644
--- a/src/util.rs
+++ b/src/util.rs
@@ -1,83 +1,149 @@
-use std::env;
+use std::ffi::OsStr;
use std::fs::{self, File, OpenOptions};
use std::io::{self, Read, Write};
-use std::mem;
use std::path::{Component, Path, PathBuf};
-use std::process::Command;
-use std::process::{Child, ChildStdin, Stdio};
+use std::process::{Child, Command, Stdio};
use std::time::SystemTime;
+use std::{env, mem};
-use anyhow::{anyhow, bail, Context, Result};
+#[cfg(windows)]
+use anyhow::anyhow;
+use anyhow::{bail, Context, Result};
-use crate::config;
-use crate::db::Epoch;
+use crate::db::{Dir, Epoch};
use crate::error::SilentExit;
-pub struct Fzf {
- child: Child,
-}
+pub const SECOND: Epoch = 1;
+pub const MINUTE: Epoch = 60 * SECOND;
+pub const HOUR: Epoch = 60 * MINUTE;
+pub const DAY: Epoch = 24 * HOUR;
+pub const WEEK: Epoch = 7 * DAY;
+pub const MONTH: Epoch = 30 * DAY;
+
+pub struct Fzf(Command);
impl Fzf {
- const ERR_NOT_FOUND: &'static str = "could not find fzf, is it installed?";
+ const ERR_FZF_NOT_FOUND: &str = "could not find fzf, is it installed?";
- pub fn new(multiple: bool) -> Result {
- let bin = if cfg!(windows) { "fzf.exe" } else { "fzf" };
- let mut command = get_command(bin).map_err(|_| anyhow!(Self::ERR_NOT_FOUND))?;
- if multiple {
- command.arg("-m");
- }
- command.arg("-n2..").stdin(Stdio::piped()).stdout(Stdio::piped());
- if let Some(fzf_opts) = config::fzf_opts() {
- command.env("FZF_DEFAULT_OPTS", fzf_opts);
- } else {
- command.args(&[
- // Search result
- "--no-sort",
- // Interface
- "--keep-right",
- // Layout
- "--height=40%",
- "--info=inline",
- "--layout=reverse",
- // Scripting
- "--exit-0",
- "--select-1",
- // Key/Event bindings
- "--bind=ctrl-z:ignore",
- ]);
- if cfg!(unix) {
- command.env("SHELL", "sh");
- command.arg(r"--preview=\command -p ls -p {2..}");
- }
- }
+ pub fn new() -> Result {
+ // On Windows, CreateProcess implicitly searches the current working
+ // directory for the executable, which is a potential security issue.
+ // Instead, we resolve the path to the executable and then pass it to
+ // CreateProcess.
+ #[cfg(windows)]
+ let program = which::which("fzf.exe").map_err(|_| anyhow!(Self::ERR_FZF_NOT_FOUND))?;
+ #[cfg(not(windows))]
+ let program = "fzf";
- let child = match command.spawn() {
- Ok(child) => child,
- Err(e) if e.kind() == io::ErrorKind::NotFound => bail!(Self::ERR_NOT_FOUND),
- Err(e) => Err(e).context("could not launch fzf")?,
- };
+ // TODO: check version of fzf here.
- Ok(Fzf { child })
+ let mut cmd = Command::new(program);
+ cmd.args([
+ // Search mode
+ "--delimiter=\t",
+ "--nth=2",
+ // Scripting
+ "--read0",
+ ])
+ .stdin(Stdio::piped())
+ .stdout(Stdio::piped());
+
+ Ok(Fzf(cmd))
}
- pub fn stdin(&mut self) -> &mut ChildStdin {
- self.child.stdin.as_mut().unwrap()
+ pub fn enable_preview(&mut self) -> &mut Self {
+ // Previews are only supported on UNIX.
+ if !cfg!(unix) {
+ return self;
+ }
+
+ self.args([
+ // Non-POSIX args are only available on certain operating systems.
+ if cfg!(target_os = "linux") {
+ r"--preview=\command -p ls -Cp --color=always --group-directories-first {2..}"
+ } else {
+ r"--preview=\command -p ls -Cp {2..}"
+ },
+ // Rounded edges don't display correctly on some terminals.
+ "--preview-window=down,30%,sharp",
+ ])
+ .envs([
+ // Enables colorized `ls` output on macOS / FreeBSD.
+ ("CLICOLOR", "1"),
+ // Forces colorized `ls` output when the output is not a
+ // TTY (like in fzf's preview window) on macOS /
+ // FreeBSD.
+ ("CLICOLOR_FORCE", "1"),
+ // Ensures that the preview command is run in a
+ // POSIX-compliant shell, regardless of what shell the
+ // user has selected.
+ ("SHELL", "sh"),
+ ])
}
- pub fn select(mut self) -> Result {
+ pub fn args(&mut self, args: I) -> &mut Self
+ where
+ I: IntoIterator- ,
+ S: AsRef,
+ {
+ self.0.args(args);
+ self
+ }
+
+ pub fn env(&mut self, key: K, val: V) -> &mut Self
+ where
+ K: AsRef,
+ V: AsRef,
+ {
+ self.0.env(key, val);
+ self
+ }
+
+ pub fn envs(&mut self, vars: I) -> &mut Self
+ where
+ I: IntoIterator
- ,
+ K: AsRef,
+ V: AsRef,
+ {
+ self.0.envs(vars);
+ self
+ }
+
+ pub fn spawn(&mut self) -> Result {
+ match self.0.spawn() {
+ Ok(child) => Ok(FzfChild(child)),
+ Err(e) if e.kind() == io::ErrorKind::NotFound => bail!(Self::ERR_FZF_NOT_FOUND),
+ Err(e) => Err(e).context("could not launch fzf"),
+ }
+ }
+}
+
+pub struct FzfChild(Child);
+
+impl FzfChild {
+ pub fn write(&mut self, dir: &Dir, now: Epoch) -> Result