Compare commits

...

88 Commits

Author SHA1 Message Date
Ajeet D'Souza
073500cbdf Add Warp sponsorship 2023-10-10 14:27:56 +05:30
dependabot[bot]
a624ceef54
Bump Swatinem/rust-cache from 2.6.2 to 2.7.0 (#622)
Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.6.2 to 2.7.0.
- [Release notes](https://github.com/swatinem/rust-cache/releases)
- [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md)
- [Commits](https://github.com/swatinem/rust-cache/compare/v2.6.2...v2.7.0)

---
updated-dependencies:
- dependency-name: Swatinem/rust-cache
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-19 17:20:53 +05:30
Connor Sullivan
e3ab444976
Specify exact winget id (#619)
Specifies exact winget identifier
2023-09-11 14:12:25 +05:30
dependabot[bot]
be051f2f5a
Bump cachix/install-nix-action from 22 to 23 (#616)
Bumps [cachix/install-nix-action](https://github.com/cachix/install-nix-action) from 22 to 23.
- [Release notes](https://github.com/cachix/install-nix-action/releases)
- [Commits](https://github.com/cachix/install-nix-action/compare/v22...v23)

---
updated-dependencies:
- dependency-name: cachix/install-nix-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-05 02:28:58 +05:30
dependabot[bot]
bc34c49480
Bump actions/checkout from 3 to 4 (#615)
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-05 02:28:49 +05:30
Ajeet D'Souza
9cbd029438 Add pacstall instructions 2023-09-03 12:24:13 +05:30
dependabot[bot]
3ca0cf5fc6
Bump Swatinem/rust-cache from 2.6.1 to 2.6.2 (#611)
Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.6.1 to 2.6.2.
- [Release notes](https://github.com/swatinem/rust-cache/releases)
- [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md)
- [Commits](https://github.com/swatinem/rust-cache/compare/v2.6.1...v2.6.2)

---
updated-dependencies:
- dependency-name: Swatinem/rust-cache
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-28 01:36:02 +05:30
dependabot[bot]
28563105ba
Bump Swatinem/rust-cache from 2.6.0 to 2.6.1 (#606)
Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.6.0 to 2.6.1.
- [Release notes](https://github.com/swatinem/rust-cache/releases)
- [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md)
- [Commits](https://github.com/swatinem/rust-cache/compare/v2.6.0...v2.6.1)

---
updated-dependencies:
- dependency-name: Swatinem/rust-cache
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-15 00:32:47 +05:30
dependabot[bot]
01da2dbff6
Bump Swatinem/rust-cache from 2.5.1 to 2.6.0 (#603)
Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.5.1 to 2.6.0.
- [Release notes](https://github.com/swatinem/rust-cache/releases)
- [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md)
- [Commits](https://github.com/swatinem/rust-cache/compare/v2.5.1...v2.6.0)

---
updated-dependencies:
- dependency-name: Swatinem/rust-cache
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-08 02:23:28 +05:30
sitiom
66d073ee72
Change Winget Releaser job to ubuntu-latest (#602) 2023-08-04 10:14:05 +05:30
Ajeet D'Souza
5b9c54881f Fix crates.io keyword limit 2023-08-04 08:52:03 +05:30
Ajeet D'Souza
1bfcdfacf2 chore(release): v0.9.2 2023-08-04 07:57:38 +05:30
matan h
3898d99f0c
Use global scope for PowerShell variables / functions (#597)
Co-authored-by: Ajeet D'Souza <98ajeet@gmail.com>
2023-08-04 07:25:48 +05:30
Ajeet D'Souza
42e1d5dd3b Fix README formatting 2023-07-15 18:33:49 +05:30
Ajeet D'Souza
acf3fcc7fb
Add nix install instructions for macOS 2023-07-11 11:31:55 +05:30
dependabot[bot]
72a49ec9c9
Bump Swatinem/rust-cache from 2.5.0 to 2.5.1 (#588)
Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.5.0 to 2.5.1.
- [Release notes](https://github.com/swatinem/rust-cache/releases)
- [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md)
- [Commits](https://github.com/swatinem/rust-cache/compare/v2.5.0...v2.5.1)

---
updated-dependencies:
- dependency-name: Swatinem/rust-cache
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-04 00:29:10 +05:30
Ajeet D'Souza
651fc3c00e Fix CI 2023-07-03 21:01:55 +05:30
JT
786519aa8f
Update nushell env update syntax (#587) 2023-07-01 01:01:08 +05:30
Ajeet D'Souza
5f8974fc5c
Add aerc under third-party integrations 2023-06-26 13:58:15 +05:30
dependabot[bot]
03c465b778
Bump cachix/install-nix-action from 21 to 22 (#585)
Bumps [cachix/install-nix-action](https://github.com/cachix/install-nix-action) from 21 to 22.
- [Release notes](https://github.com/cachix/install-nix-action/releases)
- [Commits](https://github.com/cachix/install-nix-action/compare/v21...v22)

---
updated-dependencies:
- dependency-name: cachix/install-nix-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-20 01:50:58 +05:30
dependabot[bot]
469b858278
Bump Swatinem/rust-cache from 2.4.0 to 2.5.0 (#584)
Bumps [Swatinem/rust-cache](https://github.com/Swatinem/rust-cache) from 2.4.0 to 2.5.0.
- [Release notes](https://github.com/Swatinem/rust-cache/releases)
- [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Swatinem/rust-cache/compare/v2.4.0...v2.5.0)

---
updated-dependencies:
- dependency-name: Swatinem/rust-cache
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-20 01:48:51 +05:30
hyperpuncher
e938034423
fix link to arch repo (#583)
* fix link to arch repo

Community packages were merged to extra.

* fix link title

* fix link title
2023-06-18 12:02:36 +05:30
Ajeet D'Souza
6dedfcd74a Update clap 2023-06-11 01:08:07 +05:30
Ajeet D'Souza
49e7824c42
Fix CI 2023-06-07 07:27:46 +05:30
mataha
157b221055
Remove underlined text artifacts on Windows (#581) 2023-06-07 07:17:20 +05:30
dependabot[bot]
1d64ae0b87
Bump cachix/install-nix-action from 20 to 21 (#578)
Bumps [cachix/install-nix-action](https://github.com/cachix/install-nix-action) from 20 to 21.
- [Release notes](https://github.com/cachix/install-nix-action/releases)
- [Commits](https://github.com/cachix/install-nix-action/compare/v20...v21)

---
updated-dependencies:
- dependency-name: cachix/install-nix-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-30 01:17:59 +05:30
dependabot[bot]
25b4c057fb
Bump Swatinem/rust-cache from 2.3.0 to 2.4.0 (#575)
Bumps [Swatinem/rust-cache](https://github.com/Swatinem/rust-cache) from 2.3.0 to 2.4.0.
- [Release notes](https://github.com/Swatinem/rust-cache/releases)
- [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Swatinem/rust-cache/compare/v2.3.0...v2.4.0)

---
updated-dependencies:
- dependency-name: Swatinem/rust-cache
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-23 09:52:38 +05:30
dependabot[bot]
c6ab1012e4
Bump Swatinem/rust-cache from 1.0.2 to 2.3.0 (#572)
Bumps [Swatinem/rust-cache](https://github.com/Swatinem/rust-cache) from 1.0.2 to 2.3.0.
- [Release notes](https://github.com/Swatinem/rust-cache/releases)
- [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Swatinem/rust-cache/compare/v1.0.2...v2.3.0)

---
updated-dependencies:
- dependency-name: Swatinem/rust-cache
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-16 04:13:28 +05:30
Ajeet D'Souza
153e060043 Add short option -a for --all 2023-05-09 00:51:26 +05:30
Ajeet D'Souza
e05637ded1 Improve clarity of setup instructions 2023-05-08 22:40:04 +05:30
Ajeet D'Souza
ce8f2395d5 Job-level permission for action 2023-05-07 23:48:30 +05:30
sitiom
54e9f9aa7a
Add Winget Releaser workflow (#532) 2023-05-07 22:47:02 +05:30
Ajeet D'Souza
686d116ad5 chore(release): v0.9.1 2023-05-07 21:07:00 +05:30
Ajeet D'Souza
21211e43d1 Add DeepSource 2023-05-07 19:54:24 +05:30
deepsource-io[bot]
3483ab013a
ci: Update .deepsource.toml 2023-05-07 14:00:34 +00:00
Ajeet D'Souza
74ccc0232f
Support for PWD hooks on all versions of PowerShell (#563) 2023-05-07 19:19:53 +05:30
Ajeet D'Souza
0ecfbf7e68 Format + lint more filetypes 2023-05-06 14:25:33 +05:30
Ajeet D'Souza
0b51cb6591
Improve completions (#562) 2023-05-06 20:48:19 +05:30
Ajeet D'Souza
5de13befbc Replace xtask with justfile 2023-05-06 03:55:18 +05:30
Ajeet D'Souza
a7c8231e56 Update GitHub Actions 2023-05-05 13:24:44 +05:30
Ajeet D'Souza
03df9d4d1f Update install script to support Windows 2023-04-30 15:05:51 +05:30
Ajeet D'Souza
56956779b2 Consistent style for environment variables 2023-04-16 19:54:55 +05:30
Ajeet D'Souza
4dff2b1602 Add support for older Fish versions 2023-04-15 00:08:16 +05:30
Lily Mara
4f475a75cb
Nushell: use new lambda syntax 2023-04-15 00:05:05 +05:30
StepSecurity Bot
fa1cfb490d Harden GitHub Actions (#544)
* Harden GitHub Actions

* Update release.yml

---------

Co-authored-by: Ajeet D'Souza <98ajeet@gmail.com>
2023-03-19 15:20:22 +05:30
Josh Medeski
8f1cb75732 docs: add t to third-party integrations (#537) 2023-03-01 14:15:46 +05:30
Ajeet D'Souza
39f5aaa308 Formatting 2023-03-01 13:55:22 +05:30
Ajeet D'Souza
3c8a0d6b13 Add alias for import --from=fasd 2023-02-18 10:55:45 +05:30
sitiom
4ded81e277
Add Winget installation in the README (#530)
Co-authored-by: Ajeet D'Souza <98ajeet@gmail.com>
2023-01-31 10:37:50 +05:30
Ajeet D'Souza
7299e33a80 Update README 2023-01-27 00:02:24 +05:30
Ajeet D'Souza
1c651bf8cd
Upgrade minimum supported fzf version (#523) 2023-01-23 12:04:11 +05:30
Ajeet D'Souza
066730f95d Add MSRV to CI 2023-01-18 11:05:19 +05:30
Ajeet D'Souza
575cfd485c
Add instructions to import from fasd (#519) 2023-01-18 09:20:45 +05:30
Ajeet D'Souza
59d80a7b60
fzf: disable sorting of results (#515) 2023-01-16 01:17:41 +05:30
Blonteractor
2c2286cea2
Added better output in query when already in the matched directory (#463) 2023-01-09 15:55:33 +05:30
Ajeet D'Souza
65c5e86968 Simplify generics 2023-01-08 22:34:07 +05:30
Ajeet D'Souza
86741bbe6a Define completions on __zoxide_z 2023-01-08 07:51:03 +05:30
Ajeet D'Souza
2555870c31 Handle rate limits in installer 2023-01-08 05:21:00 +05:30
Ajeet D'Souza
b747a0cfc3 Use toml-action 2023-01-08 05:03:09 +05:30
Ajeet D'Souza
21520d9bbf chore(release): v0.9.0 2023-01-08 04:42:50 +05:30
Ajeet D'Souza
3ab0a7b8fd
Edit subcommand (#498) 2023-01-07 22:58:10 +05:30
Ajeet D'Souza
cf0c9c002e Add no response workflow 2023-01-07 09:12:05 +05:30
Ajeet D'Souza
7af4da1dab fzf: enable colors in preview when possible on macOS / BSD 2022-12-09 02:29:01 +05:30
Ajeet D'Souza
82d4f73aae
Use tab to cycle through completions (#496) 2022-12-09 01:35:13 +05:30
Ajeet D'Souza
e8c5f7a975
Upgrade Nushell to v0.73.0 (#495) 2022-12-08 23:24:43 +05:30
Ajeet D'Souza
f74873b803 Improve help page consistency 2022-12-08 00:57:36 +05:30
Ajeet D'Souza
d99e9b7d86 Fix badge 2022-11-15 14:04:21 +05:30
Ajeet D'Souza
c7b8f2f011
Use workspace dependencies (#477) 2022-10-29 21:28:53 +05:30
Ajeet D'Souza
6210d28d5f
Remove unneeded audit (#478) 2022-10-29 21:06:16 +05:30
Elbert Ronnie
b7d6b1eea7
Updated clap to version 4.0 (#476)
Co-authored-by: Ajeet D'Souza <98ajeet@gmail.com>
2022-10-29 19:46:10 +05:30
Ajeet D'Souza
1c947110db
Add Slackware to README (#475) 2022-10-28 20:20:23 +05:30
Ajeet D'Souza
4a47da0ed4
Don't hide output from chpwd hooks (#474) 2022-10-28 18:58:07 +05:30
Ajeet D'Souza
c3e3c855ca Use GitHub note tag 2022-09-25 20:08:13 +05:30
Ajeet D'Souza
9d9bcfcac2 Handle UTF-8 output correctly in PowerShell 2022-09-18 11:33:41 +05:30
Shingo
c137019b83
Fix illegal option in fzf preview window (#460)
Co-authored-by: zhuchunyan <zhuchunyuan@gaodun.com>
Co-authored-by: Ajeet D'Souza <98ajeet@gmail.com>
2022-09-18 00:17:25 +05:30
Ajeet D'Souza
0f3ae894f1 Use formatter from nightly 2022-09-16 22:24:46 +05:30
Ajeet D'Souza
e8a1e11848 Fix remove not throwing errors 2022-09-07 11:56:38 +05:30
Ajeet D'Souza
e4aa0692a4 Return exit code from main 2022-09-07 04:19:45 +05:30
Aaron Kollasch
1d102d4ad2
Fix interactive completion with zsh-autocomplete (#449) 2022-09-06 13:18:44 +05:30
Ajeet D'Souza
0e21153107 Fix CHANGELOG 2022-09-02 14:33:49 +05:30
Ajeet D'Souza
818ac67e7e chore(release): v0.8.3 2022-09-02 14:03:44 +05:30
Ajeet D'Souza
118a37f576 Remove dot from manpage URL 2022-08-30 20:07:58 +05:30
Ajeet D'Souza
a88be74e3e Add CI to next branch 2022-08-29 02:55:19 +05:30
Ajeet D'Souza
8feeec2536 Add ZLocation import instructions 2022-08-29 02:52:47 +05:30
Ajeet D'Souza
b38b89eb3b Add PWD hooks to Nushell (#439) 2022-08-13 13:18:21 +05:30
Ajeet D'Souza
b0c899b99f Support z - in Nushell 2022-08-12 13:03:19 +05:30
Ajeet D'Souza
535762b80a
Fix double forward slash in Bash completions (#438) 2022-08-12 08:16:28 +05:30
Ajeet D'Souza
e4b9e12b0f Improved preview window for fzf 2022-08-04 21:45:08 +05:30
57 changed files with 2716 additions and 2139 deletions

View File

@ -1,2 +0,0 @@
[advisories]
ignore = ["RUSTSEC-2020-0095"]

View File

@ -2,4 +2,9 @@ version = 1
[[analyzers]]
name = "rust"
enabled = true
[analyzers.meta]
msrv = "stable"
[[analyzers]]
name = "shell"

View File

@ -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

6
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly

View File

@ -4,48 +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:
# FIXME: Enable macos-latest when this is merged: https://nixpk.gs/pr-tracker.html?pr=163924
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@v16
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:nixos-unstable
- uses: cachix/cachix-action@v10
- 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@v1
if:
${{ matrix.os == 'windows-latest' }}
- 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' }}
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

22
.github/workflows/no-response.yml vendored Normal file
View File

@ -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.

View File

@ -1,18 +1,14 @@
name: release
on:
push:
branches: [main]
pull_request:
workflow_dispatch:
env:
CARGO_INCREMENTAL: 0
permissions:
contents: write
jobs:
release:
name: ${{ matrix.target }}
permissions:
contents: write
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
@ -28,39 +24,25 @@ jobs:
- 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: Get version
id: get_version
uses: SebRollen/toml-action@v1.0.0
uses: SebRollen/toml-action@v1.0.2
with:
file: Cargo.toml
field: package.version
- 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: Install Rust
uses: actions-rs/toolchain@v1
with:
@ -68,60 +50,52 @@ jobs:
profile: minimal
override: true
target: ${{ matrix.target }}
- name: Setup cache
uses: Swatinem/rust-cache@v1
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
> 'zoxide-${{ steps.get_version.outputs.value }}-${{ matrix.target }}.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 'zoxide-${{ steps.get_version.outputs.value }}-${{ matrix.target }}.zip'
CHANGELOG.md LICENSE README.md
./man/
./contrib/completions/
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: |
*.deb
*.tar.gz
*.zip
- name: Create release
if: startsWith(github.event.head_commit.message, 'chore(release)')
if: |
github.ref == 'refs/heads/main' && startsWith(github.event.head_commit.message, 'chore(release)')
uses: softprops/action-gh-release@v1
with:
draft: true

13
.github/workflows/winget.yml vendored Normal file
View File

@ -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 }}

View File

@ -7,12 +7,69 @@ 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
@ -22,7 +79,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- Bash/Fish/Posix/Zsh: paths on Cygwin.
- Bash/Fish/POSIX/Zsh: paths on Cygwin.
- Fish: completions not working on certain systems.
- Bash: completions not escaping spaces correctly.
@ -95,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
@ -393,6 +450,10 @@ 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

848
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -4,33 +4,32 @@ categories = ["command-line-utilities", "filesystem"]
description = "A smarter cd command for your terminal"
edition = "2021"
homepage = "https://github.com/ajeetdsouza/zoxide"
keywords = ["cli"]
keywords = ["cli", "filesystem", "shell", "tool", "utility"]
license = "MIT"
name = "zoxide"
readme = "README.md"
repository = "https://github.com/ajeetdsouza/zoxide"
rust-version = "1.62"
version = "0.8.2"
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"
ouroboros = "0.17.2"
serde = { version = "1.0.116", features = ["derive"] }
[target.'cfg(unix)'.dependencies]
nix = { version = "0.24.1", default-features = false, features = [
nix = { version = "0.26.1", default-features = false, features = [
"fs",
"user",
] }
@ -39,23 +38,21 @@ nix = { version = "0.24.1", default-features = false, features = [
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.15.0"
rstest_reuse = "0.3.0"
rstest = { version = "0.18.0", default-features = false }
rstest_reuse = "0.6.0"
tempfile = "3.1.0"
[features]
default = []
nix-dev = []
[profile.dev]
debug = 0
[profile.release]
codegen-units = 1
debug = 0

404
README.md
View File

@ -9,11 +9,24 @@
<div align="center">
<sup>Special thanks to:</sup>
<a href="https://www.warp.dev/?utm_source=github&utm_medium=referral&utm_campaign=zoxide_20231001">
<div>
<img src="contrib/warp.png" width="230" alt="Warp" />
</div>
<b>Warp is a modern, Rust-based terminal with AI built in so you and your team can build great software, faster.</b>
<div>
<sup>Visit <u>warp.dev</u> to learn more.</sup>
</div>
</a>
<hr />
# zoxide
[![crates.io][crates.io-badge]][crates.io]
[![Downloads][downloads-badge]][releases]
[![License][license-badge]][license]
[![Built with Nix][builtwithnix-badge]][builtwithnix]
zoxide is a **smarter cd command**, inspired by z and autojump.
@ -52,7 +65,9 @@ Read more about the matching algorithm [here][algorithm-matching].
## Installation
### *Step 1: Install zoxide*
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].
@ -60,256 +75,288 @@ please [open an issue][issues].
<details>
<summary>Linux</summary>
To install zoxide, run this command in your terminal:
```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* | [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` <br /> `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` <br /> `emerge --sync guru` <br /> `emerge app-shells/zoxide` |
| Manjaro | | `pacman -S zoxide` |
| NixOS | [nixpkgs] | `nix-env -iA nixpkgs.zoxide` |
| openSUSE Tumbleweed | [openSUSE Factory] | `zypper install 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` |
> 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` <br /> `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` <br /> `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` <br /> `emerge --sync guru` <br /> `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` |
</details>
<details>
<summary>macOS</summary>
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` |
Or, run this command in your terminal:
```sh
curl -sS https://raw.githubusercontent.com/ajeetdsouza/zoxide/main/install.sh | bash
```
> To install zoxide, use a package manager:
>
> | Repository | Instructions |
> | --------------- | ----------------------------------------------------------------------------------------------------- |
> | **[crates.io]** | `cargo install zoxide --locked` |
> | **[Homebrew]** | `brew install zoxide` |
> | [asdf] | `asdf plugin add zoxide https://github.com/nyrst/asdf-zoxide.git` <br /> `asdf install zoxide latest` |
> | [conda-forge] | `conda install -c conda-forge zoxide` |
> | [MacPorts] | `port install zoxide` |
> | [nixpkgs] | `nix-env -iA nixpkgs.zoxide` |
>
> Or, run this command in your terminal:
>
> ```sh
> curl -sS https://raw.githubusercontent.com/ajeetdsouza/zoxide/main/install.sh | bash
> ```
</details>
<details>
<summary>Windows</summary>
To install zoxide, run this command in your command prompt:
```sh
curl.exe -A "MS" https://webinstall.dev/zoxide | powershell
```
Or, 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` |
> 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
> ```
</details>
<details>
<summary>BSD</summary>
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` |
> 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` |
</details>
<details>
<summary>Android</summary>
To install zoxide, use a package manager:
| Repository | Instructions |
| ---------- | -------------------- |
| [Termux] | `pkg install zoxide` |
> To install zoxide, use a package manager:
>
> | Repository | Instructions |
> | ---------- | -------------------- |
> | [Termux] | `pkg install zoxide` |
</details>
### *Step 2: Add zoxide to your shell*
2. **Setup zoxide on your shell**
To start using zoxide, add it to your shell.
<details>
<summary>Bash</summary>
Add this to your configuration (usually `~/.bashrc`):
```sh
eval "$(zoxide init bash)"
```
> Add this to the **end** of your config file (usually `~/.bashrc`):
>
> ```sh
> eval "$(zoxide init bash)"
> ```
</details>
<details>
<summary>Elvish</summary>
Add this to your configuration (usually `~/.elvish/rc.elv`):
```sh
eval (zoxide init elvish | slurp)
```
Note: zoxide only supports elvish v0.18.0 and above.
> 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.
</details>
<details>
<summary>Fish</summary>
Add this to your configuration (usually `~/.config/fish/config.fish`):
```fish
zoxide init fish | source
```
Note: zoxide only supports fish v3.4.0 and above.
> Add this to the **end** of your config file (usually
> `~/.config/fish/config.fish`):
>
> ```fish
> zoxide init fish | source
> ```
</details>
<details>
<summary>Nushell</summary>
Add this to your env file (find it by running `$nu.env-path` in Nushell):
```sh
zoxide init nushell --hook prompt | save ~/.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.61.0 and above.
> 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.
</details>
<details>
<summary>PowerShell</summary>
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"
})
```
> 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) })
> ```
</details>
<details>
<summary>Xonsh</summary>
Add this to your configuration (usually `~/.xonshrc`):
```python
execx($(zoxide init xonsh), 'exec', __xonsh__.ctx, filename='zoxide')
```
> Add this to the **end** of your config file (usually `~/.xonshrc`):
>
> ```python
> execx($(zoxide init xonsh), 'exec', __xonsh__.ctx, filename='zoxide')
> ```
</details>
<details>
<summary>Zsh</summary>
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`.
> 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`.
</details>
<details>
<summary>Any POSIX shell</summary>
Add this to your configuration:
```sh
eval "$(zoxide init posix --hook prompt)"
```
> Add this to the **end** of your config file:
>
> ```sh
> eval "$(zoxide init posix --hook prompt)"
> ```
</details>
### *Step 3: Install fzf (optional)*
3. **Install fzf** <sup>(optional)</sup>
[fzf] is a command-line fuzzy finder, used by zoxide for interactive selection.
It can be installed from [here][fzf-installation].
[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.21.0 and above.
> **Note**
> zoxide only supports fzf v0.33.0 and above.
### *Step 4: Import your data (optional)*
4. **Import your data** <sup>(optional)</sup>
If you currently use any of the following utilities, you may want to import
your data into zoxide:
If you currently use any of these plugins, you may want to import your data
into zoxide:
<details>
<summary>autojump</summary>
```sh
zoxide import --from autojump path/to/db
```
The default path 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` |
> Run this command in your terminal:
>
> ```sh
> zoxide import --from=autojump "/path/to/autojump/db"
> ```
>
> The path usually varies according to your system:
>
> | OS | Path | Example |
> | ------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------ |
> | Linux | `$XDG_DATA_HOME/autojump/autojump.txt` or `$HOME/.local/share/autojump/autojump.txt` | `/home/alice/.local/share/autojump/autojump.txt` |
> | macOS | `$HOME/Library/autojump/autojump.txt` | `/Users/Alice/Library/autojump/autojump.txt` |
> | Windows | `%APPDATA%\autojump\autojump.txt` | `C:\Users\Alice\AppData\Roaming\autojump\autojump.txt` |
</details>
<details>
<summary>z, z.lua, or zsh-z</summary>
<summary>fasd, z, z.lua, zsh-z</summary>
```sh
zoxide import --from z path/to/db
```
> Run this command in your terminal:
>
> ```sh
> zoxide import --from=z "path/to/z/db"
> ```
>
> The path usually varies according to your system:
>
> | Plugin | Path |
> | ---------------- | ----------------------------------------------------------------------------------- |
> | fasd | `$_FASD_DATA` or `$HOME/.fasd` |
> | z (bash/zsh) | `$_Z_DATA` or `$HOME/.z` |
> | z (fish) | `$Z_DATA` or `$XDG_DATA_HOME/z/data` or `$HOME/.local/share/z/data` |
> | z.lua (bash/zsh) | `$_ZL_DATA` or `$HOME/.zlua` |
> | z.lua (fish) | `$XDG_DATA_HOME/zlua/zlua.txt` or `$HOME/.local/share/zlua/zlua.txt` or `$_ZL_DATA` |
> | zsh-z | `$ZSHZ_DATA` or `$_Z_DATA` or `$HOME/.z` |
</details>
<details>
<summary>ZLocation</summary>
> Run this command in PowerShell:
>
> ```powershell
> $db = New-TemporaryFile
> (Get-ZLocation).GetEnumerator() | ForEach-Object { Write-Output ($_.Name+'|'+$_.Value+'|0') } | Out-File $db
> zoxide import --from=z $db
> ```
</details>
@ -337,8 +384,8 @@ When calling `zoxide init`, the following flags are available:
### Environment variables
Environment variables<sup>[?][wiki-env]</sup> 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.
@ -374,7 +421,8 @@ They must be set before `zoxide init` is called.
## Third-party integrations
| 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 |
@ -383,28 +431,40 @@ They must be set before `zoxide init` is called.
| [nnn] | File manager | [nnn-autojump] |
| [ranger] | File manager | [ranger-zoxide] |
| [telescope.nvim] | Fuzzy finder for Neovim | [telescope-zoxide] |
| [vim] | Text editor | [zoxide.vim] |
| [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/endpoint?color=bright-green&label=downloads&style=flat-square&cacheSeconds=3600&url=https%3A%2F%2Fzoxide-dl-rlvir7rbe5ac.runkit.sh%2F
[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
@ -420,8 +480,6 @@ They must be set before `zoxide init` is called.
[joshuto]: https://github.com/kamiyaa/joshuto
[lf]: https://github.com/gokcehan/lf
[lf-wiki]: https://github.com/gokcehan/lf/wiki/Integrations#zoxide
[license-badge]: https://img.shields.io/github/license/ajeetdsouza/zoxide?color=lightgray&style=flat-square
[license]: https://github.com/ajeetdsouza/zoxide/blob/main/LICENSE
[linuxbrew]: https://formulae.brew.sh/formula-linux/zoxide
[macports]: https://ports.macports.org/port/zoxide/summary
[neovim]: https://github.com/neovim/neovim
@ -429,15 +487,19 @@ They must be set before `zoxide init` is called.
[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/jammy/zoxide
[vim]: https://github.com/vim/vim

View File

@ -1,56 +1,34 @@
use std::process::Command;
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);
// 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<String> {
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()
}
fn generate_completions() -> io::Result<()> {
#[path = "src/cmd/_cmd.rs"]
#[path = "src/cmd/cmd.rs"]
mod cmd;
use std::{env, io};
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;
let cmd = &mut Cmd::command();
let bin_name = env!("CARGO_PKG_NAME");
let out_dir = "contrib/completions";
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()
}
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)?;
fn generate_completions() -> io::Result<()> {
const BIN_NAME: &str = env!("CARGO_PKG_NAME");
const OUT_DIR: &str = "contrib/completions";
let cmd = &mut Cmd::command();
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(())
}

View File

@ -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=[Changes the prefix of the `z` and `zi` commands]:CMD: ' \
'--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 information]' \
'--help[Print help information]' \
'-V[Print version information]' \
'--version[Print version information]' \
'--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]' \
'-s[Print score with results]' \
'--score[Print score with results]' \
'-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]' \
'*::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 "$@"
}
if [ "$funcstack[1]" = "_zoxide" ]; then
_zoxide "$@"
else
compdef _zoxide zoxide
fi

View File

@ -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, '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 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;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
}
})

View File

@ -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 <PATH>"
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 <PATH>"
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 <PATH>"
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 <PATH>"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
@ -102,7 +187,7 @@ _zoxide() {
return 0
;;
zoxide__query)
opts="-i -l -s -h -V --all --interactive --list --score --exclude --help --version <KEYWORDS>..."
opts="-a -i -l -s -h -V --all --interactive --list --score --exclude --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 <PATHS>..."
opts="-h -V --help --version [PATHS]..."
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0

View File

@ -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 '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 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;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]

View File

@ -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 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 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 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'

View File

@ -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,6 +109,7 @@ const completion: Fig.Spec = {
{
name: "--from",
description: "Application to import from",
isRepeatable: true,
args: {
name: "from",
suggestions: [
@ -42,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: {
@ -61,6 +143,7 @@ const completion: Fig.Spec = {
{
name: "--cmd",
description: "Changes the prefix of the `z` and `zi` commands",
isRepeatable: true,
args: {
name: "cmd",
isOptional: true,
@ -69,6 +152,7 @@ const completion: Fig.Spec = {
{
name: "--hook",
description: "Changes how often zoxide increments a directory's score",
isRepeatable: true,
args: {
name: "hook",
isOptional: true,
@ -85,11 +169,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: {
@ -112,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,
@ -120,8 +205,8 @@ const completion: Fig.Spec = {
},
},
{
name: "--all",
description: "Show deleted directories",
name: ["-a", "--all"],
description: "Show unavailable directories",
},
{
name: ["-i", "--interactive"],
@ -145,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,
},
},
@ -161,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",
},
@ -184,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",
},
],
};

BIN
contrib/warp.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 817 KiB

View File

@ -7,7 +7,7 @@
# extension. Note: Most shells limit `local` to 1 var per line, contra bash.
main() {
if [ "$KSH_VERSION" = 'Version JM 93t+ 2010-03-05' ]; then
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:
@ -18,56 +18,57 @@ main() {
# Detect and print host target triple.
ensure get_architecture
local _arch="$RETVAL"
assert_nz "$_arch" "arch"
echo "Detected architecture: $_arch"
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"
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
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"
ensure tar -xf "${_package}"
;;
*.zip)
need_cmd unzip
ensure unzip -oq "$_package"
ensure unzip -oq "${_package}"
;;
*)
err "unsupported package format: $_package"
err "unsupported package format: ${_package}"
;;
esac
# Install binary.
local _bin_dir="$HOME/.local/bin"
local _bin_dir="${HOME}/.local/bin"
local _bin_name
case "$_arch" in
case "${_arch}" in
*windows*) _bin_name="zoxide.exe" ;;
*) _bin_name="zoxide" ;;
esac
ensure mkdir -p "$_bin_dir"
ensure cp "$_bin_name" "$_bin_dir"
echo "Installed zoxide to $_bin_dir"
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"
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."
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
}
@ -85,31 +86,35 @@ download_zoxide() {
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" ;;
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 at https://github.com/ajeetdsouza/zoxide/issues"
_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
case "${_package_url}" in
*.tar.gz) _ext="tar.gz" ;;
*.zip) _ext="zip" ;;
*) err "unsupported package format: $_package_url" ;;
*) 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" ;;
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"
RETVAL="${_package}"
}
# The below functions have been extracted with minor modifications from the
@ -123,25 +128,25 @@ get_architecture() {
_cputype="$(uname -m)"
_clibtype="musl"
if [ "$_ostype" = Linux ]; then
if [ "$(uname -o)" = Android ]; then
if [ "${_ostype}" = Linux ]; then
if [ "$(uname -o || true)" = Android ]; then
_ostype=Android
fi
fi
if [ "$_ostype" = Darwin ] && [ "$_cputype" = i386 ]; then
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
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)" = illumos ]; then
if [ "$(/usr/bin/uname -o || true)" = illumos ]; then
_ostype=illumos
fi
@ -149,18 +154,18 @@ get_architecture() {
# 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
if [ "${_cputype}" = i86pc ]; then
_cputype="$(isainfo -n)"
fi
fi
case "$_ostype" in
case "${_ostype}" in
Android)
_ostype=linux-android
;;
Linux)
check_proc
_ostype=unknown-linux-$_clibtype
_ostype=unknown-linux-${_clibtype}
_bitness=$(get_bitness)
;;
FreeBSD)
@ -182,23 +187,23 @@ get_architecture() {
_ostype=pc-windows-msvc
;;
*)
err "unrecognized OS type: $_ostype"
err "unrecognized OS type: ${_ostype}"
;;
esac
case "$_cputype" in
case "${_cputype}" in
i386 | i486 | i686 | i786 | x86)
_cputype=i686
;;
xscale | arm)
_cputype=arm
if [ "$_ostype" = "linux-android" ]; then
if [ "${_ostype}" = "linux-android" ]; then
_ostype=linux-androideabi
fi
;;
armv6l)
_cputype=arm
if [ "$_ostype" = "linux-android" ]; then
if [ "${_ostype}" = "linux-android" ]; then
_ostype=linux-androideabi
else
_ostype="${_ostype}eabihf"
@ -206,7 +211,7 @@ get_architecture() {
;;
armv7l | armv8l)
_cputype=armv7
if [ "$_ostype" = "linux-android" ]; then
if [ "${_ostype}" = "linux-android" ]; then
_ostype=linux-androideabi
else
_ostype="${_ostype}eabihf"
@ -222,7 +227,7 @@ get_architecture() {
_cputype=$(get_endianness mips '' el)
;;
mips64)
if [ "$_bitness" -eq 64 ]; then
if [ "${_bitness}" -eq 64 ]; then
# only n64 ABI is supported for now
_ostype="${_ostype}abi64"
_cputype=$(get_endianness mips64 '' el)
@ -244,13 +249,13 @@ get_architecture() {
_cputype=riscv64gc
;;
*)
err "unknown CPU type: $_cputype"
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
case ${_cputype} in
x86_64)
# 32-bit executable for amd64 = x32
if is_host_amd64_elf; then {
@ -268,7 +273,7 @@ get_architecture() {
;;
aarch64)
_cputype=armv7
if [ "$_ostype" = "linux-android" ]; then
if [ "${_ostype}" = "linux-android" ]; then
_ostype=linux-androideabi
else
_ostype="${_ostype}eabihf"
@ -277,13 +282,14 @@ get_architecture() {
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 [ "${_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
@ -291,7 +297,7 @@ get_architecture() {
fi
_arch="${_cputype}-${_ostype}"
RETVAL="$_arch"
RETVAL="${_arch}"
}
get_bitness() {
@ -304,9 +310,9 @@ get_bitness() {
# 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
if [ "${_current_exe_head}" = "$(printf '\177ELF\001')" ]; then
echo 32
elif [ "$_current_exe_head" = "$(printf '\177ELF\002')" ]; then
elif [ "${_current_exe_head}" = "$(printf '\177ELF\002')" ]; then
echo 64
else
err "unknown platform bitness"
@ -324,9 +330,9 @@ get_endianness() {
local _current_exe_endianness
_current_exe_endianness="$(head -c 6 /proc/self/exe | tail -c 1)"
if [ "$_current_exe_endianness" = "$(printf '\001')" ]; then
if [ "${_current_exe_endianness}" = "$(printf '\001')" ]; then
echo "${cputype}${suffix_el}"
elif [ "$_current_exe_endianness" = "$(printf '\002')" ]; then
elif [ "${_current_exe_endianness}" = "$(printf '\002')" ]; then
echo "${cputype}${suffix_eb}"
else
err "unknown platform endianness"
@ -341,7 +347,7 @@ is_host_amd64_elf() {
# 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')" ]
[ "${_current_exe_machine}" = "$(printf '\076')" ]
}
check_proc() {

39
justfile Normal file
View File

@ -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}}

View File

@ -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

View File

@ -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

View File

@ -7,14 +7,14 @@
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
@ -23,50 +23,47 @@ Add this to your configuration (usually \fB~/.elvish/rc.elv\fR):
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
.sp
Note: zoxide only supports fish v3.4.0 and above.
.TP
.B nushell
Add this to your env file (find it by running \fB$nu.env-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
\fBzoxide init nushell --hook prompt | save ~/.zoxide.nu\fR
\fBzoxide init nushell | save -f ~/.zoxide.nu\fR
.fi
.sp
Now, add this to the end of your config file (find it by running
Now, add this to the \fBend\fR of your config file (find it by running
\fB$nu.config-path\fR in Nushell):
.sp
.nf
\fBsource ~/.zoxide.nu\fR
.fi
.sp
Note: zoxide only supports Nushell v0.61.0 and above.
Note: zoxide only supports Nushell v0.73.0 and above.
.TP
.B powershell
Add this to your configuration (find it by running \fBecho $profile\fR in
PowerShell):
Add this to the \fBend\fR of your config file (find it by running \fBecho
$profile\fR in PowerShell):
.sp
.nf
\fBInvoke-Expression (& {
$hook = if ($PSVersionTable.PSVersion.Major -lt 6) { 'prompt' } else { 'pwd' }
(zoxide init --hook $hook powershell | Out-String)
})\fR
\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
@ -74,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
@ -109,6 +106,6 @@ 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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -1,13 +1,22 @@
let
rust = import (builtins.fetchTarball
"https://github.com/oxalica/rust-overlay/archive/8b4c5bef319198920fd03a916dd5f6600147358b.tar.gz");
pkgs = import (builtins.fetchTarball
"https://github.com/NixOS/nixpkgs/archive/0323e1f8bac882f19905174639a89397db1930f1.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,8 +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
@ -31,6 +43,7 @@ in pkgs.mkShell {
pkgs.python3Packages.pylint
pkgs.shellcheck
pkgs.shfmt
pkgs.yamlfmt
# Dependencies
pkgs.cacert
@ -39,6 +52,5 @@ in pkgs.mkShell {
pkgs.libiconv
];
CARGO_INCREMENTAL = builtins.getEnv "CI" != "";
CARGO_TARGET_DIR = "target_nix";
}

View File

@ -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<PathBuf>,
}
/// 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 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(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<String>,
/// 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)]
pub score: bool,
/// Exclude a path from results
#[clap(long, value_hint = ValueHint::DirPath, value_name = "path")]
pub exclude: Option<String>,
}
/// 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<String>,
}

View File

@ -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()
}
}

186
src/cmd/cmd.rs Normal file
View File

@ -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}<bold><underline>{name} {version}</underline></bold>
{author}
https://github.com/ajeetdsouza/zoxide
{about}
{usage-heading}
{tab}{usage}
{all-args}{after-help}
<bold><underline>Environment variables:</underline></bold>
{tab}<bold>_ZO_DATA_DIR</bold> {tab}Path for zoxide data files
{tab}<bold>_ZO_ECHO</bold> {tab}Print the matched directory before navigating to it when set to 1
{tab}<bold>_ZO_EXCLUDE_DIRS</bold> {tab}List of directory globs to be excluded
{tab}<bold>_ZO_FZF_OPTS</bold> {tab}Custom flags to pass to fzf
{tab}<bold>_ZO_MAXAGE</bold> {tab}Maximum total age after which entries start getting deleted
{tab}<bold>_ZO_RESOLVE_SYMLINKS</bold>{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<PathBuf>,
}
/// Edit the database
#[derive(Debug, Parser)]
#[clap(
author,
help_template = HELP_TEMPLATE,
)]
pub struct Edit {
#[clap(subcommand)]
pub cmd: Option<EditCommand>,
}
#[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<String>,
/// 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<String>,
}
/// 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<String>,
}

84
src/cmd/edit.rs Normal file
View File

@ -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<FzfChild> {
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()
}
}

View File

@ -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::<f64>().with_context(|| format!("invalid rank: {}", rank))?;
// Normalize the rank using a sigmoid function. Don't import actual ranks from autojump,
// since its scoring algorithm is very different and might take a while to get normalized.
let mut rank = rank.parse::<f64>().with_context(|| format!("invalid rank: {rank}"))?;
// Normalize the rank using a sigmoid function. Don't import actual ranks from
// autojump, since its scoring algorithm is very different and might
// take a while to get normalized.
rank = sigmoid(rank);
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);

View File

@ -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_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")
}
}

View File

@ -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(),

View File

@ -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);
}
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")?,
stream
}
};
if self.score {
print!("{}", selection);
fn get_fzf() -> Result<FzfChild> {
let mut fzf = Fzf::new()?;
if let Some(fzf_opts) = config::fzf_opts() {
fzf.env("FZF_DEFAULT_OPTS", fzf_opts)
} else {
let path = selection.get(5..).context("could not read selection from fzf")?;
print!("{}", path);
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()
}
} 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")?;
} 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")?;
}
Ok(())
.spawn()
}
}

View File

@ -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)
}
if path_abs == path || !db.remove(path_abs) {
bail!("path not found in database: {path}")
}
}
}

View File

@ -26,7 +26,8 @@ pub fn exclude_dirs() -> Result<Vec<Pattern>> {
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))
Pattern::new(pattern)
.with_context(|| format!("invalid glob in _ZO_EXCLUDE_DIRS: {pattern}"))
})
.collect(),
None => {
@ -47,8 +48,9 @@ pub fn fzf_opts() -> Option<OsString> {
pub fn maxage() -> Result<Rank> {
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::<u32>().with_context(|| format!("unable to parse _ZO_MAXAGE as integer: {}", maxage))?;
let maxage = maxage
.parse::<u32>()
.with_context(|| format!("unable to parse _ZO_MAXAGE as integer: {maxage}"))?;
Ok(maxage as Rank)
})
}

View File

@ -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<Dir<'a>>);
impl DirList<'_> {
const VERSION: u32 = 3;
pub fn new() -> DirList<'static> {
DirList(Vec::new())
}
pub fn from_bytes(bytes: &[u8]) -> Result<DirList> {
// 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<Vec<u8>> {
(|| -> 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<Dir<'a>>;
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<Vec<Dir<'a>>> for DirList<'a> {
fn from(dirs: Vec<Dir<'a>>) -> 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,56 +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<Epoch>,
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).clamp(0.0, 9999.0) as u32;
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(_)))
}
}
}

View File

@ -1,61 +1,154 @@
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<u8>,
#[borrows(bytes)]
#[covariant]
pub dirs: Vec<Dir<'this>>,
dirty: bool,
}
impl Database {
const VERSION: u32 = 3;
pub fn open() -> Result<Self> {
let data_dir = config::data_dir()?;
Self::open_dir(data_dir)
}
pub fn open_dir(data_dir: impl AsRef<Path>) -> Result<Self> {
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()))
}
}
}
impl<'file> Database<'file> {
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<S: AsRef<str>>(&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<str> + Into<String>, 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<str> + Into<String>, 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<str> + Into<String>, 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<str>) -> 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::<Rank>();
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() {
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 = &self.dirs[idx];
let next_dir = &self.dirs[idx - 1];
let curr_dir = &dirs[idx];
let next_dir = &dirs[idx - 1];
if next_dir.path != curr_dir.path {
continue;
}
@ -63,87 +156,82 @@ impl<'file> Database<'file> {
// 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];
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.
self.dirs.swap_remove(idx);
self.modified = true;
dirs.swap_remove(idx);
dirty = true;
}
});
self.with_dirty_mut(|dirty_prev| *dirty_prev |= dirty);
}
// Streaming iterator for directories.
pub fn stream(&mut self, now: Epoch) -> Stream<'_, 'file> {
Stream::new(self, now)
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);
}
/// Removes the directory with `path` from the store. This does not preserve ordering, but is
/// O(1).
pub fn remove<S: AsRef<str>>(&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;
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);
}
false
pub fn dirty(&self) -> bool {
*self.borrow_dirty()
}
pub fn age(&mut self, max_age: Rank) {
let sum_age = self.dirs.iter().map(|dir| dir.rank).sum::<Rank>();
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);
}
}
self.modified = true;
}
}
pub fn dirs(&self) -> &[Dir] {
self.borrow_dirs()
}
pub struct DatabaseFile {
buffer: Vec<u8>,
data_dir: PathBuf,
fn serialize(dirs: &[Dir<'_>]) -> Result<Vec<u8>> {
(|| -> 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")
}
impl DatabaseFile {
pub fn new<P: Into<PathBuf>>(data_dir: P) -> Self {
DatabaseFile { buffer: Vec::new(), data_dir: data_dir.into() }
}
fn deserialize(bytes: &[u8]) -> Result<Vec<Dir>> {
// 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);
pub fn open(&mut self) -> Result<Database> {
// 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 })
}
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 })
}
Err(e) => Err(e).with_context(|| format!("could not read from database: {}", path.display())),
}
}
// 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);
fn db_path<P: AsRef<Path>>(data_dir: P) -> PathBuf {
const DB_FILENAME: &str = "db.zo";
data_dir.as_ref().join(DB_FILENAME)
// Deserialize sections.
let version = deserializer.deserialize(bytes_version)?;
let dirs = match version {
Self::VERSION => {
deserializer.deserialize(bytes_dirs).context("could not deserialize database")?
}
version => {
bail!("unsupported version (got {version}, supports {})", Self::VERSION)
}
};
Ok(dirs)
}
}
#[cfg(test)]
@ -152,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();
}

View File

@ -3,33 +3,35 @@ use std::ops::Range;
use std::{fs, path};
use crate::db::{Database, Dir, Epoch};
use crate::util;
use crate::util::{self, MONTH};
pub struct Stream<'db, 'file> {
db: &'db mut Database<'file>,
pub struct Stream<'a> {
// State
db: &'a mut Database,
idxs: Rev<Range<usize>>,
did_exclude: bool,
// Configuration
keywords: Vec<String>,
check_exists: bool,
expire_below: Epoch,
resolve_symlinks: bool,
exclude_path: Option<String>,
}
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(|dir1, dir2| dir1.score(now).total_cmp(&dir2.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,
@ -38,7 +40,7 @@ impl<'db, 'file> Stream<'db, 'file> {
}
}
pub fn with_exclude<S: Into<String>>(mut self, path: S) -> Self {
pub fn with_exclude(mut self, path: impl Into<String>) -> Self {
self.exclude_path = Some(path.into());
self
}
@ -49,14 +51,14 @@ impl<'db, 'file> Stream<'db, 'file> {
self
}
pub fn with_keywords<S: AsRef<str>>(mut self, keywords: &[S]) -> Self {
pub fn with_keywords(mut self, keywords: &[impl AsRef<str>]) -> 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;
@ -64,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<S: AsRef<str>>(&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<S: AsRef<str>>(&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,
@ -124,7 +130,7 @@ mod tests {
use rstest::rstest;
use super::Database;
use super::*;
#[rstest]
// Case normalization
@ -147,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));
}
}

View File

@ -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}")),
}
}
}

View File

@ -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::<SilentExit>() {
Ok(SilentExit { code }) => process::exit(code),
match Cmd::parse().run() {
Ok(()) => ExitCode::SUCCESS,
Err(e) => match e.downcast::<SilentExit>() {
Ok(SilentExit { code }) => code.into(),
Err(e) => {
let _ = writeln!(io::stderr(), "zoxide: {:?}", e);
process::exit(1);
}
}
_ = writeln!(io::stderr(), "zoxide: {e:?}");
ExitCode::FAILURE
}
},
}
}

View File

@ -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("")
@ -151,8 +156,12 @@ mod tests {
let tempdir = tempfile::tempdir().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("")

View File

@ -1,91 +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};
#[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 {
pub fn new(multiple: bool) -> Result<Self> {
const ERR_FZF_NOT_FOUND: &str = "could not find fzf, is it installed?";
pub fn new() -> Result<Self> {
// 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 mut command = Command::new(which::which("fzf.exe").map_err(|_| anyhow!(ERR_FZF_NOT_FOUND))?);
let program = which::which("fzf.exe").map_err(|_| anyhow!(Self::ERR_FZF_NOT_FOUND))?;
#[cfg(not(windows))]
let mut command = Command::new("fzf");
if multiple {
command.arg("-m");
}
command.arg("--nth=2..").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=50%",
"--info=inline",
"--layout=reverse",
let program = "fzf";
// TODO: check version of fzf here.
let mut cmd = Command::new(program);
cmd.args([
// Search mode
"--delimiter=\t",
"--nth=2",
// Scripting
"--exit-0",
"--select-1",
// Key/Event bindings
"--bind=ctrl-z:ignore",
]);
if cfg!(unix) {
command.env("SHELL", "sh");
command.args(&[r"--preview=\command -p ls -p {2..}", "--preview-window=down"]);
"--read0",
])
.stdin(Stdio::piped())
.stdout(Stdio::piped());
Ok(Fzf(cmd))
}
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 args<I, S>(&mut self, args: I) -> &mut Self
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
self.0.args(args);
self
}
pub fn env<K, V>(&mut self, key: K, val: V) -> &mut Self
where
K: AsRef<OsStr>,
V: AsRef<OsStr>,
{
self.0.env(key, val);
self
}
pub fn envs<I, K, V>(&mut self, vars: I) -> &mut Self
where
I: IntoIterator<Item = (K, V)>,
K: AsRef<OsStr>,
V: AsRef<OsStr>,
{
self.0.envs(vars);
self
}
pub fn spawn(&mut self) -> Result<FzfChild> {
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"),
}
}
}
let child = match command.spawn() {
Ok(child) => child,
Err(e) if e.kind() == io::ErrorKind::NotFound => bail!(ERR_FZF_NOT_FOUND),
Err(e) => Err(e).context("could not launch fzf")?,
};
pub struct FzfChild(Child);
Ok(Fzf { child })
impl FzfChild {
pub fn write(&mut self, dir: &Dir, now: Epoch) -> Result<Option<String>> {
let handle = self.0.stdin.as_mut().unwrap();
match write!(handle, "{}\0", dir.display().with_score(now).with_separator('\t')) {
Ok(()) => Ok(None),
Err(e) if e.kind() == io::ErrorKind::BrokenPipe => self.wait().map(Some),
Err(e) => Err(e).context("could not write to fzf"),
}
}
pub fn stdin(&mut self) -> &mut ChildStdin {
self.child.stdin.as_mut().unwrap()
}
pub fn select(mut self) -> Result<String> {
pub fn wait(&mut self) -> Result<String> {
// Drop stdin to prevent deadlock.
mem::drop(self.child.stdin.take());
mem::drop(self.0.stdin.take());
let mut stdout = self.child.stdout.take().unwrap();
let mut output = String::new();
let mut stdout = self.0.stdout.take().unwrap();
let mut output = String::default();
stdout.read_to_string(&mut output).context("failed to read from fzf")?;
let status = self.child.wait().context("wait failed on fzf")?;
let status = self.0.wait().context("wait failed on fzf")?;
match status.code() {
Some(0) => Ok(output),
Some(1) => bail!("no match found"),
Some(2) => bail!("fzf returned an error"),
Some(code @ 130) => bail!(SilentExit { code }),
Some(130) => bail!(SilentExit { code: 130 }),
Some(128..=254) | None => bail!("fzf was terminated"),
_ => bail!("fzf returned an unknown error"),
}
@ -93,7 +151,7 @@ impl Fzf {
}
/// Similar to [`fs::write`], but atomic (best effort on Windows).
pub fn write<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()> {
pub fn write(path: impl AsRef<Path>, contents: impl AsRef<[u8]>) -> Result<()> {
let path = path.as_ref();
let contents = contents.as_ref();
let dir = path.parent().unwrap();
@ -102,19 +160,22 @@ pub fn write<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()>
let (mut tmp_file, tmp_path) = tmpfile(dir)?;
let result = (|| {
// Write to the tmpfile.
let _ = tmp_file.set_len(contents.len() as u64);
tmp_file.write_all(contents).with_context(|| format!("could not write to file: {}", tmp_path.display()))?;
_ = tmp_file.set_len(contents.len() as u64);
tmp_file
.write_all(contents)
.with_context(|| format!("could not write to file: {}", tmp_path.display()))?;
// Set the owner of the tmpfile (UNIX only).
#[cfg(unix)]
if let Ok(metadata) = path.metadata() {
use nix::unistd::{self, Gid, Uid};
use std::os::unix::fs::MetadataExt;
use std::os::unix::io::AsRawFd;
use nix::unistd::{self, Gid, Uid};
let uid = Uid::from_raw(metadata.uid());
let gid = Gid::from_raw(metadata.gid());
let _ = unistd::fchown(tmp_file.as_raw_fd(), Some(uid), Some(gid));
_ = unistd::fchown(tmp_file.as_raw_fd(), Some(uid), Some(gid));
}
// Close and rename the tmpfile.
@ -123,13 +184,13 @@ pub fn write<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()>
})();
// In case of an error, delete the tmpfile.
if result.is_err() {
let _ = fs::remove_file(&tmp_path);
_ = fs::remove_file(&tmp_path);
}
result
}
/// Atomically create a tmpfile in the given directory.
fn tmpfile<P: AsRef<Path>>(dir: P) -> Result<(File, PathBuf)> {
fn tmpfile(dir: impl AsRef<Path>) -> Result<(File, PathBuf)> {
const MAX_ATTEMPTS: usize = 5;
const TMP_NAME_LEN: usize = 16;
let dir = dir.as_ref();
@ -149,35 +210,39 @@ fn tmpfile<P: AsRef<Path>>(dir: P) -> Result<(File, PathBuf)> {
// Atomically create the tmpfile.
match OpenOptions::new().write(true).create_new(true).open(&path) {
Ok(file) => break Ok((file, path)),
Err(e) if e.kind() == io::ErrorKind::AlreadyExists && attempts < MAX_ATTEMPTS => (),
Err(e) => break Err(e).with_context(|| format!("could not create file: {}", path.display())),
Err(e) if e.kind() == io::ErrorKind::AlreadyExists && attempts < MAX_ATTEMPTS => {}
Err(e) => {
break Err(e).with_context(|| format!("could not create file: {}", path.display()));
}
}
}
}
/// Similar to [`fs::rename`], but retries on Windows.
fn rename<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> Result<()> {
const MAX_ATTEMPTS: usize = 5;
/// Similar to [`fs::rename`], but with retries on Windows.
fn rename(from: impl AsRef<Path>, to: impl AsRef<Path>) -> Result<()> {
let from = from.as_ref();
let to = to.as_ref();
if cfg!(windows) {
const MAX_ATTEMPTS: usize = if cfg!(windows) { 5 } else { 1 };
let mut attempts = 0;
loop {
attempts += 1;
match fs::rename(from, to) {
Err(e) if e.kind() == io::ErrorKind::PermissionDenied && attempts < MAX_ATTEMPTS => (),
result => break result,
Err(e) if e.kind() == io::ErrorKind::PermissionDenied && attempts < MAX_ATTEMPTS => {
attempts += 1
}
result => {
break result.with_context(|| {
format!("could not rename file: {} -> {}", from.display(), to.display())
});
}
}
} else {
fs::rename(from, to)
}
.with_context(|| format!("could not rename file: {} -> {}", from.display(), to.display()))
}
pub fn canonicalize<P: AsRef<Path>>(path: &P) -> Result<PathBuf> {
dunce::canonicalize(path).with_context(|| format!("could not resolve path: {}", path.as_ref().display()))
pub fn canonicalize(path: impl AsRef<Path>) -> Result<PathBuf> {
dunce::canonicalize(&path)
.with_context(|| format!("could not resolve path: {}", path.as_ref().display()))
}
pub fn current_dir() -> Result<PathBuf> {
@ -185,20 +250,22 @@ pub fn current_dir() -> Result<PathBuf> {
}
pub fn current_time() -> Result<Epoch> {
let current_time =
SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).context("system clock set to invalid time")?.as_secs();
let current_time = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.context("system clock set to invalid time")?
.as_secs();
Ok(current_time)
}
pub fn path_to_str<P: AsRef<Path>>(path: &P) -> Result<&str> {
pub fn path_to_str(path: &impl AsRef<Path>) -> Result<&str> {
let path = path.as_ref();
path.to_str().with_context(|| format!("invalid unicode in path: {}", path.display()))
}
/// Returns the absolute version of a path. Like [`std::path::Path::canonicalize`], but doesn't
/// resolve symlinks.
pub fn resolve_path<P: AsRef<Path>>(path: &P) -> Result<PathBuf> {
/// Returns the absolute version of a path. Like
/// [`std::path::Path::canonicalize`], but doesn't resolve symlinks.
pub fn resolve_path(path: impl AsRef<Path>) -> Result<PathBuf> {
let path = path.as_ref();
let base_path;
@ -209,13 +276,15 @@ pub fn resolve_path<P: AsRef<Path>>(path: &P) -> Result<PathBuf> {
if cfg!(windows) {
use std::path::Prefix;
fn get_drive_letter<P: AsRef<Path>>(path: P) -> Option<u8> {
fn get_drive_letter(path: impl AsRef<Path>) -> Option<u8> {
let path = path.as_ref();
let mut components = path.components();
match components.next() {
Some(Component::Prefix(prefix)) => match prefix.kind() {
Prefix::Disk(drive_letter) | Prefix::VerbatimDisk(drive_letter) => Some(drive_letter),
Prefix::Disk(drive_letter) | Prefix::VerbatimDisk(drive_letter) => {
Some(drive_letter)
}
_ => None,
},
_ => None,
@ -268,8 +337,9 @@ pub fn resolve_path<P: AsRef<Path>>(path: &P) -> Result<PathBuf> {
components.next();
let current_dir = env::current_dir()?;
let drive_letter = get_drive_letter(&current_dir)
.with_context(|| format!("could not get drive letter: {}", current_dir.display()))?;
let drive_letter = get_drive_letter(&current_dir).with_context(|| {
format!("could not get drive letter: {}", current_dir.display())
})?;
base_path = get_drive_path(drive_letter);
stack.extend(base_path.components());
}
@ -289,7 +359,7 @@ pub fn resolve_path<P: AsRef<Path>>(path: &P) -> Result<PathBuf> {
for component in components {
match component {
Component::Normal(_) => stack.push(component),
Component::CurDir => (),
Component::CurDir => {}
Component::ParentDir => {
if stack.last() != Some(&Component::RootDir) {
stack.pop();
@ -303,11 +373,7 @@ pub fn resolve_path<P: AsRef<Path>>(path: &P) -> Result<PathBuf> {
}
/// Convert a string to lowercase, with a fast path for ASCII strings.
pub fn to_lowercase<S: AsRef<str>>(s: S) -> String {
pub fn to_lowercase(s: impl AsRef<str>) -> String {
let s = s.as_ref();
if s.is_ascii() {
s.to_ascii_lowercase()
} else {
s.to_lowercase()
}
if s.is_ascii() { s.to_ascii_lowercase() } else { s.to_lowercase() }
}

View File

@ -75,7 +75,7 @@ function __zoxide_z() {
__zoxide_cd "${OLDPWD}"
elif [[ $# -eq 1 && -d $1 ]]; then
__zoxide_cd "$1"
elif [[ ${@: -1} == "${__zoxide_z_prefix}"* ]]; then
elif [[ ${@: -1} == "${__zoxide_z_prefix}"?* ]]; then
# shellcheck disable=SC2124
\builtin local result="${@: -1}"
__zoxide_cd "{{ "${result:${#__zoxide_z_prefix}}" }}"
@ -90,7 +90,7 @@ function __zoxide_z() {
# Jump to a directory using interactive search.
function __zoxide_zi() {
\builtin local result
result="$(\command zoxide query -i -- "$@")" && __zoxide_cd "${result}"
result="$(\command zoxide query --interactive -- "$@")" && __zoxide_cd "${result}"
}
{{ section }}
@ -127,13 +127,13 @@ if [[ ${BASH_VERSINFO[0]:-0} -eq 4 && ${BASH_VERSINFO[1]:-0} -ge 4 || ${BASH_VER
# If there is only one argument, use `cd` completions.
if [[ {{ "${#COMP_WORDS[@]}" }} -eq 2 ]]; then
\builtin mapfile -t COMPREPLY < <(
\builtin compgen -A directory -S / -- "${COMP_WORDS[-1]}" || \builtin true
\builtin compgen -A directory -- "${COMP_WORDS[-1]}" || \builtin true
)
# If there is a space after the last word, use interactive selection.
elif [[ -z ${COMP_WORDS[-1]} ]]; then
elif [[ -z ${COMP_WORDS[-1]} ]] && [[ ${COMP_WORDS[-2]} != "${__zoxide_z_prefix}"?* ]]; then
\builtin local result
# shellcheck disable=SC2312
result="$(\command zoxide query --exclude "$(__zoxide_pwd)" -i -- "{{ "${COMP_WORDS[@]:1:${#COMP_WORDS[@]}-2}" }}")" &&
result="$(\command zoxide query --exclude "$(__zoxide_pwd)" --interactive -- "{{ "${COMP_WORDS[@]:1:${#COMP_WORDS[@]}-2}" }}")" &&
COMPREPLY=("${__zoxide_z_prefix}${result}/")
\builtin printf '\e[5n'
fi

View File

@ -68,7 +68,7 @@ edit:add-var __zoxide_z~ $__zoxide_z~
fn __zoxide_zi {|@rest|
var path
try {
set path = (zoxide query -i -- $@rest)
set path = (zoxide query --interactive -- $@rest)
} catch {
} else {
__zoxide_cd $path
@ -87,8 +87,10 @@ edit:add-var {{cmd}}~ $__zoxide_z~
edit:add-var {{cmd}}i~ $__zoxide_zi~
# Load completions.
{# zoxide-based completions are currently not possible, because Elvish only
# prints a completion if the current token is a prefix of it. -#}
{#-
zoxide-based completions are currently not possible, because Elvish only prints
a completion if the current token is a prefix of it.
#}
fn __zoxide_z_complete {|@rest|
if (!= (builtin:count $rest) 2) {
builtin:return

View File

@ -18,9 +18,9 @@ end
# A copy of fish's internal cd function. This makes it possible to use
# `alias cd=z` without causing an infinite loop.
if ! builtin functions -q __zoxide_cd_internal
if builtin functions -q cd
builtin functions -c cd __zoxide_cd_internal
if ! builtin functions --query __zoxide_cd_internal
if builtin functions --query cd
builtin functions --copy cd __zoxide_cd_internal
else
alias __zoxide_cd_internal='builtin cd'
end
@ -62,20 +62,21 @@ end
# When using zoxide with --no-cmd, alias these internal functions as desired.
#
if test -z $__zoxide_z_prefix
set __zoxide_z_prefix 'z!'
end
set __zoxide_z_prefix_regex ^(string escape --style=regex $__zoxide_z_prefix)
# Jump to a directory using only keywords.
function __zoxide_z
set -l argc (count $argv)
set -l completion_regex '^'(string escape --style=regex $__zoxide_z_prefix)'(.*)$'
if test $argc -eq 0
__zoxide_cd $HOME
else if test "$argv" = -
__zoxide_cd -
else if test $argc -eq 1 -a -d $argv[1]
__zoxide_cd $argv[1]
else if set -l result (string match --groups-only --regex $completion_regex $argv[-1])
else if set -l result (string replace --regex $__zoxide_z_prefix_regex '' $argv[-1]); and test -n $result
__zoxide_cd $result
else
set -l result (command zoxide query --exclude (__zoxide_pwd) -- $argv)
@ -83,26 +84,28 @@ function __zoxide_z
end
end
# Completions for `z`.
# Completions.
function __zoxide_z_complete
set -l tokens (commandline --current-process --tokenize)
set -l curr_tokens (commandline --cut-at-cursor --current-process --tokenize)
if test (count $tokens) -le 2 -a (count $curr_tokens) -eq 1
# If there are < 2 arguments, use `cd` completions.
__fish_complete_directories "$tokens[2]" ''
else if test (count $tokens) -eq (count $curr_tokens)
# If the last argument is empty, use interactive selection.
complete --do-complete "'' "(commandline --cut-at-cursor --current-token) | string match --regex '.*/$'
else if test (count $tokens) -eq (count $curr_tokens); and ! string match --quiet --regex $__zoxide_z_prefix_regex. $tokens[-1]
# If the last argument is empty and the one before doesn't start with
# $__zoxide_z_prefix, use interactive selection.
set -l query $tokens[2..-1]
set -l result (zoxide query --exclude (__zoxide_pwd) -i -- $query)
set -l result (zoxide query --exclude (__zoxide_pwd) --interactive -- $query)
and echo $__zoxide_z_prefix$result
commandline --function repaint
end
end
complete --command __zoxide_z --no-files --arguments '(__zoxide_z_complete)'
# Jump to a directory using interactive search.
function __zoxide_zi
set -l result (command zoxide query -i -- $argv)
set -l result (command zoxide query --interactive -- $argv)
and __zoxide_cd $result
end
@ -114,17 +117,10 @@ end
{%- when Some with (cmd) %}
abbr --erase {{cmd}} &>/dev/null
complete -c {{cmd}} -e
function {{cmd}}
__zoxide_z $argv
end
complete -c {{cmd}} -f -a '(__zoxide_z_complete)'
alias {{cmd}}=__zoxide_z
abbr --erase {{cmd}}i &>/dev/null
complete -c {{cmd}}i -e
function {{cmd}}i
__zoxide_zi $argv
end
alias {{cmd}}i=__zoxide_zi
{%- when None %}
@ -137,5 +133,3 @@ end
# ~/.config/fish/config.fish):
#
# zoxide init fish | source
#
# Note: zoxide only supports fish v3.4.0 and above.

View File

@ -6,35 +6,33 @@
{{ section }}
# Hook configuration for zoxide.
#
{% match hook %}
{%- when InitHook::None %}
{% if hook == InitHook::None -%}
{{ not_configured }}
{%- when InitHook::Prompt %}
# Default prompt for Nushell.
let-env __zoxide_oldprompt = (if '__zoxide_oldprompt' in (env).name {
$env.__zoxide_oldprompt
} else if 'PROMPT_COMMAND' in (env).name {
$env.PROMPT_COMMAND
} else {
{ $env.PWD }
})
# Hook to add new entries to the database.
def __zoxide_hook [] {
{%- else -%}
# Initialize hook to add new entries to the database.
if (not ($env | default false __zoxide_hooked | get __zoxide_hooked)) {
$env.__zoxide_hooked = true
{%- if hook == InitHook::Prompt %}
$env.config = ($env | default {} config).config
$env.config = ($env.config | default {} hooks)
$env.config = ($env.config | update hooks ($env.config.hooks | default [] pre_prompt))
$env.config = ($env.config | update hooks.pre_prompt ($env.config.hooks.pre_prompt | append { ||
zoxide add -- $env.PWD
}))
{%- else if hook == InitHook::Pwd %}
$env.config = ($env | default {} config).config
$env.config = ($env.config | default {} hooks)
$env.config = ($env.config | update hooks ($env.config.hooks | default {} env_change))
$env.config = ($env.config | update hooks.env_change ($env.config.hooks.env_change | default [] PWD))
$env.config = ($env.config | update hooks.env_change.PWD ($env.config.hooks.env_change.PWD | append {|_, dir|
zoxide add -- $dir
}))
{%- endif %}
}
# Initialize hook.
let-env PROMPT_COMMAND = {
__zoxide_hook
do $env.__zoxide_oldprompt
}
{%- when InitHook::Pwd %}
echo "zoxide: PWD hooks are not supported on Nushell.\nUse `zoxide init nushell --hook prompt` instead."
{%- endmatch %}
{%- endif %}
{{ section }}
# When using zoxide with --no-cmd, alias these internal functions as desired.
@ -42,9 +40,8 @@ echo "zoxide: PWD hooks are not supported on Nushell.\nUse `zoxide init nushell
# Jump to a directory using only keywords.
def-env __zoxide_z [...rest:string] {
# `z -` does not work yet, see https://github.com/nushell/nushell/issues/4769
let arg0 = ($rest | append '~').0
let path = if ($rest | length) <= 1 && ($arg0 | path expand | path type) == dir {
let path = if (($rest | length) <= 1) and ($arg0 == '-' or ($arg0 | path expand | path type) == dir) {
$arg0
} else {
(zoxide query --exclude $env.PWD -- $rest | str trim -r -c "\n")
@ -57,7 +54,7 @@ def-env __zoxide_z [...rest:string] {
# Jump to a directory using interactive search.
def-env __zoxide_zi [...rest:string] {
cd $'(zoxide query -i -- $rest | str trim -r -c "\n")'
cd $'(zoxide query --interactive -- $rest | str trim -r -c "\n")'
{%- if echo %}
echo $env.PWD
{%- endif %}
@ -82,11 +79,11 @@ alias {{cmd}}i = __zoxide_zi
{{ section }}
# Add this to your env file (find it by running `$nu.env-path` in Nushell):
#
# zoxide init nushell --hook prompt | save ~/.zoxide.nu
# 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):
#
# source ~/.zoxide.nu
#
# Note: zoxide only supports Nushell v0.61.0 and above.
# Note: zoxide only supports Nushell v0.73.0 and above.

View File

@ -74,7 +74,7 @@ __zoxide_z() {
# Jump to a directory using interactive search.
__zoxide_zi() {
__zoxide_result="$(\command zoxide query -i -- "$@")" && __zoxide_cd "${__zoxide_result}"
__zoxide_result="$(\command zoxide query --interactive -- "$@")" && __zoxide_cd "${__zoxide_result}"
}
{{ section }}

View File

@ -5,8 +5,20 @@
# Utility functions for zoxide.
#
# Call zoxide binary, returning the output as UTF-8.
function global:__zoxide_bin {
$encoding = [Console]::OutputEncoding
try {
[Console]::OutputEncoding = [System.Text.Utf8Encoding]::new()
$result = zoxide @args
return $result
} finally {
[Console]::OutputEncoding = $encoding
}
}
# pwd based on zoxide's format.
function __zoxide_pwd {
function global:__zoxide_pwd {
$cwd = Get-Location
if ($cwd.Provider.Name -eq "FileSystem") {
$cwd.ProviderPath
@ -14,12 +26,20 @@ function __zoxide_pwd {
}
# cd + custom logic based on the value of _ZO_ECHO.
function __zoxide_cd($dir, $literal) {
function global:__zoxide_cd($dir, $literal) {
$dir = if ($literal) {
Set-Location -LiteralPath $dir -Passthru -ErrorAction Stop
} else {
if ($dir -eq '-' -and ($PSVersionTable.PSVersion -lt 6.1)) {
Write-Error "cd - is not supported below PowerShell 6.1. Please upgrade your version of PowerShell."
}
elseif ($dir -eq '+' -and ($PSVersionTable.PSVersion -lt 6.2)) {
Write-Error "cd + is not supported below PowerShell 6.2. Please upgrade your version of PowerShell."
}
else {
Set-Location -Path $dir -Passthru -ErrorAction Stop
}
}
{%- if echo %}
Write-Output $dir.Path
{%- endif %}
@ -29,65 +49,73 @@ function __zoxide_cd($dir, $literal) {
# Hook configuration for zoxide.
#
{% if hook == InitHook::None -%}
{{ not_configured }}
{%- else -%}
{#-
Initialize $__zoxide_hooked if it does not exist. Removing this will cause an
unset variable error in StrictMode.
-#}
{%- if hook == InitHook::Prompt -%}
# Hook to add new entries to the database.
function __zoxide_hook {
function global:__zoxide_hook {
$result = __zoxide_pwd
if ($null -ne $result) {
zoxide add -- $result
}
}
{%- else if hook == InitHook::Pwd -%}
# Hook to add new entries to the database.
$global:__zoxide_oldpwd = __zoxide_pwd
function global:__zoxide_hook {
$result = __zoxide_pwd
if ($result -ne $global:__zoxide_oldpwd) {
if ($null -ne $result) {
zoxide add -- $result
}
$global:__zoxide_oldpwd = $result
}
}
{%- endif %}
# Initialize hook.
{# Initialize $__zoxide_hooked if it does not exist. Removing this will cause
# an unset variable error in StrictMode. #}
$__zoxide_hooked = (Get-Variable __zoxide_hooked -ValueOnly -ErrorAction SilentlyContinue)
if ($__zoxide_hooked -ne 1) {
$__zoxide_hooked = 1
{%- match hook %}
{%- when InitHook::None %}
{{ not_configured }}
{%- when InitHook::Prompt %}
$prompt_old = $function:prompt
function prompt {
$null = __zoxide_hook
& $prompt_old
$global:__zoxide_hooked = (Get-Variable __zoxide_hooked -ErrorAction SilentlyContinue -ValueOnly)
if ($global:__zoxide_hooked -ne 1) {
$global:__zoxide_hooked = 1
$global:__zoxide_prompt_old = $function:prompt
function global:prompt {
if ($null -ne $__zoxide_prompt_old) {
& $__zoxide_prompt_old
}
{%- when InitHook::Pwd %}
if ($PSVersionTable.PSVersion.Major -ge 6) {
$ExecutionContext.InvokeCommand.LocationChangedAction = {
$null = __zoxide_hook
}
}
else {
Write-Error ("`n" +
"zoxide: PWD hooks are not supported below powershell 6.`n" +
" Use 'zoxide init powershell --hook prompt' instead.")
}
{%- endmatch %}
}
{%- endif %}
{{ section }}
# When using zoxide with --no-cmd, alias these internal functions as desired.
#
# Jump to a directory using only keywords.
function __zoxide_z {
function global:__zoxide_z {
if ($args.Length -eq 0) {
__zoxide_cd ~ $true
}
elseif (
$args.Length -eq 1 -and
(($args[0] -eq '-' -or $args[0] -eq '+') -or (Test-Path $args[0] -PathType Container))
) {
elseif ($args.Length -eq 1 -and ($args[0] -eq '-' -or $args[0] -eq '+')) {
__zoxide_cd $args[0] $false
}
elseif ($args.Length -eq 1 -and (Test-Path $args[0] -PathType Container)) {
__zoxide_cd $args[0] $true
}
else {
$result = __zoxide_pwd
if ($null -ne $result) {
$result = zoxide query --exclude $result -- @args
$result = __zoxide_bin query --exclude $result -- @args
}
else {
$result = zoxide query -- @args
$result = __zoxide_bin query -- @args
}
if ($LASTEXITCODE -eq 0) {
__zoxide_cd $result $true
@ -96,8 +124,8 @@ function __zoxide_z {
}
# Jump to a directory using interactive search.
function __zoxide_zi {
$result = zoxide query -i -- @args
function global:__zoxide_zi {
$result = __zoxide_bin query -i -- @args
if ($LASTEXITCODE -eq 0) {
__zoxide_cd $result $true
}
@ -123,4 +151,4 @@ Set-Alias -Name {{cmd}}i -Value __zoxide_zi -Option AllScope -Scope Global -Forc
# To initialize zoxide, add this to your configuration (find it by running
# `echo $profile` in PowerShell):
#
# Invoke-Expression (& { $hook = if ($PSVersionTable.PSVersion.Major -ge 6) { 'pwd' } else { 'prompt' } (zoxide init powershell --hook $hook | Out-String) })
# Invoke-Expression (& { (zoxide init powershell | Out-String) })

View File

@ -19,7 +19,7 @@ function __zoxide_pwd() {
# cd + custom logic based on the value of _ZO_ECHO.
function __zoxide_cd() {
# shellcheck disable=SC2164
\builtin cd -- "$@" >/dev/null {%- if echo %} && __zoxide_pwd {%- endif %}
\builtin cd -- "$@" {%- if echo %} && __zoxide_pwd {%- endif %}
}
{{ section }}
@ -61,7 +61,7 @@ function __zoxide_z() {
__zoxide_cd ~
elif [[ "$#" -eq 1 ]] && { [[ -d "$1" ]] || [[ "$1" = '-' ]] || [[ "$1" =~ ^[-+][0-9]$ ]]; }; then
__zoxide_cd "$1"
elif [[ "$@[-1]" == "${__zoxide_z_prefix}"* ]]; then
elif [[ "$@[-1]" == "${__zoxide_z_prefix}"?* ]]; then
# shellcheck disable=SC2124
\builtin local result="${@[-1]}"
__zoxide_cd "{{ "${result:${#__zoxide_z_prefix}}" }}"
@ -76,9 +76,35 @@ function __zoxide_z() {
# Jump to a directory using interactive search.
function __zoxide_zi() {
\builtin local result
result="$(\command zoxide query -i -- "$@")" && __zoxide_cd "${result}"
result="$(\command zoxide query --interactive -- "$@")" && __zoxide_cd "${result}"
}
# Completions.
if [[ -o zle ]]; then
function __zoxide_z_complete() {
# Only show completions when the cursor is at the end of the line.
# shellcheck disable=SC2154
[[ "{{ "${#words[@]}" }}" -eq "${CURRENT}" ]] || return 0
if [[ "{{ "${#words[@]}" }}" -eq 2 ]]; then
_files -/
elif [[ "${words[-1]}" == '' ]] && [[ "${words[-2]}" != "${__zoxide_z_prefix}"?* ]]; then
\builtin local result
# shellcheck disable=SC2086,SC2312
if result="$(\command zoxide query --exclude "$(__zoxide_pwd)" --interactive -- ${words[2,-1]})"; then
result="${__zoxide_z_prefix}${result}"
# shellcheck disable=SC2296
compadd -Q "${(q-)result}"
fi
\builtin printf '\e[5n'
fi
return 0
}
\builtin bindkey '\e[0n' 'reset-prompt'
[[ "${+functions[compdef]}" -ne 0 ]] && \compdef __zoxide_z_complete __zoxide_z
fi
{{ section }}
# Commands for zoxide. Disable these using --no-cmd.
#
@ -86,51 +112,8 @@ function __zoxide_zi() {
{%- match cmd %}
{%- when Some with (cmd) %}
\builtin unalias {{cmd}} &>/dev/null || \builtin true
function {{cmd}}() {
__zoxide_z "$@"
}
\builtin unalias {{cmd}}i &>/dev/null || \builtin true
function {{cmd}}i() {
__zoxide_zi "$@"
}
if [[ -o zle ]]; then
function __zoxide_z_complete() {
# Only show completions when the cursor is at the end of the line.
# shellcheck disable=SC2154
[[ "{{ "${#words[@]}" }}" -eq "${CURRENT}" ]] || return
if [[ "{{ "${#words[@]}" }}" -eq 2 ]]; then
_files -/
elif [[ "${words[-1]}" == '' ]]; then
\builtin local result
# shellcheck disable=SC2086,SC2312
if result="$(\command zoxide query --exclude "$(__zoxide_pwd)" -i -- ${words[2,-1]})"; then
__zoxide_result="${result}"
else
__zoxide_result=''
fi
\builtin printf '\e[5n'
fi
}
function __zoxide_z_complete_helper() {
\builtin local result="${__zoxide_z_prefix}${__zoxide_result}"
# shellcheck disable=SC2296
[[ -n "${__zoxide_result}" ]] && LBUFFER="${LBUFFER}${(q-)result}"
\builtin zle reset-prompt
}
\builtin zle -N __zoxide_z_complete_helper
\builtin bindkey "\e[0n" __zoxide_z_complete_helper
if [[ "${+functions[compdef]}" -ne 0 ]]; then
\compdef -d {{cmd}}
\compdef -d {{cmd}}i
\compdef __zoxide_z_complete {{cmd}}
fi
fi
\builtin alias {{cmd}}=__zoxide_z
\builtin alias {{cmd}}i=__zoxide_zi
{%- when None %}

View File

@ -6,12 +6,17 @@ use assert_cmd::Command;
#[test]
fn completions_bash() {
let source = include_str!("../contrib/completions/zoxide.bash");
Command::new("bash").args(&["--noprofile", "--norc", "-c", source]).assert().success().stdout("").stderr("");
Command::new("bash")
.args(["--noprofile", "--norc", "-c", source])
.assert()
.success()
.stdout("")
.stderr("");
}
// Elvish: the completions file uses editor commands to add completions to the shell. However,
// Elvish does not support running editor commands from a script, so we can't create a test for
// this. See: https://github.com/elves/elvish/issues/1299
// Elvish: the completions file uses editor commands to add completions to the
// shell. However, Elvish does not support running editor commands from a
// script, so we can't create a test for this. See: https://github.com/elves/elvish/issues/1299
#[test]
fn completions_fish() {
@ -21,7 +26,7 @@ fn completions_fish() {
Command::new("fish")
.env("HOME", tempdir)
.args(&["--command", source, "--private"])
.args(["--command", source, "--private"])
.assert()
.success()
.stdout("")
@ -32,7 +37,7 @@ fn completions_fish() {
fn completions_powershell() {
let source = include_str!("../contrib/completions/_zoxide.ps1");
Command::new("pwsh")
.args(&["-NoLogo", "-NonInteractive", "-NoProfile", "-Command", source])
.args(["-NoLogo", "-NonInteractive", "-NoProfile", "-Command", source])
.assert()
.success()
.stdout("")
@ -50,5 +55,5 @@ fn completions_zsh() {
compinit -u
"#;
Command::new("zsh").args(&["-c", source, "--no-rcs"]).assert().success().stdout("").stderr("");
Command::new("zsh").args(["-c", source, "--no-rcs"]).assert().success().stdout("").stderr("");
}

View File

@ -1,11 +0,0 @@
[package]
name = "xtask"
version = "0.1.0"
edition = "2021"
publish = false
[dependencies]
anyhow = "1.0.32"
clap = { version = "3.1.0", features = ["derive"] }
ignore = "0.4.18"
shell-words = "1.0.0"

View File

@ -1,136 +0,0 @@
use anyhow::{bail, Context, Result};
use clap::Parser;
use ignore::Walk;
use std::env;
use std::ffi::OsStr;
use std::path::PathBuf;
use std::process::{self, Command};
fn main() -> Result<()> {
let dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let dir = dir.parent().with_context(|| format!("could not find workspace root: {}", dir.display()))?;
env::set_current_dir(dir).with_context(|| format!("could not set current directory: {}", dir.display()))?;
let nix_enabled = enable_nix();
let app = App::parse();
match app {
App::CI => run_ci(nix_enabled)?,
App::Fmt { check } => run_fmt(nix_enabled, check)?,
App::Lint => run_lint(nix_enabled)?,
App::Test { name } => run_tests(nix_enabled, &name)?,
}
Ok(())
}
#[derive(Parser)]
enum App {
CI,
Fmt {
#[clap(long)]
check: bool,
},
Lint,
Test {
#[clap(default_value = "")]
name: String,
},
}
trait CommandExt {
fn run(self) -> Result<()>;
}
impl CommandExt for &mut Command {
fn run(self) -> Result<()> {
println!(">>> {:?}", self);
let status = self.status().with_context(|| format!("command failed to start: {:?}", self))?;
if !status.success() {
bail!("command failed: {:?} with status: {:?}", self, status);
}
Ok(())
}
}
fn run_ci(nix_enabled: bool) -> Result<()> {
// Run cargo-clippy.
Command::new("cargo").args(&["clippy", "--all-features", "--all-targets"]).args(&["--", "-Dwarnings"]).run()?;
run_fmt(nix_enabled, true)?;
run_lint(nix_enabled)?;
run_tests(nix_enabled, "")
}
fn run_fmt(nix_enabled: bool, check: bool) -> Result<()> {
// Run cargo-fmt.
let check_args: &[&str] = if check { &["--check", "--files-with-diff"] } else { &[] };
Command::new("cargo").args(&["fmt", "--all", "--"]).args(check_args).run()?;
// Run nixfmt.
if nix_enabled {
for result in Walk::new("./") {
let entry = result.unwrap();
let path = entry.path();
if path.is_file() && path.extension() == Some(OsStr::new("nix")) {
let check_args: &[&str] = if check { &["--check"] } else { &[] };
Command::new("nixfmt").args(check_args).arg("--").arg(path).run()?;
}
}
}
Ok(())
}
fn run_lint(nix_enabled: bool) -> Result<()> {
if nix_enabled {
// Run cargo-audit.
Command::new("cargo").args(&["audit", "--deny=warnings"]).run()?;
// Run markdownlint.
for result in Walk::new("./") {
let entry = result.unwrap();
let path = entry.path();
if path.is_file() && path.extension() == Some(OsStr::new("md")) {
Command::new("markdownlint").arg(path).run()?;
}
}
// Run mandoc with linting enabled.
for result in Walk::new("./man/") {
let entry = result.unwrap();
let path = entry.path();
if path.is_file() && path.extension() == Some(OsStr::new("1")) {
Command::new("mandoc").args(&["-man", "-Wall", "-Tlint", "--"]).arg(path).run()?;
}
}
}
Ok(())
}
fn run_tests(nix_enabled: bool, name: &str) -> Result<()> {
let args: &[&str] = if nix_enabled { &["nextest", "run", "--all-features"] } else { &["test"] };
Command::new("cargo").args(args).args(&["--no-fail-fast", "--workspace", "--", name]).run()
}
fn enable_nix() -> bool {
let nix_supported = cfg!(any(target_os = "linux", target_os = "macos"));
if !nix_supported {
return false;
}
let nix_enabled = env::var_os("IN_NIX_SHELL").unwrap_or_default() == "pure";
if nix_enabled {
return true;
}
let nix_detected = Command::new("nix-shell").arg("--version").status().map(|s| s.success()).unwrap_or(false);
if !nix_detected {
return false;
}
println!("Detected Nix in environment, re-running in Nix.");
let args = env::args();
let cmd = shell_words::join(args);
let status = Command::new("nix-shell").args(&["--pure", "--run", &cmd, "--", "shell.nix"]).status().unwrap();
process::exit(status.code().unwrap_or(1));
}