Compare commits

...

273 Commits
v0.7.9 ... main

Author SHA1 Message Date
Ajeet D'Souza 194f8e31e4
Update README.md 2025-10-03 02:59:45 +05:30
Ajeet D'Souza eb7b08fed3
Update README.md 2025-09-30 18:34:43 +05:30
dependabot[bot] 6ac39b7f41
Bump Swatinem/rust-cache from 2.8.0 to 2.8.1 (#1119)
Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.8.0 to 2.8.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.8.0...v2.8.1)

---
updated-dependencies:
- dependency-name: Swatinem/rust-cache
  dependency-version: 2.8.1
  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>
2025-09-23 01:57:49 +05:30
Ajeet D'Souza 76125d9bd3 Update sponsorship 2025-09-18 21:11:43 +05:30
Ben Beasley 2299f2834b
Unpin and update tempfile (#1105) 2025-08-23 02:27:21 +05:30
Bryan Corey c070535968
Add link to raycast-zoxide integration (#1050) 2025-08-16 09:57:04 +05:30
dependabot[bot] 4549438ffa
Bump actions/checkout from 4 to 5 (#1101)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-12 08:12:29 +05:30
Azalea Colburn 261cf8846a
--basedir query option (#1027) 2025-08-10 13:26:52 +05:30
Ben Beasley 30129f4344
Update rstest from 0.25 to 0.26 (#1099) 2025-08-07 21:55:06 +05:30
dependabot[bot] 9b899e90e3
Bump Swatinem/rust-cache from 2.7.8 to 2.8.0 (#1083)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-07 19:51:56 +05:30
Tony 6324b4e347
Add support for multi digits dirstack entry (e.g. +12). (#1080) 2025-06-17 15:27:46 +05:30
Steven Xu db14bdc2ff
nushell: use sigil operator (#1070) 2025-05-31 04:53:54 +05:30
Ajeet D'Souza d6c0203c09 Add Ubuntu install instructions 2025-05-27 05:28:12 +05:30
Ajeet D'Souza 859268aec8 chore(release): v0.9.8 2025-05-27 03:13:06 +05:30
Ajeet D'Souza b318a2a190 Nushell: add CLI completions 2025-05-27 02:34:48 +05:30
smolsbs ee8bbe57d3
Fixed the arg[0] argument position (#1056) 2025-05-16 03:19:09 -07:00
Koichi Murase 9c0dcf12f3
Run the cd command after "cd ... <space><tab>" in Bash integration (#1055) 2025-05-14 08:38:54 +05:30
benz 628f8542a0
Fixes #995: Wildcard expansion for PowerShell (#1001)
Co-authored-by: Ajeet D'Souza <98ajeet@gmail.com>
2025-05-13 17:48:55 +05:30
Ajeet D'Souza 3d9f928e01 Upgrade to Rust 2024 2025-05-12 09:42:37 -07:00
Ajeet D'Souza 4807518c4b Call fsync before close 2025-05-12 09:37:04 -07:00
Ajeet D'Souza 8e14038811 Fix lints 2025-05-12 06:22:54 -07:00
Ajeet D'Souza 1d08fe2b84 Add doctor for POSIX shells 2025-05-11 23:24:35 -07:00
Ajeet D'Souza 36537410cd Doctor should handle PROMPT_COMMAND being an array 2025-05-10 23:41:55 -07:00
Ajeet D'Souza 306d7ae143 Fix shellcheck lint 2025-05-10 21:52:39 -07:00
Ajeet D'Souza 9768e645c7 Remove unneeded quotes 2025-05-10 11:27:02 -07:00
Ajeet D'Souza 8f0288b0c9 Check if zoxide is running under VS Code 2025-05-10 10:22:20 -07:00
Koichi Murase 6ec0436859
Add fixes to Bash integration (#1048) 2025-05-10 13:21:47 +05:30
XTY f1d848820a
docs(install): fix broken Nixpkgs link (#1051) 2025-05-10 00:19:12 +05:30
Ajeet D'Souza 095b270fea csh -> tcsh 2025-05-06 00:26:07 -07:00
Ajeet D'Souza 09aa626a86
Add support for csh (#1047) 2025-05-06 11:28:54 +05:30
Lisa FS 7691d7e9a5
nushell: fix jumping to symlinks that are not in the database (#1032)
Expand path before checking for its type. This solves the issue of ignoring symlinks.
2025-04-13 08:46:27 -05:00
Brian 6902d375c5
powershell: Ignore errors from `__zoxide_hooked` (#1038) 2025-04-10 15:46:57 +05:30
Y.D.X. b193a84eb0
docs: Update minimum supported fzf version (#1034)
Before v0.51.0, fzf generates redundant escaping of backslashes for Windows, which breaks `zoxide edit`.
https://github.com/junegunn/fzf/releases/tag/0.51.0

Closes #539

Co-authored-by: Loïc Riegel <loic.riegel@outlook.fr>
2025-04-04 20:49:14 +05:30
Azalea Colburn e1e2f41ecb
feat: add --score option (#1030) 2025-03-31 11:43:40 +05:30
dependabot[bot] 7617799eb5
Bump Swatinem/rust-cache from 2.7.7 to 2.7.8 (#1025)
Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.7.7 to 2.7.8.
- [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.7.7...v2.7.8)

---
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>
2025-03-25 01:33:55 +05:30
dependabot[bot] 2480ed0853
Bump cachix/cachix-action from 15 to 16 (#1018)
Bumps [cachix/cachix-action](https://github.com/cachix/cachix-action) from 15 to 16.
- [Release notes](https://github.com/cachix/cachix-action/releases)
- [Commits](https://github.com/cachix/cachix-action/compare/v15...v16)

---
updated-dependencies:
- dependency-name: cachix/cachix-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>
2025-03-20 11:47:18 +05:30
Ajeet D'Souza 8118af567c
Update integrations 2025-03-20 11:46:13 +05:30
Ajeet D'Souza 6679619c00 bash: rewrite prompt after space-tab 2025-03-19 06:01:14 +05:30
dependabot[bot] 2ecb310108
Bump cachix/install-nix-action from 30 to 31 (#1012)
Bumps [cachix/install-nix-action](https://github.com/cachix/install-nix-action) from 30 to 31.
- [Release notes](https://github.com/cachix/install-nix-action/releases)
- [Commits](https://github.com/cachix/install-nix-action/compare/v30...v31)

---
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>
2025-03-11 12:55:35 +05:30
Ajeet D'Souza 0f07314d33
Update README.md 2025-02-27 00:06:36 +05:30
Ajeet D'Souza 845fe79b5e Lazy initialize help template 2025-02-13 13:00:58 +05:30
oliviajohnsto 868a242e05
Add Warp Pack sponsorship (#978) 2025-02-10 03:44:02 +05:30
Ajeet D'Souza d74bce3b74 Fix link in CHANGELOG 2025-02-10 03:19:05 +05:30
Ajeet D'Souza 5659f3c7dc chore(release): v0.9.7 2025-02-10 03:16:52 +05:30
Ajeet D'Souza 06f9f3f27c Release v0.9.7 2025-02-10 03:10:20 +05:30
Bahex da0fdb2bae
Update nushell init script (#966) 2025-02-10 02:47:41 +05:30
Ajeet D'Souza 3fe42e901e bash doctor 2025-01-09 04:36:18 +05:30
Ajeet D'Souza 791deec8ac zsh doctor 2025-01-09 03:53:39 +05:30
Ajeet D'Souza 678bbdefbc Use POSIX implementation for ksh shells
Only ksh93 supports DEBUG traps, and the rest don't have any features
that can be used for setting up hooks. May as well use the POSIX
implementation for all ksh shells.
2025-01-09 02:19:20 +05:30
dependabot[bot] d99d82f141
Bump Swatinem/rust-cache from 2.7.5 to 2.7.7 (#963)
Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.7.5 to 2.7.7.
- [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.7.5...v2.7.7)

---
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>
2024-12-31 08:12:42 +05:30
Ajeet D'Souza 1bdf393f9d Update Warp logo 2024-12-25 22:13:55 +05:30
Ben Beasley d52f6bffda
Update rstest to 0.23.0, the latest version (#937)
* Update rstest to 0.23.0 (the latest version)

* Update Cargo.lock for rstest 0.23.0
2024-11-16 03:22:46 +05:30
Ajeet D'Souza 60e870d704
Allow only one CI job at a time 2024-11-06 08:02:07 +05:30
nisbet-hubbard ede1224259 Update package info on Red Hat-based distros (#905)
---------

Co-authored-by: Ajeet D'Souza <98ajeet@gmail.com>
2024-10-16 04:37:42 +05:30
dependabot[bot] c61bdc1d54
Bump Swatinem/rust-cache from 2.7.3 to 2.7.5 (#921)
Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.7.3 to 2.7.5.
- [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.7.3...v2.7.5)

---
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>
2024-10-16 04:02:18 +05:30
dependabot[bot] 7696872637
Bump cachix/install-nix-action from V28 to 30 (#912)
Bumps [cachix/install-nix-action](https://github.com/cachix/install-nix-action) from V28 to 30. This release includes the previously tagged commit.
- [Release notes](https://github.com/cachix/install-nix-action/releases)
- [Commits](https://github.com/cachix/install-nix-action/compare/V28...v30)

---
updated-dependencies:
- dependency-name: cachix/install-nix-action
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-16 04:02:09 +05:30
Ajeet D'Souza a0c4049bf9
Remove Windows from CI 2024-10-09 19:12:57 +05:30
Ajeet D'Souza 7113ccddea Add envrc 2024-10-06 14:27:41 +05:30
Ajeet D'Souza d5f12fccea
Update Rust in CI 2024-09-20 21:31:59 +05:30
Peter Jeschke 075fd2c695
Add note about the default hook behaviour (#890) 2024-09-20 20:41:34 +05:30
Ajeet D'Souza 6d3a711115 Remove extra import 2024-09-20 23:05:53 +08:00
Ajeet D'Souza eb9bd54e59 cargo update 2024-09-19 16:27:01 +08:00
Ajeet D'Souza 3d3267b4fd chore(release): v0.9.6 2024-09-19 16:11:26 +08:00
Ajeet D'Souza e9d5af3f95 Remove builtin from abbr 2024-09-19 15:21:34 +08:00
dependabot[bot] 991cb0a144
Bump cachix/install-nix-action from V27 to 28 (#889)
Bumps [cachix/install-nix-action](https://github.com/cachix/install-nix-action) from V27 to 28. This release includes the previously tagged commit.
- [Release notes](https://github.com/cachix/install-nix-action/releases)
- [Commits](https://github.com/cachix/install-nix-action/compare/V27...V28)

---
updated-dependencies:
- dependency-name: cachix/install-nix-action
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-17 03:38:13 +05:30
Ajeet D'Souza 4403326d58
Update ksh instructions 2024-09-13 07:29:36 +05:30
Ajeet D'Souza 55ffe7446d chore(release): v0.9.5 2024-09-13 06:17:34 +05:30
Ajeet D'Souza efd49e7f3d Handle symlinked database files correctly 2024-09-13 06:05:41 +05:30
Ajeet D'Souza 450433644f Add symlinks to the database 2024-09-13 05:44:35 +05:30
Ajeet D'Souza c1d7c4f4c7
Add support for ksh (#885) 2024-09-13 05:00:42 +05:30
Ajeet D'Souza a1be4012e0 fish: fix typo 2024-09-12 22:37:47 +05:30
Ajeet D'Souza dc0d039be2 fish: use cd from data_dir 2024-09-12 04:43:06 +05:30
Ajeet D'Souza 1ef6d5b8fc fish: Jump to directory from interactive selector (experimental) 2024-09-12 04:42:17 +05:30
Ajeet D'Souza 878b41c937
Use macos-latest 2024-09-07 23:16:07 +05:30
Guillaume Gomez 550bca6458
Switch from askama to rinja (#879) 2024-09-02 13:28:30 +05:30
phanium 297499a4fe
fix(fish): prefix builtin for all non-reserved words (#865) 2024-08-03 23:12:09 +05:30
Han Li d655e026f5
Support `z -- dir` for zsh and fish (#858) 2024-07-20 10:49:43 +05:30
solodov 8da8f50eaa
support autocd option (#695)
* support autocd option

In bash, when autocd option is set and user enters a directory name as a
command, it results in a very specific call to cd:

cd -- [directory name]

zoxide's directory changing function passes all arguments to __zoxide_z as is,
including the "--" first argument. By detecting this and skipping the first
argument changing directory works with autocd set.

* remove directory check

just skip the first argument and pass the rest as is. checking for directory
breaks cd'ing to symlinked directories

* undo some of the hackery

tests are failing
2024-06-25 22:47:33 +05:30
Ajeet D'Souza b881866216
Verify that _ZO_DATA_DIR is an absolute path 2024-06-20 20:41:20 +05:30
Ajeet D'Souza dbe6f185cf
Remove --select-1 2024-06-03 13:04:07 +05:30
Ajeet D'Souza 063f9c1632
Add i686-unknown-linux under releases (#833) 2024-05-30 02:48:56 +05:30
alaviss 0dfe1c4073
fix(elvish): before-chdir should assign to oldpwd upvalue (#818)
elvish uses lexical scoping for closure capture, as such all `oldpwd`
references within the script refers to the `oldpwd` defined at the top.
However, before-chdir hook is trying to assign to `oldpwd` within the
editor scope, which is not used by this script.

This manifested as a bug in which:

```
~
> mkdir -p /tmp/another

~
> z /tmp

/tmp
> z another

/tmp/another
> z -

~
> # The previous dir should be /tmp not ~!
```

Because the hook was updating a variable that was not used.

Fix the hook so that before-chdir assign to the proper upvalue.
2024-05-29 15:06:28 +05:30
dedebenui 1a4c4933ca
fixed nushell init for new `path type` behavior (#830) 2024-05-29 13:15:56 +05:30
dependabot[bot] df0f6e525c
--- (#824)
updated-dependencies:
- dependency-name: cachix/cachix-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>
2024-05-21 09:17:24 +05:30
dependabot[bot] 8d28840a89
--- (#823)
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>
2024-05-21 09:16:51 +05:30
huajingyun 226aa66c3b
Update Cargo.lock (#817) 2024-05-16 23:09:05 +05:30
Ajeet D'Souza e2891634e2
Feature alfred-zoxide on README 2024-05-12 03:17:13 +05:30
Ajeet D'Souza 796fee720e
Add build for Android (#814) 2024-05-11 18:01:57 +05:30
Ajeet D'Souza ae33940331
chmod -- doesn't work on macOS 2024-05-10 18:07:19 +05:30
Ajeet D'Souza 1cb6312fb2 Return 0 only for Space+Tab 2024-05-06 10:47:50 +05:30
Aaron Dill 801d5e2f22
New installer options (v2) (#672)
Adds support for serveral new command line options (taken from the usage):
    - `--bin-dir   Override the bin installation directory`
    - `--man-dir   Override the man installation directory`
    - `--arch      Override the architecture identified by the installer`
    - `--sudo      Override the default sudo command`
    - `-h, --help      Display this help message`

---------

Co-authored-by: Ajeet D'Souza <98ajeet@gmail.com>
2024-05-06 02:51:23 +05:30
Ajeet D'Souza 4cba9808ff
Lazily delete excluded directories (#809) 2024-05-05 19:48:33 +05:30
gi1242 208bb6375e
zsh: better local directory completion (#787) 2024-05-05 19:40:29 +05:30
Ashley Bartlett 5d8a7ea7b2
fix: Space-Tab interactive completions repeating (#785) 2024-04-13 02:30:59 +05:30
David Legrand 36f440f3e3
docs: add install instructions for Exherbo Linux in README.md (#783) 2024-04-13 02:12:00 +05:30
Ajeet D'Souza 9f67fb5bb9
Add yazi integration 2024-03-30 02:07:55 +05:30
Ajeet D'Souza 94d3cba60e nushell: handle queries that look like args (#761) 2024-03-16 23:10:35 +05:30
Ajeet D'Souza 5b2c9222f9
pwsh: handle queries that look like args (#760) 2024-03-16 12:30:19 +05:30
Anselm Schüler 88d494fc2d
Fix handling of directories that look like flags in fish (#605) 2024-03-16 11:31:42 +05:30
dependabot[bot] 73a2eca43c
Bump softprops/action-gh-release from 1 to 2 (#751)
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 1 to 2.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](https://github.com/softprops/action-gh-release/compare/v1...v2)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  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>
2024-03-13 23:00:15 +05:30
dependabot[bot] 06f6ed56b5
Bump cachix/install-nix-action from 25 to 26 (#752)
Bumps [cachix/install-nix-action](https://github.com/cachix/install-nix-action) from 25 to 26.
- [Release notes](https://github.com/cachix/install-nix-action/releases)
- [Commits](https://github.com/cachix/install-nix-action/compare/v25...v26)

---
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>
2024-03-13 23:00:04 +05:30
xfzv 18001773f3
Add Gentoo repository (#743) 2024-03-05 10:36:09 +05:30
Ajeet D'Souza 012c67de63
Detect infinite loop on Fish (#732) 2024-02-24 04:00:38 +05:30
Ajeet D'Souza 65941f60a4
Highlight instructions 2024-02-22 23:47:39 +05:30
Ajeet D'Souza 418a78d348 chore(release): v0.9.4 2024-02-21 04:36:08 +05:30
Ajeet D'Souza c9183ce327 Always bind keys 2024-02-21 04:30:49 +05:30
Ajeet D'Souza 5592506456
Improve zsh completions (#725) 2024-02-21 01:45:20 +05:30
Ajeet D'Souza e0158a72a9 Remove install script from Windows instructions 2024-02-20 15:34:31 +05:30
Ajeet D'Souza 346f3aa90f
Use install script for BSD 2024-02-20 14:51:19 +05:30
Ajeet D'Souza c5451205f4
Add Solus packages 2024-02-20 14:44:50 +05:30
Z572 e55e0b27e2
Add GNU guix to install methods 2024-02-16 02:45:36 +05:30
Ajeet D'Souza cf34d02288 --cmd cd works on all shells 2024-02-16 02:31:47 +05:30
Ajeet D'Souza 90c66df2a8
More strikethrough 2024-02-16 00:01:38 +05:30
Ajeet D'Souza 0488b8cee2
Strikeout out-of-date packages 2024-02-15 21:48:20 +05:30
Ajeet D'Souza cbb8e77d60 chore(release): v0.9.3 2024-02-13 04:06:46 +05:30
Zuruh f32dc6bcd8
Update nushell template to use rest operator where needed (#663) 2024-02-13 03:01:04 +05:30
dependabot[bot] c8dda80716
Bump cachix/cachix-action from 12 to 14 (#665)
Bumps [cachix/cachix-action](https://github.com/cachix/cachix-action) from 12 to 14.
- [Release notes](https://github.com/cachix/cachix-action/releases)
- [Commits](https://github.com/cachix/cachix-action/compare/v12...v14)

---
updated-dependencies:
- dependency-name: cachix/cachix-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>
2024-02-13 02:47:29 +05:30
dependabot[bot] 4c3f771ad0
Bump actions/upload-artifact from 3 to 4 (#657)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 3 to 4.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  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>
2024-02-13 02:47:16 +05:30
dependabot[bot] d0f3d8726f
Bump Swatinem/rust-cache from 2.7.1 to 2.7.3 (#667)
Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.7.1 to 2.7.3.
- [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.7.1...v2.7.3)

---
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>
2024-02-13 02:46:57 +05:30
dependabot[bot] bb092cf9d6
Bump cachix/install-nix-action from 23 to 25 (#666)
Bumps [cachix/install-nix-action](https://github.com/cachix/install-nix-action) from 23 to 25.
- [Release notes](https://github.com/cachix/install-nix-action/releases)
- [Commits](https://github.com/cachix/install-nix-action/compare/v23...v25)

---
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>
2024-02-13 02:46:50 +05:30
dependabot[bot] 3e43eb4090
Bump SebRollen/toml-action from 1.0.2 to 1.2.0 (#676)
Bumps [SebRollen/toml-action](https://github.com/sebrollen/toml-action) from 1.0.2 to 1.2.0.
- [Release notes](https://github.com/sebrollen/toml-action/releases)
- [Commits](https://github.com/sebrollen/toml-action/compare/v1.0.2...v1.2.0)

---
updated-dependencies:
- dependency-name: SebRollen/toml-action
  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>
2024-02-13 02:46:41 +05:30
Ajeet D'Souza 3022cf3686 Fix CI 2023-11-21 02:48:58 +05:30
hackers267 f537a4e6d2
Update min version of Nushell 0.86.0 (#632)
Co-authored-by: Ajeet D'Souza <98ajeet@gmail.com>
2023-11-19 14:10:52 +05:30
Ajeet D'Souza afdcf6bdff Fix CI 2023-11-19 13:29:11 +05:30
dependabot[bot] 2014a89d50
Bump Swatinem/rust-cache from 2.7.0 to 2.7.1 (#634)
Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.7.0 to 2.7.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.7.0...v2.7.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-11-19 05:19:01 +05:30
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
Mark Keisler 1306fba4ac zsh: allow `z` to navigate dirstack via `+n` and `-n` 2022-07-26 13:27:06 +05:30
Ajeet D'Souza 209d86ab2f
Install script for zoxide (#425) 2022-07-15 07:30:48 +05:30
Jacob Baungård Hansen 31c44f0649
Add install instructions for openSUSE (#418) 2022-07-07 20:56:24 +05:30
Ajeet D'Souza e7cc34b359 Change change fuzzy completion prefix to `z!` 2022-07-04 17:16:35 +05:30
Ajeet D'Souza f830fbd670 Add minimum supported version for Fish 2022-07-01 15:03:30 +05:30
Ajeet D'Souza 3df60eb3d3 chore(release): v0.8.2 2022-06-26 01:22:49 +05:30
Ajeet D'Souza 7c7a2a0a3c
Add cargo-deb metadata (#409) 2022-06-26 00:37:44 +05:30
Ajeet D'Souza 10c11310ed
Escape spaces correctly in Bash completions (#408) 2022-06-25 17:20:28 +05:30
Eric Wang c7400cf0ed
Add default import path for autojump (#387) 2022-06-01 00:39:00 +05:30
Ajeet D'Souza 4bf470dff6 Use nextest in CI 2022-05-19 17:12:25 +05:30
Ajeet D'Souza 1105ab8107
Add joshuto under Integrations 2022-05-05 02:53:36 +05:30
NextAlone 7d0ddedc6b
fix: interactive completion for fish (#385)
Co-authored-by: Ajeet D'Souza <98ajeet@gmail.com>
2022-04-27 19:05:52 +05:30
Ajeet D'Souza 6b2c6a2bc3 Fix Windows paths on Fish+Cygwin 2022-04-27 02:53:22 +05:30
Ajeet D'Souza f628845fb7 Show fzf preview window below results 2022-04-26 20:09:17 +05:30
Ajeet D'Souza 3620189582 Speed up CI 2022-04-25 19:56:48 +05:30
Ryan Zoeller bf54e7f0cd
Limit nix features (#382) 2022-04-25 09:26:05 +05:30
Ajeet D'Souza 094baaa998 Update README 2022-04-25 08:27:15 +05:30
Ajeet D'Souza 96a43e9c79 Release v0.8.1 2022-04-23 06:39:14 +05:30
Ajeet D'Souza 612f264dd4
Upgrade to Nushell 0.61 (#362) 2022-04-22 13:11:11 +05:30
Kid 24d21ec8ab
Simplify fish init script (#370) 2022-04-17 14:36:47 +05:30
c02y 302ec231ba
--interactive/-i and --score/-s do not conflict in Query::query() (#342) 2022-04-13 17:03:06 +05:30
Kyohei Uto 212585ce74
Add felix to 3rd party integrations (#379) 2022-04-13 13:00:10 +05:30
Ajeet D'Souza e6bc1576ca Use `target_nix` when running xtasks in Nix 2022-04-11 03:58:35 +05:30
Ajeet D'Souza b71d33b5c1 Replace `--no-aliases` with `--no-cmd` 2022-04-11 03:41:51 +05:30
skwerlman a0242d7c9a
Switch elvish template to new try/catch syntax (#371) 2022-03-31 16:50:29 +05:30
Ajeet D'Souza 17365710af Copy owner from previous database file (#364) 2022-03-09 19:59:39 +05:30
Ajeet D'Souza 90e781a192
Bypass alias in fzf preview (#329) 2022-03-08 10:53:30 +05:30
Ajeet D'Souza dc64d912ab Reuse values in shell tests 2022-03-07 12:06:04 +05:30
Ajeet D'Souza 6307146fbb
Add zabb and clink-zoxide to README 2022-03-04 05:06:32 +05:30
sitiom f2857f229f
Fix Windows _ZO_DATA_DIR path in docs (#355) 2022-02-27 19:10:08 +05:30
Ajeet D'Souza ea096ac3ca Upgrade to Rust 1.59, strip binaries 2022-02-25 09:49:45 +05:30
Ajeet D'Souza df148c834f
Require bash 4.4+ for completions (#354) 2022-02-25 04:15:19 +05:30
Ajeet D'Souza b3366dd5b6 Upgrade to clap v3.1.0 2022-02-17 04:16:45 +05:30
Samuel Tschiedel 66d882bad4
Use --keep-right in fzf (#346) 2022-02-02 03:55:21 +05:30
Ajeet D'Souza bd71cf3a5d Update README 2022-01-21 03:19:12 +05:30
Ajeet D'Souza 4ce6e4b25d Remove persistence in Pylint tests 2022-01-12 13:52:13 +05:30
cherryblossom000 e34b6f930a Use elvish v0.17 lambda syntax in completions generation (#334) 2022-01-12 13:11:48 +05:30
Ajeet D'Souza b6b024c452 Upgrade to clap v3 stable 2022-01-03 05:53:19 +05:30
Ajeet D'Souza fe553585e9 Use -- with cd so paths aren't treated as options 2021-12-31 04:52:14 +05:30
Ajeet D'Souza 027d259c89 Use long arguments for commands in Fish shell 2021-12-30 04:02:23 +05:30
Ajeet D'Souza d106f4e358
Rename _z function (#324) 2021-12-28 23:40:10 +05:30
Ajeet D'Souza cf8dc69dda Move manpages to man/man1 2021-12-27 02:17:06 +05:30
Ajeet D'Souza 5c51c808aa Update GIF 2021-12-25 14:29:52 +05:30
Ajeet D'Souza a0d544adfd Release v0.8.0 2021-12-25 04:07:14 +05:30
Ajeet D'Souza 89dbb1fda6 Update GIF 2021-12-25 03:55:33 +05:30
Ajeet D'Souza 0a9081383d Fix interactive remove 2021-12-24 17:49:36 +05:30
Ajeet D'Souza fd088b43db Launch correct fzf binary on Windows 2021-12-24 16:20:22 +05:30
Ajeet D'Souza 339a789261 Avoid using completions on older Bash versions 2021-12-24 10:04:37 +05:30
Ajeet D'Souza bd54ea3ea9 Ignore CRLF for shell scripts (#315) 2021-12-15 01:15:39 +05:30
Tushar ff28b02801
Add path escapes for powershell (#313) 2021-12-13 12:16:12 +05:30
Ajeet D'Souza 5d81f1f22d Add macOS to CI 2021-12-06 00:43:34 +05:30
Ajeet D'Souza 58430d8c54 Add z completions for zsh (#309) 2021-12-05 14:51:32 +05:30
Ajeet D'Souza 72fd48ed97 Make fish completions consistent with Bash 2021-12-03 03:52:20 +05:30
Ajeet D'Souza 2a2848f55c
Handle early selection on fzf (#307) 2021-12-02 22:47:11 +05:30
Ajeet D'Souza 3e03ccccd6 Use better defaults for fzf 2021-12-02 00:55:33 +05:30
Oliver Mannion c392a8bb75
Fix errors with set -eu (#302) 2021-11-20 01:52:15 +05:30
Ajeet D'Souza 9f2f2e978e
Use global scope for aliases (#300) 2021-11-11 02:22:23 +05:30
Ajeet D'Souza e38f4d11bd Update README.md 2021-11-10 14:22:29 +05:30
crazystylus 5374d599d2
Add rust-version attribute (#297) 2021-11-09 17:08:26 +05:30
Ajeet D'Souza 1aea7450f5 Update README 2021-11-08 20:13:46 +05:30
72 changed files with 4994 additions and 2919 deletions

View File

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

View File

@ -1,2 +1,7 @@
[alias] [alias]
xtask = "run --package xtask --" xtask = "run --package xtask --"
# On Windows MSVC, statically link the C runtime so that the resulting EXE does
# not depend on the vcruntime DLL.
[target.'cfg(all(windows, target_env = "msvc"))']
rustflags = ["-C", "target-feature=+crt-static"]

View File

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

1
.envrc Normal file
View File

@ -0,0 +1 @@
use nix

7
.gitattributes vendored
View File

@ -1,2 +1,5 @@
contrib/completions/* linguist-generated=true /contrib/completions/* eol=lf linguist-generated=true text
contrib/completions/README.md linguist-generated=false /contrib/completions/README.md -eol -linguist-generated -text
/init.fish eol=lf text
/templates/*.txt eol=lf text
/zoxide.plugin.zsh eol=lf text

View File

@ -17,23 +17,23 @@ diverse, inclusive, and healthy community.
Examples of behavior that contributes to a positive environment for our Examples of behavior that contributes to a positive environment for our
community include: community include:
* Demonstrating empathy and kindness toward other people - Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences - Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback - Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes, - Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience 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 overall community
Examples of unacceptable behavior include: 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 advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks - Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment - Public or private harassment
* Publishing others' private information, such as a physical or email - Publishing others' private information, such as a physical or email
address, without their explicit permission 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 professional setting
## Enforcement Responsibilities ## Enforcement Responsibilities
@ -106,7 +106,7 @@ Violating these terms may lead to a permanent ban.
### 4. Permanent Ban ### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community **Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals. individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within **Consequence**: A permanent ban from any sort of public interaction within

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,29 +4,54 @@ on:
branches: [main] branches: [main]
pull_request: pull_request:
workflow_dispatch: workflow_dispatch:
env:
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
CARGO_INCREMENTAL: 0
CARGO_TERM_COLOR: always
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs: jobs:
ci: ci:
name: ${{ matrix.os }} name: ${{ matrix.os }}
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:
os: [ubuntu-latest, macos-latest, windows-latest] os: [ubuntu-latest]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v5
with:
fetch-depth: 0
- uses: actions-rs/toolchain@v1 - uses: actions-rs/toolchain@v1
if: ${{ matrix.os == 'windows-latest' }} if: ${{ matrix.os == 'windows-latest' }}
with: with:
toolchain: stable components: clippy
components: rustfmt, clippy
profile: minimal profile: minimal
override: true toolchain: stable
- uses: cachix/install-nix-action@v12 - uses: actions-rs/toolchain@v1
if: ${{ matrix.os == 'windows-latest' }}
with:
components: rustfmt
profile: minimal
toolchain: nightly
- uses: cachix/install-nix-action@v31
if: ${{ matrix.os != 'windows-latest' }} if: ${{ matrix.os != 'windows-latest' }}
with: with:
nix_path: nixpkgs=https://github.com/NixOS/nixpkgs/archive/20.09.tar.gz nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@v16
- run: cargo xtask ci if: ${{ matrix.os != 'windows-latest' && env.CACHIX_AUTH_TOKEN != '' }}
if: ${{ matrix.os == 'windows-latest' }} with:
- run: nix-shell --cores 0 --pure --run 'cargo xtask ci' authToken: ${{ env.CACHIX_AUTH_TOKEN }}
if: ${{ matrix.os != 'windows-latest' }} name: zoxide
- name: Setup cache
uses: Swatinem/rust-cache@v2.8.1
with:
key: ${{ matrix.os }}
- name: Install just
uses: taiki-e/install-action@v2
with:
tool: just
- name: Run lints + tests
run: just lint test

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

@ -0,0 +1,21 @@
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

@ -2,10 +2,12 @@ name: release
on: on:
push: push:
branches: [main] branches: [main]
tags: ["v[0-9]+.[0-9]+.[0-9]+"]
pull_request: pull_request:
workflow_dispatch: workflow_dispatch:
env:
CARGO_INCREMENTAL: 0
permissions:
contents: write
jobs: jobs:
release: release:
name: ${{ matrix.target }} name: ${{ matrix.target }}
@ -16,38 +18,39 @@ jobs:
include: include:
- os: ubuntu-latest - os: ubuntu-latest
target: x86_64-unknown-linux-musl target: x86_64-unknown-linux-musl
deb: true
- os: ubuntu-latest - os: ubuntu-latest
target: arm-unknown-linux-musleabihf target: arm-unknown-linux-musleabihf
- os: ubuntu-latest - os: ubuntu-latest
target: armv7-unknown-linux-musleabihf target: armv7-unknown-linux-musleabihf
deb: true
- os: ubuntu-latest - os: ubuntu-latest
target: aarch64-unknown-linux-musl target: aarch64-unknown-linux-musl
deb: true
- os: macos-11 - os: ubuntu-latest
target: i686-unknown-linux-musl
deb: true
- os: ubuntu-latest
target: aarch64-linux-android
- os: macos-latest
target: x86_64-apple-darwin target: x86_64-apple-darwin
- os: macos-11 - os: macos-latest
target: aarch64-apple-darwin target: aarch64-apple-darwin
- os: windows-latest - os: windows-latest
target: x86_64-pc-windows-msvc target: x86_64-pc-windows-msvc
- os: windows-latest - os: windows-latest
target: aarch64-pc-windows-msvc target: aarch64-pc-windows-msvc
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v2 uses: actions/checkout@v5
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Get version
- name: Set artifact name id: get_version
shell: bash uses: SebRollen/toml-action@v1.2.0
run: | with:
version="$(git describe --tags --match='v*.*.*' --always)" file: Cargo.toml
name="zoxide-$version-${{ matrix.target }}" field: package.version
echo "ARTIFACT_NAME=$name" >> $GITHUB_ENV
echo "version: $version"
echo "artifact: $name"
- name: Install Rust - name: Install Rust
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
with: with:
@ -55,45 +58,64 @@ jobs:
profile: minimal profile: minimal
override: true override: true
target: ${{ matrix.target }} target: ${{ matrix.target }}
- name: Setup cache
uses: Swatinem/rust-cache@v2.8.1
with:
key: ${{ matrix.target }}
- name: Install cross
if: ${{ runner.os == 'Linux' }}
uses: actions-rs/cargo@v1
with:
command: install
args: --color=always --git=https://github.com/cross-rs/cross.git --locked --rev=e281947ca900da425e4ecea7483cfde646c8a1ea --verbose cross
- name: Build binary - name: Build binary
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
command: build command: build
args: --release --locked --target=${{ matrix.target }} --color=always --verbose args: --release --locked --target=${{ matrix.target }} --color=always --verbose
use-cross: ${{ runner.os == 'Linux' }} 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) - name: Package (*nix)
if: runner.os != 'Windows' if: runner.os != 'Windows'
run: > run: |
tar -cv tar -cv CHANGELOG.md LICENSE README.md man/ \
CHANGELOG.md LICENSE README.md -C contrib/ completions/ -C ../ \
man/ -C target/${{ matrix.target }}/release/ zoxide |
-C contrib/ completions/ -C ../ gzip --best > \
-C target/${{ matrix.target }}/release/ zoxide zoxide-${{ steps.get_version.outputs.value }}-${{ matrix.target }}.tar.gz
| gzip --best > '${{ env.ARTIFACT_NAME }}.tar.gz'
- name: Package (Windows) - name: Package (Windows)
if: runner.os == 'Windows' if: runner.os == 'Windows'
run: > run: |
7z a ${{ env.ARTIFACT_NAME }}.zip 7z a zoxide-${{ steps.get_version.outputs.value }}-${{ matrix.target }}.zip `
CHANGELOG.md LICENSE README.md CHANGELOG.md LICENSE README.md ./man/ ./contrib/completions/ `
./man/ ./target/${{ matrix.target }}/release/zoxide.exe
./contrib/completions/
./target/${{ matrix.target }}/release/zoxide.exe
- name: Upload artifact - name: Upload artifact
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v4
with: with:
name: ${{ matrix.target }} name: ${{ matrix.target }}
path: | path: |
*.zip *.deb
*.tar.gz *.tar.gz
*.zip
- name: Create release - name: Create release
if: startsWith(github.ref, 'refs/tags/v') if: |
uses: softprops/action-gh-release@v1 github.ref == 'refs/heads/main' && startsWith(github.event.head_commit.message, 'chore(release)')
uses: softprops/action-gh-release@v2
with: with:
draft: true draft: true
files: | files: |
*.zip *.deb
*.tar.gz *.tar.gz
*.zip
name: ${{ steps.get_version.outputs.value }}
tag_name: ""

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

1
.gitignore vendored
View File

@ -5,6 +5,7 @@
# Compiled files and executables # Compiled files and executables
debug/ debug/
target/ target/
target_nix/
# Backup files generated by rustfmt # Backup files generated by rustfmt
**/*.rs.bk **/*.rs.bk

View File

@ -7,6 +7,212 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.9.8] - 2025-05-27
### Added
- Support for Tcsh.
- Added `--score` flag to `zoxide add`.
- POSIX: add doctor to diagnose common issues.
- Nushell: add CLI completions.
### Changed
- Bash: zoxide will now automatically `cd` when selecting Space-Tab completions.
### Fixed
- Bash: doctor now handles `PROMPT_COMMAND` being an array.
- Bash: doctor now handles Visual Studio Code's shell integration.
- Bash: completions now work with `ble.sh`.
- Nushell: stop ignoring symlinks when `cd`-ing into a directory.
- Fzf: updated minimum supported version to v0.51.0.
- PowerShell: avoid setting `$error` when defining `__zoxide_hooked`.
- PowerShell: handle special characters in file paths when `cd`-ing into them.
- Database corruption issue when the filesystem is 100% full.
## [0.9.7] - 2025-02-10
### Added
- Nushell: support for 0.102.0.
- Bash / Zsh: add doctor to diagnose common issues.
### Fixed
- ksh: alias to regular POSIX implementation for better compatibility.
## [0.9.6] - 2024-09-19
### Fixed
- Fish: `builtin abbr` doesn't work on older versions.
- Zsh: make `__zoxide_z_complete` available with `--no-cmd`.
## [0.9.5] - 2024-09-13
### Added
- Zsh: improved `cd` completions.
- Lazily delete excluded directories from the database.
- Fish: detect infinite loop when using `alias cd=z`.
- Installer: added flags for `--bin-dir`, `--man-dir`, `--arch`, and `--sudo`.
- Nushell: support for v0.94.0+.
- Bash/Fish/Zsh: support for `z -- dir` style queries.
- Fish: improved Space-Tab completions.
- Ksh: added support for the Korn shell.
### Changed
- fzf: removed `--select-1` from default options. The interactive selector will
now open up even if there is only one match.
- Enforce that `$_ZO_DATA_DIR` is an absolute path.
### Fixed
- Zsh: Space-Tab completion repeating output multiple times when matching single
directory
- Fish / Nushell / PowerShell: handle queries that look like args (e.g. `z -x`).
- Elvish: `z -` now works as expected.
- Fish: generated shell code avoids using aliased builtins.
- Fish: `cd` command is now copied directly from
`$__fish_data_dir/functions/cd.fish`. This should minimize the chances of an
infinite loop when aliasing `cd=z`.
- Symlinks not getting added to the database when `$_ZO_RESOLVE_SYMLINKS=0`.
- Symlinked database files getting replaced instead of the actual files.
## [0.9.4] - 2024-02-21
### Changed
- Zsh: improved Space-Tab completions.
## [0.9.3] - 2024-02-13
### Added
- Nushell: support for v0.89.0.
## [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: support for v0.78.0.
- Fish: plugin now works on older versions.
- PowerShell: warn when PowerShell version is too old for `z -` and `z +`.
- PowerShell: support for PWD hooks on all versions.
### Fixed
- Fish: not providing `cd` completions when there is a space in the path.
- Bash/Fish/Zsh: providing `z` completions when the last argument starts with `z!`.
- Bash/Fish/Zsh: attempting to `cd` when the last argument is `z!`.
## [0.9.0] - 2023-01-08
### Added
- `edit` subcommand to adjust the scores of entries.
### Fixed
- Zsh: completions clashing with `zsh-autocomplete`.
- Fzf: 'invalid option' on macOS.
- PowerShell: handle UTF-8 encoding correctly.
- Zsh: don't hide output from `chpwd` hooks.
- Nushell: upgrade minimum supported version to v0.73.0.
- Zsh: fix extra space in interactive completions when no match is found.
- Fzf: various improvements, upgrade minimum supported version to v0.33.0.
- Nushell: accidental redefinition of hooks when initialized twice.
### Removed
- `remove -i` subcommand: use `edit` instead.
## [0.8.3] - 2022-09-02
### Added
- Nushell: support for `z -`.
- Nushell: support for PWD hooks.
### Changed
- Fish: change fuzzy completion prefix to `z!`.
- Zsh: allow `z` to navigate dirstack via `+n` and `-n`.
- Fzf: improved preview window.
### Fixed
- Bash: double forward slash in completions.
## [0.8.2] - 2022-06-26
### Changed
- Fzf: show preview window below results.
### Fixed
- Bash/Fish/POSIX/Zsh: paths on Cygwin.
- Fish: completions not working on certain systems.
- Bash: completions not escaping spaces correctly.
## [0.8.1] - 2021-04-23
### Changed
- Manpages: moved to `man/man1/*.1`.
- Replace `--no-aliases` with `--no-cmd`.
- Elvish: upgrade minimum supported version to v0.18.0.
- Nushell: upgrade minimum supported version to v0.61.0.
### Fixed
- Bash/Zsh: rename `_z` completion function to avoid conflicts with other shell
plugins.
- Fzf: added `--keep-right` option by default, upgrade minimum supported version
to v0.21.0.
- Bash: only enable completions on 4.4+.
- Fzf: bypass `ls` alias in preview window.
- Retain ownership of database file.
- `zoxide query --interactive` should not conflict with `--score`.
## [0.8.0] - 2021-12-25
### Added
- Zsh: completions for `z` command.
### Changed
- Fzf: better default options.
- Fish: interactive completions are only triggered when the last argument is
empty.
- PowerShell: installation instructions.
### Fixed
- PowerShell: use global scope for aliases.
- Zsh: fix errors with `set -eu`.
- Fzf: handle early selection.
- PowerShell: correctly handle escape characters in paths.
- Parse error on Cygwin/MSYS due to CRLF line endings.
- Fzf: handle spaces correctly in preview window.
- Bash: avoid initializing completions on older versions.
- Fzf: avoid launching binary from current directory on Windows.
## [0.7.9] - 2021-11-02 ## [0.7.9] - 2021-11-02
### Changed ### Changed
@ -32,7 +238,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed ### Fixed
- PowerShell: Hook not initializing correctly. - PowerShell: hook not initializing correctly.
## [0.7.6] - 2021-10-13 ## [0.7.6] - 2021-10-13
@ -94,7 +300,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Auto-generated shell completions. - Auto-generated shell completions.
- `zoxide query --all` for listing deleted directories. - `zoxide query --all` for listing deleted directories.
- Lazy deletion for removed directories that have not been accessed in > 90 days. - Lazy deletion for removed directories that have not been accessed in > 90
days.
- Nushell: support for 0.32.0+. - Nushell: support for 0.32.0+.
### Fixed ### Fixed
@ -120,8 +327,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed ### Fixed
- `cd -` on fish shells. - `cd -` on Fish shells.
- `__zoxide_hook` no longer changes value of `$?` within `$PROMPT_COMMAND` on bash. - `__zoxide_hook` no longer changes value of `$?` within `$PROMPT_COMMAND` on
Bash.
### Removed ### Removed
@ -158,8 +366,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- `$_ZO_EXCLUDE_DIRS` now supports globs. - `$_ZO_EXCLUDE_DIRS` now supports globs.
- `zoxide init` now defines `__zoxide_z*` functions that can be aliased as needed. - `zoxide init` now defines `__zoxide_z*` functions that can be aliased as
- Support for the [xonsh](https://xon.sh/) shell. needed.
- Support for the [Xonsh](https://xon.sh/) shell.
- `zoxide import` can now import from Autojump. - `zoxide import` can now import from Autojump.
### Changed ### Changed
@ -168,7 +377,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed ### Fixed
- Clobber conflicting alias definitions in bash/fish/zsh/POSIX shells. - Clobber conflicting alias definitions in Bash/Fish/Zsh/POSIX shells.
### Removed ### Removed
@ -179,7 +388,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed ### Fixed
- Bug in Fish init script - Bug in Fish init script.
## [0.4.2] - 2020-07-03 ## [0.4.2] - 2020-07-03
@ -204,7 +413,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- Support for powershell. - Support for PowerShell.
### Removed ### Removed
@ -232,7 +441,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed ### Fixed
- fish no longer `cd`s to the user's home when no match is found. - Fish no longer `cd`s to the user's home when no match is found.
## [0.3.1] - 2020-04-03 ## [0.3.1] - 2020-04-03
@ -272,7 +481,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed ### Fixed
- Incorrect exit codes in `z` command on fish. - Incorrect exit codes in `z` command on Fish.
### Removed ### Removed
@ -285,7 +494,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `$_ZO_ECHO` to echo match before `cd`ing. - `$_ZO_ECHO` to echo match before `cd`ing.
- Minimal `ranger` plugin. - Minimal `ranger` plugin.
- PWD hook to only update the database when the current directory is changed. - PWD hook to only update the database when the current directory is changed.
- Support for bash. - Support for Bash.
- `migrate` subcommand to allow users to migrate from `z`. - `migrate` subcommand to allow users to migrate from `z`.
### Fixed ### Fixed
@ -299,11 +508,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `init` subcommand to remove dependency on shell plugin managers. - `init` subcommand to remove dependency on shell plugin managers.
- Support for `z -` command to go to previous directory. - Support for `z -` command to go to previous directory.
- `Cargo.lock` for more reproducible builds. - `Cargo.lock` for more reproducible builds.
- Support for the fish shell. - Support for the Fish shell.
### Fixed ### Fixed
- `_zoxide_precmd` overriding other precmd hooks on zsh. - `_zoxide_precmd` overriding other precmd hooks on Zsh.
## [0.1.1] - 2020-03-08 ## [0.1.1] - 2020-03-08
@ -325,8 +534,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- GitHub Actions pipeline to build and upload releases. - GitHub Actions pipeline to build and upload releases.
- Support for zsh. - Add support for Zsh.
[0.9.8]: https://github.com/ajeetdsouza/zoxide/compare/v0.9.7...v0.9.8
[0.9.7]: https://github.com/ajeetdsouza/zoxide/compare/v0.9.6...v0.9.7
[0.9.6]: https://github.com/ajeetdsouza/zoxide/compare/v0.9.5...v0.9.6
[0.9.5]: https://github.com/ajeetdsouza/zoxide/compare/v0.9.4...v0.9.5
[0.9.4]: https://github.com/ajeetdsouza/zoxide/compare/v0.9.3...v0.9.4
[0.9.3]: https://github.com/ajeetdsouza/zoxide/compare/v0.9.2...v0.9.3
[0.9.2]: https://github.com/ajeetdsouza/zoxide/compare/v0.9.1...v0.9.2
[0.9.1]: https://github.com/ajeetdsouza/zoxide/compare/v0.9.0...v0.9.1
[0.9.0]: https://github.com/ajeetdsouza/zoxide/compare/v0.8.3...v0.9.0
[0.8.3]: https://github.com/ajeetdsouza/zoxide/compare/v0.8.2...v0.8.3
[0.8.2]: https://github.com/ajeetdsouza/zoxide/compare/v0.8.1...v0.8.2
[0.8.1]: https://github.com/ajeetdsouza/zoxide/compare/v0.8.0...v0.8.1
[0.8.0]: https://github.com/ajeetdsouza/zoxide/compare/v0.7.9...v0.8.0
[0.7.9]: https://github.com/ajeetdsouza/zoxide/compare/v0.7.8...v0.7.9 [0.7.9]: https://github.com/ajeetdsouza/zoxide/compare/v0.7.8...v0.7.9
[0.7.8]: https://github.com/ajeetdsouza/zoxide/compare/v0.7.7...v0.7.8 [0.7.8]: https://github.com/ajeetdsouza/zoxide/compare/v0.7.7...v0.7.8
[0.7.7]: https://github.com/ajeetdsouza/zoxide/compare/v0.7.6...v0.7.7 [0.7.7]: https://github.com/ajeetdsouza/zoxide/compare/v0.7.6...v0.7.7

1050
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,51 +1,114 @@
[package] [package]
name = "zoxide"
version = "0.7.9"
authors = ["Ajeet D'Souza <98ajeet@gmail.com>"] authors = ["Ajeet D'Souza <98ajeet@gmail.com>"]
edition = "2018"
description = "A smarter cd command for your terminal"
repository = "https://github.com/ajeetdsouza/zoxide"
license = "MIT"
keywords = ["cli"]
categories = ["command-line-utilities", "filesystem"] categories = ["command-line-utilities", "filesystem"]
description = "A smarter cd command for your terminal"
edition = "2024"
homepage = "https://github.com/ajeetdsouza/zoxide"
keywords = ["cli", "filesystem", "shell", "tool", "utility"]
license = "MIT"
name = "zoxide"
readme = "README.md"
repository = "https://github.com/ajeetdsouza/zoxide"
rust-version = "1.85.0"
version = "0.9.8"
[badges] [badges]
maintenance = { status = "actively-developed" } maintenance = { status = "actively-developed" }
[workspace]
members = ["xtask/"]
[dependencies] [dependencies]
anyhow = "1.0.32" anyhow = "1.0.32"
askama = { version = "0.10.3", default-features = false } askama = { version = "0.14.0", default-features = false, features = [
"derive",
"std",
] }
bincode = "1.3.1" bincode = "1.3.1"
clap = "=3.0.0-beta.5" clap = { version = "4.3.0", features = ["derive"] }
dirs-next = "2.0.0" color-print = "0.3.4"
dirs = "6.0.0"
dunce = "1.0.1" dunce = "1.0.1"
fastrand = "2.0.0"
glob = "0.3.0" glob = "0.3.0"
ordered-float = "2.0.0" ouroboros = "0.18.3"
serde = { version = "1.0.116", features = ["derive"] } serde = { version = "1.0.116", features = ["derive"] }
tempfile = "3.1.0"
[target.'cfg(unix)'.dependencies]
nix = { version = "0.30.1", default-features = false, features = [
"fs",
"user",
] }
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
rand = { version = "0.8.4", features = [ which = "7.0.3"
"getrandom",
"small_rng",
], default-features = false }
[build-dependencies] [build-dependencies]
clap = "=3.0.0-beta.5" clap = { version = "4.3.0", features = ["derive"] }
clap_generate = "=3.0.0-beta.5" clap_complete = "4.5.50"
clap_generate_fig = "=3.0.0-beta.5" clap_complete_fig = "4.5.2"
clap_complete_nushell = "4.5.5"
color-print = "0.3.4"
[dev-dependencies] [dev-dependencies]
assert_cmd = "2.0.0" assert_cmd = "2.0.0"
rstest = "0.11.0" rstest = { version = "0.26.0", default-features = false }
rstest_reuse = "0.7.0"
tempfile = "3.15.0"
[features] [features]
default = [] default = []
nix = [] nix-dev = []
[profile.release] [profile.release]
codegen-units = 1 codegen-units = 1
debug = 0
lto = true lto = true
strip = true
[package.metadata.deb]
assets = [
[
"target/release/zoxide",
"usr/bin/",
"755",
],
[
"contrib/completions/zoxide.bash",
"usr/share/bash-completion/completions/zoxide",
"644",
],
[
"contrib/completions/zoxide.fish",
"usr/share/fish/vendor_completions.d/",
"664",
],
[
"contrib/completions/_zoxide",
"usr/share/zsh/vendor-completions/",
"644",
],
[
"man/man1/*",
"usr/share/man/man1/",
"644",
],
[
"README.md",
"usr/share/doc/zoxide/",
"644",
],
[
"CHANGELOG.md",
"usr/share/doc/zoxide/",
"644",
],
[
"LICENSE",
"usr/share/doc/zoxide/",
"644",
],
]
extended-description = """\
zoxide is a smarter cd command, inspired by z and autojump. It remembers which \
directories you use most frequently, so you can "jump" to them in just a few \
keystrokes."""
priority = "optional"
section = "utils"

2
Cross.toml Normal file
View File

@ -0,0 +1,2 @@
[build.env]
passthrough = ["CARGO_INCREMENTAL"]

699
README.md
View File

@ -9,6 +9,20 @@
<div align="center"> <div align="center">
<sup>Special thanks to:</sup>
<!-- markdownlint-disable-next-line MD013 -->
<div><a href="https://go.warp.dev/zoxide"><img alt="Sponsored by Warp" width="230" src="https://raw.githubusercontent.com/warpdotdev/brand-assets/refs/heads/main/Github/Sponsor/Warp-Github-LG-03.png" /></a></div>
<div><sup><b>Warp, built for coding with multiple AI agents.</b></sup></div>
<div><sup>Available for macOS, Linux, and Windows.</sup></div>
<div><sup>
Visit
<a href="https://go.warp.dev/zoxide"><u>warp.dev</u></a>
to learn more.
</sup></div>
<hr />
# zoxide # zoxide
[![crates.io][crates.io-badge]][crates.io] [![crates.io][crates.io-badge]][crates.io]
@ -33,244 +47,352 @@ zoxide works on all major shells.
![Tutorial][tutorial] ![Tutorial][tutorial]
```sh ```sh
z foo # cd into highest ranked directory matching foo z foo # cd into highest ranked directory matching foo
z foo bar # cd into highest ranked directory matching foo and bar z foo bar # cd into highest ranked directory matching foo and bar
z foo / # cd into a subdirectory starting with foo
z ~/foo # z also works like a regular cd command z ~/foo # z also works like a regular cd command
z foo/ # cd into relative path z foo/ # cd into relative path
z .. # cd one level up z .. # cd one level up
z - # cd into previous directory z - # cd into previous directory
zi foo # cd with interactive selection (using fzf) zi foo # cd with interactive selection (using fzf)
z foo<SPACE><TAB> # show interactive completions (zoxide v0.8.0+, bash 4.4+/fish/zsh only)
``` ```
Read more about the matching algorithm [here][algorithm-matching]. Read more about the matching algorithm [here][algorithm-matching].
## Installation ## Installation
### *Step 1: Install zoxide* zoxide can be installed in 4 easy steps:
zoxide runs on most major platforms. If your platform isn't listed below, 1. **Install binary**
please [open an issue][issues].
zoxide runs on most major platforms. If your platform isn't listed below,
<details> please [open an issue][issues].
<summary>Linux</summary>
<details>
To install zoxide, run this command in your terminal: <summary>Linux / WSL</summary>
```sh > The recommended way to install zoxide is via the install script:
curl -sS https://webinstall.dev/zoxide | bash >
``` > ```sh
> curl -sSfL https://raw.githubusercontent.com/ajeetdsouza/zoxide/main/install.sh | sh
Alternatively, you can use a package manager: > ```
>
| Distribution | Repository | Instructions | > Or, you can use a package manager:
| ------------------ | ----------------------- | ---------------------------------------------------------------------------------------------- | >
| ***Any*** | **[crates.io]** | `cargo install zoxide --locked` | > | Distribution | Repository | Instructions |
| *Any* | [conda-forge] | `conda install -c conda-forge zoxide` | > | ------------------- | ------------------------- | ----------------------------------------------------------------------------------------------------- |
| *Any* | [Linuxbrew] | `brew install zoxide` | > | **_Any_** | **[crates.io]** | `cargo install zoxide --locked` |
| Alpine Linux 3.13+ | [Alpine Linux Packages] | `apk add zoxide` | > | _Any_ | [asdf] | `asdf plugin add zoxide https://github.com/nyrst/asdf-zoxide.git` <br /> `asdf install zoxide latest` |
| Arch Linux | [Arch Linux Community] | `pacman -S zoxide` | > | _Any_ | [conda-forge] | `conda install -c conda-forge zoxide` |
| CentOS 7+ | [Copr] | `dnf copr enable atim/zoxide` <br /> `dnf install zoxide` | > | _Any_ | [guix] | `guix install zoxide` |
| Debian 11+ | [Debian Packages] | `apt install zoxide` | > | _Any_ | [Linuxbrew] | `brew install zoxide` |
| Devuan 4.0+ | [Devuan Packages] | `apt install zoxide` | > | _Any_ | [nixpkgs] | `nix-env -iA nixpkgs.zoxide` |
| Fedora 32+ | [Fedora Packages] | `dnf install zoxide` | > | AlmaLinux | | `dnf install zoxide` |
| Gentoo | [GURU Overlay] | `eselect repository enable guru` <br /> `emerge --sync guru` <br /> `emerge app-shells/zoxide` | > | Alpine Linux 3.13+ | [Alpine Linux Packages] | `apk add zoxide` |
| Manjaro | | `pacman -S zoxide` | > | Arch Linux | [Arch Linux Extra] | `pacman -S zoxide` |
| NixOS | [nixpkgs] | `nix-env -iA nixpkgs.zoxide` | > | CentOS Stream | | `dnf install zoxide` |
| Parrot OS | | `apt install zoxide` | > | ~Debian 11+~[^1] | ~[Debian Packages]~ | ~`apt install zoxide`~ |
| Raspbian | | `apt install zoxide` | > | Devuan 4.0+ | [Devuan Packages] | `apt install zoxide` |
| Ubuntu 21.04+ | [Ubuntu Packages] | `apt install zoxide` | > | Exherbo Linux | [Exherbo packages] | `cave resolve -x repository/rust` <br /> `cave resolve -x zoxide` |
| Void Linux | [Void Linux Packages] | `xbps-install -S zoxide` | > | Fedora 32+ | [Fedora Packages] | `dnf install zoxide` |
> | Gentoo | [Gentoo Packages] | `emerge app-shells/zoxide` |
</details> > | Linux Mint | [apt.cli.rs] (unofficial) | [Setup the repository][apt.cli.rs-setup], then `apt install zoxide` |
> | Manjaro | | `pacman -S zoxide` |
<details> > | openSUSE Tumbleweed | [openSUSE Factory] | `zypper install zoxide` |
<summary>macOS</summary> > | ~Parrot OS~[^1] | | ~`apt install zoxide`~ |
> | ~Raspbian 11+~[^1] | ~[Raspbian Packages]~ | ~`apt install zoxide`~ |
To install zoxide, use a package manager: > | RHEL 8+ | | `dnf install zoxide` |
> | Rhino Linux | [Pacstall Packages] | `pacstall -I zoxide-deb` |
| Repository | Instructions | > | Rocky Linux | | `dnf install zoxide` |
| --------------- | ------------------------------------- | > | Slackware 15.0+ | [SlackBuilds] | [Instructions][slackbuilds-howto] |
| **[crates.io]** | `cargo install zoxide --locked` | > | Solus | [Solus Packages] | `eopkg install zoxide` |
| [conda-forge] | `conda install -c conda-forge zoxide` | > | Ubuntu | [apt.cli.rs] (unofficial) | [Setup the repository][apt.cli.rs-setup], then `apt install zoxide` |
| [Homebrew] | `brew install zoxide` | > | Void Linux | [Void Linux Packages] | `xbps-install -S zoxide` |
| [MacPorts] | `port install zoxide` |
</details>
</details>
<details>
<details> <summary>macOS</summary>
<summary>Windows</summary>
> To install zoxide, use a package manager:
To install zoxide, run this command in your command prompt: >
> | Repository | Instructions |
```sh > | --------------- | ----------------------------------------------------------------------------------------------------- |
curl.exe -A "MS" https://webinstall.dev/zoxide | powershell > | **[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` |
Alternatively, you can use a package manager: > | [conda-forge] | `conda install -c conda-forge zoxide` |
> | [MacPorts] | `port install zoxide` |
| Repository | Instructions | > | [nixpkgs] | `nix-env -iA nixpkgs.zoxide` |
| --------------- | ------------------------------------- | >
| **[crates.io]** | `cargo install zoxide --locked` | > Or, run this command in your terminal:
| [Chocolatey] | `choco install zoxide` | >
| [conda-forge] | `conda install -c conda-forge zoxide` | > ```sh
| [Scoop] | `scoop install zoxide` | > curl -sSfL https://raw.githubusercontent.com/ajeetdsouza/zoxide/main/install.sh | sh
> ```
</details>
</details>
<details>
<summary>BSD</summary> <details>
<summary>Windows</summary>
To install zoxide, use a package manager:
> zoxide works with PowerShell, as well as shells running in Cygwin, Git
| Distribution | Repository | Instructions | > Bash, and MSYS2.
| ------------- | --------------- | ------------------------------- | >
| ***Any*** | **[crates.io]** | `cargo install zoxide --locked` | > The recommended way to install zoxide is via `winget`:
| DragonFly BSD | [DPorts] | `pkg install zoxide` | >
| FreeBSD | [FreshPorts] | `pkg install zoxide` | > ```sh
| NetBSD | [pkgsrc] | `pkgin install zoxide` | > winget install ajeetdsouza.zoxide
> ```
</details> >
> Or, you can use an alternative package manager:
<details> >
<summary>Android</summary> > | Repository | Instructions |
> | --------------- | ------------------------------------- |
To install zoxide, use a package manager: > | **[crates.io]** | `cargo install zoxide --locked` |
> | [Chocolatey] | `choco install zoxide` |
| Repository | Instructions | > | [conda-forge] | `conda install -c conda-forge zoxide` |
| ---------- | -------------------- | > | [Scoop] | `scoop install zoxide` |
| [Termux] | `pkg install zoxide` | >
> If you're using Cygwin, Git Bash, or MSYS2, you can also use the install script:
</details> >
> ```sh
### *Step 2: Add zoxide to your shell* > curl -sSfL https://raw.githubusercontent.com/ajeetdsouza/zoxide/main/install.sh | sh
> ```
To start using zoxide, add it to your shell.
</details>
<details>
<summary>Bash</summary> <details>
<summary>BSD</summary>
Add this to your configuration (usually `~/.bashrc`):
> To install zoxide, use a package manager:
```sh >
eval "$(zoxide init bash)" > | Distribution | Repository | Instructions |
``` > | ------------- | --------------- | ------------------------------- |
> | **_Any_** | **[crates.io]** | `cargo install zoxide --locked` |
</details> > | DragonFly BSD | [DPorts] | `pkg install zoxide` |
> | FreeBSD | [FreshPorts] | `pkg install zoxide` |
<details> > | NetBSD | [pkgsrc] | `pkgin install zoxide` |
<summary>Elvish</summary> >
> Or, run this command in your terminal:
Add this to your configuration (usually `~/.elvish/rc.elv`): >
> ```sh
```sh > curl -sS https://raw.githubusercontent.com/ajeetdsouza/zoxide/main/install.sh | bash
eval (zoxide init elvish | slurp) > ```
```
</details>
Note: zoxide only supports elvish v0.16.0 and above.
<details>
</details> <summary>Android</summary>
<details> > To install zoxide, use a package manager:
<summary>Fish</summary> >
> | Repository | Instructions |
Add this to your configuration (usually `~/.config/fish/config.fish`): > | ---------- | -------------------- |
> | [Termux] | `pkg install zoxide` |
```fish >
zoxide init fish | source > Or, run this command in your terminal:
``` >
> ```sh
</details> > curl -sS https://raw.githubusercontent.com/ajeetdsouza/zoxide/main/install.sh | bash
> ```
<details>
<summary>Nushell</summary> </details>
Add this to your configuration (find it by running `config path` in Nushell): 2. **Setup zoxide on your shell**
```toml To start using zoxide, add it to your shell.
startup = ["zoxide init nushell --hook prompt | save ~/.zoxide.nu", "source ~/.zoxide.nu"]
``` <details>
<summary>Bash</summary>
Note: zoxide only supports Nushell v0.37.0 and above.
> Add this to the <ins>**end**</ins> of your config file (usually `~/.bashrc`):
</details> >
> ```sh
<details> > eval "$(zoxide init bash)"
<summary>PowerShell</summary> > ```
Add this to your configuration (find it by running `echo $profile` in </details>
PowerShell):
<details>
```powershell <summary>Elvish</summary>
Invoke-Expression (& {
$hook = if ($PSVersionTable.PSVersion.Major -lt 6) { 'prompt' } else { 'pwd' } > Add this to the <ins>**end**</ins> of your config file (usually `~/.elvish/rc.elv`):
(zoxide init --hook $hook powershell) -join "`n" >
}) > ```sh
``` > eval (zoxide init elvish | slurp)
> ```
</details> >
> **Note**
<details> > zoxide only supports elvish v0.18.0 and above.
<summary>Xonsh</summary>
</details>
Add this to your configuration (usually `~/.xonshrc`):
<details>
```python <summary>Fish</summary>
execx($(zoxide init xonsh), 'exec', __xonsh__.ctx, filename='zoxide')
``` > Add this to the <ins>**end**</ins> of your config file (usually
> `~/.config/fish/config.fish`):
</details> >
> ```sh
<details> > zoxide init fish | source
<summary>Zsh</summary> > ```
Add this to your configuration (usually `~/.zshrc`): </details>
```sh <details>
eval "$(zoxide init zsh)" <summary>Nushell</summary>
```
> Add this to the <ins>**end**</ins> of your env file (find it by running `$nu.env-path`
</details> > in Nushell):
>
<details> > ```sh
<summary>Any POSIX shell</summary> > zoxide init nushell | save -f ~/.zoxide.nu
> ```
Add this to your configuration: >
> Now, add this to the <ins>**end**</ins> of your config file (find it by running
```sh > `$nu.config-path` in Nushell):
eval "$(zoxide init posix --hook prompt)" >
``` > ```sh
> source ~/.zoxide.nu
</details> > ```
>
### *Step 3: Install fzf (optional)* > **Note**
> zoxide only supports Nushell v0.89.0+.
[fzf] is a command-line fuzzy finder, used by zoxide for interactive
selection. It can be installed from [here][fzf-installation]. </details>
### *Step 4: Import your data (optional)* <details>
<summary>PowerShell</summary>
If you currently use any of the following utilities, you may want to import
your data into zoxide: > Add this to the <ins>**end**</ins> of your config file (find it by running
> `echo $profile` in PowerShell):
<details> >
<summary>autojump</summary> > ```powershell
> Invoke-Expression (& { (zoxide init powershell | Out-String) })
```sh > ```
zoxide import --from autojump path/to/db
``` </details>
</details> <details>
<summary>Tcsh</summary>
<details>
<summary>z, z.lua, or zsh-z</summary> > Add this to the <ins>**end**</ins> of your config file (usually `~/.tcshrc`):
>
```sh > ```sh
zoxide import --from z path/to/db > zoxide init tcsh > ~/.zoxide.tcsh
``` > source ~/.zoxide.tcsh
> ```
</details>
</details>
<details>
<summary>Xonsh</summary>
> Add this to the <ins>**end**</ins> of your config file (usually `~/.xonshrc`):
>
> ```python
> execx($(zoxide init xonsh), 'exec', __xonsh__.ctx, filename='zoxide')
> ```
</details>
<details>
<summary>Zsh</summary>
> Add this to the <ins>**end**</ins> 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 the <ins>**end**</ins> of your config file:
>
> ```sh
> eval "$(zoxide init posix --hook prompt)"
> ```
</details>
3. **Install fzf** <sup>(optional)</sup>
[fzf] is a command-line fuzzy finder, used by zoxide for completions /
interactive selection. It can be installed from [here][fzf-installation].
> **Note**
> The minimum supported fzf version is v0.51.0.
4. **Import your data** <sup>(optional)</sup>
If you currently use any of these plugins, you may want to import your data
into zoxide:
<details>
<summary>autojump</summary>
> Run this command in your terminal:
>
> ```sh
> zoxide import --from=autojump "/path/to/autojump/db"
> ```
>
> The path usually varies according to your system:
>
> | OS | Path | Example |
> | ------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------ |
> | Linux | `$XDG_DATA_HOME/autojump/autojump.txt` or `$HOME/.local/share/autojump/autojump.txt` | `/home/alice/.local/share/autojump/autojump.txt` |
> | macOS | `$HOME/Library/autojump/autojump.txt` | `/Users/Alice/Library/autojump/autojump.txt` |
> | Windows | `%APPDATA%\autojump\autojump.txt` | `C:\Users\Alice\AppData\Roaming\autojump\autojump.txt` |
</details>
<details>
<summary>fasd, z, z.lua, zsh-z</summary>
> Run this command in your terminal:
>
> ```sh
> zoxide import --from=z "path/to/z/db"
> ```
>
> The path usually varies according to your system:
>
> | Plugin | Path |
> | ---------------- | ----------------------------------------------------------------------------------- |
> | fasd | `$_FASD_DATA` or `$HOME/.fasd` |
> | z (bash/zsh) | `$_Z_DATA` or `$HOME/.z` |
> | z (fish) | `$Z_DATA` or `$XDG_DATA_HOME/z/data` or `$HOME/.local/share/z/data` |
> | z.lua (bash/zsh) | `$_ZL_DATA` or `$HOME/.zlua` |
> | z.lua (fish) | `$XDG_DATA_HOME/zlua/zlua.txt` or `$HOME/.local/share/zlua/zlua.txt` or `$_ZL_DATA` |
> | zsh-z | `$ZSHZ_DATA` or `$_Z_DATA` or `$HOME/.z` |
</details>
<details>
<summary>ZLocation</summary>
> Run this command in PowerShell:
>
> ```powershell
> $db = New-TemporaryFile
> (Get-ZLocation).GetEnumerator() | ForEach-Object { Write-Output ($_.Name+'|'+$_.Value+'|0') } | Out-File $db
> zoxide import --from=z $db
> ```
</details>
## Configuration ## Configuration
@ -279,34 +401,38 @@ zoxide import --from z path/to/db
When calling `zoxide init`, the following flags are available: When calling `zoxide init`, the following flags are available:
- `--cmd` - `--cmd`
- Changes the prefix of predefined aliases (`z`, `zi`). - Changes the prefix of the `z` and `zi` commands.
- `--cmd j` would change the aliases to (`j`, `ji`). - `--cmd j` would change the commands to (`j`, `ji`).
- `--cmd cd` would replace the `cd` command (doesn't work on Nushell / POSIX shells). - `--cmd cd` would replace the `cd` command.
- `--hook <HOOK>` - `--hook <HOOK>`
- Changes how often zoxide increments a directory's score: - Changes how often zoxide increments a directory's score:
| Hook | Description |
| -------- | --------------------------------- | | Hook | Description |
| `none` | Never | | --------------- | --------------------------------- |
| `prompt` | At every shell prompt | | `none` | Never |
| `pwd` | Whenever the directory is changed | | `prompt` | At every shell prompt |
- `--no-aliases` | `pwd` (default) | Whenever the directory is changed |
- Don't define aliases (`z`, `zi`).
- `--no-cmd`
- Prevents zoxide from defining the `z` and `zi` commands.
- These functions will still be available in your shell as `__zoxide_z` and - These functions will still be available in your shell as `__zoxide_z` and
`__zoxide_zi`, should you choose to redefine them. `__zoxide_zi`, should you choose to redefine them.
### Environment variables ### Environment variables
Environment variables<sup>[?][wiki-env]</sup> can be used for configuration. Environment variables[^2] can be used for configuration. They must be set before
They must be set before `zoxide init` is called. `zoxide init` is called.
- `_ZO_DATA_DIR` - `_ZO_DATA_DIR`
- Specifies the directory in which the database is stored. - Specifies the directory in which the database is stored.
- The default value varies across OSes: - The default value varies across OSes:
| OS | Path | Example | | OS | Path | Example |
| ----------- | ---------------------------------------- | ------------------------------------------ | | ----------- | ---------------------------------------- | ------------------------------------------ |
| Linux / BSD | `$XDG_DATA_HOME` or `$HOME/.local/share` | `/home/alice/.local/share` | | Linux / BSD | `$XDG_DATA_HOME` or `$HOME/.local/share` | `/home/alice/.local/share` |
| macOS | `$HOME/Library/Application Support` | `/Users/Alice/Library/Application Support` | | macOS | `$HOME/Library/Application Support` | `/Users/Alice/Library/Application Support` |
| Windows | `{FOLDERID_RoamingAppData}` | `C:\Users\Alice\AppData\Roaming` | | Windows | `%LOCALAPPDATA%` | `C:\Users\Alice\AppData\Local` |
- `_ZO_ECHO` - `_ZO_ECHO`
- When set to 1, `z` will print the matched directory before navigating to - When set to 1, `z` will print the matched directory before navigating to
it. it.
@ -314,10 +440,12 @@ They must be set before `zoxide init` is called.
- Excludes the specified directories from the database. - Excludes the specified directories from the database.
- This is provided as a list of [globs][glob], separated by OS-specific - This is provided as a list of [globs][glob], separated by OS-specific
characters: characters:
| OS | Separator | Example | | OS | Separator | Example |
| ------------------- | --------- | ----------------------- | | ------------------- | --------- | ----------------------- |
| Linux / macOS / BSD | `:` | `$HOME:$HOME/private/*` | | Linux / macOS / BSD | `:` | `$HOME:$HOME/private/*` |
| Windows | `;` | `$HOME;$HOME/private/*` | | Windows | `;` | `$HOME;$HOME/private/*` |
- By default, this is set to `"$HOME"`. - By default, this is set to `"$HOME"`.
- `_ZO_FZF_OPTS` - `_ZO_FZF_OPTS`
- Custom options to pass to [fzf] during interactive selection. See - Custom options to pass to [fzf] during interactive selection. See
@ -332,64 +460,113 @@ They must be set before `zoxide init` is called.
## Third-party integrations ## Third-party integrations
| Application | Description | Plugin | | Application | Description | Plugin |
| ------------------ | --------------------------------------- | -------------------------- | | --------------------- | -------------------------------------------- | -------------------------- |
| [emacs] | Text editor | [zoxide.el] | | [aerc] | Email client | Natively supported |
| [nnn] | File manager | [nnn-autojump] | | [alfred] | macOS launcher | [alfred-zoxide] |
| [ranger] | File manager | [ranger-zoxide] | | [clink] | Improved cmd.exe for Windows | [clink-zoxide] |
| [telescope.nvim] | Fuzzy finder for Neovim | [telescope-zoxide] | | [emacs] | Text editor | [zoxide.el] |
| [vim] | Text editor | [zoxide.vim] | | [felix] | File manager | Natively supported |
| [xplr] | File manager | [zoxide.xplr] | | [joshuto] | File manager | Natively supported |
| [xxh] | Transports shell configuration over SSH | [xxh-plugin-prerun-zoxide] | | [lf] | File manager | See the [wiki][lf-wiki] |
| [zsh-autocomplete] | Realtime completions for zsh | Supported by default | | [nnn] | File manager | [nnn-autojump] |
| [ranger] | File manager | [ranger-zoxide] |
| [raycast] | macOS launcher | [raycast-zoxide] |
| [rfm] | File manager | Natively supported |
| [sesh] | `tmux` session manager | Natively supported |
| [telescope.nvim] | Fuzzy finder for Neovim | [telescope-zoxide] |
| [tmux-session-wizard] | `tmux` session manager | Natively supported |
| [tmux-sessionx] | `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] |
| [yazi] | File manager | Natively supported |
| [zabb] | Finds the shortest possible query for a path | Natively supported |
| [zesh] | `zellij` session manager | Natively supported |
| [zsh-autocomplete] | Realtime completions for zsh | Natively supported |
[^1]:
Debian and its 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
[alfred]: https://www.alfredapp.com/
[alfred-zoxide]: https://github.com/yihou/alfred-zoxide
[algorithm-aging]: https://github.com/ajeetdsouza/zoxide/wiki/Algorithm#aging [algorithm-aging]: https://github.com/ajeetdsouza/zoxide/wiki/Algorithm#aging
[algorithm-matching]: https://github.com/ajeetdsouza/zoxide/wiki/Algorithm#matching [algorithm-matching]: https://github.com/ajeetdsouza/zoxide/wiki/Algorithm#matching
[alpine linux packages]: https://pkgs.alpinelinux.org/packages?name=zoxide [alpine linux packages]: https://pkgs.alpinelinux.org/packages?name=zoxide
[arch linux community]: https://archlinux.org/packages/community/x86_64/zoxide/ [apt.cli.rs]: https://apt.cli.rs/
[builtwithnix-badge]: https://img.shields.io/badge/builtwith-nix-7d81f7 [apt.cli.rs-setup]: https://github.com/emmatyping/apt.cli.rs#how-to-add-the-repo
[arch linux extra]: https://archlinux.org/packages/extra/x86_64/zoxide/
[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/ [builtwithnix]: https://builtwithnix.org/
[chocolatey]: https://community.chocolatey.org/packages/zoxide [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 [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?logo=rust&logoColor=white&style=flat-square
[crates.io-badge]: https://img.shields.io/crates/v/zoxide
[crates.io]: https://crates.io/crates/zoxide [crates.io]: https://crates.io/crates/zoxide
[debian packages]: https://packages.debian.org/stable/admin/zoxide [debian packages]: https://packages.debian.org/stable/admin/zoxide
[exherbo packages]: https://gitlab.exherbo.org/exherbo/rust/-/tree/master/packages/sys-apps/zoxide
[devuan packages]: https://pkginfo.devuan.org/cgi-bin/package-query.html?c=package&q=zoxide [devuan packages]: https://pkginfo.devuan.org/cgi-bin/package-query.html?c=package&q=zoxide
[downloads-badge]: https://img.shields.io/github/downloads/ajeetdsouza/zoxide/total [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 [dports]: https://github.com/DragonFlyBSD/DPorts/tree/master/sysutils/zoxide
[emacs]: https://www.gnu.org/software/emacs/ [emacs]: https://www.gnu.org/software/emacs/
[fedora packages]: https://src.fedoraproject.org/rpms/rust-zoxide [fedora packages]: https://src.fedoraproject.org/rpms/rust-zoxide
[felix]: https://github.com/kyoheiu/felix
[freshports]: https://www.freshports.org/sysutils/zoxide/ [freshports]: https://www.freshports.org/sysutils/zoxide/
[fzf-installation]: https://github.com/junegunn/fzf#installation [fzf-installation]: https://github.com/junegunn/fzf#installation
[fzf-man]: https://manpages.ubuntu.com/manpages/en/man1/fzf.1.html [fzf-man]: https://manpages.ubuntu.com/manpages/en/man1/fzf.1.html
[fzf]: https://github.com/junegunn/fzf [fzf]: https://github.com/junegunn/fzf
[gentoo packages]: https://packages.gentoo.org/packages/app-shells/zoxide
[glob]: https://man7.org/linux/man-pages/man7/glob.7.html [glob]: https://man7.org/linux/man-pages/man7/glob.7.html
[guru overlay]: https://github.com/gentoo-mirror/guru [guix]: https://packages.guix.gnu.org/packages/zoxide/
[homebrew]: https://formulae.brew.sh/formula/zoxide [homebrew]: https://formulae.brew.sh/formula/zoxide
[issues]: https://github.com/ajeetdsouza/zoxide/issues/new [issues]: https://github.com/ajeetdsouza/zoxide/issues/new
[joshuto]: https://github.com/kamiyaa/joshuto
[lf]: https://github.com/gokcehan/lf
[lf-wiki]: https://github.com/gokcehan/lf/wiki/Integrations#zoxide
[linuxbrew]: https://formulae.brew.sh/formula-linux/zoxide [linuxbrew]: https://formulae.brew.sh/formula-linux/zoxide
[macports]: https://ports.macports.org/port/zoxide/summary [macports]: https://ports.macports.org/port/zoxide/summary
[neovim]: https://github.com/neovim/neovim [neovim]: https://github.com/neovim/neovim
[nixpkgs]: https://github.com/NixOS/nixpkgs/blob/master/pkgs/tools/misc/zoxide/default.nix [nixpkgs]: https://github.com/NixOS/nixpkgs/blob/master/pkgs/by-name/zo/zoxide/package.nix
[nnn-autojump]: https://github.com/jarun/nnn/blob/master/plugins/autojump [nnn-autojump]: https://github.com/jarun/nnn/blob/master/plugins/autojump
[nnn]: https://github.com/jarun/nnn [nnn]: https://github.com/jarun/nnn
[opensuse factory]: https://build.opensuse.org/package/show/openSUSE:Factory/zoxide
[pacstall packages]: https://pacstall.dev/packages/zoxide-deb
[pkgsrc]: https://pkgsrc.se/sysutils/zoxide [pkgsrc]: https://pkgsrc.se/sysutils/zoxide
[ranger-zoxide]: https://github.com/jchook/ranger-zoxide [ranger-zoxide]: https://github.com/jchook/ranger-zoxide
[ranger]: https://github.com/ranger/ranger [ranger]: https://github.com/ranger/ranger
[raspbian packages]: https://archive.raspbian.org/raspbian/pool/main/r/rust-zoxide/
[raycast]: https://www.raycast.com/
[raycast-zoxide]: https://www.raycast.com/mrpunkin/raycast-zoxide
[releases]: https://github.com/ajeetdsouza/zoxide/releases [releases]: https://github.com/ajeetdsouza/zoxide/releases
[rfm]: https://github.com/dsxmachina/rfm
[scoop]: https://github.com/ScoopInstaller/Main/tree/master/bucket/zoxide.json [scoop]: https://github.com/ScoopInstaller/Main/tree/master/bucket/zoxide.json
[sesh]: https://github.com/joshmedeski/sesh
[slackbuilds]: https://slackbuilds.org/repository/15.0/system/zoxide/
[slackbuilds-howto]: https://slackbuilds.org/howto/
[solus packages]: https://github.com/getsolus/packages/tree/main/packages/z/zoxide/
[telescope-zoxide]: https://github.com/jvgrootveld/telescope-zoxide [telescope-zoxide]: https://github.com/jvgrootveld/telescope-zoxide
[telescope.nvim]: https://github.com/nvim-telescope/telescope.nvim [telescope.nvim]: https://github.com/nvim-telescope/telescope.nvim
[termux]: https://github.com/termux/termux-packages/tree/master/packages/zoxide [termux]: https://github.com/termux/termux-packages/tree/master/packages/zoxide
[tmux-session-wizard]: https://github.com/27medkamal/tmux-session-wizard
[tmux-sessionx]: https://github.com/omerxx/tmux-sessionx
[tutorial]: contrib/tutorial.webp [tutorial]: contrib/tutorial.webp
[ubuntu packages]: https://packages.ubuntu.com/hirsute/zoxide
[vim]: https://github.com/vim/vim [vim]: https://github.com/vim/vim
[void linux packages]: https://github.com/void-linux/void-packages/tree/master/srcpkgs/zoxide [void linux packages]: https://github.com/void-linux/void-packages/tree/master/srcpkgs/zoxide
[wiki-env]: https://github.com/ajeetdsouza/zoxide/wiki/HOWTO:-set-environment-variables "HOWTO: set environment variables" [wiki-env]: https://github.com/ajeetdsouza/zoxide/wiki/HOWTO:-set-environment-variables "HOWTO: set environment variables"
[xplr]: https://github.com/sayanarijit/xplr [xplr]: https://github.com/sayanarijit/xplr
[xxh-plugin-prerun-zoxide]: https://github.com/xxh/xxh-plugin-prerun-zoxide [xxh-plugin-prerun-zoxide]: https://github.com/xxh/xxh-plugin-prerun-zoxide
[xxh]: https://github.com/xxh/xxh [xxh]: https://github.com/xxh/xxh
[yazi]: https://github.com/sxyazi/yazi
[zabb]: https://github.com/Mellbourn/zabb
[zesh]: https://github.com/roberte777/zesh
[zoxide.el]: https://gitlab.com/Vonfry/zoxide.el [zoxide.el]: https://gitlab.com/Vonfry/zoxide.el
[zoxide.vim]: https://github.com/nanotee/zoxide.vim [zoxide.vim]: https://github.com/nanotee/zoxide.vim
[zoxide.xplr]: https://github.com/sayanarijit/zoxide.xplr [zoxide.xplr]: https://github.com/sayanarijit/zoxide.xplr

View File

@ -1,56 +1,36 @@
use std::process::Command; #[path = "src/cmd/cmd.rs"]
mod cmd;
use std::{env, io}; use std::{env, io};
fn main() { use clap::CommandFactory as _;
let pkg_version = env!("CARGO_PKG_VERSION"); use clap_complete::shells::{Bash, Elvish, Fish, PowerShell, Zsh};
let version = match env::var_os("PROFILE") { use clap_complete_fig::Fig;
Some(profile) if profile == "release" => format!("v{}", pkg_version), use clap_complete_nushell::Nushell;
_ => git_version().unwrap_or_else(|| format!("v{}-unknown", pkg_version)), use cmd::Cmd;
};
println!("cargo:rustc-env=ZOXIDE_VERSION={}", version);
// Since we are generating completions in the package directory, we need to set this so that fn main() -> io::Result<()> {
// Cargo doesn't rebuild every time. // 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=build.rs");
println!("cargo:rerun-if-changed=src"); println!("cargo:rerun-if-changed=src/");
println!("cargo:rerun-if-changed=templates"); println!("cargo:rerun-if-changed=templates/");
println!("cargo:rerun-if-changed=tests"); println!("cargo:rerun-if-changed=tests/");
generate_completions()
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<()> { fn generate_completions() -> io::Result<()> {
#[path = "src/app/_app.rs"] const BIN_NAME: &str = env!("CARGO_PKG_NAME");
mod app; const OUT_DIR: &str = "contrib/completions";
let cmd = &mut Cmd::command();
use app::App; clap_complete::generate_to(Bash, cmd, BIN_NAME, OUT_DIR)?;
use clap::IntoApp; clap_complete::generate_to(Elvish, cmd, BIN_NAME, OUT_DIR)?;
use clap_generate::generate_to; clap_complete::generate_to(Fig, cmd, BIN_NAME, OUT_DIR)?;
use clap_generate::generators::{Bash, Elvish, Fish, PowerShell, Zsh}; clap_complete::generate_to(Fish, cmd, BIN_NAME, OUT_DIR)?;
use clap_generate_fig::Fig; clap_complete::generate_to(Nushell, cmd, BIN_NAME, OUT_DIR)?;
clap_complete::generate_to(PowerShell, cmd, BIN_NAME, OUT_DIR)?;
let app = &mut App::into_app(); clap_complete::generate_to(Zsh, cmd, BIN_NAME, OUT_DIR)?;
let bin_name = env!("CARGO_PKG_NAME");
let out_dir = "contrib/completions";
generate_to(Bash, app, bin_name, out_dir)?;
generate_to(Elvish, app, bin_name, out_dir)?;
generate_to(Fig, app, bin_name, out_dir)?;
generate_to(Fish, app, bin_name, out_dir)?;
generate_to(PowerShell, app, bin_name, out_dir)?;
generate_to(Zsh, app, bin_name, out_dir)?;
Ok(()) Ok(())
} }

View File

@ -14,11 +14,11 @@ _zoxide() {
fi fi
local context curcontext="$curcontext" state line local context curcontext="$curcontext" state line
_arguments "${_arguments_options[@]}" \ _arguments "${_arguments_options[@]}" : \
'-h[Print help information]' \ '-h[Print help]' \
'--help[Print help information]' \ '--help[Print help]' \
'-V[Print version information]' \ '-V[Print version]' \
'--version[Print version information]' \ '--version[Print version]' \
":: :_zoxide_commands" \ ":: :_zoxide_commands" \
"*::: :->zoxide" \ "*::: :->zoxide" \
&& ret=0 && ret=0
@ -29,62 +29,119 @@ _zoxide() {
curcontext="${curcontext%:*:*}:zoxide-command-$line[1]:" curcontext="${curcontext%:*:*}:zoxide-command-$line[1]:"
case $line[1] in case $line[1] in
(add) (add)
_arguments "${_arguments_options[@]}" \ _arguments "${_arguments_options[@]}" : \
'-h[Print help information]' \ '-s+[The rank to increment the entry if it exists or initialize it with if it doesn'\''t]:SCORE:_default' \
'--help[Print help information]' \ '--score=[The rank to increment the entry if it exists or initialize it with if it doesn'\''t]:SCORE:_default' \
'-V[Print version information]' \ '-h[Print help]' \
'--version[Print version information]' \ '--help[Print help]' \
'-V[Print version]' \
'--version[Print version]' \
'*::paths:_files -/' \ '*::paths:_files -/' \
&& ret=0 && 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:_default' \
&& ret=0
;;
(delete)
_arguments "${_arguments_options[@]}" : \
'-h[Print help]' \
'--help[Print help]' \
'-V[Print version]' \
'--version[Print version]' \
':path:_default' \
&& ret=0
;;
(increment)
_arguments "${_arguments_options[@]}" : \
'-h[Print help]' \
'--help[Print help]' \
'-V[Print version]' \
'--version[Print version]' \
':path:_default' \
&& ret=0
;;
(reload)
_arguments "${_arguments_options[@]}" : \
'-h[Print help]' \
'--help[Print help]' \
'-V[Print version]' \
'--version[Print version]' \
&& ret=0
;;
esac
;;
esac
;;
(import) (import)
_arguments "${_arguments_options[@]}" \ _arguments "${_arguments_options[@]}" : \
'--from=[Application to import from]:FROM:(autojump z)' \ '--from=[Application to import from]:FROM:(autojump z)' \
'--merge[Merge into existing database]' \ '--merge[Merge into existing database]' \
'-h[Print help information]' \ '-h[Print help]' \
'--help[Print help information]' \ '--help[Print help]' \
'-V[Print version information]' \ '-V[Print version]' \
'--version[Print version information]' \ '--version[Print version]' \
':path:_files' \ ':path:_files' \
&& ret=0 && ret=0
;; ;;
(init) (init)
_arguments "${_arguments_options[@]}" \ _arguments "${_arguments_options[@]}" : \
'--cmd=[Renames the '\''z'\'' command and corresponding aliases]:CMD: ' \ '--cmd=[Changes the prefix of the \`z\` and \`zi\` commands]:CMD:_default' \
'--hook=[Chooses event upon which an entry is added to the database]:HOOK:(none prompt pwd)' \ '--hook=[Changes how often zoxide increments a directory'\''s score]:HOOK:(none prompt pwd)' \
'--no-aliases[Prevents zoxide from defining any commands]' \ '--no-cmd[Prevents zoxide from defining the \`z\` and \`zi\` commands]' \
'-h[Print help information]' \ '-h[Print help]' \
'--help[Print help information]' \ '--help[Print help]' \
'-V[Print version information]' \ '-V[Print version]' \
'--version[Print version information]' \ '--version[Print version]' \
':shell:(bash elvish fish nushell posix powershell xonsh zsh)' \ ':shell:(bash elvish fish nushell posix powershell tcsh xonsh zsh)' \
&& ret=0 && ret=0
;; ;;
(query) (query)
_arguments "${_arguments_options[@]}" \ _arguments "${_arguments_options[@]}" : \
'--exclude=[Exclude a path from results]:path:_files -/' \ '--exclude=[Exclude the current directory]:path:_files -/' \
'--all[Show deleted directories]' \ '--base-dir=[Only search within this directory]:path:_files -/' \
'-a[Show unavailable directories]' \
'--all[Show unavailable directories]' \
'(-l --list)-i[Use interactive selection]' \ '(-l --list)-i[Use interactive selection]' \
'(-l --list)--interactive[Use interactive selection]' \ '(-l --list)--interactive[Use interactive selection]' \
'(-i --interactive)-l[List all matching directories]' \ '(-i --interactive)-l[List all matching directories]' \
'(-i --interactive)--list[List all matching directories]' \ '(-i --interactive)--list[List all matching directories]' \
'(-i --interactive)-s[Print score with results]' \ '-s[Print score with results]' \
'(-i --interactive)--score[Print score with results]' \ '--score[Print score with results]' \
'-h[Print help information]' \ '-h[Print help]' \
'--help[Print help information]' \ '--help[Print help]' \
'-V[Print version information]' \ '-V[Print version]' \
'--version[Print version information]' \ '--version[Print version]' \
'*::keywords:' \ '*::keywords:_default' \
&& ret=0 && ret=0
;; ;;
(remove) (remove)
_arguments "${_arguments_options[@]}" \ _arguments "${_arguments_options[@]}" : \
'()-i+[]:keywords: ' \ '-h[Print help]' \
'()--interactive=[]:keywords: ' \ '--help[Print help]' \
'-h[Print help information]' \ '-V[Print version]' \
'--help[Print help information]' \ '--version[Print version]' \
'-V[Print version information]' \
'--version[Print version information]' \
'*::paths:_files -/' \ '*::paths:_files -/' \
&& ret=0 && ret=0
;; ;;
@ -97,6 +154,7 @@ esac
_zoxide_commands() { _zoxide_commands() {
local commands; commands=( local commands; commands=(
'add:Add a new directory or increment its rank' \ 'add:Add a new directory or increment its rank' \
'edit:Edit the database' \
'import:Import entries from another application' \ 'import:Import entries from another application' \
'init:Generate shell configuration' \ 'init:Generate shell configuration' \
'query:Search for a directory in the database' \ 'query:Search for a directory in the database' \
@ -109,6 +167,36 @@ _zoxide__add_commands() {
local commands; commands=() local commands; commands=()
_describe -t commands 'zoxide add commands' commands "$@" _describe -t commands 'zoxide add 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__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__increment_commands] )) ||
_zoxide__edit__increment_commands() {
local commands; commands=()
_describe -t commands 'zoxide edit increment commands' commands "$@"
}
(( $+functions[_zoxide__edit__reload_commands] )) ||
_zoxide__edit__reload_commands() {
local commands; commands=()
_describe -t commands 'zoxide edit reload commands' commands "$@"
}
(( $+functions[_zoxide__import_commands] )) || (( $+functions[_zoxide__import_commands] )) ||
_zoxide__import_commands() { _zoxide__import_commands() {
local commands; commands=() local commands; commands=()
@ -130,4 +218,8 @@ _zoxide__remove_commands() {
_describe -t commands 'zoxide remove commands' commands "$@" _describe -t commands 'zoxide remove commands' commands "$@"
} }
_zoxide "$@" if [ "$funcstack[1]" = "_zoxide" ]; then
_zoxide "$@"
else
compdef _zoxide zoxide
fi

View File

@ -12,7 +12,8 @@ Register-ArgumentCompleter -Native -CommandName 'zoxide' -ScriptBlock {
$element = $commandElements[$i] $element = $commandElements[$i]
if ($element -isnot [StringConstantExpressionAst] -or if ($element -isnot [StringConstantExpressionAst] -or
$element.StringConstantType -ne [StringConstantType]::BareWord -or $element.StringConstantType -ne [StringConstantType]::BareWord -or
$element.Value.StartsWith('-')) { $element.Value.StartsWith('-') -or
$element.Value -eq $wordToComplete) {
break break
} }
$element.Value $element.Value
@ -20,11 +21,12 @@ Register-ArgumentCompleter -Native -CommandName 'zoxide' -ScriptBlock {
$completions = @(switch ($command) { $completions = @(switch ($command) {
'zoxide' { 'zoxide' {
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help information') [CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help information') [CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Print version information') [CompletionResult]::new('-V', '-V ', [CompletionResultType]::ParameterName, 'Print version')
[CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Print version information') [CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'Print version')
[CompletionResult]::new('add', 'add', [CompletionResultType]::ParameterValue, 'Add a new directory or increment its rank') [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('import', 'import', [CompletionResultType]::ParameterValue, 'Import entries from another application')
[CompletionResult]::new('init', 'init', [CompletionResultType]::ParameterValue, 'Generate shell configuration') [CompletionResult]::new('init', 'init', [CompletionResultType]::ParameterValue, 'Generate shell configuration')
[CompletionResult]::new('query', 'query', [CompletionResultType]::ParameterValue, 'Search for a directory in the database') [CompletionResult]::new('query', 'query', [CompletionResultType]::ParameterValue, 'Search for a directory in the database')
@ -32,53 +34,94 @@ Register-ArgumentCompleter -Native -CommandName 'zoxide' -ScriptBlock {
break break
} }
'zoxide;add' { 'zoxide;add' {
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help information') [CompletionResult]::new('-s', '-s', [CompletionResultType]::ParameterName, 'The rank to increment the entry if it exists or initialize it with if it doesn''t')
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help information') [CompletionResult]::new('--score', '--score', [CompletionResultType]::ParameterName, 'The rank to increment the entry if it exists or initialize it with if it doesn''t')
[CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Print version information') [CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Print version information') [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 break
} }
'zoxide;import' { 'zoxide;import' {
[CompletionResult]::new('--from', 'from', [CompletionResultType]::ParameterName, 'Application to import from') [CompletionResult]::new('--from', '--from', [CompletionResultType]::ParameterName, 'Application to import from')
[CompletionResult]::new('--merge', 'merge', [CompletionResultType]::ParameterName, 'Merge into existing database') [CompletionResult]::new('--merge', '--merge', [CompletionResultType]::ParameterName, 'Merge into existing database')
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help information') [CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help information') [CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Print version information') [CompletionResult]::new('-V', '-V ', [CompletionResultType]::ParameterName, 'Print version')
[CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Print version information') [CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'Print version')
break break
} }
'zoxide;init' { 'zoxide;init' {
[CompletionResult]::new('--cmd', 'cmd', [CompletionResultType]::ParameterName, 'Renames the ''z'' command and corresponding aliases') [CompletionResult]::new('--cmd', '--cmd', [CompletionResultType]::ParameterName, 'Changes the prefix of the `z` and `zi` commands')
[CompletionResult]::new('--hook', 'hook', [CompletionResultType]::ParameterName, 'Chooses event upon which an entry is added to the database') [CompletionResult]::new('--hook', '--hook', [CompletionResultType]::ParameterName, 'Changes how often zoxide increments a directory''s score')
[CompletionResult]::new('--no-aliases', 'no-aliases', [CompletionResultType]::ParameterName, 'Prevents zoxide from defining any commands') [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('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help information') [CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Print version information') [CompletionResult]::new('-V', '-V ', [CompletionResultType]::ParameterName, 'Print version')
[CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Print version information') [CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'Print version')
break break
} }
'zoxide;query' { 'zoxide;query' {
[CompletionResult]::new('--exclude', 'exclude', [CompletionResultType]::ParameterName, 'Exclude a path from results') [CompletionResult]::new('--exclude', '--exclude', [CompletionResultType]::ParameterName, 'Exclude the current directory')
[CompletionResult]::new('--all', 'all', [CompletionResultType]::ParameterName, 'Show deleted directories') [CompletionResult]::new('--base-dir', '--base-dir', [CompletionResultType]::ParameterName, 'Only search within this directory')
[CompletionResult]::new('-i', 'i', [CompletionResultType]::ParameterName, 'Use interactive selection') [CompletionResult]::new('-a', '-a', [CompletionResultType]::ParameterName, 'Show unavailable directories')
[CompletionResult]::new('--interactive', 'interactive', [CompletionResultType]::ParameterName, 'Use interactive selection') [CompletionResult]::new('--all', '--all', [CompletionResultType]::ParameterName, 'Show unavailable directories')
[CompletionResult]::new('-l', 'l', [CompletionResultType]::ParameterName, 'List all matching directories') [CompletionResult]::new('-i', '-i', [CompletionResultType]::ParameterName, 'Use interactive selection')
[CompletionResult]::new('--list', 'list', [CompletionResultType]::ParameterName, 'List all matching directories') [CompletionResult]::new('--interactive', '--interactive', [CompletionResultType]::ParameterName, 'Use interactive selection')
[CompletionResult]::new('-s', 's', [CompletionResultType]::ParameterName, 'Print score with results') [CompletionResult]::new('-l', '-l', [CompletionResultType]::ParameterName, 'List all matching directories')
[CompletionResult]::new('--score', 'score', [CompletionResultType]::ParameterName, 'Print score with results') [CompletionResult]::new('--list', '--list', [CompletionResultType]::ParameterName, 'List all matching directories')
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help information') [CompletionResult]::new('-s', '-s', [CompletionResultType]::ParameterName, 'Print score with results')
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help information') [CompletionResult]::new('--score', '--score', [CompletionResultType]::ParameterName, 'Print score with results')
[CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Print version information') [CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Print version information') [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 break
} }
'zoxide;remove' { 'zoxide;remove' {
[CompletionResult]::new('-i', 'i', [CompletionResultType]::ParameterName, 'i') [CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('--interactive', 'interactive', [CompletionResultType]::ParameterName, 'interactive') [CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help information') [CompletionResult]::new('-V', '-V ', [CompletionResultType]::ParameterName, 'Print version')
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help information') [CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'Print version')
[CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Print version information')
[CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Print version information')
break break
} }
}) })

View File

@ -1,31 +1,50 @@
_zoxide() { _zoxide() {
local i cur prev opts cmds local i cur prev opts cmd
COMPREPLY=() COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}" if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
prev="${COMP_WORDS[COMP_CWORD-1]}" cur="$2"
else
cur="${COMP_WORDS[COMP_CWORD]}"
fi
prev="$3"
cmd="" cmd=""
opts="" opts=""
for i in ${COMP_WORDS[@]} for i in "${COMP_WORDS[@]:0:COMP_CWORD}"
do do
case "${i}" in case "${cmd},${i}" in
zoxide) ",$1")
cmd="zoxide" cmd="zoxide"
;; ;;
add) zoxide,add)
cmd+="__add" cmd="zoxide__add"
;; ;;
import) zoxide,edit)
cmd+="__import" cmd="zoxide__edit"
;; ;;
init) zoxide,import)
cmd+="__init" cmd="zoxide__import"
;; ;;
query) zoxide,init)
cmd+="__query" cmd="zoxide__init"
;; ;;
remove) zoxide,query)
cmd+="__remove" 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 +53,7 @@ _zoxide() {
case "${cmd}" in case "${cmd}" in
zoxide) 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 if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0 return 0
@ -48,11 +67,89 @@ _zoxide() {
return 0 return 0
;; ;;
zoxide__add) zoxide__add)
opts="-h -V --help --version <PATHS>..." opts="-s -h -V --score --help --version <PATHS>..."
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0 return 0
fi fi
case "${prev}" in
--score)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
-s)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
*)
COMPREPLY=()
;;
esac
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 case "${prev}" in
*) *)
COMPREPLY=() COMPREPLY=()
@ -80,7 +177,7 @@ _zoxide() {
return 0 return 0
;; ;;
zoxide__init) zoxide__init)
opts="-h -V --no-aliases --cmd --hook --help --version bash elvish fish nushell posix powershell xonsh zsh" opts="-h -V --no-cmd --cmd --hook --help --version bash elvish fish nushell posix powershell tcsh xonsh zsh"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0 return 0
@ -102,14 +199,24 @@ _zoxide() {
return 0 return 0
;; ;;
zoxide__query) 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 --base-dir --help --version [KEYWORDS]..."
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0 return 0
fi fi
case "${prev}" in case "${prev}" in
--exclude) --exclude)
COMPREPLY=($(compgen -f "${cur}")) COMPREPLY=()
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
compopt -o plusdirs
fi
return 0
;;
--base-dir)
COMPREPLY=()
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
compopt -o plusdirs
fi
return 0 return 0
;; ;;
*) *)
@ -120,20 +227,12 @@ _zoxide() {
return 0 return 0
;; ;;
zoxide__remove) zoxide__remove)
opts="-i -h -V --interactive --help --version <PATHS>..." opts="-h -V --help --version [PATHS]..."
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0 return 0
fi fi
case "${prev}" in case "${prev}" in
--interactive)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
-i)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
*) *)
COMPREPLY=() COMPREPLY=()
;; ;;
@ -144,4 +243,8 @@ _zoxide() {
esac esac
} }
complete -F _zoxide -o bashdefault -o default zoxide if [[ "${BASH_VERSINFO[0]}" -eq 4 && "${BASH_VERSINFO[1]}" -ge 4 || "${BASH_VERSINFO[0]}" -gt 4 ]]; then
complete -F _zoxide -o nosort -o bashdefault -o default zoxide
else
complete -F _zoxide -o bashdefault -o default zoxide
fi

View File

@ -2,11 +2,11 @@
use builtin; use builtin;
use str; use str;
set edit:completion:arg-completer[zoxide] = [@words]{ set edit:completion:arg-completer[zoxide] = {|@words|
fn spaces [n]{ fn spaces {|n|
builtin:repeat $n ' ' | str:join '' builtin:repeat $n ' ' | str:join ''
} }
fn cand [text desc]{ fn cand {|text desc|
edit:complex-candidate $text &display=$text' '(spaces (- 14 (wcswidth $text)))$desc edit:complex-candidate $text &display=$text' '(spaces (- 14 (wcswidth $text)))$desc
} }
var command = 'zoxide' var command = 'zoxide'
@ -18,60 +18,97 @@ set edit:completion:arg-completer[zoxide] = [@words]{
} }
var completions = [ var completions = [
&'zoxide'= { &'zoxide'= {
cand -h 'Print help information' cand -h 'Print help'
cand --help 'Print help information' cand --help 'Print help'
cand -V 'Print version information' cand -V 'Print version'
cand --version 'Print version information' cand --version 'Print version'
cand add 'Add a new directory or increment its rank' cand add 'Add a new directory or increment its rank'
cand edit 'Edit the database'
cand import 'Import entries from another application' cand import 'Import entries from another application'
cand init 'Generate shell configuration' cand init 'Generate shell configuration'
cand query 'Search for a directory in the database' cand query 'Search for a directory in the database'
cand remove 'Remove a directory from the database' cand remove 'Remove a directory from the database'
} }
&'zoxide;add'= { &'zoxide;add'= {
cand -h 'Print help information' cand -s 'The rank to increment the entry if it exists or initialize it with if it doesn''t'
cand --help 'Print help information' cand --score 'The rank to increment the entry if it exists or initialize it with if it doesn''t'
cand -V 'Print version information' cand -h 'Print help'
cand --version 'Print version information' 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'= { &'zoxide;import'= {
cand --from 'Application to import from' cand --from 'Application to import from'
cand --merge 'Merge into existing database' cand --merge 'Merge into existing database'
cand -h 'Print help information' cand -h 'Print help'
cand --help 'Print help information' cand --help 'Print help'
cand -V 'Print version information' cand -V 'Print version'
cand --version 'Print version information' cand --version 'Print version'
} }
&'zoxide;init'= { &'zoxide;init'= {
cand --cmd 'Renames the ''z'' command and corresponding aliases' cand --cmd 'Changes the prefix of the `z` and `zi` commands'
cand --hook 'Chooses event upon which an entry is added to the database' cand --hook 'Changes how often zoxide increments a directory''s score'
cand --no-aliases 'Prevents zoxide from defining any commands' cand --no-cmd 'Prevents zoxide from defining the `z` and `zi` commands'
cand -h 'Print help information' cand -h 'Print help'
cand --help 'Print help information' cand --help 'Print help'
cand -V 'Print version information' cand -V 'Print version'
cand --version 'Print version information' cand --version 'Print version'
} }
&'zoxide;query'= { &'zoxide;query'= {
cand --exclude 'Exclude a path from results' cand --exclude 'Exclude the current directory'
cand --all 'Show deleted directories' cand --base-dir 'Only search within this directory'
cand -a 'Show unavailable directories'
cand --all 'Show unavailable directories'
cand -i 'Use interactive selection' cand -i 'Use interactive selection'
cand --interactive 'Use interactive selection' cand --interactive 'Use interactive selection'
cand -l 'List all matching directories' cand -l 'List all matching directories'
cand --list 'List all matching directories' cand --list 'List all matching directories'
cand -s 'Print score with results' cand -s 'Print score with results'
cand --score 'Print score with results' cand --score 'Print score with results'
cand -h 'Print help information' cand -h 'Print help'
cand --help 'Print help information' cand --help 'Print help'
cand -V 'Print version information' cand -V 'Print version'
cand --version 'Print version information' cand --version 'Print version'
} }
&'zoxide;remove'= { &'zoxide;remove'= {
cand -i 'i' cand -h 'Print help'
cand --interactive 'interactive' cand --help 'Print help'
cand -h 'Print help information' cand -V 'Print version'
cand --help 'Print help information' cand --version 'Print version'
cand -V 'Print version information'
cand --version 'Print version information'
} }
] ]
$completions[$command] $completions[$command]

View File

@ -1,28 +1,73 @@
complete -c zoxide -n "__fish_use_subcommand" -s h -l help -d 'Print help information' # Print an optspec for argparse to handle cmd's options that are independent of any subcommand.
complete -c zoxide -n "__fish_use_subcommand" -s V -l version -d 'Print version information' function __fish_zoxide_global_optspecs
complete -c zoxide -n "__fish_use_subcommand" -f -a "add" -d 'Add a new directory or increment its rank' string join \n h/help V/version
complete -c zoxide -n "__fish_use_subcommand" -f -a "import" -d 'Import entries from another application' end
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' function __fish_zoxide_needs_command
complete -c zoxide -n "__fish_use_subcommand" -f -a "remove" -d 'Remove a directory from the database' # Figure out if the current invocation already has a command.
complete -c zoxide -n "__fish_seen_subcommand_from add" -s h -l help -d 'Print help information' set -l cmd (commandline -opc)
complete -c zoxide -n "__fish_seen_subcommand_from add" -s V -l version -d 'Print version information' set -e cmd[1]
complete -c zoxide -n "__fish_seen_subcommand_from import" -l from -d 'Application to import from' -r -f -a "{autojump ,z }" argparse -s (__fish_zoxide_global_optspecs) -- $cmd 2>/dev/null
complete -c zoxide -n "__fish_seen_subcommand_from import" -l merge -d 'Merge into existing database' or return
complete -c zoxide -n "__fish_seen_subcommand_from import" -s h -l help -d 'Print help information' if set -q argv[1]
complete -c zoxide -n "__fish_seen_subcommand_from import" -s V -l version -d 'Print version information' # Also print the command, so this can be used to figure out what it is.
complete -c zoxide -n "__fish_seen_subcommand_from init" -l cmd -d 'Renames the \'z\' command and corresponding aliases' -r echo $argv[1]
complete -c zoxide -n "__fish_seen_subcommand_from init" -l hook -d 'Chooses event upon which an entry is added to the database' -r -f -a "{none ,prompt ,pwd }" return 1
complete -c zoxide -n "__fish_seen_subcommand_from init" -l no-aliases -d 'Prevents zoxide from defining any commands' end
complete -c zoxide -n "__fish_seen_subcommand_from init" -s h -l help -d 'Print help information' return 0
complete -c zoxide -n "__fish_seen_subcommand_from init" -s V -l version -d 'Print version information' end
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' function __fish_zoxide_using_subcommand
complete -c zoxide -n "__fish_seen_subcommand_from query" -s i -l interactive -d 'Use interactive selection' set -l cmd (__fish_zoxide_needs_command)
complete -c zoxide -n "__fish_seen_subcommand_from query" -s l -l list -d 'List all matching directories' test -z "$cmd"
complete -c zoxide -n "__fish_seen_subcommand_from query" -s s -l score -d 'Print score with results' and return 1
complete -c zoxide -n "__fish_seen_subcommand_from query" -s h -l help -d 'Print help information' contains -- $cmd[1] $argv
complete -c zoxide -n "__fish_seen_subcommand_from query" -s V -l version -d 'Print version information' end
complete -c zoxide -n "__fish_seen_subcommand_from remove" -s i -l interactive -r
complete -c zoxide -n "__fish_seen_subcommand_from remove" -s h -l help -d 'Print help information' complete -c zoxide -n "__fish_zoxide_needs_command" -s h -l help -d 'Print help'
complete -c zoxide -n "__fish_seen_subcommand_from remove" -s V -l version -d 'Print version information' complete -c zoxide -n "__fish_zoxide_needs_command" -s V -l version -d 'Print version'
complete -c zoxide -n "__fish_zoxide_needs_command" -f -a "add" -d 'Add a new directory or increment its rank'
complete -c zoxide -n "__fish_zoxide_needs_command" -f -a "edit" -d 'Edit the database'
complete -c zoxide -n "__fish_zoxide_needs_command" -f -a "import" -d 'Import entries from another application'
complete -c zoxide -n "__fish_zoxide_needs_command" -f -a "init" -d 'Generate shell configuration'
complete -c zoxide -n "__fish_zoxide_needs_command" -f -a "query" -d 'Search for a directory in the database'
complete -c zoxide -n "__fish_zoxide_needs_command" -f -a "remove" -d 'Remove a directory from the database'
complete -c zoxide -n "__fish_zoxide_using_subcommand add" -s s -l score -d 'The rank to increment the entry if it exists or initialize it with if it doesn\'t' -r
complete -c zoxide -n "__fish_zoxide_using_subcommand add" -s h -l help -d 'Print help'
complete -c zoxide -n "__fish_zoxide_using_subcommand add" -s V -l version -d 'Print version'
complete -c zoxide -n "__fish_zoxide_using_subcommand edit; and not __fish_seen_subcommand_from decrement delete increment reload" -s h -l help -d 'Print help'
complete -c zoxide -n "__fish_zoxide_using_subcommand edit; and not __fish_seen_subcommand_from decrement delete increment reload" -s V -l version -d 'Print version'
complete -c zoxide -n "__fish_zoxide_using_subcommand edit; and not __fish_seen_subcommand_from decrement delete increment reload" -f -a "decrement"
complete -c zoxide -n "__fish_zoxide_using_subcommand edit; and not __fish_seen_subcommand_from decrement delete increment reload" -f -a "delete"
complete -c zoxide -n "__fish_zoxide_using_subcommand edit; and not __fish_seen_subcommand_from decrement delete increment reload" -f -a "increment"
complete -c zoxide -n "__fish_zoxide_using_subcommand edit; and not __fish_seen_subcommand_from decrement delete increment reload" -f -a "reload"
complete -c zoxide -n "__fish_zoxide_using_subcommand edit; and __fish_seen_subcommand_from decrement" -s h -l help -d 'Print help'
complete -c zoxide -n "__fish_zoxide_using_subcommand edit; and __fish_seen_subcommand_from decrement" -s V -l version -d 'Print version'
complete -c zoxide -n "__fish_zoxide_using_subcommand edit; and __fish_seen_subcommand_from delete" -s h -l help -d 'Print help'
complete -c zoxide -n "__fish_zoxide_using_subcommand edit; and __fish_seen_subcommand_from delete" -s V -l version -d 'Print version'
complete -c zoxide -n "__fish_zoxide_using_subcommand edit; and __fish_seen_subcommand_from increment" -s h -l help -d 'Print help'
complete -c zoxide -n "__fish_zoxide_using_subcommand edit; and __fish_seen_subcommand_from increment" -s V -l version -d 'Print version'
complete -c zoxide -n "__fish_zoxide_using_subcommand edit; and __fish_seen_subcommand_from reload" -s h -l help -d 'Print help'
complete -c zoxide -n "__fish_zoxide_using_subcommand edit; and __fish_seen_subcommand_from reload" -s V -l version -d 'Print version'
complete -c zoxide -n "__fish_zoxide_using_subcommand import" -l from -d 'Application to import from' -r -f -a "autojump\t''
z\t''"
complete -c zoxide -n "__fish_zoxide_using_subcommand import" -l merge -d 'Merge into existing database'
complete -c zoxide -n "__fish_zoxide_using_subcommand import" -s h -l help -d 'Print help'
complete -c zoxide -n "__fish_zoxide_using_subcommand import" -s V -l version -d 'Print version'
complete -c zoxide -n "__fish_zoxide_using_subcommand init" -l cmd -d 'Changes the prefix of the `z` and `zi` commands' -r
complete -c zoxide -n "__fish_zoxide_using_subcommand init" -l hook -d 'Changes how often zoxide increments a directory\'s score' -r -f -a "none\t''
prompt\t''
pwd\t''"
complete -c zoxide -n "__fish_zoxide_using_subcommand init" -l no-cmd -d 'Prevents zoxide from defining the `z` and `zi` commands'
complete -c zoxide -n "__fish_zoxide_using_subcommand init" -s h -l help -d 'Print help'
complete -c zoxide -n "__fish_zoxide_using_subcommand init" -s V -l version -d 'Print version'
complete -c zoxide -n "__fish_zoxide_using_subcommand query" -l exclude -d 'Exclude the current directory' -r -f -a "(__fish_complete_directories)"
complete -c zoxide -n "__fish_zoxide_using_subcommand query" -l base-dir -d 'Only search within this directory' -r -f -a "(__fish_complete_directories)"
complete -c zoxide -n "__fish_zoxide_using_subcommand query" -s a -l all -d 'Show unavailable directories'
complete -c zoxide -n "__fish_zoxide_using_subcommand query" -s i -l interactive -d 'Use interactive selection'
complete -c zoxide -n "__fish_zoxide_using_subcommand query" -s l -l list -d 'List all matching directories'
complete -c zoxide -n "__fish_zoxide_using_subcommand query" -s s -l score -d 'Print score with results'
complete -c zoxide -n "__fish_zoxide_using_subcommand query" -s h -l help -d 'Print help'
complete -c zoxide -n "__fish_zoxide_using_subcommand query" -s V -l version -d 'Print version'
complete -c zoxide -n "__fish_zoxide_using_subcommand remove" -s h -l help -d 'Print help'
complete -c zoxide -n "__fish_zoxide_using_subcommand remove" -s V -l version -d 'Print version'

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

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

View File

@ -6,13 +6,22 @@ const completion: Fig.Spec = {
name: "add", name: "add",
description: "Add a new directory or increment its rank", description: "Add a new directory or increment its rank",
options: [ options: [
{
name: ["-s", "--score"],
description: "The rank to increment the entry if it exists or initialize it with if it doesn't",
isRepeatable: true,
args: {
name: "score",
isOptional: true,
},
},
{ {
name: ["-h", "--help"], name: ["-h", "--help"],
description: "Print help information", description: "Print help",
}, },
{ {
name: ["-V", "--version"], name: ["-V", "--version"],
description: "Print version information", description: "Print version",
}, },
], ],
args: { args: {
@ -21,6 +30,87 @@ const completion: Fig.Spec = {
template: "folders", 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", name: "import",
description: "Import entries from another application", description: "Import entries from another application",
@ -28,16 +118,13 @@ const completion: Fig.Spec = {
{ {
name: "--from", name: "--from",
description: "Application to import from", description: "Application to import from",
isRepeatable: true,
args: { args: {
name: "from", name: "from",
suggestions: [ suggestions: [
{ "autojump",
name: "autojump", "z",
}, ],
{
name: "z",
},
]
}, },
}, },
{ {
@ -46,11 +133,11 @@ const completion: Fig.Spec = {
}, },
{ {
name: ["-h", "--help"], name: ["-h", "--help"],
description: "Print help information", description: "Print help",
}, },
{ {
name: ["-V", "--version"], name: ["-V", "--version"],
description: "Print version information", description: "Print version",
}, },
], ],
args: { args: {
@ -64,7 +151,8 @@ const completion: Fig.Spec = {
options: [ options: [
{ {
name: "--cmd", name: "--cmd",
description: "Renames the 'z' command and corresponding aliases", description: "Changes the prefix of the `z` and `zi` commands",
isRepeatable: true,
args: { args: {
name: "cmd", name: "cmd",
isOptional: true, isOptional: true,
@ -72,64 +160,44 @@ const completion: Fig.Spec = {
}, },
{ {
name: "--hook", name: "--hook",
description: "Chooses event upon which an entry is added to the database", description: "Changes how often zoxide increments a directory's score",
isRepeatable: true,
args: { args: {
name: "hook", name: "hook",
isOptional: true, isOptional: true,
suggestions: [ suggestions: [
{ "none",
name: "none", "prompt",
}, "pwd",
{ ],
name: "prompt",
},
{
name: "pwd",
},
]
}, },
}, },
{ {
name: "--no-aliases", name: "--no-cmd",
description: "Prevents zoxide from defining any commands", description: "Prevents zoxide from defining the `z` and `zi` commands",
}, },
{ {
name: ["-h", "--help"], name: ["-h", "--help"],
description: "Print help information", description: "Print help",
}, },
{ {
name: ["-V", "--version"], name: ["-V", "--version"],
description: "Print version information", description: "Print version",
}, },
], ],
args: { args: {
name: "shell", name: "shell",
suggestions: [ suggestions: [
{ "bash",
name: "bash", "elvish",
}, "fish",
{ "nushell",
name: "elvish", "posix",
}, "powershell",
{ "tcsh",
name: "fish", "xonsh",
}, "zsh",
{ ],
name: "nushell",
},
{
name: "posix",
},
{
name: "powershell",
},
{
name: "xonsh",
},
{
name: "zsh",
},
]
}, },
}, },
{ {
@ -138,7 +206,8 @@ const completion: Fig.Spec = {
options: [ options: [
{ {
name: "--exclude", name: "--exclude",
description: "Exclude a path from results", description: "Exclude the current directory",
isRepeatable: true,
args: { args: {
name: "exclude", name: "exclude",
isOptional: true, isOptional: true,
@ -146,16 +215,34 @@ const completion: Fig.Spec = {
}, },
}, },
{ {
name: "--all", name: "--base-dir",
description: "Show deleted directories", description: "Only search within this directory",
isRepeatable: true,
args: {
name: "base_dir",
isOptional: true,
template: "folders",
},
},
{
name: ["-a", "--all"],
description: "Show unavailable directories",
}, },
{ {
name: ["-i", "--interactive"], name: ["-i", "--interactive"],
description: "Use interactive selection", description: "Use interactive selection",
exclusiveOn: [
"-l",
"--list",
],
}, },
{ {
name: ["-l", "--list"], name: ["-l", "--list"],
description: "List all matching directories", description: "List all matching directories",
exclusiveOn: [
"-i",
"--interactive",
],
}, },
{ {
name: ["-s", "--score"], name: ["-s", "--score"],
@ -163,11 +250,11 @@ const completion: Fig.Spec = {
}, },
{ {
name: ["-h", "--help"], name: ["-h", "--help"],
description: "Print help information", description: "Print help",
}, },
{ {
name: ["-V", "--version"], name: ["-V", "--version"],
description: "Print version information", description: "Print version",
}, },
], ],
args: { args: {
@ -180,21 +267,13 @@ const completion: Fig.Spec = {
name: "remove", name: "remove",
description: "Remove a directory from the database", description: "Remove a directory from the database",
options: [ options: [
{
name: ["-i", "--interactive"],
args: {
name: "interactive",
isVariadic: true,
isOptional: true,
},
},
{ {
name: ["-h", "--help"], name: ["-h", "--help"],
description: "Print help information", description: "Print help",
}, },
{ {
name: ["-V", "--version"], name: ["-V", "--version"],
description: "Print version information", description: "Print version",
}, },
], ],
args: { args: {
@ -208,11 +287,11 @@ const completion: Fig.Spec = {
options: [ options: [
{ {
name: ["-h", "--help"], name: ["-h", "--help"],
description: "Print help information", description: "Print help",
}, },
{ {
name: ["-V", "--version"], name: ["-V", "--version"],
description: "Print version information", description: "Print version",
}, },
], ],
}; };

Binary file not shown.

Before

Width:  |  Height:  |  Size: 269 KiB

After

Width:  |  Height:  |  Size: 107 KiB

466
install.sh Executable file
View File

@ -0,0 +1,466 @@
#!/bin/sh
# shellcheck shell=dash
# shellcheck disable=SC3043 # Assume `local` extension
# The official zoxide installer.
#
# It runs on Unix shells like {a,ba,da,k,z}sh. It uses the common `local`
# extension. Note: Most shells limit `local` to 1 var per line, contra bash.
main() {
# 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:
if [ "${KSH_VERSION-}" = 'Version JM 93t+ 2010-03-05' ]; then
err 'the installer does not work with this ksh93 version; please try bash'
fi
set -u
parse_args "$@"
local _arch
_arch="${ARCH:-$(ensure get_architecture)}"
assert_nz "${_arch}" "arch"
echo "Detected architecture: ${_arch}"
local _bin_name
case "${_arch}" in
*windows*) _bin_name="zoxide.exe" ;;
*) _bin_name="zoxide" ;;
esac
# Create and enter a temporary directory.
local _tmp_dir
_tmp_dir="$(mktemp -d)" || err "mktemp: could not create temporary directory"
cd "${_tmp_dir}" || err "cd: failed to enter directory: ${_tmp_dir}"
# Download and extract zoxide.
local _package
_package="$(ensure download_zoxide "${_arch}")"
assert_nz "${_package}" "package"
echo "Downloaded package: ${_package}"
case "${_package}" in
*.tar.gz)
need_cmd tar
ensure tar -xf "${_package}"
;;
*.zip)
need_cmd unzip
ensure unzip -oq "${_package}"
;;
*)
err "unsupported package format: ${_package}"
;;
esac
# Install binary.
ensure try_sudo mkdir -p -- "${BIN_DIR}"
ensure try_sudo cp -- "${_bin_name}" "${BIN_DIR}/${_bin_name}"
ensure try_sudo chmod +x "${BIN_DIR}/${_bin_name}"
echo "Installed zoxide to ${BIN_DIR}"
# Install manpages.
ensure try_sudo mkdir -p -- "${MAN_DIR}/man1"
ensure try_sudo cp -- "man/man1/"* "${MAN_DIR}/man1/"
echo "Installed manpages to ${MAN_DIR}"
# Print success message and check $PATH.
echo ""
echo "zoxide is installed!"
if ! echo ":${PATH}:" | grep -Fq ":${BIN_DIR}:"; then
echo "Note: ${BIN_DIR} is not on your \$PATH. zoxide will not work unless it is added to \$PATH."
fi
}
# Parse the arguments passed and set variables accordingly.
parse_args() {
BIN_DIR_DEFAULT="${HOME}/.local/bin"
MAN_DIR_DEFAULT="${HOME}/.local/share/man"
SUDO_DEFAULT="sudo"
BIN_DIR="${BIN_DIR_DEFAULT}"
MAN_DIR="${MAN_DIR_DEFAULT}"
SUDO="${SUDO_DEFAULT}"
while [ "$#" -gt 0 ]; do
case "$1" in
--arch) ARCH="$2" && shift 2 ;;
--arch=*) ARCH="${1#*=}" && shift 1 ;;
--bin-dir) BIN_DIR="$2" && shift 2 ;;
--bin-dir=*) BIN_DIR="${1#*=}" && shift 1 ;;
--man-dir) MAN_DIR="$2" && shift 2 ;;
--man-dir=*) MAN_DIR="${1#*=}" && shift 1 ;;
--sudo) SUDO="$2" && shift 2 ;;
--sudo=*) SUDO="${1#*=}" && shift 1 ;;
-h | --help) usage && exit 0 ;;
*) err "Unknown option: $1" ;;
esac
done
}
usage() {
# heredocs are not defined in POSIX.
local _text_heading _text_reset
_text_heading="$(tput bold || true 2>/dev/null)$(tput smul || true 2>/dev/null)"
_text_reset="$(tput sgr0 || true 2>/dev/null)"
local _arch
_arch="$(get_architecture || true)"
echo "\
${_text_heading}zoxide installer${_text_reset}
Ajeet D'Souza <98ajeet@gmail.com>
https://github.com/ajeetdsouza/zoxide
Fetches and installs zoxide. If zoxide is already installed, it will be updated to the latest version.
${_text_heading}Usage:${_text_reset}
install.sh [OPTIONS]
${_text_heading}Options:${_text_reset}
--arch Override the architecture identified by the installer [current: ${_arch}]
--bin-dir Override the installation directory [default: ${BIN_DIR_DEFAULT}]
--man-dir Override the manpage installation directory [default: ${MAN_DIR_DEFAULT}]
--sudo Override the command used to elevate to root privileges [default: ${SUDO_DEFAULT}]
-h, --help Print help"
}
download_zoxide() {
local _arch="$1"
if check_cmd curl; then
_dld=curl
elif check_cmd wget; then
_dld=wget
else
need_cmd 'curl or wget'
fi
need_cmd grep
local _releases_url="https://api.github.com/repos/ajeetdsouza/zoxide/releases/latest"
local _releases
case "${_dld}" in
curl) _releases="$(curl -sL "${_releases_url}")" ||
err "curl: failed to download ${_releases_url}" ;;
wget) _releases="$(wget -qO- "${_releases_url}")" ||
err "wget: failed to download ${_releases_url}" ;;
*) err "unsupported downloader: ${_dld}" ;;
esac
(echo "${_releases}" | grep -q 'API rate limit exceeded') &&
err "you have exceeded GitHub's API rate limit. Please try again later, or use a different installation method: https://github.com/ajeetdsouza/zoxide/#installation"
local _package_url
_package_url="$(echo "${_releases}" | grep "browser_download_url" | cut -d '"' -f 4 | grep -- "${_arch}")" ||
err "zoxide has not yet been packaged for your architecture (${_arch}), please file an issue: https://github.com/ajeetdsouza/zoxide/issues"
local _ext
case "${_package_url}" in
*.tar.gz) _ext="tar.gz" ;;
*.zip) _ext="zip" ;;
*) err "unsupported package format: ${_package_url}" ;;
esac
local _package="zoxide.${_ext}"
case "${_dld}" in
curl) _releases="$(curl -sLo "${_package}" "${_package_url}")" || err "curl: failed to download ${_package_url}" ;;
wget) _releases="$(wget -qO "${_package}" "${_package_url}")" || err "wget: failed to download ${_package_url}" ;;
*) err "unsupported downloader: ${_dld}" ;;
esac
echo "${_package}"
}
try_sudo() {
if "$@" >/dev/null 2>&1; then
return 0
fi
need_sudo
"${SUDO}" "$@"
}
need_sudo() {
if ! check_cmd "${SUDO}"; then
err "\
could not find the command \`${SUDO}\` needed to get permissions for install.
If you are on Windows, please run your shell as an administrator, then rerun this script.
Otherwise, please run this script as root, or install \`sudo\`."
fi
if ! "${SUDO}" -v; then
err "sudo permissions not granted, aborting installation"
fi
}
# The below functions have been extracted with minor modifications from the
# Rustup install script:
#
# https://github.com/rust-lang/rustup/blob/4c1289b2c3f3702783900934a38d7c5f912af787/rustup-init.sh
get_architecture() {
local _ostype _cputype _bitness _arch _clibtype
_ostype="$(uname -s)"
_cputype="$(uname -m)"
_clibtype="musl"
if [ "${_ostype}" = Linux ]; then
if [ "$(uname -o || true)" = Android ]; then
_ostype=Android
fi
fi
if [ "${_ostype}" = Darwin ] && [ "${_cputype}" = i386 ]; then
# Darwin `uname -m` lies
if sysctl hw.optional.x86_64 | grep -q ': 1'; then
_cputype=x86_64
fi
fi
if [ "${_ostype}" = SunOS ]; then
# Both Solaris and illumos presently announce as "SunOS" in "uname -s"
# so use "uname -o" to disambiguate. We use the full path to the
# system uname in case the user has coreutils uname first in PATH,
# which has historically sometimes printed the wrong value here.
if [ "$(/usr/bin/uname -o || true)" = illumos ]; then
_ostype=illumos
fi
# illumos systems have multi-arch userlands, and "uname -m" reports the
# machine hardware name; e.g., "i86pc" on both 32- and 64-bit x86
# systems. Check for the native (widest) instruction set on the
# running kernel:
if [ "${_cputype}" = i86pc ]; then
_cputype="$(isainfo -n)"
fi
fi
case "${_ostype}" in
Android)
_ostype=linux-android
;;
Linux)
check_proc
_ostype=unknown-linux-${_clibtype}
_bitness=$(get_bitness)
;;
FreeBSD)
_ostype=unknown-freebsd
;;
NetBSD)
_ostype=unknown-netbsd
;;
DragonFly)
_ostype=unknown-dragonfly
;;
Darwin)
_ostype=apple-darwin
;;
illumos)
_ostype=unknown-illumos
;;
MINGW* | MSYS* | CYGWIN* | Windows_NT)
_ostype=pc-windows-msvc
;;
*)
err "unrecognized OS type: ${_ostype}"
;;
esac
case "${_cputype}" in
i386 | i486 | i686 | i786 | x86)
_cputype=i686
;;
xscale | arm)
_cputype=arm
if [ "${_ostype}" = "linux-android" ]; then
_ostype=linux-androideabi
fi
;;
armv6l)
_cputype=arm
if [ "${_ostype}" = "linux-android" ]; then
_ostype=linux-androideabi
else
_ostype="${_ostype}eabihf"
fi
;;
armv7l | armv8l)
_cputype=armv7
if [ "${_ostype}" = "linux-android" ]; then
_ostype=linux-androideabi
else
_ostype="${_ostype}eabihf"
fi
;;
aarch64 | arm64)
_cputype=aarch64
;;
x86_64 | x86-64 | x64 | amd64)
_cputype=x86_64
;;
mips)
_cputype=$(get_endianness mips '' el)
;;
mips64)
if [ "${_bitness}" -eq 64 ]; then
# only n64 ABI is supported for now
_ostype="${_ostype}abi64"
_cputype=$(get_endianness mips64 '' el)
fi
;;
ppc)
_cputype=powerpc
;;
ppc64)
_cputype=powerpc64
;;
ppc64le)
_cputype=powerpc64le
;;
s390x)
_cputype=s390x
;;
riscv64)
_cputype=riscv64gc
;;
*)
err "unknown CPU type: ${_cputype}"
;;
esac
# Detect 64-bit linux with 32-bit userland
if [ "${_ostype}" = unknown-linux-musl ] && [ "${_bitness}" -eq 32 ]; then
case ${_cputype} in
x86_64)
# 32-bit executable for amd64 = x32
if is_host_amd64_elf; then {
err "x32 userland is unsupported"
}; else
_cputype=i686
fi
;;
mips64)
_cputype=$(get_endianness mips '' el)
;;
powerpc64)
_cputype=powerpc
;;
aarch64)
_cputype=armv7
if [ "${_ostype}" = "linux-android" ]; then
_ostype=linux-androideabi
else
_ostype="${_ostype}eabihf"
fi
;;
riscv64gc)
err "riscv64 with 32-bit userland unsupported"
;;
*) ;;
esac
fi
# Detect armv7 but without the CPU features Rust needs in that build,
# and fall back to arm.
# See https://github.com/rust-lang/rustup.rs/issues/587.
if [ "${_ostype}" = "unknown-linux-musleabihf" ] && [ "${_cputype}" = armv7 ]; then
if ensure grep '^Features' /proc/cpuinfo | grep -q -v neon; then
# At least one processor does not have NEON.
_cputype=arm
fi
fi
_arch="${_cputype}-${_ostype}"
echo "${_arch}"
}
get_bitness() {
need_cmd head
# Architecture detection without dependencies beyond coreutils.
# ELF files start out "\x7fELF", and the following byte is
# 0x01 for 32-bit and
# 0x02 for 64-bit.
# The printf builtin on some shells like dash only supports octal
# escape sequences, so we use those.
local _current_exe_head
_current_exe_head=$(head -c 5 /proc/self/exe)
if [ "${_current_exe_head}" = "$(printf '\177ELF\001')" ]; then
echo 32
elif [ "${_current_exe_head}" = "$(printf '\177ELF\002')" ]; then
echo 64
else
err "unknown platform bitness"
fi
}
get_endianness() {
local cputype="$1"
local suffix_eb="$2"
local suffix_el="$3"
# detect endianness without od/hexdump, like get_bitness() does.
need_cmd head
need_cmd tail
local _current_exe_endianness
_current_exe_endianness="$(head -c 6 /proc/self/exe | tail -c 1)"
if [ "${_current_exe_endianness}" = "$(printf '\001')" ]; then
echo "${cputype}${suffix_el}"
elif [ "${_current_exe_endianness}" = "$(printf '\002')" ]; then
echo "${cputype}${suffix_eb}"
else
err "unknown platform endianness"
fi
}
is_host_amd64_elf() {
need_cmd head
need_cmd tail
# ELF e_machine detection without dependencies beyond coreutils.
# Two-byte field at offset 0x12 indicates the CPU,
# but we're interested in it being 0x3E to indicate amd64, or not that.
local _current_exe_machine
_current_exe_machine=$(head -c 19 /proc/self/exe | tail -c 1)
[ "${_current_exe_machine}" = "$(printf '\076')" ]
}
check_proc() {
# Check for /proc by looking for the /proc/self/exe link.
# This is only run on Linux.
if ! test -L /proc/self/exe; then
err "unable to find /proc/self/exe. Is /proc mounted? Installation cannot proceed without /proc."
fi
}
need_cmd() {
if ! check_cmd "$1"; then
err "need '$1' (command not found)"
fi
}
check_cmd() {
command -v -- "$1" >/dev/null 2>&1
}
# Run a command that should never fail. If the command fails execution
# will immediately terminate with an error showing the failing
# command.
ensure() {
if ! "$@"; then err "command failed: $*"; fi
}
assert_nz() {
if [ -z "$1" ]; then err "found empty string: $2"; fi
}
err() {
echo "Error: $1" >&2
exit 1
}
# This is put in braces to ensure that the script does not run until it is
# downloaded completely.
{
main "$@" || exit 1
}

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 .SH REPORTING BUGS
For any issues, feature requests, or questions, please visit: For any issues, feature requests, or questions, please visit:
.sp .sp
\fBhttps://github.com/ajeetdsouza/zoxide/issues\fR. \fBhttps://github.com/ajeetdsouza/zoxide/issues\fR
.SH AUTHOR .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(|); tab(|);
l l. l l.
\fBautojump\fR \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 .TE
.sp .sp
Note: zoxide only imports paths from autojump, since its matching 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 .SH REPORTING BUGS
For any issues, feature requests, or questions, please visit: For any issues, feature requests, or questions, please visit:
.sp .sp
\fBhttps://github.com/ajeetdsouza/zoxide/issues\fR. \fBhttps://github.com/ajeetdsouza/zoxide/issues\fR
.SH AUTHOR .SH AUTHOR
Ajeet D'Souza <\fB98ajeet@gmail.com\fR> Ajeet D'Souza \fB<98ajeet@gmail.com>\fR

119
man/man1/zoxide-init.1 Normal file
View File

@ -0,0 +1,119 @@
.TH "ZOXIDE" "1" "2021-04-12" "" "zoxide"
.SH NAME
\fBzoxide-init\fR - generate shell configuration for zoxide
.SH SYNOPSIS
.B zoxide init SHELL [OPTIONS]
.SH DESCRIPTION
To initialize zoxide on your shell:
.TP
.B bash
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 the \fBend\fR of your config file (usually \fB~/.elvish/rc.elv\fR):
.sp
.nf
\fBeval $(zoxide init elvish | slurp)\fR
.fi
.sp
Note: zoxide only supports elvish v0.18.0 and above.
.TP
.B fish
Add this to the \fBend\fR of your config file (usually
\fB~/.config/fish/config.fish\fR):
.sp
.nf
\fBzoxide init fish | source\fR
.fi
.TP
.B nushell
Add this to the \fBend\fR of your env file (find it by running
\fB$nu.env-path\fR in Nushell):
.sp
.nf
\fBzoxide init nushell | save -f ~/.zoxide.nu\fR
.fi
.sp
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.89.0+.
.TP
.B powershell
Add this to the \fBend\fR of your config file (find it by running \fBecho
$profile\fR in PowerShell):
.sp
.nf
\fBInvoke-Expression (& { (zoxide init powershell | Out-String) })\fR
.fi
.TP
.B tcsh
Add this to the \fBend\fR of your config file (usually \fB~/.tcshrc\fR):
.sp
.nf
\fBzoxide init tcsh > ~/.zoxide.tcsh\fR
\fBsource ~/.zoxide.tcsh\fR
.fi
.TP
.B xonsh
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 the \fBend\fR of your config file (usually \fB~/.zshrc\fR):
.sp
.nf
\fBeval "$(zoxide init zsh)"\fR
.fi
.TP
.B any POSIX shell
.sp
Add this to the \fBend\fR of your config file:
.sp
.nf
\fBeval "$(zoxide init posix --hook prompt)"\fR
.fi
.SH OPTIONS
.TP
.B --cmd
Changes the prefix of the \fBz\fR and \fBzi\fR commands.
.br
\fB--cmd j\fR would change the commands to (\fBj\fR, \fBji\fR).
.br
\fB--cmd cd\fR would replace the \fBcd\fR command (doesn't work on Nushell /
POSIX shells).
.TP
.B -h, --help
Print help information.
.TP
.B --hook HOOK
Changes how often zoxide increments a directory's score:
.TS
tab(|);
l l.
\fBnone\fR|Never
\fBprompt\fR|At every shell prompt
\fBpwd\fR|Whenever the directory is changed
.TE
.TP
.B --no-cmd
Prevents zoxide from defining the \fBz\fR and \fBzi\fR commands. These functions
will still be available in your shell as \fB__zoxide_z\fR and \fB__zoxide_zi\fR,
should you choose to redefine them.
.SH REPORTING BUGS
For any issues, feature requests, or questions, please visit:
.sp
\fBhttps://github.com/ajeetdsouza/zoxide/issues\fR
.SH AUTHOR
Ajeet D'Souza \fB<98ajeet@gmail.com>\fR

View File

@ -18,7 +18,7 @@ Exclude a path from query results.
Print help information. Print help information.
.TP .TP
.B -i, --interactive .B -i, --interactive
Use interactive selection. This option requires fzf. Use interactive selection. This option requires \fBfzf\fR(1).
.TP .TP
.B -l, --list .B -l, --list
List all results, rather than just the one with the highest frecency. List all results, rather than just the one with the highest frecency.
@ -28,6 +28,6 @@ Print the calculated score as well as the matched path.
.SH REPORTING BUGS .SH REPORTING BUGS
For any issues, feature requests, or questions, please visit: For any issues, feature requests, or questions, please visit:
.sp .sp
\fBhttps://github.com/ajeetdsouza/zoxide/issues\fR. \fBhttps://github.com/ajeetdsouza/zoxide/issues\fR
.SH AUTHOR .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 .TP
.B -h, --help .B -h, --help
Print help information. Print help information.
.TP
.B -i, --interactive [KEYWORDS]
Use interactive selection. This option requires fzf.
.SH REPORTING BUGS .SH REPORTING BUGS
For any issues, feature requests, or questions, please visit: For any issues, feature requests, or questions, please visit:
.sp .sp
\fBhttps://github.com/ajeetdsouza/zoxide/issues\fR. \fBhttps://github.com/ajeetdsouza/zoxide/issues\fR
.SH AUTHOR .SH AUTHOR
Ajeet D'Souza <\fB98ajeet@gmail.com\fR> Ajeet D'Souza \fB<98ajeet@gmail.com>\fR

View File

@ -9,15 +9,18 @@ directories you use most frequently, and uses a ranking algorithm to navigate
to the best match. to the best match.
.SH USAGE .SH USAGE
.nf .nf
$ z foo # cd into highest ranked directory matching foo z foo # cd into highest ranked directory matching foo
$ z foo bar # cd into highest ranked directory matching foo and bar z foo bar # cd into highest ranked directory matching foo and bar
z foo / # cd into a subdirectory starting with foo
.sp .sp
$ z ~/foo # z also works like a regular cd command z ~/foo # z also works like a regular cd command
$ z foo/ # cd into relative path z foo/ # cd into relative path
$ z .. # cd one level up z .. # cd one level up
$ z - # cd into previous directory z - # cd into previous directory
.sp .sp
$ zi foo # cd with interactive selection (using fzf) zi foo # cd with interactive selection (using fzf)
.sp
z foo<SPACE><TAB> # show interactive completions (bash 4.4+/fish/zsh only)
.fi .fi
.SH SUBCOMMANDS .SH SUBCOMMANDS
.TP .TP
@ -62,7 +65,7 @@ T}
\fB/Users/Alice/Library/Application Support\fR \fB/Users/Alice/Library/Application Support\fR
T} T}
\fBWindows\fR|T{ \fBWindows\fR|T{
\fB{FOLDERID_RoamingAppData}\fR, eg. \fBC:\\Users\\Alice\\AppData\\Roaming\fR \fB%LOCALAPPDATA%\fR, eg. \fBC:\\Users\\Alice\\AppData\\Local\fR
T} T}
.TE .TE
.TP .TP
@ -86,8 +89,8 @@ By default, this is set to \fB$HOME\fR. After setting this up, you might need
to use \fBzoxide-remove\fR(1) to remove any existing entries from the database. to use \fBzoxide-remove\fR(1) to remove any existing entries from the database.
.TP .TP
.B _ZO_FZF_OPTS .B _ZO_FZF_OPTS
Custom options to pass to fzf during interactive selection. See \fBfzf\fR(1) for Custom options to pass to \fBfzf\fR(1) during interactive selection. See the
the list of options. manpage for the full list of options.
.TP .TP
.B _ZO_MAXAGE .B _ZO_MAXAGE
Configures the aging algorithm, which limits the maximum number of entries in Configures the aging algorithm, which limits the maximum number of entries in
@ -126,6 +129,6 @@ l l.
.SH REPORTING BUGS .SH REPORTING BUGS
For any issues, feature requests, or questions, please visit: For any issues, feature requests, or questions, please visit:
.sp .sp
\fBhttps://github.com/ajeetdsouza/zoxide/issues\fR. \fBhttps://github.com/ajeetdsouza/zoxide/issues\fR
.SH AUTHOR .SH AUTHOR
Ajeet D'Souza <\fB98ajeet@gmail.com\fR> Ajeet D'Souza \fB<98ajeet@gmail.com>\fR

View File

@ -1,106 +0,0 @@
.TH "ZOXIDE" "1" "2021-04-12" "" "zoxide"
.SH NAME
\fBzoxide-init\fR - generate shell configuration for zoxide
.SH SYNOPSIS
.B zoxide init SHELL [OPTIONS]
.SH DESCRIPTION
To initialize zoxide on your shell:
.TP
.B bash
Add this to your configuration (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):
.sp
.nf
\fBeval $(zoxide init elvish | slurp)\fR
.fi
.sp
Note: zoxide only supports elvish v0.16.0 and above.
.TP
.B fish
Add this to your configuration (usually \fB~/.config/fish/config.fish\fR):
.sp
.nf
\fBzoxide init fish | source\fR
.fi
.TP
.B nushell
Add this to your configuration (find it by running \fBconfig path\fR in
Nushell):
.sp
.nf
\fBstartup = ["zoxide init nushell --hook prompt | save ~/.zoxide.nu", "source ~/.zoxide.nu"]\fR
.fi
.sp
Note: zoxide only supports Nushell v0.37.0 and above.
.TP
.B powershell
Add this to your configuration (find it by running \fBecho $profile\fR in
PowerShell):
.sp
.nf
\fBInvoke-Expression (& {
$hook = if ($PSVersionTable.PSVersion.Major -lt 6) { 'prompt' } else { 'pwd' }
(zoxide init --hook $hook powershell) -join "`n"
})\fR
.fi
.TP
.B xonsh
Add this to your configuration (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):
.sp
.nf
\fBeval "$(zoxide init zsh)"\fR
.fi
.TP
.B any POSIX shell
.sp
Add this to your configuration:
.sp
.nf
\fBeval "$(zoxide init posix --hook prompt)"\fR
.fi
.SH OPTIONS
.TP
.B --cmd
Changes the prefix of predefined aliases (\fBz\fR, \fBzi\fR).
.br
\fB--cmd j\fR would change the aliases to (\fBj\fR, \fBji\fR).
.br
\fB--cmd cd\fR would replace the \fBcd\fR command (doesn't work on Nushell /
POSIX shells).
.TP
.B -h, --help
Print help information.
.TP
.B --hook HOOK
Changes how often zoxide increments a directory's score:
.TS
tab(|);
l l.
\fBnone\fR|Never
\fBprompt\fR|At every shell prompt
\fBpwd\fR|Whenever the directory is changed
.TE
.TP
.B --no-aliases
Don't define extra aliases (\fBz\fR, \fBzi\fR). These functions will still be
available in your shell as \fB__zoxide_z\fR and \fB__zoxide_zi\fR, should you
choose to redefine them.
.SH REPORTING BUGS
For any issues, feature requests, or questions, please visit:
.sp
\fBhttps://github.com/ajeetdsouza/zoxide/issues\fR.
.SH AUTHOR
Ajeet D'Souza <\fB98ajeet@gmail.com\fR>

View File

@ -1,12 +1,8 @@
# comment_width = 100 group_imports = "StdExternalCrate"
# error_on_line_overflow = true imports_granularity = "Module"
# error_on_unformatted = true
# group_imports = "StdExternalCrate"
# imports_granularity = "Module"
max_width = 120
newline_style = "Native" newline_style = "Native"
use_field_init_shorthand = true use_field_init_shorthand = true
use_small_heuristics = "Max" use_small_heuristics = "Max"
use_try_shorthand = true use_try_shorthand = true
# wrap_comments = true wrap_comments = true
# version = "Two" style_edition = "2024"

View File

@ -1,42 +1,58 @@
let let
pkgs = import (builtins.fetchTarball
"https://github.com/NixOS/nixpkgs/archive/ec9ef366451af88284d7dfd18ee017b7e86a0710.tar.gz") {
overlays = [ rust ];
};
rust = import (builtins.fetchTarball rust = import (builtins.fetchTarball
"https://github.com/oxalica/rust-overlay/archive/ad311f5bb5c5ef475985f1e0f264e831470a8510.tar.gz"); "https://github.com/oxalica/rust-overlay/archive/026e8fedefd6b167d92ed04b195c658d95ffc7a5.tar.gz");
pkgs = import <nixpkgs> { overlays = [ rust ]; };
pkgs-latest = import (builtins.fetchTarball rust-nightly =
"https://github.com/NixOS/nixpkgs/archive/3ef1d2a9602c18f8742e1fb63d5ae9867092e3d6.tar.gz") 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 { in pkgs.mkShell {
buildInputs = [ buildInputs = [
# Rust # Rust
(pkgs.rust-bin.selectLatestNightlyWith (toolchain: toolchain.rustfmt))
pkgs.rust-bin.stable.latest.default pkgs.rust-bin.stable.latest.default
# Shells # Shells
pkgs-latest.elvish
pkgs-latest.fish
pkgs-latest.nushell
pkgs-latest.xonsh
pkgs.bash pkgs.bash
pkgs.dash pkgs.dash
pkgs.elvish
pkgs.fish
pkgs.ksh
pkgs.nushell
pkgs.powershell pkgs.powershell
pkgs.tcsh
pkgs.xonsh
pkgs.zsh pkgs.zsh
# Tools # Tools
pkgs-latest.cargo-audit cargo-udeps
pkgs-latest.mandoc pkgs.cargo-msrv
pkgs-latest.nixfmt pkgs.cargo-nextest
pkgs-latest.nodePackages.markdownlint-cli pkgs.cargo-udeps
pkgs-latest.python3Packages.black pkgs.just
pkgs-latest.python3Packages.mypy pkgs.mandoc
pkgs-latest.python3Packages.pylint pkgs.nixfmt
pkgs-latest.shellcheck pkgs.nodePackages.markdownlint-cli
pkgs-latest.shfmt pkgs.python3Packages.black
pkgs.python3Packages.mypy
pkgs.python3Packages.pylint
pkgs.shellcheck
pkgs.shfmt
pkgs.yamlfmt
# Dependencies # Dependencies
pkgs.cacert pkgs.cacert
pkgs.libiconv
pkgs.fzf pkgs.fzf
pkgs.git pkgs.git
pkgs.libiconv
]; ];
RUST_BACKTRACE = 1; CARGO_TARGET_DIR = "target_nix";
} }

View File

@ -1,135 +0,0 @@
use std::path::PathBuf;
use clap::{AppSettings, 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,
global_setting(AppSettings::DisableHelpSubcommand),
global_setting(AppSettings::PropagateVersion),
version = option_env!("ZOXIDE_VERSION").unwrap_or_default()
)]
pub enum App {
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 any commands
#[clap(long)]
pub no_aliases: bool,
/// Renames the 'z' command and corresponding aliases
#[clap(long, default_value = "z")]
pub cmd: String,
/// Chooses event upon which an entry is added to the database
#[clap(arg_enum, long, default_value = "pwd")]
pub hook: InitHook,
}
#[derive(ArgEnum, Clone, Copy, Debug, Eq, PartialEq)]
pub enum InitHook {
None,
Prompt,
Pwd,
}
#[derive(ArgEnum, Clone, Debug)]
pub enum InitShell {
Bash,
Elvish,
Fish,
Nushell,
Posix,
Powershell,
Xonsh,
Zsh,
}
/// Search for a directory in the database
#[derive(Debug, Parser)]
pub struct Query {
pub keywords: Vec<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, conflicts_with = "interactive")]
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(conflicts_with = "paths", long, short, value_name = "keywords")]
pub interactive: Option<Vec<String>>,
#[clap(
conflicts_with = "interactive",
required_unless_present = "interactive",
value_hint = ValueHint::DirPath
)]
pub paths: Vec<String>,
}

View File

@ -1,172 +0,0 @@
use std::fs;
use anyhow::{bail, Context, Result};
use crate::app::{Import, ImportFrom, Run};
use crate::config;
use crate::db::{Database, DatabaseFile, Dir};
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 data_dir = config::data_dir()?;
let mut db = DatabaseFile::new(data_dir);
let db = &mut db.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),
}
.context("import error")?;
db.save()
}
}
fn from_autojump<'a>(db: &mut Database<'a>, buffer: &'a str) -> Result<()> {
for line in buffer.lines() {
if line.is_empty() {
continue;
}
let mut split = line.splitn(2, '\t');
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.
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;
}
if db.modified {
db.dedup();
}
Ok(())
}
fn from_z<'a>(db: &mut Database<'a>, buffer: &'a str) -> Result<()> {
for line in buffer.lines() {
if line.is_empty() {
continue;
}
let mut split = line.rsplitn(3, '|');
let last_accessed = split.next().with_context(|| format!("invalid entry: {}", line))?;
let last_accessed = last_accessed.parse().with_context(|| format!("invalid epoch: {}", last_accessed))?;
let rank = split.next().with_context(|| format!("invalid entry: {}", line))?;
let rank = rank.parse().with_context(|| format!("invalid rank: {}", rank))?;
let path = split.next().with_context(|| format!("invalid entry: {}", line))?;
db.dirs.push(Dir { path: path.into(), rank, last_accessed });
db.modified = true;
}
if db.modified {
db.dedup();
}
Ok(())
}
fn sigmoid(x: f64) -> f64 {
1.0 / (1.0 + (-x).exp())
}
#[cfg(test)]
mod tests {
use super::sigmoid;
use crate::db::{Database, Dir};
#[test]
fn from_autojump() {
let buffer = r#"
7.0 /baz
2.0 /foo/bar
5.0 /quux/quuz
"#;
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 };
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 = &[
Dir { path: "/baz".into(), rank: sigmoid(7.0), last_accessed: 0 },
Dir { path: "/corge/grault/garply".into(), rank: 6.0, last_accessed: 600 },
Dir { path: "/foo/bar".into(), rank: 9.0 + sigmoid(2.0), last_accessed: 900 },
Dir { path: "/quux/quuz".into(), rank: 1.0 + sigmoid(5.0), last_accessed: 100 },
Dir { path: "/waldo/fred/plugh".into(), rank: 3.0, last_accessed: 300 },
Dir { path: "/xyzzy/thud".into(), rank: 8.0, last_accessed: 800 },
];
println!("exp: {:?}", &exp);
for (dir1, dir2) in db.dirs.iter().zip(exp) {
assert_eq!(dir1.path, dir2.path);
assert!((dir1.rank - dir2.rank).abs() < 0.01);
assert_eq!(dir1.last_accessed, dir2.last_accessed);
}
}
#[test]
fn from_z() {
let buffer = r#"
/baz|7|700
/quux/quuz|4|400
/foo/bar|2|200
/quux/quuz|5|500
"#;
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 };
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 = &[
Dir { path: "/baz".into(), rank: 7.0, last_accessed: 700 },
Dir { path: "/corge/grault/garply".into(), rank: 6.0, last_accessed: 600 },
Dir { path: "/foo/bar".into(), rank: 11.0, last_accessed: 900 },
Dir { path: "/quux/quuz".into(), rank: 10.0, last_accessed: 500 },
Dir { path: "/waldo/fred/plugh".into(), rank: 3.0, last_accessed: 300 },
Dir { path: "/xyzzy/thud".into(), rank: 8.0, last_accessed: 800 },
];
println!("exp: {:?}", &exp);
for (dir1, dir2) in db.dirs.iter().zip(exp) {
assert_eq!(dir1.path, dir2.path);
assert!((dir1.rank - dir2.rank).abs() < 0.01);
assert_eq!(dir1.last_accessed, dir2.last_accessed);
}
}
}

View File

@ -1,33 +0,0 @@
use std::io::{self, Write};
use anyhow::{Context, Result};
use askama::Template;
use crate::app::{Init, InitShell, Run};
use crate::config;
use crate::error::BrokenPipeHandler;
use crate::shell::{self, Opts};
impl Run for Init {
fn run(&self) -> Result<()> {
let cmd = if self.no_aliases { 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(),
}
.context("could not render template")?;
writeln!(io::stdout(), "{}", source).pipe_exit("stdout")
}
}

View File

@ -1,26 +0,0 @@
mod _app;
mod add;
mod import;
mod init;
mod query;
mod remove;
use anyhow::Result;
pub use crate::app::_app::*;
pub trait Run {
fn run(&self) -> Result<()>;
}
impl Run for App {
fn run(&self) -> Result<()> {
match self {
App::Add(cmd) => cmd.run(),
App::Import(cmd) => cmd.run(),
App::Init(cmd) => cmd.run(),
App::Query(cmd) => cmd.run(),
App::Remove(cmd) => cmd.run(),
}
}
}

View File

@ -1,70 +0,0 @@
use std::io::{self, Write};
use anyhow::{Context, Result};
use crate::app::{Query, Run};
use crate::db::{Database, DatabaseFile};
use crate::error::BrokenPipeHandler;
use crate::fzf::Fzf;
use crate::{config, util};
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()?;
self.query(&mut db).and(db.save())
}
}
impl Query {
fn query(&self, db: &mut Database) -> Result<()> {
let now = util::current_time()?;
let mut stream = db.stream(now).with_keywords(&self.keywords);
if !self.all {
let resolve_symlinks = config::resolve_symlinks();
stream = stream.with_exists(resolve_symlinks);
}
if let Some(path) = &self.exclude {
stream = stream.with_exclude(path);
}
if self.interactive {
let mut fzf = Fzf::new(false)?;
while let Some(dir) = stream.next() {
writeln!(fzf.stdin(), "{}", dir.display_score(now)).pipe_exit("fzf")?;
}
let selection = fzf.wait_select()?;
if self.score {
print!("{}", selection);
} else {
let path = selection.get(5..).context("could not read selection from fzf")?;
print!("{}", path);
}
} else if self.list {
let stdout = io::stdout();
let handle = &mut stdout.lock();
while let Some(dir) = stream.next() {
if self.score {
writeln!(handle, "{}", dir.display_score(now))
} else {
writeln!(handle, "{}", dir.display())
}
.pipe_exit("stdout")?;
}
handle.flush().pipe_exit("stdout")?;
} 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(())
}
}

View File

@ -1,51 +0,0 @@
use std::io::Write;
use anyhow::{bail, Result};
use crate::app::{Remove, Run};
use crate::db::DatabaseFile;
use crate::error::BrokenPipeHandler;
use crate::fzf::Fzf;
use crate::{config, 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 selection;
match &self.interactive {
Some(keywords) => {
let now = util::current_time()?;
let mut stream = db.stream(now).with_keywords(keywords);
let mut fzf = Fzf::new(true)?;
while let Some(dir) = stream.next() {
writeln!(fzf.stdin(), "{}", dir.display_score(now)).pipe_exit("fzf")?;
}
selection = fzf.wait_select()?;
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);
}
}
}
None => {
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)
}
}
}
}
}
db.save()
}
}

View File

@ -1,44 +1,46 @@
use std::path::Path; use std::path::Path;
use anyhow::{bail, Result}; use anyhow::{Result, bail};
use crate::app::{Add, Run}; use crate::cmd::{Add, Run};
use crate::db::DatabaseFile; use crate::db::Database;
use crate::{config, util}; use crate::{config, util};
impl Run for Add { impl Run for Add {
fn run(&self) -> Result<()> { fn run(&self) -> Result<()> {
// These characters can't be printed cleanly to a single line, so they can cause confusion // These characters can't be printed cleanly to a single line, so they can cause
// when writing to fzf / stdout. // confusion when writing to stdout.
const EXCLUDE_CHARS: &[char] = &['\n', '\r']; const EXCLUDE_CHARS: &[char] = &['\n', '\r'];
let data_dir = config::data_dir()?;
let exclude_dirs = config::exclude_dirs()?; let exclude_dirs = config::exclude_dirs()?;
let max_age = config::maxage()?; let max_age = config::maxage()?;
let now = util::current_time()?; let now = util::current_time()?;
let mut db = DatabaseFile::new(data_dir); let mut db = Database::open()?;
let mut db = db.open()?;
for path in &self.paths { 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)?; 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)) { if path.contains(EXCLUDE_CHARS) || exclude_dirs.iter().any(|glob| glob.matches(path)) {
continue; continue;
} }
if !Path::new(path).is_dir() { if !Path::new(path).is_dir() {
bail!("not a directory: {}", path); bail!("not a directory: {path}");
} }
db.add(path, now);
let by = self.score.unwrap_or(1.0);
db.add_update(path, by, now);
} }
if db.modified { if db.dirty() {
db.age(max_age); db.age(max_age);
db.save()?;
} }
db.save()
Ok(())
} }
} }

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

@ -0,0 +1,204 @@
#![allow(clippy::module_inception)]
use std::path::PathBuf;
use clap::builder::{IntoResettable, Resettable, StyledStr};
use clap::{Parser, Subcommand, ValueEnum, ValueHint};
struct HelpTemplate;
impl IntoResettable<StyledStr> for HelpTemplate {
fn into_resettable(self) -> Resettable<StyledStr> {
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").into_resettable()
}
}
#[derive(Debug, Parser)]
#[clap(
about,
author,
help_template = HelpTemplate,
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 = HelpTemplate,
)]
pub struct Add {
#[clap(num_args = 1.., required = true, value_hint = ValueHint::DirPath)]
pub paths: Vec<PathBuf>,
/// The rank to increment the entry if it exists or initialize it with if it
/// doesn't
#[clap(short, long)]
pub score: Option<f64>,
}
/// Edit the database
#[derive(Debug, Parser)]
#[clap(
author,
help_template = HelpTemplate,
)]
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 = HelpTemplate,
)]
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 = HelpTemplate,
)]
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,
#[clap(alias = "ksh")]
Posix,
Powershell,
Tcsh,
Xonsh,
Zsh,
}
/// Search for a directory in the database
#[derive(Debug, Parser)]
#[clap(
author,
help_template = HelpTemplate,
)]
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>,
/// Only search within this directory
#[clap(long, value_hint = ValueHint::DirPath, value_name = "path")]
pub base_dir: Option<String>,
}
/// Remove a directory from the database
#[derive(Debug, Parser)]
#[clap(
author,
help_template = HelpTemplate,
)]
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()
}
}

166
src/cmd/import.rs Normal file
View File

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

32
src/cmd/init.rs Normal file
View File

@ -0,0 +1,32 @@
use std::io::{self, Write};
use anyhow::{Context, Result};
use askama::Template;
use crate::cmd::{Init, InitShell, Run};
use crate::config;
use crate::error::BrokenPipeHandler;
use crate::shell::{Bash, Elvish, Fish, Nushell, Opts, Posix, Powershell, Tcsh, 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 => 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::Tcsh => Tcsh(opts).render(),
InitShell::Xonsh => Xonsh(opts).render(),
InitShell::Zsh => Zsh(opts).render(),
}
.context("could not render template")?;
writeln!(io::stdout(), "{source}").pipe_exit("stdout")
}
}

28
src/cmd/mod.rs Normal file
View File

@ -0,0 +1,28 @@
mod add;
mod cmd;
mod edit;
mod import;
mod init;
mod query;
mod remove;
use anyhow::Result;
pub use crate::cmd::cmd::*;
pub trait Run {
fn run(&self) -> Result<()>;
}
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(),
Cmd::Remove(cmd) => cmd.run(),
}
}
}

121
src/cmd/query.rs Normal file
View File

@ -0,0 +1,121 @@
use std::io::{self, Write};
use anyhow::{Context, Result};
use crate::cmd::{Query, Run};
use crate::config;
use crate::db::{Database, Epoch, Stream, StreamOptions};
use crate::error::BrokenPipeHandler;
use crate::util::{self, Fzf, FzfChild};
impl Run for Query {
fn run(&self) -> Result<()> {
let mut db = crate::db::Database::open()?;
self.query(&mut db).and(db.save())
}
}
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 {
self.query_interactive(&mut stream, now)
} else if self.list {
self.query_list(&mut stream, now)
} else {
self.query_first(&mut stream, now)
}
}
fn query_interactive(&self, stream: &mut Stream, now: Epoch) -> Result<()> {
let mut fzf = Self::get_fzf()?;
let selection = loop {
match stream.next() {
Some(dir) if Some(dir.path.as_ref()) == self.exclude.as_deref() => continue,
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}");
}
Ok(())
}
fn query_list(&self, stream: &mut Stream, now: Epoch) -> Result<()> {
let handle = &mut io::stdout().lock();
while let Some(dir) = stream.next() {
if Some(dir.path.as_ref()) == self.exclude.as_deref() {
continue;
}
let dir = if self.score { dir.display().with_score(now) } else { dir.display() };
writeln!(handle, "{dir}").pipe_exit("stdout")?;
}
Ok(())
}
fn query_first(&self, stream: &mut Stream, now: Epoch) -> Result<()> {
let handle = &mut io::stdout();
let mut dir = stream.next().context("no match found")?;
while Some(dir.path.as_ref()) == self.exclude.as_deref() {
dir = stream.next().context("you are already in the only match")?;
}
let dir = if self.score { dir.display().with_score(now) } else { dir.display() };
writeln!(handle, "{dir}").pipe_exit("stdout")
}
fn get_stream<'a>(&self, db: &'a mut Database, now: Epoch) -> Result<Stream<'a>> {
let mut options = StreamOptions::new(now)
.with_keywords(self.keywords.iter().map(|s| s.as_str()))
.with_exclude(config::exclude_dirs()?)
.with_base_dir(self.base_dir.clone());
if !self.all {
let resolve_symlinks = config::resolve_symlinks();
options = options.with_exists(true).with_resolve_symlinks(resolve_symlinks);
}
let stream = Stream::new(db, options);
Ok(stream)
}
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 {
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",
])
.enable_preview()
}
.spawn()
}
}

23
src/cmd/remove.rs Normal file
View File

@ -0,0 +1,23 @@
use anyhow::{Result, bail};
use crate::cmd::{Remove, Run};
use crate::db::Database;
use crate::util;
impl Run for Remove {
fn run(&self) -> Result<()> {
let mut db = Database::open()?;
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}")
}
}
}
db.save()
}
}

View File

@ -2,54 +2,45 @@ use std::env;
use std::ffi::OsString; use std::ffi::OsString;
use std::path::PathBuf; use std::path::PathBuf;
use anyhow::{bail, Context, Result}; use anyhow::{Context, Result, ensure};
use dirs_next as dirs;
use glob::Pattern; use glob::Pattern;
use crate::db::Rank; use crate::db::Rank;
pub fn data_dir() -> Result<PathBuf> { pub fn data_dir() -> Result<PathBuf> {
let path = match env::var_os("_ZO_DATA_DIR") { let dir = match env::var_os("_ZO_DATA_DIR") {
Some(path) => PathBuf::from(path), Some(path) => PathBuf::from(path),
None => match dirs::data_local_dir() { None => dirs::data_local_dir()
Some(mut path) => { .context("could not find data directory, please set _ZO_DATA_DIR manually")?
path.push("zoxide"); .join("zoxide"),
path
}
None => bail!("could not find data directory, please set _ZO_DATA_DIR manually"),
},
}; };
Ok(path) ensure!(dir.is_absolute(), "_ZO_DATA_DIR must be an absolute path");
Ok(dir)
} }
pub fn echo() -> bool { pub fn echo() -> bool {
match env::var_os("_ZO_ECHO") { env::var_os("_ZO_ECHO").is_some_and(|var| var == "1")
Some(var) => var == "1",
None => false,
}
} }
pub fn exclude_dirs() -> Result<Vec<Pattern>> { pub fn exclude_dirs() -> Result<Vec<Pattern>> {
env::var_os("_ZO_EXCLUDE_DIRS").map_or_else( match env::var_os("_ZO_EXCLUDE_DIRS") {
|| { Some(paths) => env::split_paths(&paths)
.map(|path| {
let pattern = path.to_str().context("invalid unicode in _ZO_EXCLUDE_DIRS")?;
Pattern::new(pattern)
.with_context(|| format!("invalid glob in _ZO_EXCLUDE_DIRS: {pattern}"))
})
.collect(),
None => {
let pattern = (|| { let pattern = (|| {
let home = dirs::home_dir()?; let home = dirs::home_dir()?;
let home = home.to_str()?; let home = Pattern::escape(home.to_str()?);
let home = Pattern::escape(home);
Pattern::new(&home).ok() Pattern::new(&home).ok()
})(); })();
Ok(pattern.into_iter().collect()) Ok(pattern.into_iter().collect())
}, }
|paths| { }
env::split_paths(&paths)
.map(|path| {
let pattern = path.to_str().context("invalid unicode in _ZO_EXCLUDE_DIRS")?;
Pattern::new(pattern).with_context(|| format!("invalid glob in _ZO_EXCLUDE_DIRS: {}", pattern))
})
.collect()
},
)
} }
pub fn fzf_opts() -> Option<OsString> { pub fn fzf_opts() -> Option<OsString> {
@ -57,20 +48,15 @@ pub fn fzf_opts() -> Option<OsString> {
} }
pub fn maxage() -> Result<Rank> { pub fn maxage() -> Result<Rank> {
match env::var_os("_ZO_MAXAGE") { env::var_os("_ZO_MAXAGE").map_or(Ok(10_000.0), |maxage| {
Some(maxage) => { let maxage = maxage.to_str().context("invalid unicode in _ZO_MAXAGE")?;
let maxage = maxage.to_str().context("invalid unicode in _ZO_MAXAGE")?; let maxage = maxage
let maxage = .parse::<u32>()
maxage.parse::<u64>().with_context(|| format!("unable to parse _ZO_MAXAGE as integer: {}", maxage))?; .with_context(|| format!("unable to parse _ZO_MAXAGE as integer: {maxage}"))?;
Ok(maxage as Rank) Ok(maxage as Rank)
} })
None => Ok(10000.0),
}
} }
pub fn resolve_symlinks() -> bool { pub fn resolve_symlinks() -> bool {
match env::var_os("_ZO_RESOLVE_SYMLINKS") { env::var_os("_ZO_RESOLVE_SYMLINKS").is_some_and(|var| var == "1")
Some(var) => var == "1",
None => false,
}
} }

View File

@ -1,83 +1,9 @@
use std::borrow::Cow; use std::borrow::Cow;
use std::fmt::{self, Display, Formatter}; use std::fmt::{self, Display, Formatter};
use std::ops::{Deref, DerefMut};
use anyhow::{bail, Context, Result};
use bincode::Options as _;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize)] use crate::util::{DAY, HOUR, WEEK};
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)
}
}
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Dir<'a> { pub struct Dir<'a> {
@ -88,11 +14,11 @@ pub struct Dir<'a> {
} }
impl Dir<'_> { impl Dir<'_> {
pub fn score(&self, now: Epoch) -> Rank { pub fn display(&self) -> DirDisplay<'_> {
const HOUR: Epoch = 60 * 60; DirDisplay::new(self)
const DAY: Epoch = 24 * HOUR; }
const WEEK: Epoch = 7 * DAY;
pub fn score(&self, now: Epoch) -> Rank {
// The older the entry, the lesser its importance. // The older the entry, the lesser its importance.
let duration = now.saturating_sub(self.last_accessed); let duration = now.saturating_sub(self.last_accessed);
if duration < HOUR { if duration < HOUR {
@ -105,63 +31,39 @@ impl Dir<'_> {
self.rank * 0.25 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> { pub struct DirDisplay<'a> {
dir: &'a Dir<'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<'_> { impl Display for DirDisplay<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 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) write!(f, "{}", self.dir.path)
} }
} }
pub struct DirDisplayScore<'a> {
dir: &'a Dir<'a>,
now: Epoch,
}
impl Display for DirDisplayScore<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let score = self.dir.score(self.now);
let score = if score > 9999.0 {
9999
} else if score > 0.0 {
score as u32
} else {
0
};
write!(f, "{:>4} {}", score, self.dir.path)
}
}
pub type Rank = f64; pub type Rank = f64;
pub type Epoch = u64; 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,199 +1,234 @@
mod dir; mod dir;
mod stream; mod stream;
use std::fs;
use std::io::{self, Write};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::{fs, io};
use anyhow::{Context, Result}; use anyhow::{Context, Result, bail};
pub use dir::{Dir, DirList, Epoch, Rank}; use bincode::Options;
pub use stream::Stream; use ouroboros::self_referencing;
use tempfile::{NamedTempFile, PersistError};
#[derive(Debug)] pub use crate::db::dir::{Dir, Epoch, Rank};
pub struct Database<'file> { pub use crate::db::stream::{Stream, StreamOptions};
pub dirs: DirList<'file>, use crate::{config, util};
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<'file> Database<'file> { 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");
let path = fs::canonicalize(&path).unwrap_or(path);
match fs::read(&path) {
Ok(bytes) => Self::try_new(path, bytes, |bytes| Self::deserialize(bytes), false),
Err(e) if e.kind() == io::ErrorKind::NotFound => {
// Create data directory, but don't create any file yet. The file will be
// created later by [`Database::save`] if any data is modified.
fs::create_dir_all(data_dir).with_context(|| {
format!("unable to create data directory: {}", data_dir.display())
})?;
Ok(Self::new(path, Vec::new(), |_| Vec::new(), false))
}
Err(e) => {
Err(e).with_context(|| format!("could not read from database: {}", path.display()))
}
}
}
pub fn save(&mut self) -> Result<()> { pub fn save(&mut self) -> Result<()> {
if !self.modified { // Only write to disk if the database is modified.
if !self.dirty() {
return Ok(()); return Ok(());
} }
let buffer = self.dirs.to_bytes()?; let bytes = Self::serialize(self.dirs())?;
let mut file = NamedTempFile::new_in(self.data_dir) util::write(self.borrow_path(), bytes).context("could not write to database")?;
.with_context(|| format!("could not create temporary database in: {}", self.data_dir.display()))?; self.with_dirty_mut(|dirty| *dirty = false);
// Preallocate enough space on the file, preventing copying later on. This optimization may
// fail on some filesystems, but it is safe to ignore it and proceed.
let _ = file.as_file().set_len(buffer.len() as _);
file.write_all(&buffer)
.with_context(|| format!("could not write to temporary database: {}", file.path().display()))?;
let path = db_path(&self.data_dir);
persist(file, &path).with_context(|| format!("could not replace database: {}", path.display()))?;
self.modified = false;
Ok(()) Ok(())
} }
/// Adds a new directory or increments its rank. Also updates its last accessed time. /// Increments the rank of a directory, or creates it if it does not exist.
pub fn add<S: AsRef<str>>(&mut self, path: S, now: Epoch) { pub fn add(&mut self, path: impl AsRef<str> + Into<String>, by: Rank, now: Epoch) {
let path = path.as_ref(); 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),
match self.dirs.iter_mut().find(|dir| dir.path == path) {
None => { 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; self.with_dirty_mut(|dirty| *dirty = true);
dir.rank += 1.0; }
}
};
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 dedup(&mut self) { pub fn dedup(&mut self) {
// Sort by path, so that equal paths are next to each other. // 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;
// Check if curr_dir and next_dir have equal paths. self.with_dirs_mut(|dirs| {
let curr_dir = &self.dirs[idx]; for idx in (1..dirs.len()).rev() {
let next_dir = &self.dirs[idx - 1]; // Check if curr_dir and next_dir have equal paths.
if next_dir.path != curr_dir.path { let curr_dir = &dirs[idx];
continue; let next_dir = &dirs[idx - 1];
} if next_dir.path != curr_dir.path {
continue;
// Merge curr_dir's rank and last_accessed into next_dir.
let rank = curr_dir.rank;
let last_accessed = curr_dir.last_accessed;
let next_dir = &mut self.dirs[idx - 1];
next_dir.last_accessed = next_dir.last_accessed.max(last_accessed);
next_dir.rank += rank;
// Delete curr_dir.
self.dirs.swap_remove(idx);
self.modified = true;
}
}
// Streaming iterator for directories.
pub fn stream(&mut self, now: Epoch) -> Stream<'_, 'file> {
Stream::new(self, now)
}
/// Removes the directory with `path` from the store. This does not preserve ordering, but is
/// O(1).
pub fn remove<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;
}
false
}
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; // Merge curr_dir's rank and last_accessed into next_dir.
let rank = curr_dir.rank;
let last_accessed = curr_dir.last_accessed;
let next_dir = &mut dirs[idx - 1];
next_dir.last_accessed = next_dir.last_accessed.max(last_accessed);
next_dir.rank += rank;
// Delete curr_dir.
dirs.swap_remove(idx);
dirty = true;
}
});
self.with_dirty_mut(|dirty_prev| *dirty_prev |= dirty);
}
pub fn sort_by_path(&mut self) {
self.with_dirs_mut(|dirs| dirs.sort_unstable_by(|dir1, dir2| dir1.path.cmp(&dir2.path)));
self.with_dirty_mut(|dirty| *dirty = true);
}
pub fn sort_by_score(&mut self, now: Epoch) {
self.with_dirs_mut(|dirs| {
dirs.sort_unstable_by(|dir1: &Dir, dir2: &Dir| {
dir1.score(now).total_cmp(&dir2.score(now))
})
});
self.with_dirty_mut(|dirty| *dirty = true);
}
pub fn dirty(&self) -> bool {
*self.borrow_dirty()
}
pub fn dirs(&self) -> &[Dir<'_>] {
self.borrow_dirs()
}
fn serialize(dirs: &[Dir<'_>]) -> Result<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")
}
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);
// 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);
}
#[cfg(windows)] // Deserialize sections.
fn persist<P: AsRef<Path>>(mut file: NamedTempFile, path: P) -> Result<(), PersistError> { let version = deserializer.deserialize(bytes_version)?;
use std::thread; let dirs = match version {
use std::time::Duration; Self::VERSION => {
deserializer.deserialize(bytes_dirs).context("could not deserialize database")?
use rand::distributions::{Distribution, Uniform};
use rand::rngs::SmallRng;
use rand::SeedableRng;
// File renames on Windows are not atomic and sometimes fail with `PermissionDenied`. This is
// extremely unlikely unless it's running in a loop on multiple threads. Nevertheless, we guard
// against it by retrying the rename a fixed number of times.
const MAX_TRIES: usize = 10;
let mut rng = None;
for _ in 0..MAX_TRIES {
match file.persist(&path) {
Ok(_) => break,
Err(e) if e.error.kind() == io::ErrorKind::PermissionDenied => {
let mut rng = rng.get_or_insert_with(SmallRng::from_entropy);
let between = Uniform::from(50..150);
let duration = Duration::from_millis(between.sample(&mut rng));
thread::sleep(duration);
file = e.file;
} }
Err(e) => return Err(e), version => {
} bail!("unsupported version (got {version}, supports {})", Self::VERSION)
}
Ok(())
}
#[cfg(unix)]
fn persist<P: AsRef<Path>>(file: NamedTempFile, path: P) -> Result<(), PersistError> {
file.persist(path)?;
Ok(())
}
pub struct DatabaseFile {
buffer: Vec<u8>,
data_dir: PathBuf,
}
impl DatabaseFile {
pub fn new<P: Into<PathBuf>>(data_dir: P) -> Self {
DatabaseFile { buffer: Vec::new(), data_dir: data_dir.into() }
}
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())),
}
}
}
fn db_path<P: AsRef<Path>>(data_dir: P) -> PathBuf { Ok(dirs)
const DB_FILENAME: &str = "db.zo"; }
data_dir.as_ref().join(DB_FILENAME)
} }
#[cfg(test)] #[cfg(test)]
@ -202,50 +237,49 @@ mod tests {
#[test] #[test]
fn add() { fn add() {
let data_dir = tempfile::tempdir().unwrap();
let path = if cfg!(windows) { r"C:\foo\bar" } else { "/foo/bar" }; let path = if cfg!(windows) { r"C:\foo\bar" } else { "/foo/bar" };
let now = 946684800; let now = 946684800;
let data_dir = tempfile::tempdir().unwrap();
{ {
let mut db = DatabaseFile::new(data_dir.path()); let mut db = Database::open_dir(data_dir.path()).unwrap();
let mut db = db.open().unwrap(); db.add(path, 1.0, now);
db.add(path, now); db.add(path, 1.0, now);
db.add(path, now);
db.save().unwrap(); 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_eq!(dir.path, path);
assert!((dir.rank - 2.0).abs() < 0.01);
assert_eq!(dir.last_accessed, now); assert_eq!(dir.last_accessed, now);
} }
} }
#[test] #[test]
fn remove() { fn remove() {
let data_dir = tempfile::tempdir().unwrap();
let path = if cfg!(windows) { r"C:\foo\bar" } else { "/foo/bar" }; let path = if cfg!(windows) { r"C:\foo\bar" } else { "/foo/bar" };
let now = 946684800; let now = 946684800;
let data_dir = tempfile::tempdir().unwrap();
{ {
let mut db = DatabaseFile::new(data_dir.path()); let mut db = Database::open_dir(data_dir.path()).unwrap();
let mut db = db.open().unwrap(); db.add(path, 1.0, now);
db.add(path, now);
db.save().unwrap(); db.save().unwrap();
} }
{ {
let mut db = DatabaseFile::new(data_dir.path()); let mut db = Database::open_dir(data_dir.path()).unwrap();
let mut db = db.open().unwrap();
assert!(db.remove(path)); assert!(db.remove(path));
db.save().unwrap(); db.save().unwrap();
} }
{ {
let mut db = DatabaseFile::new(data_dir.path()); let mut db = Database::open_dir(data_dir.path()).unwrap();
let mut db = db.open().unwrap(); assert!(db.dirs().is_empty());
assert!(db.dirs.is_empty());
assert!(!db.remove(path)); assert!(!db.remove(path));
db.save().unwrap(); db.save().unwrap();
} }

View File

@ -1,98 +1,84 @@
use std::iter::Rev; use std::iter::Rev;
use std::ops::Range; use std::ops::Range;
use std::path::Path;
use std::{fs, path}; use std::{fs, path};
use ordered_float::OrderedFloat; use glob::Pattern;
use super::{Database, Dir, Epoch}; use crate::db::{Database, Dir, Epoch};
use crate::util; use crate::util::{self, MONTH};
pub struct Stream<'db, 'file> { pub struct Stream<'a> {
db: &'db mut Database<'file>, db: &'a mut Database,
idxs: Rev<Range<usize>>, idxs: Rev<Range<usize>>,
options: StreamOptions,
keywords: Vec<String>,
check_exists: bool,
expire_below: Epoch,
resolve_symlinks: bool,
exclude_path: Option<String>,
} }
impl<'db, 'file> Stream<'db, 'file> { impl<'a> Stream<'a> {
pub fn new(db: &'db mut Database<'file>, now: Epoch) -> Self { pub fn new(db: &'a mut Database, options: StreamOptions) -> Self {
// Iterate in descending order of score. db.sort_by_score(options.now);
db.dirs.sort_unstable_by_key(|dir| OrderedFloat(dir.score(now))); let idxs = (0..db.dirs().len()).rev();
let idxs = (0..db.dirs.len()).rev(); Stream { db, idxs, options }
// 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);
Stream {
db,
idxs,
keywords: Vec::new(),
check_exists: false,
expire_below,
resolve_symlinks: false,
exclude_path: None,
}
} }
pub fn with_exclude<S: Into<String>>(mut self, path: S) -> Self { pub fn next(&mut self) -> Option<&Dir<'_>> {
self.exclude_path = Some(path.into());
self
}
pub fn with_exists(mut self, resolve_symlinks: bool) -> Self {
self.check_exists = true;
self.resolve_symlinks = resolve_symlinks;
self
}
pub fn with_keywords<S: AsRef<str>>(mut self, keywords: &[S]) -> Self {
self.keywords = keywords.iter().map(util::to_lowercase).collect();
self
}
pub fn next(&mut self) -> Option<&Dir<'file>> {
while let Some(idx) = self.idxs.next() { while let Some(idx) = self.idxs.next() {
let dir = &self.db.dirs[idx]; let dir = &self.db.dirs()[idx];
if !self.matches_keywords(&dir.path) { if !self.filter_by_keywords(&dir.path) {
continue; continue;
} }
if !self.matches_exists(&dir.path) { if !self.filter_by_base_dir(&dir.path) {
if dir.last_accessed < self.expire_below { continue;
self.db.dirs.swap_remove(idx); }
self.db.modified = true;
if !self.filter_by_exclude(&dir.path) {
self.db.swap_remove(idx);
continue;
}
// Exists queries are slow, this should always be checked last.
if !self.filter_by_exists(&dir.path) {
if dir.last_accessed < self.options.ttl {
self.db.swap_remove(idx);
} }
continue; continue;
} }
if Some(dir.path.as_ref()) == self.exclude_path.as_deref() { let dir = &self.db.dirs()[idx];
continue;
}
let dir = &self.db.dirs[idx];
return Some(dir); return Some(dir);
} }
None None
} }
fn matches_exists<S: AsRef<str>>(&self, path: S) -> bool { fn filter_by_base_dir(&self, path: &str) -> bool {
if !self.check_exists { match &self.options.base_dir {
return true; Some(base_dir) => Path::new(path).starts_with(base_dir),
None => 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()
} }
fn matches_keywords<S: AsRef<str>>(&self, path: S) -> bool { fn filter_by_exclude(&self, path: &str) -> bool {
let (keywords_last, keywords) = match self.keywords.split_last() { !self.options.exclude.iter().any(|pattern| pattern.matches(path))
}
fn filter_by_exists(&self, path: &str) -> bool {
if !self.options.exists {
return true;
}
// The logic here is reversed - if we resolve symlinks when adding entries to
// the database, we should not return symlinks when querying back from
// the database.
let resolver =
if self.options.resolve_symlinks { fs::symlink_metadata } else { fs::metadata };
resolver(path).map(|metadata| metadata.is_dir()).unwrap_or_default()
}
fn filter_by_keywords(&self, path: &str) -> bool {
let (keywords_last, keywords) = match self.options.keywords.split_last() {
Some(split) => split, Some(split) => split,
None => return true, None => return true,
}; };
@ -120,13 +106,81 @@ impl<'db, 'file> Stream<'db, 'file> {
} }
} }
pub struct StreamOptions {
/// The current time.
now: Epoch,
/// Only directories matching these keywords will be returned.
keywords: Vec<String>,
/// Directories that match any of these globs will be lazily removed.
exclude: Vec<Pattern>,
/// Directories will only be returned if they exist on the filesystem.
exists: bool,
/// Whether to resolve symlinks when checking if a directory exists.
resolve_symlinks: bool,
/// Directories that do not exist and haven't been accessed since TTL will
/// be lazily removed.
ttl: Epoch,
/// Only return directories within this parent directory
/// Does not check if the path exists
base_dir: Option<String>,
}
impl StreamOptions {
pub fn new(now: Epoch) -> Self {
StreamOptions {
now,
keywords: Vec::new(),
exclude: Vec::new(),
exists: false,
resolve_symlinks: false,
ttl: now.saturating_sub(3 * MONTH),
base_dir: None,
}
}
pub fn with_keywords<I>(mut self, keywords: I) -> Self
where
I: IntoIterator,
I::Item: AsRef<str>,
{
self.keywords = keywords.into_iter().map(util::to_lowercase).collect();
self
}
pub fn with_exclude(mut self, exclude: Vec<Pattern>) -> Self {
self.exclude = exclude;
self
}
pub fn with_exists(mut self, exists: bool) -> Self {
self.exists = exists;
self
}
pub fn with_resolve_symlinks(mut self, resolve_symlinks: bool) -> Self {
self.resolve_symlinks = resolve_symlinks;
self
}
pub fn with_base_dir(mut self, base_dir: Option<String>) -> Self {
self.base_dir = base_dir;
self
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::path::PathBuf; use std::path::PathBuf;
use rstest::rstest; use rstest::rstest;
use super::Database; use super::*;
#[rstest] #[rstest]
// Case normalization // Case normalization
@ -149,8 +203,9 @@ mod tests {
#[case(&["/foo/", "/bar"], "/foo/bar", false)] #[case(&["/foo/", "/bar"], "/foo/bar", false)]
#[case(&["/foo/", "/bar"], "/foo/baz/bar", true)] #[case(&["/foo/", "/bar"], "/foo/baz/bar", true)]
fn query(#[case] keywords: &[&str], #[case] path: &str, #[case] is_match: bool) { 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 db = &mut Database::new(PathBuf::new(), Vec::new(), |_| Vec::new(), false);
let stream = db.stream(0).with_keywords(keywords); let options = StreamOptions::new(0).with_keywords(keywords.iter());
assert_eq!(is_match, stream.matches_keywords(path)); let stream = Stream::new(db, options);
assert_eq!(is_match, stream.filter_by_keywords(path));
} }
} }

View File

@ -1,16 +1,16 @@
use std::fmt::{self, Display, Formatter}; use std::fmt::{self, Display, Formatter};
use std::io; use std::io;
use anyhow::{bail, Context, Result}; use anyhow::{Context, Result, bail};
/// Custom error type for early exit. /// Custom error type for early exit.
#[derive(Debug)] #[derive(Debug)]
pub struct SilentExit { pub struct SilentExit {
pub code: i32, pub code: u8,
} }
impl Display for SilentExit { impl Display for SilentExit {
fn fmt(&self, _: &mut Formatter) -> fmt::Result { fn fmt(&self, _: &mut Formatter<'_>) -> fmt::Result {
Ok(()) Ok(())
} }
} }
@ -23,7 +23,7 @@ impl BrokenPipeHandler for io::Result<()> {
fn pipe_exit(self, device: &str) -> Result<()> { fn pipe_exit(self, device: &str) -> Result<()> {
match self { match self {
Err(e) if e.kind() == io::ErrorKind::BrokenPipe => bail!(SilentExit { code: 0 }), 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,61 +0,0 @@
use std::io;
use std::process::{Child, ChildStdin, Command, Stdio};
use anyhow::{bail, Context, Result};
use crate::config;
use crate::error::SilentExit;
pub struct Fzf {
child: Child,
}
impl Fzf {
pub fn new(multiple: bool) -> Result<Self> {
let mut command = Command::new("fzf");
if multiple {
command.arg("-m");
}
command.arg("-n2..").stdin(Stdio::piped()).stdout(Stdio::piped());
if let Some(fzf_opts) = config::fzf_opts() {
command.env("FZF_DEFAULT_OPTS", fzf_opts);
}
let child = match command.spawn() {
Ok(child) => child,
Err(e) if e.kind() == io::ErrorKind::NotFound => {
bail!("could not find fzf, is it installed?")
}
Err(e) => Err(e).context("could not launch fzf")?,
};
Ok(Fzf { child })
}
pub fn stdin(&mut self) -> &mut ChildStdin {
// unwrap is safe here because command.stdin() has been piped.
self.child.stdin.as_mut().unwrap()
}
pub fn wait_select(self) -> Result<String> {
let output = self.child.wait_with_output().context("wait failed on fzf")?;
match output.status.code() {
// normal exit
Some(0) => String::from_utf8(output.stdout).context("invalid unicode in fzf output"),
// no match
Some(1) => bail!("no match found"),
// error
Some(2) => bail!("fzf returned an error"),
// terminated by a signal
Some(code @ 130) => bail!(SilentExit { code }),
Some(128..=254) | None => bail!("fzf was terminated"),
// unknown
_ => bail!("fzf returned an unknown error"),
}
}
}

View File

@ -1,31 +1,34 @@
mod app; #![allow(clippy::single_component_path_imports)]
mod cmd;
mod config; mod config;
mod db; mod db;
mod error; mod error;
mod fzf;
mod shell; mod shell;
mod util; mod util;
use std::env;
use std::io::{self, Write}; use std::io::{self, Write};
use std::{env, process}; use std::process::ExitCode;
use clap::Parser; use clap::Parser;
use crate::app::{App, Run}; use crate::cmd::{Cmd, Run};
use crate::error::SilentExit; use crate::error::SilentExit;
pub fn main() { pub fn main() -> ExitCode {
// Forcibly disable backtraces. // Forcibly disable backtraces.
env::remove_var("RUST_LIB_BACKTRACE"); unsafe { env::remove_var("RUST_LIB_BACKTRACE") };
env::remove_var("RUST_BACKTRACE"); unsafe { env::remove_var("RUST_BACKTRACE") };
if let Err(e) = App::parse().run() { match Cmd::parse().run() {
match e.downcast::<SilentExit>() { Ok(()) => ExitCode::SUCCESS,
Ok(SilentExit { code }) => process::exit(code), Err(e) => match e.downcast::<SilentExit>() {
Ok(SilentExit { code }) => code.into(),
Err(e) => { Err(e) => {
let _ = writeln!(io::stderr(), "zoxide: {:?}", e); _ = writeln!(io::stderr(), "zoxide: {e:?}");
process::exit(1); ExitCode::FAILURE
} }
} },
} }
} }

View File

@ -1,13 +1,4 @@
use crate::app::InitHook; use crate::cmd::InitHook;
const FZF_COMPLETE_OPTS: &str = "\
--bind=ctrl-z:ignore \
--exit-0 \
--height=35% \
--inline-info \
--no-sort \
--reverse \
--select-1";
#[derive(Debug, Eq, PartialEq)] #[derive(Debug, Eq, PartialEq)]
pub struct Opts<'a> { pub struct Opts<'a> {
@ -38,47 +29,49 @@ make_template!(Fish, "fish.txt");
make_template!(Nushell, "nushell.txt"); make_template!(Nushell, "nushell.txt");
make_template!(Posix, "posix.txt"); make_template!(Posix, "posix.txt");
make_template!(Powershell, "powershell.txt"); make_template!(Powershell, "powershell.txt");
make_template!(Tcsh, "tcsh.txt");
make_template!(Xonsh, "xonsh.txt"); make_template!(Xonsh, "xonsh.txt");
make_template!(Zsh, "zsh.txt"); make_template!(Zsh, "zsh.txt");
#[cfg(feature = "nix")] #[cfg(feature = "nix-dev")]
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use askama::Template; use askama::Template;
use assert_cmd::Command; use assert_cmd::Command;
use rstest::rstest; use rstest::rstest;
use rstest_reuse::{apply, template};
use super::*; use super::*;
#[template]
#[rstest] #[rstest]
fn bash_bash( fn opts(
#[values(None, Some("z"))] cmd: Option<&str>, #[values(None, Some("z"))] cmd: Option<&str>,
#[values(InitHook::None, InitHook::Prompt, InitHook::Pwd)] hook: InitHook, #[values(InitHook::None, InitHook::Prompt, InitHook::Pwd)] hook: InitHook,
#[values(false, true)] echo: bool, #[values(false, true)] echo: bool,
#[values(false, true)] resolve_symlinks: bool, #[values(false, true)] resolve_symlinks: bool,
) { ) {
}
#[apply(opts)]
fn bash_bash(cmd: Option<&str>, hook: InitHook, echo: bool, resolve_symlinks: bool) {
let opts = Opts { cmd, hook, echo, resolve_symlinks }; let opts = Opts { cmd, hook, echo, resolve_symlinks };
let source = Bash(&opts).render().unwrap(); let source = Bash(&opts).render().unwrap();
Command::new("bash") Command::new("bash")
.args(&["--noprofile", "--norc", "-e", "-u", "-o", "pipefail", "-c", &source]) .args(["--noprofile", "--norc", "-e", "-u", "-o", "pipefail", "-c", &source])
.assert() .assert()
.success() .success()
.stdout("") .stdout("")
.stderr(""); .stderr("");
} }
#[rstest] #[apply(opts)]
fn bash_shellcheck( fn bash_shellcheck(cmd: Option<&str>, hook: InitHook, echo: bool, resolve_symlinks: bool) {
#[values(None, Some("z"))] cmd: Option<&str>,
#[values(InitHook::None, InitHook::Prompt, InitHook::Pwd)] hook: InitHook,
#[values(false, true)] echo: bool,
#[values(false, true)] resolve_symlinks: bool,
) {
let opts = Opts { cmd, hook, echo, resolve_symlinks }; let opts = Opts { cmd, hook, echo, resolve_symlinks };
let source = Bash(&opts).render().unwrap(); let source = Bash(&opts).render().unwrap();
Command::new("shellcheck") Command::new("shellcheck")
.args(&["--enable", "all", "--shell", "bash", "-"]) .args(["--enable=all", "-"])
.write_stdin(source) .write_stdin(source)
.assert() .assert()
.success() .success()
@ -86,19 +79,14 @@ mod tests {
.stderr(""); .stderr("");
} }
#[rstest] #[apply(opts)]
fn bash_shfmt( fn bash_shfmt(cmd: Option<&str>, hook: InitHook, echo: bool, resolve_symlinks: bool) {
#[values(None, Some("z"))] cmd: Option<&str>,
#[values(InitHook::None, InitHook::Prompt, InitHook::Pwd)] hook: InitHook,
#[values(false, true)] echo: bool,
#[values(false, true)] resolve_symlinks: bool,
) {
let opts = Opts { cmd, hook, echo, resolve_symlinks }; let opts = Opts { cmd, hook, echo, resolve_symlinks };
let mut source = Bash(&opts).render().unwrap(); let mut source = Bash(&opts).render().unwrap();
source.push('\n'); source.push('\n');
Command::new("shfmt") Command::new("shfmt")
.args(&["-d", "-s", "-ln", "bash", "-i", "4", "-ci", "-"]) .args(["--diff", "--indent=4", "--language-dialect=bash", "--simplify", "-"])
.write_stdin(source) .write_stdin(source)
.assert() .assert()
.success() .success()
@ -106,33 +94,38 @@ mod tests {
.stderr(""); .stderr("");
} }
#[rstest] #[apply(opts)]
fn elvish_elvish( fn elvish_elvish(cmd: Option<&str>, hook: InitHook, echo: bool, resolve_symlinks: bool) {
#[values(None, Some("z"))] cmd: Option<&str>,
#[values(InitHook::None, InitHook::Prompt, InitHook::Pwd)] hook: InitHook,
#[values(false, true)] echo: bool,
#[values(false, true)] resolve_symlinks: bool,
) {
let opts = Opts { cmd, hook, echo, resolve_symlinks }; let opts = Opts { cmd, hook, echo, resolve_symlinks };
let mut source = String::new(); let mut source = String::new();
// Filter out lines using edit:*, since those functions are only available in the // Filter out lines using edit:*, since those functions are only available in
// interactive editor. // the interactive editor.
for line in Elvish(&opts).render().unwrap().split('\n').filter(|line| !line.contains("edit:")) { for line in Elvish(&opts).render().unwrap().lines().filter(|line| !line.contains("edit:")) {
source.push_str(line); source.push_str(line);
source.push('\n'); 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("");
} }
#[rstest] #[apply(opts)]
fn fish_fish( fn fish_no_builtin_abbr(cmd: Option<&str>, hook: InitHook, echo: bool, resolve_symlinks: bool) {
#[values(None, Some("z"))] cmd: Option<&str>, let opts = Opts { cmd, hook, echo, resolve_symlinks };
#[values(InitHook::None, InitHook::Prompt, InitHook::Pwd)] hook: InitHook, let source = Fish(&opts).render().unwrap();
#[values(false, true)] echo: bool, assert!(
#[values(false, true)] resolve_symlinks: bool, !source.contains("builtin abbr"),
) { "`builtin abbr` does not work on older versions of Fish"
);
}
#[apply(opts)]
fn fish_fish(cmd: Option<&str>, hook: InitHook, echo: bool, resolve_symlinks: bool) {
let opts = Opts { cmd, hook, echo, resolve_symlinks }; let opts = Opts { cmd, hook, echo, resolve_symlinks };
let source = Fish(&opts).render().unwrap(); let source = Fish(&opts).render().unwrap();
@ -141,20 +134,15 @@ mod tests {
Command::new("fish") Command::new("fish")
.env("HOME", tempdir) .env("HOME", tempdir)
.args(&["--command", &source, "--private"]) .args(["--command", &source, "--no-config", "--private"])
.assert() .assert()
.success() .success()
.stdout("") .stdout("")
.stderr(""); .stderr("");
} }
#[rstest] #[apply(opts)]
fn fish_fishindent( fn fish_fishindent(cmd: Option<&str>, hook: InitHook, echo: bool, resolve_symlinks: bool) {
#[values(None, Some("z"))] cmd: Option<&str>,
#[values(InitHook::None, InitHook::Prompt, InitHook::Pwd)] hook: InitHook,
#[values(false, true)] echo: bool,
#[values(false, true)] resolve_symlinks: bool,
) {
let opts = Opts { cmd, hook, echo, resolve_symlinks }; let opts = Opts { cmd, hook, echo, resolve_symlinks };
let mut source = Fish(&opts).render().unwrap(); let mut source = Fish(&opts).render().unwrap();
source.push('\n'); source.push('\n');
@ -171,39 +159,33 @@ mod tests {
.stderr(""); .stderr("");
} }
#[rstest] #[apply(opts)]
fn nushell_nushell( fn nushell_nushell(cmd: Option<&str>, hook: InitHook, echo: bool, resolve_symlinks: bool) {
#[values(None, Some("z"))] cmd: Option<&str>,
#[values(InitHook::None, InitHook::Prompt, InitHook::Pwd)] hook: InitHook,
#[values(false, true)] echo: bool,
#[values(false, true)] resolve_symlinks: bool,
) {
let opts = Opts { cmd, hook, echo, resolve_symlinks }; let opts = Opts { cmd, hook, echo, resolve_symlinks };
let source = Nushell(&opts).render().unwrap(); let source = Nushell(&opts).render().unwrap();
let tempdir = tempfile::tempdir().unwrap(); let tempdir = tempfile::tempdir().unwrap();
let tempdir = tempdir.path().to_str().unwrap(); let tempdir = tempdir.path();
let assert = let assert = Command::new("nu")
Command::new("nu").env("HOME", tempdir).args(&["--commands", &source]).assert().success().stderr(""); .env("HOME", tempdir)
.args(["--commands", &source])
.assert()
.success()
.stderr("");
if opts.hook != InitHook::Pwd { if opts.hook != InitHook::Pwd {
assert.stdout(""); assert.stdout("");
} }
} }
#[rstest] #[apply(opts)]
fn posix_bash( fn posix_bash(cmd: Option<&str>, hook: InitHook, echo: bool, resolve_symlinks: bool) {
#[values(None, Some("z"))] cmd: Option<&str>,
#[values(InitHook::None, InitHook::Prompt, InitHook::Pwd)] hook: InitHook,
#[values(false, true)] echo: bool,
#[values(false, true)] resolve_symlinks: bool,
) {
let opts = Opts { cmd, hook, echo, resolve_symlinks }; let opts = Opts { cmd, hook, echo, resolve_symlinks };
let source = Posix(&opts).render().unwrap(); let source = Posix(&opts).render().unwrap();
let assert = Command::new("bash") 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() .assert()
.success() .success()
.stderr(""); .stderr("");
@ -212,34 +194,25 @@ mod tests {
} }
} }
#[rstest] #[apply(opts)]
fn posix_dash( fn posix_dash(cmd: Option<&str>, hook: InitHook, echo: bool, resolve_symlinks: bool) {
#[values(None, Some("z"))] cmd: Option<&str>,
#[values(InitHook::None, InitHook::Prompt, InitHook::Pwd)] hook: InitHook,
#[values(false, true)] echo: bool,
#[values(false, true)] resolve_symlinks: bool,
) {
let opts = Opts { cmd, hook, echo, resolve_symlinks }; let opts = Opts { cmd, hook, echo, resolve_symlinks };
let source = Posix(&opts).render().unwrap(); let source = Posix(&opts).render().unwrap();
let assert = Command::new("dash").args(&["-c", &source, "-e", "-u"]).assert().success().stderr(""); let assert =
Command::new("dash").args(["-e", "-u", "-c", &source]).assert().success().stderr("");
if opts.hook != InitHook::Pwd { if opts.hook != InitHook::Pwd {
assert.stdout(""); assert.stdout("");
} }
} }
#[rstest] #[apply(opts)]
fn posix_shellcheck_( fn posix_shellcheck(cmd: Option<&str>, hook: InitHook, echo: bool, resolve_symlinks: bool) {
#[values(None, Some("z"))] cmd: Option<&str>,
#[values(InitHook::None, InitHook::Prompt, InitHook::Pwd)] hook: InitHook,
#[values(false, true)] echo: bool,
#[values(false, true)] resolve_symlinks: bool,
) {
let opts = Opts { cmd, hook, echo, resolve_symlinks }; let opts = Opts { cmd, hook, echo, resolve_symlinks };
let source = Posix(&opts).render().unwrap(); let source = Posix(&opts).render().unwrap();
Command::new("shellcheck") Command::new("shellcheck")
.args(&["--enable", "all", "--shell", "sh", "-"]) .args(["--enable=all", "-"])
.write_stdin(source) .write_stdin(source)
.assert() .assert()
.success() .success()
@ -247,19 +220,14 @@ mod tests {
.stderr(""); .stderr("");
} }
#[rstest] #[apply(opts)]
fn posix_shfmt( fn posix_shfmt(cmd: Option<&str>, hook: InitHook, echo: bool, resolve_symlinks: bool) {
#[values(None, Some("z"))] cmd: Option<&str>,
#[values(InitHook::None, InitHook::Prompt, InitHook::Pwd)] hook: InitHook,
#[values(false, true)] echo: bool,
#[values(false, true)] resolve_symlinks: bool,
) {
let opts = Opts { cmd, hook, echo, resolve_symlinks }; let opts = Opts { cmd, hook, echo, resolve_symlinks };
let mut source = Posix(&opts).render().unwrap(); let mut source = Posix(&opts).render().unwrap();
source.push('\n'); source.push('\n');
Command::new("shfmt") Command::new("shfmt")
.args(&["-d", "-s", "-ln", "posix", "-i", "4", "-ci", "-"]) .args(["--diff", "--indent=4", "--language-dialect=posix", "--simplify", "-"])
.write_stdin(source) .write_stdin(source)
.assert() .assert()
.success() .success()
@ -267,73 +235,72 @@ mod tests {
.stderr(""); .stderr("");
} }
#[rstest] #[apply(opts)]
fn powershell_pwsh( fn powershell_pwsh(cmd: Option<&str>, hook: InitHook, echo: bool, resolve_symlinks: bool) {
#[values(None, Some("z"))] cmd: Option<&str>,
#[values(InitHook::None, InitHook::Prompt, InitHook::Pwd)] hook: InitHook,
#[values(false, true)] echo: bool,
#[values(false, true)] resolve_symlinks: bool,
) {
let opts = Opts { cmd, hook, echo, resolve_symlinks }; let opts = Opts { cmd, hook, echo, resolve_symlinks };
let mut source = "Set-StrictMode -Version latest\n".to_string(); let mut source = "Set-StrictMode -Version latest\n".to_string();
Powershell(&opts).render_into(&mut source).unwrap(); Powershell(&opts).render_into(&mut source).unwrap();
Command::new("pwsh") Command::new("pwsh")
.args(&["-NoLogo", "-NonInteractive", "-NoProfile", "-Command", &source]) .args(["-NoLogo", "-NonInteractive", "-NoProfile", "-Command", &source])
.assert() .assert()
.success() .success()
.stdout("") .stdout("")
.stderr(""); .stderr("");
} }
#[rstest] #[apply(opts)]
fn xonsh_black( fn tcsh_tcsh(cmd: Option<&str>, hook: InitHook, echo: bool, resolve_symlinks: bool) {
#[values(None, Some("z"))] cmd: Option<&str>, let opts = Opts { cmd, hook, echo, resolve_symlinks };
#[values(InitHook::None, InitHook::Prompt, InitHook::Pwd)] hook: InitHook, let source = Tcsh(&opts).render().unwrap();
#[values(false, true)] echo: bool,
#[values(false, true)] resolve_symlinks: bool, Command::new("tcsh")
) { .args(["-e", "-f", "-s"])
.write_stdin(source)
.assert()
.success()
.stdout("")
.stderr("");
}
#[apply(opts)]
fn xonsh_black(cmd: Option<&str>, hook: InitHook, echo: bool, resolve_symlinks: bool) {
let opts = Opts { cmd, hook, echo, resolve_symlinks }; let opts = Opts { cmd, hook, echo, resolve_symlinks };
let mut source = Xonsh(&opts).render().unwrap(); let mut source = Xonsh(&opts).render().unwrap();
source.push('\n'); 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("");
} }
#[rstest] #[apply(opts)]
fn xonsh_mypy( fn xonsh_mypy(cmd: Option<&str>, hook: InitHook, echo: bool, resolve_symlinks: bool) {
#[values(None, Some("z"))] cmd: Option<&str>,
#[values(InitHook::None, InitHook::Prompt, InitHook::Pwd)] hook: InitHook,
#[values(false, true)] echo: bool,
#[values(false, true)] resolve_symlinks: bool,
) {
let opts = Opts { cmd, hook, echo, resolve_symlinks }; let opts = Opts { cmd, hook, echo, resolve_symlinks };
let source = Xonsh(&opts).render().unwrap(); 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("");
} }
#[rstest] #[apply(opts)]
fn xonsh_pylint( fn xonsh_pylint(cmd: Option<&str>, hook: InitHook, echo: bool, resolve_symlinks: bool) {
#[values(None, Some("z"))] cmd: Option<&str>,
#[values(InitHook::None, InitHook::Prompt, InitHook::Pwd)] hook: InitHook,
#[values(false, true)] echo: bool,
#[values(false, true)] resolve_symlinks: bool,
) {
let opts = Opts { cmd, hook, echo, resolve_symlinks }; let opts = Opts { cmd, hook, echo, resolve_symlinks };
let mut source = Xonsh(&opts).render().unwrap(); let mut source = Xonsh(&opts).render().unwrap();
source.push('\n'); source.push('\n');
Command::new("pylint").args(&["--from-stdin", "zoxide"]).write_stdin(source).assert().success().stderr(""); Command::new("pylint")
.args(["--from-stdin", "--persistent=n", "zoxide"])
.write_stdin(source)
.assert()
.success()
.stderr("");
} }
#[rstest] #[apply(opts)]
fn xonsh_xonsh( fn xonsh_xonsh(cmd: Option<&str>, hook: InitHook, echo: bool, resolve_symlinks: bool) {
#[values(None, Some("z"))] cmd: Option<&str>,
#[values(InitHook::None, InitHook::Prompt, InitHook::Pwd)] hook: InitHook,
#[values(false, true)] echo: bool,
#[values(false, true)] resolve_symlinks: bool,
) {
let opts = Opts { cmd, hook, echo, resolve_symlinks }; let opts = Opts { cmd, hook, echo, resolve_symlinks };
let source = Xonsh(&opts).render().unwrap(); let source = Xonsh(&opts).render().unwrap();
@ -341,7 +308,7 @@ mod tests {
let tempdir = tempdir.path().to_str().unwrap(); let tempdir = tempdir.path().to_str().unwrap();
Command::new("xonsh") Command::new("xonsh")
.args(&["-c", &source, "--no-rc"]) .args(["-c", &source, "--no-rc"])
.env("HOME", tempdir) .env("HOME", tempdir)
.assert() .assert()
.success() .success()
@ -349,19 +316,14 @@ mod tests {
.stderr(""); .stderr("");
} }
#[rstest] #[apply(opts)]
fn zsh_shellcheck( fn zsh_shellcheck(cmd: Option<&str>, hook: InitHook, echo: bool, resolve_symlinks: bool) {
#[values(None, Some("z"))] cmd: Option<&str>,
#[values(InitHook::None, InitHook::Prompt, InitHook::Pwd)] hook: InitHook,
#[values(false, true)] echo: bool,
#[values(false, true)] resolve_symlinks: bool,
) {
let opts = Opts { cmd, hook, echo, resolve_symlinks }; let opts = Opts { cmd, hook, echo, resolve_symlinks };
let source = Zsh(&opts).render().unwrap(); let source = Zsh(&opts).render().unwrap();
// ShellCheck doesn't support zsh yet: https://github.com/koalaman/shellcheck/issues/809 // ShellCheck doesn't support zsh yet: https://github.com/koalaman/shellcheck/issues/809
Command::new("shellcheck") Command::new("shellcheck")
.args(&["--enable", "all", "--shell", "bash", "-"]) .args(["--enable=all", "-"])
.write_stdin(source) .write_stdin(source)
.assert() .assert()
.success() .success()
@ -369,18 +331,13 @@ mod tests {
.stderr(""); .stderr("");
} }
#[rstest] #[apply(opts)]
fn zsh_zsh( fn zsh_zsh(cmd: Option<&str>, hook: InitHook, echo: bool, resolve_symlinks: bool) {
#[values(None, Some("z"))] cmd: Option<&str>,
#[values(InitHook::None, InitHook::Prompt, InitHook::Pwd)] hook: InitHook,
#[values(false, true)] echo: bool,
#[values(false, true)] resolve_symlinks: bool,
) {
let opts = Opts { cmd, hook, echo, resolve_symlinks }; let opts = Opts { cmd, hook, echo, resolve_symlinks };
let source = Zsh(&opts).render().unwrap(); let source = Zsh(&opts).render().unwrap();
Command::new("zsh") Command::new("zsh")
.args(&["-c", &source, "-e", "-u", "-o", "pipefail", "--no-globalrcs", "--no-rcs"]) .args(["-e", "-u", "-o", "pipefail", "--no-globalrcs", "--no-rcs", "-c", &source])
.assert() .assert()
.success() .success()
.stdout("") .stdout("")

View File

@ -1,13 +1,253 @@
use std::env; use std::ffi::OsStr;
use std::fs::{self, File, OpenOptions};
use std::io::{self, Read, Write};
use std::path::{Component, Path, PathBuf}; use std::path::{Component, Path, PathBuf};
use std::process::{Child, Command, Stdio};
use std::time::SystemTime; use std::time::SystemTime;
use std::{env, mem};
use anyhow::{bail, Context, Result}; #[cfg(windows)]
use anyhow::anyhow;
use anyhow::{Context, Result, bail};
use crate::db::Epoch; use crate::db::{Dir, Epoch};
use crate::error::SilentExit;
pub fn canonicalize<P: AsRef<Path>>(path: &P) -> Result<PathBuf> { pub const SECOND: Epoch = 1;
dunce::canonicalize(path).with_context(|| format!("could not resolve path: {}", path.as_ref().display())) pub const MINUTE: Epoch = 60 * SECOND;
pub const HOUR: Epoch = 60 * MINUTE;
pub const DAY: Epoch = 24 * HOUR;
pub const WEEK: Epoch = 7 * DAY;
pub const MONTH: Epoch = 30 * DAY;
pub struct Fzf(Command);
impl Fzf {
const ERR_FZF_NOT_FOUND: &'static 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 program = which::which("fzf.exe").map_err(|_| anyhow!(Self::ERR_FZF_NOT_FOUND))?;
#[cfg(not(windows))]
let program = "fzf";
// TODO: check version of fzf here.
let mut cmd = Command::new(program);
cmd.args([
// Search mode
"--delimiter=\t",
"--nth=2",
// Scripting
"--read0",
])
.stdin(Stdio::piped())
.stdout(Stdio::piped());
Ok(Fzf(cmd))
}
pub fn 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"),
}
}
}
pub struct FzfChild(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 wait(&mut self) -> Result<String> {
// Drop stdin to prevent deadlock.
mem::drop(self.0.stdin.take());
let mut stdout = self.0.stdout.take().unwrap();
let mut output = String::new();
stdout.read_to_string(&mut output).context("failed to read from 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(130) => bail!(SilentExit { code: 130 }),
Some(128..=254) | None => bail!("fzf was terminated"),
_ => bail!("fzf returned an unknown error"),
}
}
}
/// Similar to [`fs::write`], but atomic (best effort on Windows).
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();
// Create a tmpfile.
let (mut tmp_file, tmp_path) = tmpfile(dir)?;
let result = (|| {
// Write to the tmpfile.
_ = 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 std::os::unix::fs::MetadataExt;
use nix::unistd::{self, Gid, Uid};
let uid = Uid::from_raw(metadata.uid());
let gid = Gid::from_raw(metadata.gid());
_ = unistd::fchown(&tmp_file, Some(uid), Some(gid));
}
// Close and rename the tmpfile.
// In some cases, errors from the last write() are reported only on close().
// Rust ignores errors from close(), since it occurs inside `Drop`. To
// catch these errors, we manually call `File::sync_all()` first.
tmp_file
.sync_all()
.with_context(|| format!("could not sync writes to file: {}", tmp_path.display()))?;
mem::drop(tmp_file);
rename(&tmp_path, path)
})();
// In case of an error, delete the tmpfile.
if result.is_err() {
_ = fs::remove_file(&tmp_path);
}
result
}
/// Atomically create a tmpfile in the given directory.
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();
let mut attempts = 0;
loop {
attempts += 1;
// Generate a random name for the tmpfile.
let mut name = String::with_capacity(TMP_NAME_LEN);
name.push_str("tmp_");
while name.len() < TMP_NAME_LEN {
name.push(fastrand::alphanumeric());
}
let path = dir.join(name);
// 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()));
}
}
}
}
/// 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();
const MAX_ATTEMPTS: usize = if cfg!(windows) { 5 } else { 1 };
let mut attempts = 0;
loop {
match fs::rename(from, to) {
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())
});
}
}
}
}
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> { pub fn current_dir() -> Result<PathBuf> {
@ -15,20 +255,22 @@ pub fn current_dir() -> Result<PathBuf> {
} }
pub fn current_time() -> Result<Epoch> { pub fn current_time() -> Result<Epoch> {
let current_time = let current_time = SystemTime::now()
SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).context("system clock set to invalid time")?.as_secs(); .duration_since(SystemTime::UNIX_EPOCH)
.context("system clock set to invalid time")?
.as_secs();
Ok(current_time) 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(); let path = path.as_ref();
path.to_str().with_context(|| format!("invalid unicode in path: {}", path.display())) 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 /// Returns the absolute version of a path. Like
/// resolve symlinks. /// [`std::path::Path::canonicalize`], but doesn't resolve symlinks.
pub fn resolve_path<P: AsRef<Path>>(path: &P) -> Result<PathBuf> { pub fn resolve_path(path: impl AsRef<Path>) -> Result<PathBuf> {
let path = path.as_ref(); let path = path.as_ref();
let base_path; let base_path;
@ -39,13 +281,15 @@ pub fn resolve_path<P: AsRef<Path>>(path: &P) -> Result<PathBuf> {
if cfg!(windows) { if cfg!(windows) {
use std::path::Prefix; 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 path = path.as_ref();
let mut components = path.components(); let mut components = path.components();
match components.next() { match components.next() {
Some(Component::Prefix(prefix)) => match prefix.kind() { 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,
}, },
_ => None, _ => None,
@ -98,8 +342,9 @@ pub fn resolve_path<P: AsRef<Path>>(path: &P) -> Result<PathBuf> {
components.next(); components.next();
let current_dir = env::current_dir()?; let current_dir = env::current_dir()?;
let drive_letter = get_drive_letter(&current_dir) let drive_letter = get_drive_letter(&current_dir).with_context(|| {
.with_context(|| format!("could not get drive letter: {}", current_dir.display()))?; format!("could not get drive letter: {}", current_dir.display())
})?;
base_path = get_drive_path(drive_letter); base_path = get_drive_path(drive_letter);
stack.extend(base_path.components()); stack.extend(base_path.components());
} }
@ -119,7 +364,7 @@ pub fn resolve_path<P: AsRef<Path>>(path: &P) -> Result<PathBuf> {
for component in components { for component in components {
match component { match component {
Component::Normal(_) => stack.push(component), Component::Normal(_) => stack.push(component),
Component::CurDir => (), Component::CurDir => {}
Component::ParentDir => { Component::ParentDir => {
if stack.last() != Some(&Component::RootDir) { if stack.last() != Some(&Component::RootDir) {
stack.pop(); stack.pop();
@ -133,11 +378,7 @@ pub fn resolve_path<P: AsRef<Path>>(path: &P) -> Result<PathBuf> {
} }
/// Convert a string to lowercase, with a fast path for ASCII strings. /// 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(); let s = s.as_ref();
if s.is_ascii() { if s.is_ascii() { s.to_ascii_lowercase() } else { s.to_lowercase() }
s.to_ascii_lowercase()
} else {
s.to_lowercase()
}
} }

View File

@ -1,13 +1,17 @@
{%- let section = "# =============================================================================\n#" -%} {%- let section = "# =============================================================================\n#" -%}
{%- let not_configured = "# -- not configured --" -%} {%- let not_configured = "# -- not configured --" -%}
# shellcheck shell=bash
{{ section }} {{ section }}
# Utility functions for zoxide. # Utility functions for zoxide.
# #
# pwd based on the value of _ZO_RESOLVE_SYMLINKS. # pwd based on the value of _ZO_RESOLVE_SYMLINKS.
function __zoxide_pwd() { function __zoxide_pwd() {
{%- if resolve_symlinks %} {%- if cfg!(windows) %}
\command cygpath -w "$(\builtin pwd -P)"
{%- else if resolve_symlinks %}
\builtin pwd -P \builtin pwd -P
{%- else %} {%- else %}
\builtin pwd -L \builtin pwd -L
@ -17,33 +21,33 @@ function __zoxide_pwd() {
# cd + custom logic based on the value of _ZO_ECHO. # cd + custom logic based on the value of _ZO_ECHO.
function __zoxide_cd() { function __zoxide_cd() {
# shellcheck disable=SC2164 # shellcheck disable=SC2164
\builtin cd "$@" {%- if echo %} && __zoxide_pwd {%- endif %} \builtin cd -- "$@" {%- if echo %} && __zoxide_pwd {%- endif %}
} }
{{ section }} {{ section }}
# Hook configuration for zoxide. # Hook configuration for zoxide.
# #
{%- if hook != InitHook::None %}
{% if hook == InitHook::None -%}
{{ not_configured }}
{%- else -%}
# Hook to add new entries to the database. # Hook to add new entries to the database.
{%- if hook == InitHook::Prompt %} {%- if hook == InitHook::Prompt %}
function __zoxide_hook() { function __zoxide_hook() {
\builtin local -r retval="$?" \builtin local -r retval="$?"
\builtin command zoxide add -- "$(__zoxide_pwd)" # shellcheck disable=SC2312
\command zoxide add -- "$(__zoxide_pwd)"
return "${retval}" return "${retval}"
} }
{%- else if hook == InitHook::Pwd %} {%- else if hook == InitHook::Pwd %}
__zoxide_oldpwd="$(__zoxide_pwd)" __zoxide_oldpwd="$(__zoxide_pwd)"
function __zoxide_hook() { function __zoxide_hook() {
\builtin local -r retval="$?" \builtin local -r retval="$?"
\builtin local -r pwd_tmp="$(__zoxide_pwd)" \builtin local pwd_tmp
pwd_tmp="$(__zoxide_pwd)"
if [[ ${__zoxide_oldpwd} != "${pwd_tmp}" ]]; then if [[ ${__zoxide_oldpwd} != "${pwd_tmp}" ]]; then
__zoxide_oldpwd="${pwd_tmp}" __zoxide_oldpwd="${pwd_tmp}"
\builtin command zoxide add -- "${__zoxide_oldpwd}" \command zoxide add -- "${__zoxide_oldpwd}"
fi fi
return "${retval}" return "${retval}"
} }
@ -54,88 +58,132 @@ if [[ ${PROMPT_COMMAND:=} != *'__zoxide_hook'* ]]; then
PROMPT_COMMAND="__zoxide_hook;${PROMPT_COMMAND#;}" PROMPT_COMMAND="__zoxide_hook;${PROMPT_COMMAND#;}"
fi fi
{% endif -%} {%- endif %}
# Report common issues.
function __zoxide_doctor() {
{%- if hook == InitHook::None %}
return 0
{%- else %}
[[ ${_ZO_DOCTOR:-1} -eq 0 ]] && return 0
# shellcheck disable=SC2199
[[ ${PROMPT_COMMAND[@]:-} == *'__zoxide_hook'* ]] && return 0
# shellcheck disable=SC2199
[[ ${__vsc_original_prompt_command[@]:-} == *'__zoxide_hook'* ]] && return 0
_ZO_DOCTOR=0
\builtin printf '%s\n' \
'zoxide: detected a possible configuration issue.' \
'Please ensure that zoxide is initialized right at the end of your shell configuration file (usually ~/.bashrc).' \
'' \
'If the issue persists, consider filing an issue at:' \
'https://github.com/ajeetdsouza/zoxide/issues' \
'' \
'Disable this message by setting _ZO_DOCTOR=0.' \
'' >&2
{%- endif %}
}
{{ section }} {{ section }}
# When using zoxide with --no-aliases, alias these internal functions as # When using zoxide with --no-cmd, alias these internal functions as desired.
# desired.
# #
__zoxide_z_prefix='z#' __zoxide_z_prefix='z#'
# Jump to a directory using only keywords. # Jump to a directory using only keywords.
function __zoxide_z() { function __zoxide_z() {
__zoxide_doctor
# shellcheck disable=SC2199
if [[ $# -eq 0 ]]; then if [[ $# -eq 0 ]]; then
__zoxide_cd ~ __zoxide_cd ~
elif [[ $# -eq 1 && $1 == '-' ]]; then elif [[ $# -eq 1 && $1 == '-' ]]; then
__zoxide_cd "${OLDPWD}" __zoxide_cd "${OLDPWD}"
elif [[ $# -eq 1 && -d $1 ]]; then elif [[ $# -eq 1 && -d $1 ]]; then
__zoxide_cd "$1" __zoxide_cd "$1"
elif [[ ${*: -1} == "${__zoxide_z_prefix}"* ]]; then elif [[ $# -eq 2 && $1 == '--' ]]; then
\builtin local result="${*: -1}" __zoxide_cd "$2"
__zoxide_cd "${result:2}" elif [[ ${@: -1} == "${__zoxide_z_prefix}"?* ]]; then
# shellcheck disable=SC2124
\builtin local result="${@: -1}"
__zoxide_cd "{{ "${result:${#__zoxide_z_prefix}}" }}"
else else
\builtin local result \builtin local result
result="$(\builtin command zoxide query --exclude "$(__zoxide_pwd)" -- "$@")" && __zoxide_cd "${result}" # shellcheck disable=SC2312
result="$(\command zoxide query --exclude "$(__zoxide_pwd)" -- "$@")" &&
__zoxide_cd "${result}"
fi fi
} }
# Jump to a directory using interactive search. # Jump to a directory using interactive search.
function __zoxide_zi() { function __zoxide_zi() {
__zoxide_doctor
\builtin local result \builtin local result
result="$(\builtin command zoxide query -i -- "$@")" && __zoxide_cd "${result}" result="$(\command zoxide query --interactive -- "$@")" && __zoxide_cd "${result}"
} }
{{ section }} {{ section }}
# Convenient aliases for zoxide. Disable these using --no-aliases. # Commands for zoxide. Disable these using --no-cmd.
# #
{%- match cmd %} {%- match cmd %}
{%- when Some with (cmd) %} {%- when Some with (cmd) %}
# Remove definitions. \builtin unalias {{cmd}} &>/dev/null || \builtin true
function __zoxide_unset() {
\builtin unset -f "$@" &>/dev/null
\builtin unset -v "$@" &>/dev/null
\builtin unalias "$@" &>/dev/null || \builtin :
}
__zoxide_unset {{cmd}}
function {{cmd}}() { function {{cmd}}() {
__zoxide_z "$@" __zoxide_z "$@"
} }
__zoxide_unset {{cmd}}i \builtin unalias {{cmd}}i &>/dev/null || \builtin true
function {{cmd}}i() { function {{cmd}}i() {
__zoxide_zi "$@" __zoxide_zi "$@"
} }
# Load completions. # Load completions.
# Completions require line editing. Since Bash supports only two modes of line # - Bash 4.4+ is required to use `@Q`.
# editing (`vim` and `emacs`), we check if one of them is enabled. # - Completions require line editing. Since Bash supports only two modes of
if [[ :"${SHELLOPTS}": =~ :(vi|emacs): && ${TERM} != 'dumb' ]]; then # line editing (`vim` and `emacs`), we check if either them is enabled.
# Use `printf '\e[5n'` to redraw line after fzf closes. # - Completions don't work on `dumb` terminals.
\builtin bind '"\e[0n": redraw-current-line' &>/dev/null if [[ ${BASH_VERSINFO[0]:-0} -eq 4 && ${BASH_VERSINFO[1]:-0} -ge 4 || ${BASH_VERSINFO[0]:-0} -ge 5 ]] &&
[[ :"${SHELLOPTS}": =~ :(vi|emacs): && ${TERM} != 'dumb' ]]; then
function _{{cmd}}() { function __zoxide_z_complete_helper() {
READLINE_LINE="{{ cmd }} ${__zoxide_result@Q}"
READLINE_POINT={{ "${#READLINE_LINE}" }}
bind '"\e[0n": accept-line'
\builtin printf '\e[5n' >/dev/tty
}
function __zoxide_z_complete() {
# Only show completions when the cursor is at the end of the line. # Only show completions when the cursor is at the end of the line.
[[ {{ "${#COMP_LINE}" }} -eq ${COMP_POINT} ]] || return [[ {{ "${#COMP_WORDS[@]}" }} -eq $((COMP_CWORD + 1)) ]] || return
# If there is only one argument, use `cd` completions. # If there is only one argument, use `cd` completions.
if [[ {{ "${#COMP_WORDS[@]}" }} -eq 2 ]]; then if [[ {{ "${#COMP_WORDS[@]}" }} -eq 2 ]]; then
\builtin mapfile -t COMPREPLY < <(compgen -A directory -S / -- "${COMP_WORDS[-1]}") \builtin mapfile -t COMPREPLY < <(
\builtin compgen -A directory -- "${COMP_WORDS[-1]}" || \builtin true
)
# If there is a space after the last word, use interactive selection. # If there is a space after the last word, use interactive selection.
elif [[ -z ${COMP_WORDS[-1]} ]]; then elif [[ -z ${COMP_WORDS[-1]} ]]; then
\local result # shellcheck disable=SC2312
result="$( __zoxide_result="$(\command zoxide query --exclude "$(__zoxide_pwd)" --interactive -- "{{ "${COMP_WORDS[@]:1:${#COMP_WORDS[@]}-2}" }}")" && {
_ZO_FZF_OPTS='{{ crate::shell::FZF_COMPLETE_OPTS }}' \ # In case the terminal does not respond to \e[5n or another
\builtin command zoxide query -i -- "${COMP_WORDS[@]:1}" # mechanism steals the response, it is still worth completing
)" && COMPREPLY=("${__zoxide_z_prefix}${result}") # the directory in the command line.
\builtin printf '\e[5n' COMPREPLY=("${__zoxide_z_prefix}${__zoxide_result}/")
# Note: We here call "bind" without prefixing "\builtin" to be
# compatible with frameworks like ble.sh, which emulates Bash's
# builtin "bind".
bind -x '"\e[0n": __zoxide_z_complete_helper'
\builtin printf '\e[5n' >/dev/tty
}
fi fi
} }
\builtin complete -F _{{cmd}} -o nospace -- {{cmd}} \builtin complete -F __zoxide_z_complete -o filenames -- {{cmd}}
\builtin complete -r {{cmd}}i &>/dev/null || \builtin true
fi fi
{%- when None %} {%- when None %}
@ -145,6 +193,6 @@ fi
{%- endmatch %} {%- endmatch %}
{{ section }} {{ section }}
# To initialize zoxide, add this to your configuration (usually ~/.bashrc): # To initialize zoxide, add this to your shell configuration file (usually ~/.bashrc):
# #
# eval "$(zoxide init bash)" # eval "$(zoxide init bash)"

View File

@ -9,7 +9,7 @@ use path
# #
# cd + custom logic based on the value of _ZO_ECHO. # cd + custom logic based on the value of _ZO_ECHO.
fn __zoxide_cd [path]{ fn __zoxide_cd {|path|
builtin:cd $path builtin:cd $path
{%- if echo %} {%- if echo %}
builtin:echo $pwd builtin:echo $pwd
@ -22,7 +22,7 @@ fn __zoxide_cd [path]{
# Initialize hook to track previous directory. # Initialize hook to track previous directory.
var oldpwd = $builtin:pwd var oldpwd = $builtin:pwd
set builtin:before-chdir = [$@builtin:before-chdir [_]{ edit:add-var oldpwd $builtin:pwd }] set builtin:before-chdir = [$@builtin:before-chdir {|_| set oldpwd = $builtin:pwd }]
# Initialize hook to add directories to zoxide. # Initialize hook to add directories to zoxide.
{%- if hook == InitHook::None %} {%- if hook == InitHook::None %}
@ -32,21 +32,20 @@ set builtin:before-chdir = [$@builtin:before-chdir [_]{ edit:add-var oldpwd $bui
if (builtin:not (builtin:eq $E:__zoxide_shlvl $E:SHLVL)) { if (builtin:not (builtin:eq $E:__zoxide_shlvl $E:SHLVL)) {
set E:__zoxide_shlvl = $E:SHLVL set E:__zoxide_shlvl = $E:SHLVL
{%- if hook == InitHook::Prompt %} {%- if hook == InitHook::Prompt %}
set edit:before-readline = [$@edit:before-readline []{ zoxide add -- $pwd }] set edit:before-readline = [$@edit:before-readline {|| zoxide add -- $pwd }]
{%- else if hook == InitHook::Pwd %} {%- else if hook == InitHook::Pwd %}
set builtin:after-chdir = [$@builtin:after-chdir [_]{ zoxide add -- $pwd }] set builtin:after-chdir = [$@builtin:after-chdir {|_| zoxide add -- $pwd }]
{%- endif %} {%- endif %}
} }
{%- endif %} {%- endif %}
{{ section }} {{ section }}
# When using zoxide with --no-aliases, alias these internal functions as # When using zoxide with --no-cmd, alias these internal functions as desired.
# desired.
# #
# Jump to a directory using only keywords. # Jump to a directory using only keywords.
fn __zoxide_z [@rest]{ fn __zoxide_z {|@rest|
if (builtin:eq [] $rest) { if (builtin:eq [] $rest) {
__zoxide_cd ~ __zoxide_cd ~
} elif (builtin:eq [-] $rest) { } elif (builtin:eq [-] $rest) {
@ -57,7 +56,7 @@ fn __zoxide_z [@rest]{
var path var path
try { try {
set path = (zoxide query --exclude $pwd -- $@rest) set path = (zoxide query --exclude $pwd -- $@rest)
} except { } catch {
} else { } else {
__zoxide_cd $path __zoxide_cd $path
} }
@ -66,11 +65,11 @@ fn __zoxide_z [@rest]{
edit:add-var __zoxide_z~ $__zoxide_z~ edit:add-var __zoxide_z~ $__zoxide_z~
# Jump to a directory using interactive search. # Jump to a directory using interactive search.
fn __zoxide_zi [@rest]{ fn __zoxide_zi {|@rest|
var path var path
try { try {
set path = (zoxide query -i -- $@rest) set path = (zoxide query --interactive -- $@rest)
} except { } catch {
} else { } else {
__zoxide_cd $path __zoxide_cd $path
} }
@ -78,7 +77,7 @@ fn __zoxide_zi [@rest]{
edit:add-var __zoxide_zi~ $__zoxide_zi~ edit:add-var __zoxide_zi~ $__zoxide_zi~
{{ section }} {{ section }}
# Convenient aliases for zoxide. Disable these using --no-aliases. # Commands for zoxide. Disable these using --no-cmd.
# #
{%- match cmd %} {%- match cmd %}
@ -88,14 +87,16 @@ edit:add-var {{cmd}}~ $__zoxide_z~
edit:add-var {{cmd}}i~ $__zoxide_zi~ edit:add-var {{cmd}}i~ $__zoxide_zi~
# Load completions. # 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
fn __zoxide_z_complete [@rest]{ a completion if the current token is a prefix of it.
#}
fn __zoxide_z_complete {|@rest|
if (!= (builtin:count $rest) 2) { if (!= (builtin:count $rest) 2) {
builtin:return builtin:return
} }
edit:complete-filename $rest[1] | edit:complete-filename $rest[1] |
builtin:each [completion]{ builtin:each {|completion|
var dir = $completion[stem] var dir = $completion[stem]
if (path:is-dir $dir) { if (path:is-dir $dir) {
builtin:put $dir builtin:put $dir
@ -116,4 +117,4 @@ set edit:completion:arg-completer[{{cmd}}] = $__zoxide_z_complete~
# #
# eval (zoxide init elvish | slurp) # eval (zoxide init elvish | slurp)
# #
# Note: zoxide only supports elvish v0.16.0 and above. # Note: zoxide only supports elvish v0.18.0 and above.

View File

@ -7,7 +7,9 @@
# pwd based on the value of _ZO_RESOLVE_SYMLINKS. # pwd based on the value of _ZO_RESOLVE_SYMLINKS.
function __zoxide_pwd function __zoxide_pwd
{%- if resolve_symlinks %} {%- if cfg!(windows) %}
command cygpath -w (builtin pwd -P)
{%- else if resolve_symlinks %}
builtin pwd -P builtin pwd -P
{%- else %} {%- else %}
builtin pwd -L builtin pwd -L
@ -16,17 +18,23 @@ end
# A copy of fish's internal cd function. This makes it possible to use # A copy of fish's internal cd function. This makes it possible to use
# `alias cd=z` without causing an infinite loop. # `alias cd=z` without causing an infinite loop.
if ! builtin functions -q __zoxide_cd_internal if ! builtin functions --query __zoxide_cd_internal
if builtin functions -q cd string replace --regex -- '^function cd\s' 'function __zoxide_cd_internal ' <$__fish_data_dir/functions/cd.fish | source
builtin functions -c cd __zoxide_cd_internal
else
alias __zoxide_cd_internal='builtin cd'
end
end end
# cd + custom logic based on the value of _ZO_ECHO. # cd + custom logic based on the value of _ZO_ECHO.
function __zoxide_cd function __zoxide_cd
__zoxide_cd_internal $argv if set -q __zoxide_loop
builtin echo "zoxide: infinite loop detected"
builtin echo "Avoid aliasing `cd` to `z` directly, use `zoxide init --cmd=cd fish` instead"
return 1
end
{%- if cfg!(windows) %}
__zoxide_loop=1 __zoxide_cd_internal (cygpath -u $argv)
{%- else %}
__zoxide_loop=1 __zoxide_cd_internal $argv
{%- endif %}
{%- if echo %} {%- if echo %}
and __zoxide_pwd and __zoxide_pwd
{%- endif %} {%- endif %}
@ -53,68 +61,61 @@ end
{%- endif %} {%- endif %}
{{ section }} {{ section }}
# When using zoxide with --no-aliases, alias these internal functions as # When using zoxide with --no-cmd, alias these internal functions as desired.
# desired.
# #
# Jump to a directory using only keywords. # Jump to a directory using only keywords.
function __zoxide_z function __zoxide_z
set argc (count $argv) set -l argc (builtin count $argv)
if test $argc -eq 0 if test $argc -eq 0
__zoxide_cd $HOME __zoxide_cd $HOME
else if test "$argv" = - else if test "$argv" = -
__zoxide_cd - __zoxide_cd -
else if test $argc -eq 1 -a -d $argv[1] else if test $argc -eq 1 -a -d $argv[1]
__zoxide_cd $argv[1] __zoxide_cd $argv[1]
else if test $argc -eq 2 -a $argv[1] = --
__zoxide_cd -- $argv[2]
else else
set -l result (command zoxide query --exclude (__zoxide_pwd) -- $argv) set -l result (command zoxide query --exclude (__zoxide_pwd) -- $argv)
and __zoxide_cd $result and __zoxide_cd $result
end end
end end
# Completions for `z`. # Completions.
function __zoxide_z_complete function __zoxide_z_complete
set -l tokens (commandline -op) set -l tokens (builtin commandline --current-process --tokenize)
set -l curr_tokens (commandline -cop) set -l curr_tokens (builtin commandline --cut-at-cursor --current-process --tokenize)
if test (count $tokens) -le 2 -a (count $curr_tokens) -eq 1 if test (builtin count $tokens) -le 2 -a (builtin count $curr_tokens) -eq 1
# If there is only one argument, use `cd` completions. # If there are < 2 arguments, use `cd` completions.
__fish_complete_directories "$tokens[2]" '' complete --do-complete "'' "(builtin commandline --cut-at-cursor --current-token) | string match --regex -- '.*/$'
else else if test (builtin count $tokens) -eq (builtin count $curr_tokens)
# Otherwise, use interactive selection. # If the last argument is empty, use interactive selection.
set -l query $tokens[2..-1] set -l query $tokens[2..-1]
set -l result (_ZO_FZF_OPTS='{{ crate::shell::FZF_COMPLETE_OPTS }}' zoxide query -i -- $query) set -l result (command zoxide query --exclude (__zoxide_pwd) --interactive -- $query)
and commandline -p "$tokens[1] "(string escape $result) and __zoxide_cd $result
commandline -f repaint and builtin commandline --function cancel-commandline repaint
end end
end end
complete --command __zoxide_z --no-files --arguments '(__zoxide_z_complete)'
# Jump to a directory using interactive search. # Jump to a directory using interactive search.
function __zoxide_zi function __zoxide_zi
set -l result (command zoxide query -i -- $argv) set -l result (command zoxide query --interactive -- $argv)
and __zoxide_cd $result and __zoxide_cd $result
end end
{{ section }} {{ section }}
# Convenient aliases for zoxide. Disable these using --no-aliases. # Commands for zoxide. Disable these using --no-cmd.
# #
{%- match cmd %} {%- match cmd %}
{%- when Some with (cmd) %} {%- when Some with (cmd) %}
# Remove definitions. abbr --erase {{cmd}} &>/dev/null
function __zoxide_unset
set --erase $argv >/dev/null 2>&1
abbr --erase $argv >/dev/null 2>&1
builtin functions --erase $argv >/dev/null 2>&1
end
__zoxide_unset {{cmd}}
alias {{cmd}}=__zoxide_z alias {{cmd}}=__zoxide_z
complete -c {{cmd}} -e
complete -c {{cmd}} -f -a '(__zoxide_z_complete)'
__zoxide_unset {{cmd}}i abbr --erase {{cmd}}i &>/dev/null
alias {{cmd}}i=__zoxide_zi alias {{cmd}}i=__zoxide_zi
{%- when None %} {%- when None %}
@ -127,4 +128,4 @@ alias {{cmd}}i=__zoxide_zi
# To initialize zoxide, add this to your configuration (usually # To initialize zoxide, add this to your configuration (usually
# ~/.config/fish/config.fish): # ~/.config/fish/config.fish):
# #
# zoxide init fish | source # zoxide init fish | source

View File

@ -3,90 +3,84 @@
# Code generated by zoxide. DO NOT EDIT. # Code generated by zoxide. DO NOT EDIT.
{{ section }}
# Utility functions for zoxide.
#
# Default prompt for Nushell.
def __zoxide_prompt [] {
let git = $'(do -i {git rev-parse --abbrev-ref HEAD} | str trim -rc (char newline))'
let git = (if ($git | str length) == 0 { '' } {
build-string (char lparen) (ansi cb) $git (ansi reset) (char rparen)
})
build-string (ansi gb) (pwd) (ansi reset) $git '> '
}
{{ section }} {{ section }}
# Hook configuration for zoxide. # Hook configuration for zoxide.
# #
# Hook to add new entries to the database. {% if hook == InitHook::None -%}
{%- match hook %}
{%- when InitHook::None %}
{{ not_configured }} {{ not_configured }}
{%- when InitHook::Prompt %} {%- else -%}
def __zoxide_hook [] { # Initialize hook to add new entries to the database.
shells | where active == $true && name == filesystem | get path | each { export-env {
zoxide add -- $it {%- if hook == InitHook::Prompt %}
} $env.config = (
$env.config?
| default {}
| upsert hooks { default {} }
| upsert hooks.pre_prompt { default [] }
)
let __zoxide_hooked = (
$env.config.hooks.pre_prompt | any { try { get __zoxide_hook } catch { false } }
)
if not $__zoxide_hooked {
$env.config.hooks.pre_prompt = ($env.config.hooks.pre_prompt | append {
__zoxide_hook: true,
code: {|| ^zoxide add -- $env.PWD}
})
}
{%- else if hook == InitHook::Pwd %}
$env.config = (
$env.config?
| default {}
| upsert hooks { default {} }
| upsert hooks.env_change { default {} }
| upsert hooks.env_change.PWD { default [] }
)
let __zoxide_hooked = (
$env.config.hooks.env_change.PWD | any { try { get __zoxide_hook } catch { false } }
)
if not $__zoxide_hooked {
$env.config.hooks.env_change.PWD = ($env.config.hooks.env_change.PWD | append {
__zoxide_hook: true,
code: {|_, dir| ^zoxide add -- $dir}
})
}
{%- endif %}
} }
# Initialize hook. {%- endif %}
let-env PROMPT_COMMAND = (
let prompt = (if ($nu.env | select PROMPT_COMMAND | empty?) {
if ($nu.config | select prompt | empty?) { '__zoxide_prompt' } { $nu.config.prompt }
} { $nu.env.PROMPT_COMMAND });
if ($prompt | str contains '__zoxide_hook') { $prompt } { $'__zoxide_hook;($prompt)' }
)
{%- when InitHook::Pwd %}
$'zoxide: PWD hooks are not supported on Nushell.(char nl)Use (char sq)zoxide init nushell --hook prompt(char sq) instead.(char nl)'
{%- endmatch %}
{{ section }} {{ section }}
# When using zoxide with --no-aliases, alias these internal functions as # When using zoxide with --no-cmd, alias these internal functions as desired.
# desired.
# #
# Jump to a directory using only keywords. # Jump to a directory using only keywords.
def __zoxide_z [...rest:string] { def --env --wrapped __zoxide_z [...rest: string] {
if (shells | where active == $true | get name) != filesystem { let path = match $rest {
if ($rest | length) > 1 { [] => {'~'},
$'zoxide: can only jump directories on filesystem(char nl)' [ '-' ] => {'-'},
} { [ $arg ] if ($arg | path expand | path type) == 'dir' => {$arg}
cd $rest _ => {
{%- if echo %} ^zoxide query --exclude $env.PWD -- ...$rest | str trim -r -c "\n"
pwd
{%- endif %}
}
} {
let arg0 = ($rest | append '~' | first 1);
if ($rest | length) <= 1 && ($arg0 == '-' || ($arg0 | path expand | path exists)) {
cd $arg0
} {
cd $'(zoxide query --exclude (pwd) -- $rest | str trim -rc (char newline))'
}
{%- if echo %}
pwd
{%- endif %}
} }
}
cd $path
{%- if echo %}
echo $env.PWD
{%- endif %}
} }
# Jump to a directory using interactive search. # Jump to a directory using interactive search.
def __zoxide_zi [...rest:string] { def --env --wrapped __zoxide_zi [...rest:string] {
if (shells | where active == $true | get name) != filesystem { cd $'(^zoxide query --interactive -- ...$rest | str trim -r -c "\n")'
$'zoxide: can only jump directories on filesystem(char nl)'
} {
cd $'(zoxide query -i -- $rest | str trim -rc (char newline))'
{%- if echo %} {%- if echo %}
pwd echo $env.PWD
{%- endif %} {%- endif %}
}
} }
{{ section }} {{ section }}
# Convenient aliases for zoxide. Disable these using --no-aliases. # Commands for zoxide. Disable these using --no-cmd.
# #
{%- match cmd %} {%- match cmd %}
@ -102,9 +96,13 @@ alias {{cmd}}i = __zoxide_zi
{%- endmatch %} {%- endmatch %}
{{ section }} {{ section }}
# To initialize zoxide, add this to your configuration (find it by running # Add this to your env file (find it by running `$nu.env-path` in Nushell):
# `config path` in Nushell):
# #
# startup = ['zoxide init nushell --hook prompt | save ~/.zoxide.nu', 'source ~/.zoxide.nu'] # zoxide init nushell | save -f ~/.zoxide.nu
# #
# Note: zoxide only supports Nushell v0.37.0 and above. # 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.89.0+.

View File

@ -1,23 +1,27 @@
{%- let section = "# =============================================================================\n#" -%} {%- let section = "# =============================================================================\n#" -%}
{%- let not_configured = "# -- not configured --" -%} {%- let not_configured = "# -- not configured --" -%}
# shellcheck shell=sh
{{ section }} {{ section }}
# Utility functions for zoxide. # Utility functions for zoxide.
# #
# pwd based on the value of _ZO_RESOLVE_SYMLINKS. # pwd based on the value of _ZO_RESOLVE_SYMLINKS.
__zoxide_pwd() { __zoxide_pwd() {
{%- if resolve_symlinks %} {%- if cfg!(windows) %}
\pwd -P \command cygpath -w "$(\builtin pwd -P)"
{%- else if resolve_symlinks %}
\command pwd -P
{%- else %} {%- else %}
\pwd -L \command pwd -L
{%- endif %} {%- endif %}
} }
# cd + custom logic based on the value of _ZO_ECHO. # cd + custom logic based on the value of _ZO_ECHO.
__zoxide_cd() { __zoxide_cd() {
# shellcheck disable=SC2164 # shellcheck disable=SC2164
\cd "$@" {%- if echo %} && __zoxide_pwd {%- endif %} \command cd "$@" {%- if echo %} && __zoxide_pwd {%- endif %}
} }
{{ section }} {{ section }}
@ -31,7 +35,7 @@ __zoxide_cd() {
{%- when InitHook::Prompt -%} {%- when InitHook::Prompt -%}
# Hook to add new entries to the database. # Hook to add new entries to the database.
__zoxide_hook() { __zoxide_hook() {
zoxide add -- "$(__zoxide_pwd)" \command zoxide add -- "$(__zoxide_pwd || \builtin true)"
} }
# Initialize hook. # Initialize hook.
@ -39,20 +43,45 @@ if [ "${PS1:=}" = "${PS1#*\$(__zoxide_hook)}" ]; then
PS1="${PS1}\$(__zoxide_hook)" PS1="${PS1}\$(__zoxide_hook)"
fi fi
# Report common issues.
__zoxide_doctor() {
{%- if hook != InitHook::Prompt %}
return 0
{%- else %}
[ "${_ZO_DOCTOR:-1}" -eq 0 ] && return 0
case "${PS1:-}" in
*__zoxide_hook*) return 0 ;;
*) ;;
esac
_ZO_DOCTOR=0
\command printf '%s\n' \
'zoxide: detected a possible configuration issue.' \
'Please ensure that zoxide is initialized right at the end of your shell configuration file.' \
'' \
'If the issue persists, consider filing an issue at:' \
'https://github.com/ajeetdsouza/zoxide/issues' \
'' \
'Disable this message by setting _ZO_DOCTOR=0.' \
'' >&2
{%- endif %}
}
{%- when InitHook::Pwd -%} {%- when InitHook::Pwd -%}
\printf "%s\n%s\n" \ \command printf "%s\n%s\n" \
"zoxide: PWD hooks are not supported on POSIX shells." \ "zoxide: PWD hooks are not supported on POSIX shells." \
" Use 'zoxide init posix --hook prompt' instead." " Use 'zoxide init posix --hook prompt' instead."
{%- endmatch %} {%- endmatch %}
{{ section }} {{ section }}
# When using zoxide with --no-aliases, alias these internal functions as # When using zoxide with --no-cmd, alias these internal functions as desired.
# desired.
# #
# Jump to a directory using only keywords. # Jump to a directory using only keywords.
__zoxide_z() { __zoxide_z() {
__zoxide_doctor
if [ "$#" -eq 0 ]; then if [ "$#" -eq 0 ]; then
__zoxide_cd ~ __zoxide_cd ~
elif [ "$#" -eq 1 ] && [ "$1" = '-' ]; then elif [ "$#" -eq 1 ] && [ "$1" = '-' ]; then
@ -60,42 +89,36 @@ __zoxide_z() {
__zoxide_cd "${OLDPWD}" __zoxide_cd "${OLDPWD}"
else else
# shellcheck disable=SC2016 # shellcheck disable=SC2016
\printf 'zoxide: $OLDPWD is not set' \command printf 'zoxide: $OLDPWD is not set'
return 1 return 1
fi fi
elif [ "$#" -eq 1 ] && [ -d "$1" ]; then elif [ "$#" -eq 1 ] && [ -d "$1" ]; then
__zoxide_cd "$1" __zoxide_cd "$1"
else else
__zoxide_result="$(zoxide query --exclude "$(__zoxide_pwd)" -- "$@")" && __zoxide_cd "${__zoxide_result}" __zoxide_result="$(\command zoxide query --exclude "$(__zoxide_pwd || \builtin true)" -- "$@")" &&
__zoxide_cd "${__zoxide_result}"
fi fi
} }
# Jump to a directory using interactive search. # Jump to a directory using interactive search.
__zoxide_zi() { __zoxide_zi() {
__zoxide_result="$(zoxide query -i -- "$@")" && __zoxide_cd "${__zoxide_result}" __zoxide_doctor
__zoxide_result="$(\command zoxide query --interactive -- "$@")" && __zoxide_cd "${__zoxide_result}"
} }
{{ section }} {{ section }}
# Convenient aliases for zoxide. Disable these using --no-aliases. # Commands for zoxide. Disable these using --no-cmd.
# #
{%- match cmd %} {%- match cmd %}
{%- when Some with (cmd) %} {%- when Some with (cmd) %}
# Remove definitions. \command unalias {{cmd}} >/dev/null 2>&1 || \true
__zoxide_unset() {
\unset -f "$@" >/dev/null 2>&1
\unset -v "$@" >/dev/null 2>&1
# shellcheck disable=SC1001
\unalias "$@" >/dev/null 2>&1 || \:
}
__zoxide_unset '{{cmd}}'
{{cmd}}() { {{cmd}}() {
__zoxide_z "$@" __zoxide_z "$@"
} }
__zoxide_unset '{{cmd}}i' \command unalias {{cmd}}i >/dev/null 2>&1 || \true
{{cmd}}i() { {{cmd}}i() {
__zoxide_zi "$@" __zoxide_zi "$@"
} }

View File

@ -5,19 +5,43 @@
# Utility functions for zoxide. # 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. # pwd based on zoxide's format.
function __zoxide_pwd { function global:__zoxide_pwd {
$__zoxide_pwd = Get-Location $cwd = Get-Location
if ($__zoxide_pwd.Provider.Name -eq "FileSystem") { if ($cwd.Provider.Name -eq "FileSystem") {
$__zoxide_pwd.ProviderPath $cwd.ProviderPath
} }
} }
# cd + custom logic based on the value of _ZO_ECHO. # cd + custom logic based on the value of _ZO_ECHO.
function __zoxide_cd($dir) { function global:__zoxide_cd($dir, $literal) {
Set-Location $dir -ea Stop $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 %} {%- if echo %}
$(Get-Location).Path Write-Output $dir.Path
{%- endif %} {%- endif %}
} }
@ -25,90 +49,100 @@ function __zoxide_cd($dir) {
# Hook configuration for zoxide. # 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. # Hook to add new entries to the database.
function __zoxide_hook { function global:__zoxide_hook {
$result = __zoxide_pwd $result = __zoxide_pwd
if ($null -ne $result) { if ($null -ne $result) {
zoxide add -- $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 hook.
{# Initialize $__zoxide_hooked if it does not exist. Removing this will cause $global:__zoxide_hooked = (Get-Variable __zoxide_hooked -ErrorAction Ignore -ValueOnly)
# an unset variable error in StrictMode. #} if ($global:__zoxide_hooked -ne 1) {
$__zoxide_hooked = (Get-Variable __zoxide_hooked -ValueOnly -ErrorAction SilentlyContinue) $global:__zoxide_hooked = 1
if ($__zoxide_hooked -ne 1) { $global:__zoxide_prompt_old = $function:prompt
$__zoxide_hooked = 1
{%- match hook %} function global:prompt {
{%- when InitHook::None %} if ($null -ne $__zoxide_prompt_old) {
{{ not_configured }} & $__zoxide_prompt_old
{%- when InitHook::Prompt %}
$prompt_old = $function:prompt
function prompt {
$null = __zoxide_hook
& $prompt_old
}
{%- when InitHook::Pwd %}
if ($PSVersionTable.PSVersion.Major -ge 6) {
$ExecutionContext.InvokeCommand.LocationChangedAction = {
$null = __zoxide_hook
} }
$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 }} {{ section }}
# When using zoxide with --no-aliases, alias these internal functions as # When using zoxide with --no-cmd, alias these internal functions as desired.
# desired.
# #
# Jump to a directory using only keywords. # Jump to a directory using only keywords.
function __zoxide_z { function global:__zoxide_z {
if ($args.Length -eq 0) { if ($args.Length -eq 0) {
__zoxide_cd ~ __zoxide_cd ~ $true
} }
elseif ($args.Length -eq 1 -and $args[0] -eq '-') { elseif ($args.Length -eq 1 -and ($args[0] -eq '-' -or $args[0] -eq '+')) {
__zoxide_cd - __zoxide_cd $args[0] $false
} }
elseif ($args.Length -eq 1 -and (Test-Path $args[0] -PathType Container)) { elseif ($args.Length -eq 1 -and (Test-Path -PathType Container -LiteralPath $args[0])) {
__zoxide_cd $args[0] __zoxide_cd $args[0] $true
}
elseif ($args.Length -eq 1 -and (Test-Path -PathType Container -Path $args[0] )) {
__zoxide_cd $args[0] $false
} }
else { else {
$result = __zoxide_pwd $result = __zoxide_pwd
if ($null -ne $result) { if ($null -ne $result) {
$result = zoxide query --exclude $result -- @args $result = __zoxide_bin query --exclude $result "--" @args
} }
else { else {
$result = zoxide query -- @args $result = __zoxide_bin query "--" @args
} }
if ($LASTEXITCODE -eq 0) { if ($LASTEXITCODE -eq 0) {
__zoxide_cd $result __zoxide_cd $result $true
} }
} }
} }
# Jump to a directory using interactive search. # Jump to a directory using interactive search.
function __zoxide_zi { function global:__zoxide_zi {
$result = zoxide query -i -- @args $result = __zoxide_bin query -i "--" @args
if ($LASTEXITCODE -eq 0) { if ($LASTEXITCODE -eq 0) {
__zoxide_cd $result __zoxide_cd $result $true
} }
} }
{{ section }} {{ section }}
# Convenient aliases for zoxide. Disable these using --no-aliases. # Commands for zoxide. Disable these using --no-cmd.
# #
{%- match cmd %} {%- match cmd %}
{%- when Some with (cmd) %} {%- when Some with (cmd) %}
Set-Alias -Name {{cmd}} -Value __zoxide_z -Option AllScope Set-Alias -Name {{cmd}} -Value __zoxide_z -Option AllScope -Scope Global -Force
Set-Alias -Name {{cmd}}i -Value __zoxide_zi -Option AllScope Set-Alias -Name {{cmd}}i -Value __zoxide_zi -Option AllScope -Scope Global -Force
{%- when None %} {%- when None %}
@ -120,4 +154,4 @@ Set-Alias -Name {{cmd}}i -Value __zoxide_zi -Option AllScope
# To initialize zoxide, add this to your configuration (find it by running # To initialize zoxide, add this to your configuration (find it by running
# `echo $profile` in PowerShell): # `echo $profile` in PowerShell):
# #
# Invoke-Expression (& { $hook = if ($PSVersionTable.PSVersion.Major -ge 6) { 'pwd' } else { 'prompt' } (zoxide init powershell --hook $hook) -join "`n" }) # Invoke-Expression (& { (zoxide init powershell | Out-String) })

74
templates/tcsh.txt Normal file
View File

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

View File

@ -26,7 +26,7 @@ def __zoxide_bin() -> str:
return zoxide return zoxide
def __zoxide_env() -> typing.Dict[str, str]: def __zoxide_env() -> dict[str, str]:
"""Returns the current environment.""" """Returns the current environment."""
return builtins.__xonsh__.env.detype() # type: ignore # pylint:disable=no-member return builtins.__xonsh__.env.detype() # type: ignore # pylint:disable=no-member
@ -38,22 +38,22 @@ def __zoxide_pwd() -> str:
{%- else %} {%- else %}
pwd = __zoxide_env().get("PWD") pwd = __zoxide_env().get("PWD")
if pwd is None: if pwd is None:
raise Exception("$PWD not found") raise RuntimeError("$PWD not found")
{%- endif %} {%- endif %}
return pwd return pwd
def __zoxide_cd(path: typing.Optional[typing.AnyStr] = None) -> None: def __zoxide_cd(path: str | bytes | None = None) -> None:
"""cd + custom logic based on the value of _ZO_ECHO.""" """cd + custom logic based on the value of _ZO_ECHO."""
if path is None: if path is None:
args = [] args = []
elif isinstance(path, bytes): elif isinstance(path, bytes):
args = [path.decode("utf-8")] args = [path.decode("utf-8")]
elif isinstance(path, str): else:
args = [path] args = [path]
_, exc, _ = xonsh.dirstack.cd(args) _, exc, _ = xonsh.dirstack.cd(args)
if exc is not None: if exc is not None:
raise Exception(exc) raise RuntimeError(exc)
{%- if echo %} {%- if echo %}
print(__zoxide_pwd()) print(__zoxide_pwd())
{%- endif %} {%- endif %}
@ -64,11 +64,11 @@ class ZoxideSilentException(Exception):
def __zoxide_errhandler( def __zoxide_errhandler(
func: typing.Callable[[typing.List[str]], None] func: typing.Callable[[list[str]], None],
) -> typing.Callable[[typing.List[str]], int]: ) -> typing.Callable[[list[str]], int]:
"""Print exception and exit with error code 1.""" """Print exception and exit with error code 1."""
def wrapper(args: typing.List[str]) -> int: def wrapper(args: list[str]) -> int:
try: try:
func(args) func(args)
return 0 return 0
@ -105,18 +105,15 @@ if "__zoxide_hook" not in globals():
check=False, check=False,
env=__zoxide_env(), env=__zoxide_env(),
) )
{% endif %}
{% endif -%}
{{ section }} {{ section }}
# When using zoxide with --no-aliases, alias these internal functions as # When using zoxide with --no-cmd, alias these internal functions as desired.
# desired.
# #
@__zoxide_errhandler @__zoxide_errhandler
def __zoxide_z(args: typing.List[str]) -> None: def __zoxide_z(args: list[str]) -> None:
"""Jump to a directory using only keywords.""" """Jump to a directory using only keywords."""
if args == []: if args == []:
__zoxide_cd() __zoxide_cd()
@ -141,7 +138,7 @@ def __zoxide_z(args: typing.List[str]) -> None:
@__zoxide_errhandler @__zoxide_errhandler
def __zoxide_zi(args: typing.List[str]) -> None: def __zoxide_zi(args: list[str]) -> None:
"""Jump to a directory using interactive search.""" """Jump to a directory using interactive search."""
try: try:
zoxide = __zoxide_bin() zoxide = __zoxide_bin()
@ -159,7 +156,7 @@ def __zoxide_zi(args: typing.List[str]) -> None:
{{ section }} {{ section }}
# Convenient aliases for zoxide. Disable these using --no-aliases. # Commands for zoxide. Disable these using --no-cmd.
# #
{%- match cmd %} {%- match cmd %}

View File

@ -1,13 +1,17 @@
{%- let section = "# =============================================================================\n#" -%} {%- let section = "# =============================================================================\n#" -%}
{%- let not_configured = "# -- not configured --" -%} {%- let not_configured = "# -- not configured --" -%}
# shellcheck shell=bash
{{ section }} {{ section }}
# Utility functions for zoxide. # Utility functions for zoxide.
# #
# pwd based on the value of _ZO_RESOLVE_SYMLINKS. # pwd based on the value of _ZO_RESOLVE_SYMLINKS.
function __zoxide_pwd() { function __zoxide_pwd() {
{%- if resolve_symlinks %} {%- if cfg!(windows) %}
\command cygpath -w "$(\builtin pwd -P)"
{%- else if resolve_symlinks %}
\builtin pwd -P \builtin pwd -P
{%- else %} {%- else %}
\builtin pwd -L \builtin pwd -L
@ -17,87 +21,99 @@ function __zoxide_pwd() {
# cd + custom logic based on the value of _ZO_ECHO. # cd + custom logic based on the value of _ZO_ECHO.
function __zoxide_cd() { function __zoxide_cd() {
# shellcheck disable=SC2164 # shellcheck disable=SC2164
\builtin cd "$@" {%- if echo %} && __zoxide_pwd {%- endif %} \builtin cd -- "$@" {%- if echo %} && __zoxide_pwd {%- endif %}
} }
{{ section }} {{ section }}
# Hook configuration for zoxide. # Hook configuration for zoxide.
# #
{% if hook == InitHook::None -%}
{{ not_configured -}}
{% else -%}
# Hook to add new entries to the database. # Hook to add new entries to the database.
function __zoxide_hook() { function __zoxide_hook() {
zoxide add -- "$(__zoxide_pwd)" # shellcheck disable=SC2312
\command zoxide add -- "$(__zoxide_pwd)"
} }
# Initialize hook. # Initialize hook.
# shellcheck disable=SC2154 \builtin typeset -ga precmd_functions
if [[ ${precmd_functions[(Ie)__zoxide_hook]} -eq 0 ]] && [[ ${chpwd_functions[(Ie)__zoxide_hook]} -eq 0 ]]; then \builtin typeset -ga chpwd_functions
{%- if hook == InitHook::Prompt %} # shellcheck disable=SC2034,SC2296
precmd_functions+=(__zoxide_hook) precmd_functions=("${(@)precmd_functions:#__zoxide_hook}")
{%- else if hook == InitHook::Pwd %} # shellcheck disable=SC2034,SC2296
chpwd_functions=("${chpwd_functions[@]}" "__zoxide_hook") chpwd_functions=("${(@)chpwd_functions:#__zoxide_hook}")
{%- endif %}
fi
{%- if hook == InitHook::Prompt %}
precmd_functions+=(__zoxide_hook)
{%- else if hook == InitHook::Pwd %}
chpwd_functions+=(__zoxide_hook)
{%- endif %} {%- endif %}
# Report common issues.
function __zoxide_doctor() {
{%- if hook == InitHook::None %}
return 0
{%- else %}
[[ ${_ZO_DOCTOR:-1} -ne 0 ]] || return 0
{%- if hook == InitHook::Prompt %}
[[ ${precmd_functions[(Ie)__zoxide_hook]:-} -eq 0 ]] || return 0
{%- else if hook == InitHook::Pwd %}
[[ ${chpwd_functions[(Ie)__zoxide_hook]:-} -eq 0 ]] || return 0
{%- endif %}
_ZO_DOCTOR=0
\builtin printf '%s\n' \
'zoxide: detected a possible configuration issue.' \
'Please ensure that zoxide is initialized right at the end of your shell configuration file (usually ~/.zshrc).' \
'' \
'If the issue persists, consider filing an issue at:' \
'https://github.com/ajeetdsouza/zoxide/issues' \
'' \
'Disable this message by setting _ZO_DOCTOR=0.' \
'' >&2
{%- endif %}
}
{{ section }} {{ section }}
# When using zoxide with --no-aliases, alias these internal functions as # When using zoxide with --no-cmd, alias these internal functions as desired.
# desired.
# #
# Jump to a directory using only keywords. # Jump to a directory using only keywords.
function __zoxide_z() { function __zoxide_z() {
if [ "$#" -eq 0 ]; then __zoxide_doctor
if [[ "$#" -eq 0 ]]; then
__zoxide_cd ~ __zoxide_cd ~
elif [ "$#" -eq 1 ] && [ "$1" = '-' ]; then elif [[ "$#" -eq 1 ]] && { [[ -d "$1" ]] || [[ "$1" = '-' ]] || [[ "$1" =~ ^[-+][0-9]+$ ]]; }; then
if [ -n "${OLDPWD}" ]; then
__zoxide_cd "${OLDPWD}"
else
# shellcheck disable=SC2016
\builtin printf 'zoxide: $OLDPWD is not set'
return 1
fi
elif [ "$#" -eq 1 ] && [ -d "$1" ]; then
__zoxide_cd "$1" __zoxide_cd "$1"
elif [[ "$#" -eq 2 ]] && [[ "$1" = "--" ]]; then
__zoxide_cd "$2"
else else
\builtin local result \builtin local result
result="$(zoxide query --exclude "$(__zoxide_pwd)" -- "$@")" \ # shellcheck disable=SC2312
&& __zoxide_cd "${result}" result="$(\command zoxide query --exclude "$(__zoxide_pwd)" -- "$@")" && __zoxide_cd "${result}"
fi fi
} }
# Jump to a directory using interactive search. # Jump to a directory using interactive search.
function __zoxide_zi() { function __zoxide_zi() {
__zoxide_doctor
\builtin local result \builtin local result
result="$(zoxide query -i -- "$@")" && __zoxide_cd "${result}" result="$(\command zoxide query --interactive -- "$@")" && __zoxide_cd "${result}"
} }
{{ section }} {{ section }}
# Convenient aliases for zoxide. Disable these using --no-aliases. # Commands for zoxide. Disable these using --no-cmd.
# #
{%- match cmd %} {%- match cmd %}
{%- when Some with (cmd) %} {%- when Some with (cmd) %}
# Remove definitions. function {{ cmd }}() {
function __zoxide_unset() {
\builtin unalias "$@" &>/dev/null
\builtin unfunction "$@" &>/dev/null
\builtin unset "$@" &>/dev/null
}
__zoxide_unset '{{cmd}}'
function {{cmd}}() {
__zoxide_z "$@" __zoxide_z "$@"
} }
__zoxide_unset '{{cmd}}i' function {{ cmd }}i() {
function {{cmd}}i() {
__zoxide_zi "$@" __zoxide_zi "$@"
} }
@ -107,7 +123,57 @@ function {{cmd}}i() {
{%- endmatch %} {%- endmatch %}
# Completions.
if [[ -o zle ]]; then
__zoxide_result=''
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
# Show completions for local directories.
_cd -/
elif [[ "${words[-1]}" == '' ]]; then
# Show completions for Space-Tab.
# shellcheck disable=SC2086
__zoxide_result="$(\command zoxide query --exclude "$(__zoxide_pwd || \builtin true)" --interactive -- ${words[2,-1]})" || __zoxide_result=''
# Set a result to ensure completion doesn't re-run
compadd -Q ""
# Bind '\e[0n' to helper function.
\builtin bindkey '\e[0n' '__zoxide_z_complete_helper'
# Sends query device status code, which results in a '\e[0n' being sent to console input.
\builtin printf '\e[5n'
# Report that the completion was successful, so that we don't fall back
# to another completion function.
return 0
fi
}
function __zoxide_z_complete_helper() {
if [[ -n "${__zoxide_result}" ]]; then
# shellcheck disable=SC2034,SC2296
BUFFER="{{ cmd.unwrap_or("cd") }} ${(q-)__zoxide_result}"
__zoxide_result=''
\builtin zle reset-prompt
\builtin zle accept-line
else
\builtin zle reset-prompt
fi
}
\builtin zle -N __zoxide_z_complete_helper
{%- if let Some(cmd) = cmd %}
[[ "${+functions[compdef]}" -ne 0 ]] && \compdef __zoxide_z_complete {{ cmd }}
{%- endif %}
fi
{{ section }} {{ section }}
# To initialize zoxide, add this to your configuration (usually ~/.zshrc): # To initialize zoxide, add this to your shell configuration file (usually ~/.zshrc):
# #
# eval "$(zoxide init zsh)" # eval "$(zoxide init zsh)"

View File

@ -1,17 +1,22 @@
//! Test clap generated completions. //! Test clap generated completions.
#![cfg(feature = "nix")] #![cfg(feature = "nix-dev")]
use assert_cmd::Command; use assert_cmd::Command;
#[test] #[test]
fn completions_bash() { fn completions_bash() {
let source = include_str!("../contrib/completions/zoxide.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: the completions file uses editor commands to add completions to the
// Elvish does not support running editor commands from a script, so we can't create a test for // shell. However, Elvish does not support running editor commands from a
// this. See: https://github.com/elves/elvish/issues/1299 // script, so we can't create a test for this. See: https://github.com/elves/elvish/issues/1299
#[test] #[test]
fn completions_fish() { fn completions_fish() {
@ -21,7 +26,7 @@ fn completions_fish() {
Command::new("fish") Command::new("fish")
.env("HOME", tempdir) .env("HOME", tempdir)
.args(&["--command", source, "--private"]) .args(["--command", source, "--private"])
.assert() .assert()
.success() .success()
.stdout("") .stdout("")
@ -32,7 +37,7 @@ fn completions_fish() {
fn completions_powershell() { fn completions_powershell() {
let source = include_str!("../contrib/completions/_zoxide.ps1"); let source = include_str!("../contrib/completions/_zoxide.ps1");
Command::new("pwsh") Command::new("pwsh")
.args(&["-NoLogo", "-NonInteractive", "-NoProfile", "-Command", source]) .args(["-NoLogo", "-NonInteractive", "-NoProfile", "-Command", source])
.assert() .assert()
.success() .success()
.stdout("") .stdout("")
@ -50,5 +55,5 @@ fn completions_zsh() {
compinit -u 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 = "2018"
publish = false
[dependencies]
anyhow = "1.0.32"
clap = "=3.0.0-beta.5"
ignore = "0.4.18"
shell-words = "1.0.0"

View File

@ -1,150 +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 nix_enabled = enable_nix();
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 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<()> {
let color: &[&str] = if is_ci() { &["--color=always"] } else { &[] };
Command::new("cargo").args(&["check", "--all-features"]).args(color)._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 color: &[&str] = if is_ci() { &["--color=always"] } else { &[] };
let check_args: &[&str] = if check { &["--check", "--files-with-diff"] } else { &[] };
Command::new("cargo").args(&["fmt", "--all", "--"]).args(color).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<()> {
// Run cargo-clippy.
let color: &[&str] = if is_ci() { &["--color=always"] } else { &[] };
Command::new("cargo").args(&["clippy", "--all-features", "--all-targets"]).args(color)._run()?;
if nix_enabled {
// Run cargo-audit.
let color: &[&str] = if is_ci() { &["--color=always"] } else { &[] };
Command::new("cargo").args(&["audit", "--deny=warnings"]).args(color)._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 color: &[&str] = if is_ci() { &["--color=always"] } else { &[] };
let features: &[&str] = if nix_enabled { &["--all-features"] } else { &[] };
Command::new("cargo").args(&["test", "--no-fail-fast", "--workspace"]).args(color).args(features).arg(name)._run()
}
fn is_ci() -> bool {
env::var_os("CI").is_some()
}
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 mut nix_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
nix_path.push("../shell.nix");
let status = Command::new("nix-shell").args(&["--pure", "--run", &cmd, "--"]).arg(nix_path).status().unwrap();
process::exit(status.code().unwrap_or(1));
}