Compare commits
405 Commits
Author | SHA1 | Date |
---|---|---|
|
194f8e31e4 | |
|
eb7b08fed3 | |
|
6ac39b7f41 | |
|
76125d9bd3 | |
|
2299f2834b | |
|
c070535968 | |
|
4549438ffa | |
|
261cf8846a | |
|
30129f4344 | |
|
9b899e90e3 | |
|
6324b4e347 | |
|
db14bdc2ff | |
|
d6c0203c09 | |
|
859268aec8 | |
|
b318a2a190 | |
|
ee8bbe57d3 | |
|
9c0dcf12f3 | |
|
628f8542a0 | |
|
3d9f928e01 | |
|
4807518c4b | |
|
8e14038811 | |
|
1d08fe2b84 | |
|
36537410cd | |
|
306d7ae143 | |
|
9768e645c7 | |
|
8f0288b0c9 | |
|
6ec0436859 | |
|
f1d848820a | |
|
095b270fea | |
|
09aa626a86 | |
|
7691d7e9a5 | |
|
6902d375c5 | |
|
b193a84eb0 | |
|
e1e2f41ecb | |
|
7617799eb5 | |
|
2480ed0853 | |
|
8118af567c | |
|
6679619c00 | |
|
2ecb310108 | |
|
0f07314d33 | |
|
845fe79b5e | |
|
868a242e05 | |
|
d74bce3b74 | |
|
5659f3c7dc | |
|
06f9f3f27c | |
|
da0fdb2bae | |
|
3fe42e901e | |
|
791deec8ac | |
|
678bbdefbc | |
|
d99d82f141 | |
|
1bdf393f9d | |
|
d52f6bffda | |
|
60e870d704 | |
|
ede1224259 | |
|
c61bdc1d54 | |
|
7696872637 | |
|
a0c4049bf9 | |
|
7113ccddea | |
|
d5f12fccea | |
|
075fd2c695 | |
|
6d3a711115 | |
|
eb9bd54e59 | |
|
3d3267b4fd | |
|
e9d5af3f95 | |
|
991cb0a144 | |
|
4403326d58 | |
|
55ffe7446d | |
|
efd49e7f3d | |
|
450433644f | |
|
c1d7c4f4c7 | |
|
a1be4012e0 | |
|
dc0d039be2 | |
|
1ef6d5b8fc | |
|
878b41c937 | |
|
550bca6458 | |
|
297499a4fe | |
|
d655e026f5 | |
|
8da8f50eaa | |
|
b881866216 | |
|
dbe6f185cf | |
|
063f9c1632 | |
|
0dfe1c4073 | |
|
1a4c4933ca | |
|
df0f6e525c | |
|
8d28840a89 | |
|
226aa66c3b | |
|
e2891634e2 | |
|
796fee720e | |
|
ae33940331 | |
|
1cb6312fb2 | |
|
801d5e2f22 | |
|
4cba9808ff | |
|
208bb6375e | |
|
5d8a7ea7b2 | |
|
36f440f3e3 | |
|
9f67fb5bb9 | |
|
94d3cba60e | |
|
5b2c9222f9 | |
|
88d494fc2d | |
|
73a2eca43c | |
|
06f6ed56b5 | |
|
18001773f3 | |
|
012c67de63 | |
|
65941f60a4 | |
|
418a78d348 | |
|
c9183ce327 | |
|
5592506456 | |
|
e0158a72a9 | |
|
346f3aa90f | |
|
c5451205f4 | |
|
e55e0b27e2 | |
|
cf34d02288 | |
|
90c66df2a8 | |
|
0488b8cee2 | |
|
cbb8e77d60 | |
|
f32dc6bcd8 | |
|
c8dda80716 | |
|
4c3f771ad0 | |
|
d0f3d8726f | |
|
bb092cf9d6 | |
|
3e43eb4090 | |
|
3022cf3686 | |
|
f537a4e6d2 | |
|
afdcf6bdff | |
|
2014a89d50 | |
|
073500cbdf | |
|
a624ceef54 | |
|
e3ab444976 | |
|
be051f2f5a | |
|
bc34c49480 | |
|
9cbd029438 | |
|
3ca0cf5fc6 | |
|
28563105ba | |
|
01da2dbff6 | |
|
66d073ee72 | |
|
5b9c54881f | |
|
1bfcdfacf2 | |
|
3898d99f0c | |
|
42e1d5dd3b | |
|
acf3fcc7fb | |
|
72a49ec9c9 | |
|
651fc3c00e | |
|
786519aa8f | |
|
5f8974fc5c | |
|
03c465b778 | |
|
469b858278 | |
|
e938034423 | |
|
6dedfcd74a | |
|
49e7824c42 | |
|
157b221055 | |
|
1d64ae0b87 | |
|
25b4c057fb | |
|
c6ab1012e4 | |
|
153e060043 | |
|
e05637ded1 | |
|
ce8f2395d5 | |
|
54e9f9aa7a | |
|
686d116ad5 | |
|
21211e43d1 | |
|
3483ab013a | |
|
74ccc0232f | |
|
0ecfbf7e68 | |
|
0b51cb6591 | |
|
5de13befbc | |
|
a7c8231e56 | |
|
03df9d4d1f | |
|
56956779b2 | |
|
4dff2b1602 | |
|
4f475a75cb | |
|
fa1cfb490d | |
|
8f1cb75732 | |
|
39f5aaa308 | |
|
3c8a0d6b13 | |
|
4ded81e277 | |
|
7299e33a80 | |
|
1c651bf8cd | |
|
066730f95d | |
|
575cfd485c | |
|
59d80a7b60 | |
|
2c2286cea2 | |
|
65c5e86968 | |
|
86741bbe6a | |
|
2555870c31 | |
|
b747a0cfc3 | |
|
21520d9bbf | |
|
3ab0a7b8fd | |
|
cf0c9c002e | |
|
7af4da1dab | |
|
82d4f73aae | |
|
e8c5f7a975 | |
|
f74873b803 | |
|
d99e9b7d86 | |
|
c7b8f2f011 | |
|
6210d28d5f | |
|
b7d6b1eea7 | |
|
1c947110db | |
|
4a47da0ed4 | |
|
c3e3c855ca | |
|
9d9bcfcac2 | |
|
c137019b83 | |
|
0f3ae894f1 | |
|
e8a1e11848 | |
|
e4aa0692a4 | |
|
1d102d4ad2 | |
|
0e21153107 | |
|
818ac67e7e | |
|
118a37f576 | |
|
a88be74e3e | |
|
8feeec2536 | |
|
b38b89eb3b | |
|
b0c899b99f | |
|
535762b80a | |
|
e4b9e12b0f | |
|
1306fba4ac | |
|
209d86ab2f | |
|
31c44f0649 | |
|
e7cc34b359 | |
|
f830fbd670 | |
|
3df60eb3d3 | |
|
7c7a2a0a3c | |
|
10c11310ed | |
|
c7400cf0ed | |
|
4bf470dff6 | |
|
1105ab8107 | |
|
7d0ddedc6b | |
|
6b2c6a2bc3 | |
|
f628845fb7 | |
|
3620189582 | |
|
bf54e7f0cd | |
|
094baaa998 | |
|
96a43e9c79 | |
|
612f264dd4 | |
|
24d21ec8ab | |
|
302ec231ba | |
|
212585ce74 | |
|
e6bc1576ca | |
|
b71d33b5c1 | |
|
a0242d7c9a | |
|
17365710af | |
|
90e781a192 | |
|
dc64d912ab | |
|
6307146fbb | |
|
f2857f229f | |
|
ea096ac3ca | |
|
df148c834f | |
|
b3366dd5b6 | |
|
66d882bad4 | |
|
bd71cf3a5d | |
|
4ce6e4b25d | |
|
e34b6f930a | |
|
b6b024c452 | |
|
fe553585e9 | |
|
027d259c89 | |
|
d106f4e358 | |
|
cf8dc69dda | |
|
5c51c808aa | |
|
a0d544adfd | |
|
89dbb1fda6 | |
|
0a9081383d | |
|
fd088b43db | |
|
339a789261 | |
|
bd54ea3ea9 | |
|
ff28b02801 | |
|
5d81f1f22d | |
|
58430d8c54 | |
|
72fd48ed97 | |
|
2a2848f55c | |
|
3e03ccccd6 | |
|
c392a8bb75 | |
|
9f2f2e978e | |
|
e38f4d11bd | |
|
5374d599d2 | |
|
1aea7450f5 | |
|
bd42f0db76 | |
|
ba7b445348 | |
|
904bfd46e7 | |
|
9d8bfc90be | |
|
279e393d5c | |
|
ed47c2b1ac | |
|
ad354cf849 | |
|
07ce5a5959 | |
|
7ec9ad6a27 | |
|
6bf966f18d | |
|
d650a9ccbc | |
|
65f91d3837 | |
|
f578ec2041 | |
|
e96ea91c1c | |
|
095e9d3d6c | |
|
c872b96703 | |
|
0791356e3e | |
|
8ec3a8b6c6 | |
|
917663485f | |
|
49f406e3ed | |
|
2f2f588595 | |
|
41b8f08c46 | |
|
58313c67e7 | |
|
a60a305279 | |
|
02029aef98 | |
|
61cfb0281c | |
|
9d4c3096df | |
|
bc8958943b | |
|
a33806feea | |
|
e1127f4b72 | |
|
ecc64be909 | |
|
a80f20520e | |
|
7e6cf62c34 | |
|
9ff899308d | |
|
3ca89d0e6e | |
|
11535a3fb6 | |
|
439e5d11d6 | |
|
6be3998bb6 | |
|
bc57339473 | |
|
75310ba069 | |
|
04924bbec0 | |
|
e9e8aa04c7 | |
|
90c320c909 | |
|
53136c2982 | |
|
59717fa8d3 | |
|
05a95fe525 | |
|
3efc0c6f2f | |
|
3bb21ede97 | |
|
712d3403ef | |
|
d74cd962f0 | |
|
06f5a8e684 | |
|
e3c3c181a2 | |
|
20d9f3d18c | |
|
b9b0abe471 | |
|
340454b49b | |
|
7128b52cdb | |
|
53f713659d | |
|
273b6ed1cf | |
|
a95e3c7207 | |
|
e739168244 | |
|
84cc130fa5 | |
|
0f8008d00e | |
|
84c9121f10 | |
|
cf59aa1c0f | |
|
5b0d0009a4 | |
|
6fbd193ef4 | |
|
36f3967f9c | |
|
fcdfb19fd1 | |
|
9ef2ef5a6d | |
|
d39c9a1fc6 | |
|
b8bb6f0f85 | |
|
b4a3c46122 | |
|
85d0fbe6ee | |
|
22a9f6c32d | |
|
98f1d7277a | |
|
97dc08347d | |
|
6069bea81a | |
|
8c92cf585e | |
|
f05d33b2c1 | |
|
3c7f635902 | |
|
ba8a5f3167 | |
|
d33bfd111f | |
|
efe11ec924 | |
|
1075ba5a50 | |
|
6d3cb4bc99 | |
|
650732c82c | |
|
ef63b555a0 | |
|
297908e950 | |
|
a285b4e07d | |
|
fec064a1ef | |
|
90f49684e9 | |
|
027ae1df47 | |
|
0eb4418fd6 | |
|
697afe7ba6 | |
|
0f5fde5afa | |
|
7d9ca0de43 | |
|
5cb091d30a | |
|
1828414f21 | |
|
acc9059177 | |
|
5882bc6c4d | |
|
ce2499d528 | |
|
706863b88d | |
|
6248f539db | |
|
3398cc721d | |
|
0b7b29e0e8 | |
|
4224f494ed | |
|
d9c0584aac | |
|
e2506631e9 | |
|
c4aebf6041 | |
|
a9a8948d3b | |
|
160a4ef326 | |
|
06062e92ca | |
|
a9b731d80c | |
|
bc81154c5d | |
|
22d19a74b2 | |
|
7b00d5a423 | |
|
70c350ff96 | |
|
d5818ff0c3 | |
|
f723dbcad9 | |
|
1a5d14a825 | |
|
d89605ffef | |
|
dcdcec4a78 | |
|
ff16bf140c | |
|
eeaf8af202 | |
|
418b82524b | |
|
14b150c1f7 | |
|
753d95e5a4 | |
|
e6e67b16bc | |
|
58bcbef7aa | |
|
21018d5847 | |
|
b817053f85 | |
|
e57c1888d3 |
|
@ -0,0 +1,7 @@
|
|||
[alias]
|
||||
xtask = "run --package xtask --"
|
||||
|
||||
# On Windows MSVC, statically link the C runtime so that the resulting EXE does
|
||||
# not depend on the vcruntime DLL.
|
||||
[target.'cfg(all(windows, target_env = "msvc"))']
|
||||
rustflags = ["-C", "target-feature=+crt-static"]
|
|
@ -0,0 +1,10 @@
|
|||
version = 1
|
||||
|
||||
[[analyzers]]
|
||||
name = "rust"
|
||||
|
||||
[analyzers.meta]
|
||||
msrv = "stable"
|
||||
|
||||
[[analyzers]]
|
||||
name = "shell"
|
|
@ -0,0 +1,5 @@
|
|||
/contrib/completions/* eol=lf linguist-generated=true text
|
||||
/contrib/completions/README.md -eol -linguist-generated -text
|
||||
/init.fish eol=lf text
|
||||
/templates/*.txt eol=lf text
|
||||
/zoxide.plugin.zsh eol=lf text
|
|
@ -0,0 +1,128 @@
|
|||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
- Demonstrating empathy and kindness toward other people
|
||||
- Being respectful of differing opinions, viewpoints, and experiences
|
||||
- Giving and gracefully accepting constructive feedback
|
||||
- Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
- Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
- The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
- Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
- Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
98ajeet@gmail.com.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
|
@ -0,0 +1,6 @@
|
|||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
|
@ -1,22 +0,0 @@
|
|||
name: cargo-audit
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "**/Cargo.toml"
|
||||
- "**/Cargo.lock"
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
|
||||
jobs:
|
||||
audit:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/audit-check@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
@ -1,24 +0,0 @@
|
|||
name: cargo-clippy
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
clippy:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: nightly
|
||||
override: true
|
||||
components: clippy
|
||||
- uses: actions-rs/clippy-check@v1
|
||||
with:
|
||||
args: --all-targets --all-features -- -D warnings -D clippy::all
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
@ -1,21 +0,0 @@
|
|||
name: cargo-fmt
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
fmt:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
components: rustfmt
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: -- --check
|
|
@ -1,41 +0,0 @@
|
|||
name: cargo-test
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
test-linux:
|
||||
runs-on: ubuntu-18.04
|
||||
steps:
|
||||
- run: sudo apt update
|
||||
- run: sudo apt install wget apt-transport-https
|
||||
- run: wget -q https://packages.microsoft.com/config/ubuntu/18.04/packages-microsoft-prod.deb
|
||||
- run: sudo dpkg -i packages-microsoft-prod.deb
|
||||
- run: sudo apt update
|
||||
- run: sudo add-apt-repository universe
|
||||
- run: sudo apt install bash dash fish powershell shellcheck xonsh zsh
|
||||
- run: sudo snap install shfmt
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --all-features --no-fail-fast
|
||||
test-windows:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --all-features --no-fail-fast
|
|
@ -1,25 +0,0 @@
|
|||
name: cargo-udeps
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
udeps:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: nightly
|
||||
override: true
|
||||
- uses: actions-rs/install@v0.1
|
||||
with:
|
||||
crate: cargo-udeps
|
||||
version: latest
|
||||
use-tool-cache: true
|
||||
- run: cargo udeps --all-features --all-targets
|
|
@ -0,0 +1,57 @@
|
|||
name: ci
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
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:
|
||||
ci:
|
||||
name: ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions-rs/toolchain@v1
|
||||
if: ${{ matrix.os == 'windows-latest' }}
|
||||
with:
|
||||
components: clippy
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
- 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' }}
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v16
|
||||
if: ${{ matrix.os != 'windows-latest' && env.CACHIX_AUTH_TOKEN != '' }}
|
||||
with:
|
||||
authToken: ${{ env.CACHIX_AUTH_TOKEN }}
|
||||
name: zoxide
|
||||
- name: Setup cache
|
||||
uses: Swatinem/rust-cache@v2.8.1
|
||||
with:
|
||||
key: ${{ matrix.os }}
|
||||
- name: Install just
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: just
|
||||
- name: Run lints + tests
|
||||
run: just lint test
|
|
@ -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.
|
||||
|
|
@ -1,115 +1,121 @@
|
|||
name: release
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*" # push events to matching v*, i.e. v1.0, v20.15.10
|
||||
|
||||
branches: [main]
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
permissions:
|
||||
contents: write
|
||||
jobs:
|
||||
build-linux:
|
||||
runs-on: ubuntu-latest
|
||||
release:
|
||||
name: ${{ matrix.target }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target: ["x86_64-unknown-linux-gnu", "x86_64-unknown-linux-musl", "armv7-unknown-linux-musleabihf"]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
target: x86_64-unknown-linux-musl
|
||||
deb: true
|
||||
- os: ubuntu-latest
|
||||
target: arm-unknown-linux-musleabihf
|
||||
- os: ubuntu-latest
|
||||
target: armv7-unknown-linux-musleabihf
|
||||
deb: true
|
||||
- os: ubuntu-latest
|
||||
target: aarch64-unknown-linux-musl
|
||||
deb: true
|
||||
- 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
|
||||
- os: macos-latest
|
||||
target: aarch64-apple-darwin
|
||||
- os: windows-latest
|
||||
target: x86_64-pc-windows-msvc
|
||||
- os: windows-latest
|
||||
target: aarch64-pc-windows-msvc
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/cargo@v1
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
use-cross: true
|
||||
command: build
|
||||
args: --release --target ${{ matrix.target }}
|
||||
- uses: actions/upload-artifact@v2
|
||||
fetch-depth: 0
|
||||
- name: Get version
|
||||
id: get_version
|
||||
uses: SebRollen/toml-action@v1.2.0
|
||||
with:
|
||||
name: "zoxide-${{ matrix.target }}"
|
||||
path: "target/${{ matrix.target }}/release/zoxide"
|
||||
|
||||
build-darwin:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/cargo@v1
|
||||
file: Cargo.toml
|
||||
field: package.version
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
override: true
|
||||
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
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --release --target x86_64-apple-darwin
|
||||
- uses: actions/upload-artifact@v2
|
||||
args: --release --locked --target=${{ matrix.target }} --color=always --verbose
|
||||
use-cross: ${{ runner.os == 'Linux' }}
|
||||
- name: Install cargo-deb
|
||||
if: ${{ matrix.deb == true }}
|
||||
uses: actions-rs/install@v0.1
|
||||
with:
|
||||
name: "zoxide-x86_64-apple-darwin"
|
||||
path: "target/x86_64-apple-darwin/release/zoxide"
|
||||
|
||||
build-windows:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/cargo@v1
|
||||
crate: cargo-deb
|
||||
- name: Build deb
|
||||
if: ${{ matrix.deb == true }}
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --release --target x86_64-pc-windows-msvc
|
||||
- uses: actions/upload-artifact@v2
|
||||
command: deb
|
||||
args: --no-build --no-strip --output=. --target=${{ matrix.target }}
|
||||
- name: Package (*nix)
|
||||
if: runner.os != 'Windows'
|
||||
run: |
|
||||
tar -cv CHANGELOG.md LICENSE README.md man/ \
|
||||
-C contrib/ completions/ -C ../ \
|
||||
-C target/${{ matrix.target }}/release/ zoxide |
|
||||
gzip --best > \
|
||||
zoxide-${{ steps.get_version.outputs.value }}-${{ matrix.target }}.tar.gz
|
||||
- name: Package (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
run: |
|
||||
7z a zoxide-${{ steps.get_version.outputs.value }}-${{ matrix.target }}.zip `
|
||||
CHANGELOG.md LICENSE README.md ./man/ ./contrib/completions/ `
|
||||
./target/${{ matrix.target }}/release/zoxide.exe
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: "zoxide-x86_64-pc-windows-msvc"
|
||||
path: "target/x86_64-pc-windows-msvc/release/zoxide.exe"
|
||||
|
||||
release-upload:
|
||||
needs:
|
||||
- build-linux
|
||||
- build-darwin
|
||||
- build-windows
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
name: ${{ matrix.target }}
|
||||
path: |
|
||||
*.deb
|
||||
*.tar.gz
|
||||
*.zip
|
||||
- name: Create release
|
||||
if: |
|
||||
github.ref == 'refs/heads/main' && startsWith(github.event.head_commit.message, 'chore(release)')
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: Release ${{ github.ref }}
|
||||
|
||||
- uses: actions/download-artifact@v2
|
||||
|
||||
- name: Display structure of downloaded files
|
||||
run: ls -lR
|
||||
|
||||
- uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: "zoxide-x86_64-unknown-linux-gnu/zoxide"
|
||||
asset_name: "zoxide-x86_64-unknown-linux-gnu"
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
- uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: "zoxide-x86_64-unknown-linux-musl/zoxide"
|
||||
asset_name: "zoxide-x86_64-unknown-linux-musl"
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
- uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: "zoxide-armv7-unknown-linux-musleabihf/zoxide"
|
||||
asset_name: "zoxide-armv7-unknown-linux-musleabihf"
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
- uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: "zoxide-x86_64-apple-darwin/zoxide"
|
||||
asset_name: "zoxide-x86_64-apple-darwin"
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
- uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: "zoxide-x86_64-pc-windows-msvc/zoxide.exe"
|
||||
asset_name: "zoxide-x86_64-pc-windows-msvc.exe"
|
||||
asset_content_type: application/octet-stream
|
||||
draft: true
|
||||
files: |
|
||||
*.deb
|
||||
*.tar.gz
|
||||
*.zip
|
||||
name: ${{ steps.get_version.outputs.value }}
|
||||
tag_name: ""
|
||||
|
|
|
@ -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,7 +1,14 @@
|
|||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
### Custom ###
|
||||
.vscode/
|
||||
|
||||
### Rust ###
|
||||
# Compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
target_nix/
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
# Backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
### Python ###
|
||||
.mypy_cache/
|
||||
|
|
414
CHANGELOG.md
414
CHANGELOG.md
|
@ -1,3 +1,5 @@
|
|||
<!-- markdownlint-disable-file MD024 -->
|
||||
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
@ -5,14 +7,368 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [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
|
||||
|
||||
### Changed
|
||||
|
||||
- Bash/Fish: improved completions for `z` command.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fish: error erasing completions on older versions.
|
||||
- PowerShell: enable `--cmd cd` to replace the `cd` command.
|
||||
|
||||
## [0.7.8] - 2021-10-21
|
||||
|
||||
### Added
|
||||
|
||||
- Auto-generated completions for [Fig](https://fig.io/).
|
||||
|
||||
### Fixed
|
||||
|
||||
- Compile error with `clap v3.0.0-beta.5`.
|
||||
|
||||
## [0.7.7] - 2021-10-15
|
||||
|
||||
### Fixed
|
||||
|
||||
- PowerShell: hook not initializing correctly.
|
||||
|
||||
## [0.7.6] - 2021-10-13
|
||||
|
||||
### Changed
|
||||
|
||||
- Nushell: upgrade minimum supported version to v0.37.0.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Xonsh: error messages in `zi`.
|
||||
- Xonsh: configuration environment variables not being handled correctly.
|
||||
|
||||
## [0.7.5] - 2021-09-09
|
||||
|
||||
### Added
|
||||
|
||||
- Bash/Elvish: completions for `z` command.
|
||||
|
||||
### Changed
|
||||
|
||||
- Nushell: upgrade minimum supported version to v0.36.0.
|
||||
- Nushell: easier installation instructions.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Elvish: unable to `z` into directories by path.
|
||||
- Elvish: don't show traceback when `z` or `zi` fails.
|
||||
- Elvish: nested shells do not initialize correctly.
|
||||
|
||||
## [0.7.4] - 2021-08-15
|
||||
|
||||
### Fixed
|
||||
|
||||
- Compile error with `clap v3.0.0-beta.4`.
|
||||
|
||||
## [0.7.3] - 2021-08-05
|
||||
|
||||
### Added
|
||||
|
||||
- `zoxide add` and `zoxide remove` now accept multiple arguments.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Nushell: errors on 0.33.0.
|
||||
- PowerShell: errors when initializing in `StrictMode`.
|
||||
- Bash/POSIX: remove conflicting alias definitions when initializing.
|
||||
- Bash: remove extra semicolon when setting `$PROMPT_COMMAND`.
|
||||
- Xonsh: use shell environment instead of `os.environ`.
|
||||
|
||||
## [0.7.2] - 2021-06-10
|
||||
|
||||
### Fixed
|
||||
|
||||
- `zoxide -V` not printing version.
|
||||
|
||||
## [0.7.1] - 2021-06-09
|
||||
|
||||
### Added
|
||||
|
||||
- Auto-generated shell completions.
|
||||
- `zoxide query --all` for listing deleted directories.
|
||||
- Lazy deletion for removed directories that have not been accessed in > 90
|
||||
days.
|
||||
- Nushell: support for 0.32.0+.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Nushell: avoid calling `__zoxide_hook` on non-filesystem subshells.
|
||||
- Fish: `alias cd=z` now works, but it must be done after calling `zoxide init`.
|
||||
- PowerShell: avoid calling `__zoxide_hook` on non-filesystem providers.
|
||||
- Fish: avoid calling `__zoxide_hook` in private mode.
|
||||
|
||||
## [0.7.0] - 2021-05-02
|
||||
|
||||
### Added
|
||||
|
||||
- Manpages for all subcommands.
|
||||
- Default prompt for Nushell.
|
||||
|
||||
### Changed
|
||||
|
||||
- `zoxide remove -i` now accepts multiple selections.
|
||||
- `zoxide add` no longer accepts zero parameters.
|
||||
- `$_ZO_EXCLUDE_DIRS` now defaults to `"$HOME"`.
|
||||
- Binary releases now use `.zip` on Windows, `.tar.gz` otherwise.
|
||||
|
||||
### Fixed
|
||||
|
||||
- `cd -` on Fish shells.
|
||||
- `__zoxide_hook` no longer changes value of `$?` within `$PROMPT_COMMAND` on
|
||||
Bash.
|
||||
|
||||
### Removed
|
||||
|
||||
- GitHub install script.
|
||||
- Release binaries built with `glibc`, use `musl` instead.
|
||||
|
||||
## [0.6.0] - 2021-04-09
|
||||
|
||||
### Added
|
||||
|
||||
- Support for [Nushell](https://www.nushell.sh/).
|
||||
- Support for [Elvish](https://elv.sh/).
|
||||
|
||||
### Changed
|
||||
|
||||
- `z` now excludes the current directory from search results.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Removed backtraces on Rust nightly.
|
||||
- Generated shell code avoids using aliased builtins.
|
||||
- Handle broken pipe errors gracefully when writing to streams.
|
||||
- NUL file appearing in working directory on Windows.
|
||||
- Accidental redefinition of hooks when initialized twice on some shells.
|
||||
- zoxide unable to find itself on Xonsh shells.
|
||||
|
||||
### Removed
|
||||
|
||||
- Aliases: `za`, `zq`, `zqi`, `zr`, `zri`. These are trivial aliases that can
|
||||
easily be defined manually, and aren't very useful to most users.
|
||||
|
||||
## [0.5.0] - 2020-10-30
|
||||
|
||||
### Added
|
||||
|
||||
- Inaccessible directories are no longer removed; zoxide can now remember paths on removable devices.
|
||||
- `$_ZO_EXCLUDE_DIRS` now supports globs.
|
||||
- `zoxide init` now defines `__zoxide_z*` functions that can be aliased as needed.
|
||||
- Support for the [xonsh](https://xon.sh/) shell.
|
||||
- `zoxide init` now defines `__zoxide_z*` functions that can be aliased as
|
||||
needed.
|
||||
- Support for the [Xonsh](https://xon.sh/) shell.
|
||||
- `zoxide import` can now import from Autojump.
|
||||
|
||||
### Changed
|
||||
|
@ -21,17 +377,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
### Fixed
|
||||
|
||||
- Clobber conflicting alias definitions in bash/fish/zsh/POSIX shells.
|
||||
- Clobber conflicting alias definitions in Bash/Fish/Zsh/POSIX shells.
|
||||
|
||||
### Removed
|
||||
|
||||
- Deprecated PWD hooks for POSIX shells.
|
||||
- Lazy deletion for inaccessible directories.
|
||||
|
||||
## [0.4.3] - 2020-07-04
|
||||
|
||||
### Fixed
|
||||
|
||||
- Bug in Fish init script
|
||||
- Bug in Fish init script.
|
||||
|
||||
## [0.4.2] - 2020-07-03
|
||||
|
||||
|
@ -44,7 +401,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
### Changed
|
||||
|
||||
- Increased default value of `$_ZO_MAXAGE` to `10000`.
|
||||
- Symlinks are treated as separate directories by default, this can be changed by setting `_ZO_RESOLVE_SYMLINKS=1`.
|
||||
- Symlinks are treated as separate directories by default, this can be changed
|
||||
by setting `_ZO_RESOLVE_SYMLINKS=1`.
|
||||
|
||||
### Removed
|
||||
|
||||
|
@ -73,7 +431,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
### Changed
|
||||
|
||||
- `zoxide remove` now throws an error if there was no match in the database.
|
||||
- Interactive mode in `zoxide` no longer throws an error if `fzf` exits gracefully.
|
||||
- Interactive mode in `zoxide` no longer errors out if `fzf` exits gracefully.
|
||||
- Canonicalize to regular paths instead of UNC paths on Windows.
|
||||
- `zoxide init` now uses PWD hooks by default for better performance.
|
||||
- `$_ZO_ECHO` now only works when set to `1`.
|
||||
|
@ -83,7 +441,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
### 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
|
||||
|
||||
|
@ -94,7 +452,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
### Changed
|
||||
|
||||
- Query output no longer has the `query:` prefix, so `$(zq)` can now be used as an argument to commands.
|
||||
- `zoxide query` output no longer has the `query:` prefix.
|
||||
|
||||
### Fixed
|
||||
|
||||
|
@ -105,7 +463,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
### Added
|
||||
|
||||
- Automatic migration from `v0.2.x` databases.
|
||||
- `$_ZO_EXCLUDE_DIRS` to prevent certain directories from being added to the database.
|
||||
- `$_ZO_EXCLUDE_DIRS` to prevent directories from being added to the database.
|
||||
- Support for POSIX-compliant shells.
|
||||
|
||||
### Changed
|
||||
|
@ -116,14 +474,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
### Fixed
|
||||
|
||||
- Achieve thread safety using unique temporary database file names for each `zoxide` instance.
|
||||
- Thread safety using unique tempfile names for each `zoxide` instance.
|
||||
- Incomprehensive "could not allocate" message on database corruption.
|
||||
|
||||
## [0.2.2] - 2020-03-20
|
||||
|
||||
### Fixed
|
||||
|
||||
- Incorrect exit codes in `z` command on `fish`.
|
||||
- Incorrect exit codes in `z` command on Fish.
|
||||
|
||||
### Removed
|
||||
|
||||
|
@ -136,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.
|
||||
- Minimal `ranger` plugin.
|
||||
- PWD hook to only update the database when the current directory is changed.
|
||||
- Support for the `bash` shell.
|
||||
- Support for Bash.
|
||||
- `migrate` subcommand to allow users to migrate from `z`.
|
||||
|
||||
### Fixed
|
||||
|
@ -150,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.
|
||||
- Support for `z -` command to go to previous directory.
|
||||
- `Cargo.lock` for more reproducible builds.
|
||||
- Support for the `fish` shell.
|
||||
- Support for the Fish shell.
|
||||
|
||||
### Fixed
|
||||
|
||||
- `_zoxide_precmd` overriding other precmd hooks on `zsh`.
|
||||
- `_zoxide_precmd` overriding other precmd hooks on Zsh.
|
||||
|
||||
## [0.1.1] - 2020-03-08
|
||||
|
||||
|
@ -176,8 +534,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
### Added
|
||||
|
||||
- GitHub Actions pipeline to build and upload releases.
|
||||
- Support for the `zsh` shell.
|
||||
- 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.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.6]: https://github.com/ajeetdsouza/zoxide/compare/v0.7.5...v0.7.6
|
||||
[0.7.5]: https://github.com/ajeetdsouza/zoxide/compare/v0.7.4...v0.7.5
|
||||
[0.7.4]: https://github.com/ajeetdsouza/zoxide/compare/v0.7.3...v0.7.4
|
||||
[0.7.3]: https://github.com/ajeetdsouza/zoxide/compare/v0.7.2...v0.7.3
|
||||
[0.7.2]: https://github.com/ajeetdsouza/zoxide/compare/v0.7.1...v0.7.2
|
||||
[0.7.1]: https://github.com/ajeetdsouza/zoxide/compare/v0.7.0...v0.7.1
|
||||
[0.7.0]: https://github.com/ajeetdsouza/zoxide/compare/v0.6.0...v0.7.0
|
||||
[0.6.0]: https://github.com/ajeetdsouza/zoxide/compare/v0.5.0...v0.6.0
|
||||
[0.5.0]: https://github.com/ajeetdsouza/zoxide/compare/v0.4.3...v0.5.0
|
||||
[0.4.3]: https://github.com/ajeetdsouza/zoxide/compare/v0.4.2...v0.4.3
|
||||
[0.4.2]: https://github.com/ajeetdsouza/zoxide/compare/v0.4.1...v0.4.2
|
||||
|
|
File diff suppressed because it is too large
Load Diff
111
Cargo.toml
111
Cargo.toml
|
@ -1,33 +1,114 @@
|
|||
[package]
|
||||
name = "zoxide"
|
||||
version = "0.5.0"
|
||||
authors = ["Ajeet D'Souza <98ajeet@gmail.com>"]
|
||||
description = "A faster way to navigate your filesystem"
|
||||
repository = "https://github.com/ajeetdsouza/zoxide/"
|
||||
edition = "2018"
|
||||
keywords = ["cli"]
|
||||
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]
|
||||
maintenance = { status = "actively-developed" }
|
||||
|
||||
[dependencies]
|
||||
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"
|
||||
clap = "3.0.0-beta.2"
|
||||
dirs-next = "1.0.2"
|
||||
clap = { version = "4.3.0", features = ["derive"] }
|
||||
color-print = "0.3.4"
|
||||
dirs = "6.0.0"
|
||||
dunce = "1.0.1"
|
||||
fastrand = "2.0.0"
|
||||
glob = "0.3.0"
|
||||
once_cell = "1.4.1"
|
||||
ordered-float = "2.0.0"
|
||||
ouroboros = "0.18.3"
|
||||
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]
|
||||
rand = "0.7.3"
|
||||
which = "7.0.3"
|
||||
|
||||
[target.'cfg(unix)'.dev-dependencies]
|
||||
assert_cmd = "1.0.1"
|
||||
[build-dependencies]
|
||||
clap = { version = "4.3.0", features = ["derive"] }
|
||||
clap_complete = "4.5.50"
|
||||
clap_complete_fig = "4.5.2"
|
||||
clap_complete_nushell = "4.5.5"
|
||||
color-print = "0.3.4"
|
||||
|
||||
[dev-dependencies]
|
||||
assert_cmd = "2.0.0"
|
||||
rstest = { version = "0.26.0", default-features = false }
|
||||
rstest_reuse = "0.7.0"
|
||||
tempfile = "3.15.0"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
nix-dev = []
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
debug = 0
|
||||
lto = true
|
||||
strip = true
|
||||
|
||||
[package.metadata.deb]
|
||||
assets = [
|
||||
[
|
||||
"target/release/zoxide",
|
||||
"usr/bin/",
|
||||
"755",
|
||||
],
|
||||
[
|
||||
"contrib/completions/zoxide.bash",
|
||||
"usr/share/bash-completion/completions/zoxide",
|
||||
"644",
|
||||
],
|
||||
[
|
||||
"contrib/completions/zoxide.fish",
|
||||
"usr/share/fish/vendor_completions.d/",
|
||||
"664",
|
||||
],
|
||||
[
|
||||
"contrib/completions/_zoxide",
|
||||
"usr/share/zsh/vendor-completions/",
|
||||
"644",
|
||||
],
|
||||
[
|
||||
"man/man1/*",
|
||||
"usr/share/man/man1/",
|
||||
"644",
|
||||
],
|
||||
[
|
||||
"README.md",
|
||||
"usr/share/doc/zoxide/",
|
||||
"644",
|
||||
],
|
||||
[
|
||||
"CHANGELOG.md",
|
||||
"usr/share/doc/zoxide/",
|
||||
"644",
|
||||
],
|
||||
[
|
||||
"LICENSE",
|
||||
"usr/share/doc/zoxide/",
|
||||
"644",
|
||||
],
|
||||
]
|
||||
extended-description = """\
|
||||
zoxide is a smarter cd command, inspired by z and autojump. It remembers which \
|
||||
directories you use most frequently, so you can "jump" to them in just a few \
|
||||
keystrokes."""
|
||||
priority = "optional"
|
||||
section = "utils"
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
[build.env]
|
||||
passthrough = ["CARGO_INCREMENTAL"]
|
662
README.md
662
README.md
|
@ -1,223 +1,573 @@
|
|||
<!-- markdownlint-configure-file {
|
||||
"MD013": {
|
||||
"code_blocks": false,
|
||||
"tables": false
|
||||
},
|
||||
"MD033": false,
|
||||
"MD041": false
|
||||
} -->
|
||||
|
||||
<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
|
||||
|
||||
[](https://crates.io/crates/zoxide)
|
||||

|
||||
[![crates.io][crates.io-badge]][crates.io]
|
||||
[![Downloads][downloads-badge]][releases]
|
||||
[![Built with Nix][builtwithnix-badge]][builtwithnix]
|
||||
|
||||
A faster way to navigate your filesystem
|
||||
zoxide is a **smarter cd command**, inspired by z and autojump.
|
||||
|
||||
## Table of contents
|
||||
It remembers which directories you use most frequently, so you can "jump" to
|
||||
them in just a few keystrokes.<br />
|
||||
zoxide works on all major shells.
|
||||
|
||||
- [Introduction](#introduction)
|
||||
- [Examples](#examples)
|
||||
- [Getting started](#getting-started)
|
||||
- [Installing `zoxide`](#step-1-installing-zoxide)
|
||||
- [Installing `fzf` (optional)](#step-2-installing-fzf-optional)
|
||||
- [Adding `zoxide` to your shell](#step-3-adding-zoxide-to-your-shell)
|
||||
- [bash](#bash)
|
||||
- [fish](#fish)
|
||||
- [POSIX](#posix-shells)
|
||||
- [PowerShell](#powershell)
|
||||
- [xonsh](#xonsh)
|
||||
- [zsh](#zsh)
|
||||
- [Configuration](#configuration)
|
||||
- [`init` flags](#init-flags)
|
||||
- [Environment variables](#environment-variables)
|
||||
[Getting started](#getting-started) •
|
||||
[Installation](#installation) •
|
||||
[Configuration](#configuration) •
|
||||
[Integrations](#third-party-integrations)
|
||||
|
||||
## Introduction
|
||||
|
||||
`zoxide` is a blazing fast alternative to `cd`, inspired by
|
||||
[`z`](https://github.com/rupa/z) and [`z.lua`](https://github.com/skywind3000/z.lua).
|
||||
It keeps track of the directories you use most frequently, and uses a ranking algorithm
|
||||
to navigate to the best match.
|
||||
|
||||

|
||||
|
||||
## Examples
|
||||
|
||||
```sh
|
||||
z foo # cd to highest ranked directory matching foo
|
||||
z foo bar # cd to highest ranked directory matching foo and bar
|
||||
|
||||
z foo/ # can also cd into actual directories
|
||||
|
||||
zi foo # cd with interactive selection using fzf
|
||||
|
||||
zq foo # echo the best match, don't cd
|
||||
|
||||
za /foo # add /foo to the database
|
||||
zr /foo # remove /foo from the database
|
||||
```
|
||||
</div>
|
||||
|
||||
## Getting started
|
||||
|
||||
### Step 1: Installing `zoxide`
|
||||
|
||||
#### On Arch Linux [](https://aur.archlinux.org/packages/zoxide)
|
||||
![Tutorial][tutorial]
|
||||
|
||||
```sh
|
||||
yay -S zoxide
|
||||
z foo # cd into highest ranked directory matching foo
|
||||
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/ # cd into relative path
|
||||
z .. # cd one level up
|
||||
z - # cd into previous directory
|
||||
|
||||
zi foo # cd with interactive selection (using fzf)
|
||||
|
||||
z foo<SPACE><TAB> # show interactive completions (zoxide v0.8.0+, bash 4.4+/fish/zsh only)
|
||||
```
|
||||
|
||||
#### On CentOS
|
||||
Read more about the matching algorithm [here][algorithm-matching].
|
||||
|
||||
```sh
|
||||
dnf copr enable atim/zoxide
|
||||
dnf install zoxide
|
||||
```
|
||||
## Installation
|
||||
|
||||
#### On DragonFly BSD [](https://github.com/DragonFlyBSD/DPorts/tree/master/sysutils/zoxide)
|
||||
zoxide can be installed in 4 easy steps:
|
||||
|
||||
```sh
|
||||
pkg install zoxide
|
||||
```
|
||||
1. **Install binary**
|
||||
|
||||
#### On Fedora 32+ [](https://src.fedoraproject.org/rpms/rust-zoxide)
|
||||
zoxide runs on most major platforms. If your platform isn't listed below,
|
||||
please [open an issue][issues].
|
||||
|
||||
```sh
|
||||
dnf install zoxide
|
||||
```
|
||||
<details>
|
||||
<summary>Linux / WSL</summary>
|
||||
|
||||
#### On FreeBSD [](https://www.freshports.org/sysutils/zoxide/)
|
||||
> The recommended way to install zoxide is via the install script:
|
||||
>
|
||||
> ```sh
|
||||
> curl -sSfL https://raw.githubusercontent.com/ajeetdsouza/zoxide/main/install.sh | sh
|
||||
> ```
|
||||
>
|
||||
> Or, you can use a package manager:
|
||||
>
|
||||
> | Distribution | Repository | Instructions |
|
||||
> | ------------------- | ------------------------- | ----------------------------------------------------------------------------------------------------- |
|
||||
> | **_Any_** | **[crates.io]** | `cargo install zoxide --locked` |
|
||||
> | _Any_ | [asdf] | `asdf plugin add zoxide https://github.com/nyrst/asdf-zoxide.git` <br /> `asdf install zoxide latest` |
|
||||
> | _Any_ | [conda-forge] | `conda install -c conda-forge zoxide` |
|
||||
> | _Any_ | [guix] | `guix install zoxide` |
|
||||
> | _Any_ | [Linuxbrew] | `brew install zoxide` |
|
||||
> | _Any_ | [nixpkgs] | `nix-env -iA nixpkgs.zoxide` |
|
||||
> | AlmaLinux | | `dnf install zoxide` |
|
||||
> | Alpine Linux 3.13+ | [Alpine Linux Packages] | `apk add zoxide` |
|
||||
> | Arch Linux | [Arch Linux Extra] | `pacman -S zoxide` |
|
||||
> | CentOS Stream | | `dnf install zoxide` |
|
||||
> | ~Debian 11+~[^1] | ~[Debian Packages]~ | ~`apt install zoxide`~ |
|
||||
> | Devuan 4.0+ | [Devuan Packages] | `apt install zoxide` |
|
||||
> | Exherbo Linux | [Exherbo packages] | `cave resolve -x repository/rust` <br /> `cave resolve -x zoxide` |
|
||||
> | Fedora 32+ | [Fedora Packages] | `dnf install zoxide` |
|
||||
> | Gentoo | [Gentoo Packages] | `emerge app-shells/zoxide` |
|
||||
> | Linux Mint | [apt.cli.rs] (unofficial) | [Setup the repository][apt.cli.rs-setup], then `apt install zoxide` |
|
||||
> | Manjaro | | `pacman -S zoxide` |
|
||||
> | openSUSE Tumbleweed | [openSUSE Factory] | `zypper install zoxide` |
|
||||
> | ~Parrot OS~[^1] | | ~`apt install zoxide`~ |
|
||||
> | ~Raspbian 11+~[^1] | ~[Raspbian Packages]~ | ~`apt install zoxide`~ |
|
||||
> | RHEL 8+ | | `dnf install zoxide` |
|
||||
> | Rhino Linux | [Pacstall Packages] | `pacstall -I zoxide-deb` |
|
||||
> | Rocky Linux | | `dnf install zoxide` |
|
||||
> | Slackware 15.0+ | [SlackBuilds] | [Instructions][slackbuilds-howto] |
|
||||
> | Solus | [Solus Packages] | `eopkg install zoxide` |
|
||||
> | Ubuntu | [apt.cli.rs] (unofficial) | [Setup the repository][apt.cli.rs-setup], then `apt install zoxide` |
|
||||
> | Void Linux | [Void Linux Packages] | `xbps-install -S zoxide` |
|
||||
|
||||
```sh
|
||||
pkg install zoxide
|
||||
```
|
||||
</details>
|
||||
|
||||
#### On macOS / Linux (via Homebrew / Linuxbrew) [](https://formulae.brew.sh/formula/zoxide)
|
||||
<details>
|
||||
<summary>macOS</summary>
|
||||
|
||||
```sh
|
||||
brew install zoxide
|
||||
```
|
||||
> To install zoxide, use a package manager:
|
||||
>
|
||||
> | Repository | Instructions |
|
||||
> | --------------- | ----------------------------------------------------------------------------------------------------- |
|
||||
> | **[crates.io]** | `cargo install zoxide --locked` |
|
||||
> | **[Homebrew]** | `brew install zoxide` |
|
||||
> | [asdf] | `asdf plugin add zoxide https://github.com/nyrst/asdf-zoxide.git` <br /> `asdf install zoxide latest` |
|
||||
> | [conda-forge] | `conda install -c conda-forge zoxide` |
|
||||
> | [MacPorts] | `port install zoxide` |
|
||||
> | [nixpkgs] | `nix-env -iA nixpkgs.zoxide` |
|
||||
>
|
||||
> Or, run this command in your terminal:
|
||||
>
|
||||
> ```sh
|
||||
> curl -sSfL https://raw.githubusercontent.com/ajeetdsouza/zoxide/main/install.sh | sh
|
||||
> ```
|
||||
|
||||
#### On macOS (via MacPorts) [](https://ports.macports.org/port/zoxide/summary)
|
||||
</details>
|
||||
|
||||
```sh
|
||||
port install zoxide
|
||||
```
|
||||
<details>
|
||||
<summary>Windows</summary>
|
||||
|
||||
#### On NixOS [](https://nixos.org/nixos/packages.html?attr=zoxide&channel=nixpkgs-unstable)
|
||||
> zoxide works with PowerShell, as well as shells running in Cygwin, Git
|
||||
> Bash, and MSYS2.
|
||||
>
|
||||
> The recommended way to install zoxide is via `winget`:
|
||||
>
|
||||
> ```sh
|
||||
> winget install ajeetdsouza.zoxide
|
||||
> ```
|
||||
>
|
||||
> Or, you can use an alternative package manager:
|
||||
>
|
||||
> | Repository | Instructions |
|
||||
> | --------------- | ------------------------------------- |
|
||||
> | **[crates.io]** | `cargo install zoxide --locked` |
|
||||
> | [Chocolatey] | `choco install zoxide` |
|
||||
> | [conda-forge] | `conda install -c conda-forge zoxide` |
|
||||
> | [Scoop] | `scoop install zoxide` |
|
||||
>
|
||||
> If you're using Cygwin, Git Bash, or MSYS2, you can also use the install script:
|
||||
>
|
||||
> ```sh
|
||||
> curl -sSfL https://raw.githubusercontent.com/ajeetdsouza/zoxide/main/install.sh | sh
|
||||
> ```
|
||||
|
||||
```sh
|
||||
nix-env -iA nixpkgs.zoxide
|
||||
```
|
||||
</details>
|
||||
|
||||
#### On Void Linux [](https://github.com/void-linux/void-packages/tree/master/srcpkgs/zoxide)
|
||||
<details>
|
||||
<summary>BSD</summary>
|
||||
|
||||
```sh
|
||||
xbps-install -S zoxide
|
||||
```
|
||||
> To install zoxide, use a package manager:
|
||||
>
|
||||
> | Distribution | Repository | Instructions |
|
||||
> | ------------- | --------------- | ------------------------------- |
|
||||
> | **_Any_** | **[crates.io]** | `cargo install zoxide --locked` |
|
||||
> | DragonFly BSD | [DPorts] | `pkg install zoxide` |
|
||||
> | FreeBSD | [FreshPorts] | `pkg install zoxide` |
|
||||
> | NetBSD | [pkgsrc] | `pkgin install zoxide` |
|
||||
>
|
||||
> Or, run this command in your terminal:
|
||||
>
|
||||
> ```sh
|
||||
> curl -sS https://raw.githubusercontent.com/ajeetdsouza/zoxide/main/install.sh | bash
|
||||
> ```
|
||||
|
||||
#### On Windows (via [Scoop](https://github.com/lukesampson/scoop)) [](https://github.com/ScoopInstaller/Main/tree/master/bucket/zoxide.json)
|
||||
</details>
|
||||
|
||||
```powershell
|
||||
scoop install zoxide
|
||||
```
|
||||
<details>
|
||||
<summary>Android</summary>
|
||||
|
||||
#### Other (via Cargo) [](https://crates.io/crates/zoxide)
|
||||
> To install zoxide, use a package manager:
|
||||
>
|
||||
> | Repository | Instructions |
|
||||
> | ---------- | -------------------- |
|
||||
> | [Termux] | `pkg install zoxide` |
|
||||
>
|
||||
> Or, run this command in your terminal:
|
||||
>
|
||||
> ```sh
|
||||
> curl -sS https://raw.githubusercontent.com/ajeetdsouza/zoxide/main/install.sh | bash
|
||||
> ```
|
||||
|
||||
```sh
|
||||
cargo install zoxide -f
|
||||
```
|
||||
</details>
|
||||
|
||||
#### Other (via precompiled binary) [](https://github.com/ajeetdsouza/zoxide/releases)
|
||||
2. **Setup zoxide on your shell**
|
||||
|
||||
Use the installation script:
|
||||
To start using zoxide, add it to your shell.
|
||||
|
||||
```sh
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/ajeetdsouza/zoxide/master/install.sh | sh
|
||||
```
|
||||
<details>
|
||||
<summary>Bash</summary>
|
||||
|
||||
Alternatively, you can also download a precompiled binary from the
|
||||
[releases](https://github.com/ajeetdsouza/zoxide/releases) page and add it to
|
||||
your `PATH`.
|
||||
> Add this to the <ins>**end**</ins> of your config file (usually `~/.bashrc`):
|
||||
>
|
||||
> ```sh
|
||||
> eval "$(zoxide init bash)"
|
||||
> ```
|
||||
|
||||
### Step 2: Installing `fzf` (optional)
|
||||
</details>
|
||||
|
||||
If you want to use interactive fuzzy selection, you will also need to install
|
||||
[`fzf`](https://github.com/junegunn/fzf#installation).
|
||||
<details>
|
||||
<summary>Elvish</summary>
|
||||
|
||||
### Step 3: Adding `zoxide` to your shell
|
||||
> Add this to the <ins>**end**</ins> of your config file (usually `~/.elvish/rc.elv`):
|
||||
>
|
||||
> ```sh
|
||||
> eval (zoxide init elvish | slurp)
|
||||
> ```
|
||||
>
|
||||
> **Note**
|
||||
> zoxide only supports elvish v0.18.0 and above.
|
||||
|
||||
If you currently use `z`, `z.lua`, or `zsh-z`, you may want to first import
|
||||
your existing database into `zoxide`:
|
||||
</details>
|
||||
|
||||
```sh
|
||||
zoxide import /path/to/db
|
||||
```
|
||||
<details>
|
||||
<summary>Fish</summary>
|
||||
|
||||
#### bash
|
||||
> Add this to the <ins>**end**</ins> of your config file (usually
|
||||
> `~/.config/fish/config.fish`):
|
||||
>
|
||||
> ```sh
|
||||
> zoxide init fish | source
|
||||
> ```
|
||||
|
||||
Add the following line to your `~/.bashrc`:
|
||||
</details>
|
||||
|
||||
```sh
|
||||
eval "$(zoxide init bash)"
|
||||
```
|
||||
<details>
|
||||
<summary>Nushell</summary>
|
||||
|
||||
#### fish
|
||||
> Add this to the <ins>**end**</ins> of your env file (find it by running `$nu.env-path`
|
||||
> in Nushell):
|
||||
>
|
||||
> ```sh
|
||||
> zoxide init nushell | save -f ~/.zoxide.nu
|
||||
> ```
|
||||
>
|
||||
> Now, add this to the <ins>**end**</ins> of your config file (find it by running
|
||||
> `$nu.config-path` in Nushell):
|
||||
>
|
||||
> ```sh
|
||||
> source ~/.zoxide.nu
|
||||
> ```
|
||||
>
|
||||
> **Note**
|
||||
> zoxide only supports Nushell v0.89.0+.
|
||||
|
||||
Add the following line to your `~/.config/fish/config.fish`:
|
||||
</details>
|
||||
|
||||
```sh
|
||||
zoxide init fish | source
|
||||
```
|
||||
<details>
|
||||
<summary>PowerShell</summary>
|
||||
|
||||
#### POSIX shells
|
||||
> Add this to the <ins>**end**</ins> of your config file (find it by running
|
||||
> `echo $profile` in PowerShell):
|
||||
>
|
||||
> ```powershell
|
||||
> Invoke-Expression (& { (zoxide init powershell | Out-String) })
|
||||
> ```
|
||||
|
||||
Add the following line to your shell's configuration file:
|
||||
</details>
|
||||
|
||||
```sh
|
||||
eval "$(zoxide init posix --hook prompt)"
|
||||
```
|
||||
<details>
|
||||
<summary>Tcsh</summary>
|
||||
|
||||
#### PowerShell
|
||||
> Add this to the <ins>**end**</ins> of your config file (usually `~/.tcshrc`):
|
||||
>
|
||||
> ```sh
|
||||
> zoxide init tcsh > ~/.zoxide.tcsh
|
||||
> source ~/.zoxide.tcsh
|
||||
> ```
|
||||
|
||||
Add the following line to your profile:
|
||||
</details>
|
||||
|
||||
```powershell
|
||||
Invoke-Expression (& {
|
||||
$hook = if ($PSVersionTable.PSVersion.Major -lt 6) { 'prompt' } else { 'pwd' }
|
||||
(zoxide init --hook $hook powershell) -join "`n"
|
||||
})
|
||||
```
|
||||
<details>
|
||||
<summary>Xonsh</summary>
|
||||
|
||||
#### xonsh
|
||||
> Add this to the <ins>**end**</ins> of your config file (usually `~/.xonshrc`):
|
||||
>
|
||||
> ```python
|
||||
> execx($(zoxide init xonsh), 'exec', __xonsh__.ctx, filename='zoxide')
|
||||
> ```
|
||||
|
||||
Add the following line to your profile (usually `~/.xonshrc`):
|
||||
</details>
|
||||
|
||||
```xonsh
|
||||
execx($(zoxide init xonsh), 'exec', __xonsh__.ctx, filename='zoxide')
|
||||
```
|
||||
<details>
|
||||
<summary>Zsh</summary>
|
||||
|
||||
#### zsh
|
||||
> 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`.
|
||||
|
||||
Add the following line to your `~/.zshrc`:
|
||||
</details>
|
||||
|
||||
```sh
|
||||
eval "$(zoxide init zsh)"
|
||||
```
|
||||
<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
|
||||
|
||||
### `init` flags
|
||||
### Flags
|
||||
|
||||
- `--cmd`: change the `z` command (and corresponding aliases) to something else
|
||||
- `--hook <HOOK>`: change the event that adds a new entry to the database
|
||||
(default: `pwd`)
|
||||
- `none`: never add entries
|
||||
(this will make `zoxide` useless unless you manually configure a hook)
|
||||
- `prompt`: add an entry at every prompt
|
||||
- `pwd`: add an entry whenever the current directory is changed
|
||||
- `--no-aliases`: don't define extra aliases like `zi`, `zq`, `za`, and `zr`
|
||||
When calling `zoxide init`, the following flags are available:
|
||||
|
||||
- `--cmd`
|
||||
- Changes the prefix of the `z` and `zi` commands.
|
||||
- `--cmd j` would change the commands to (`j`, `ji`).
|
||||
- `--cmd cd` would replace the `cd` command.
|
||||
- `--hook <HOOK>`
|
||||
- Changes how often zoxide increments a directory's score:
|
||||
|
||||
| Hook | Description |
|
||||
| --------------- | --------------------------------- |
|
||||
| `none` | Never |
|
||||
| `prompt` | At every shell prompt |
|
||||
| `pwd` (default) | Whenever the directory is changed |
|
||||
|
||||
- `--no-cmd`
|
||||
- Prevents zoxide from defining the `z` and `zi` commands.
|
||||
- These functions will still be available in your shell as `__zoxide_z` and
|
||||
`__zoxide_zi`, should you choose to redefine them.
|
||||
|
||||
### Environment variables
|
||||
|
||||
- `$_ZO_DATA_DIR`: directory where `zoxide` will store its data files
|
||||
(default: platform-specific; see the [`dirs` documentation] for more information)
|
||||
- `$_ZO_ECHO`: when set to `1`, `z` will print the matched directory before navigating to it
|
||||
- `$_ZO_EXCLUDE_DIRS`: list of directories separated by platform-specific characters
|
||||
("`:`" on Linux/macOS, "`;`" on Windows) to be excluded from the database
|
||||
- `$_ZO_FZF_OPTS`: custom flags to pass to `fzf`
|
||||
- `$_ZO_MAXAGE`: sets the maximum total age after which entries start getting deleted
|
||||
- `$_ZO_RESOLVE_SYMLINKS`: when set to `1`, `z add` will resolve symlinks.
|
||||
Environment variables[^2] can be used for configuration. They must be set before
|
||||
`zoxide init` is called.
|
||||
|
||||
[`dirs` documentation]: https://docs.rs/dirs/latest/dirs/fn.data_local_dir.html
|
||||
- `_ZO_DATA_DIR`
|
||||
- Specifies the directory in which the database is stored.
|
||||
- The default value varies across OSes:
|
||||
|
||||
| OS | Path | Example |
|
||||
| ----------- | ---------------------------------------- | ------------------------------------------ |
|
||||
| Linux / BSD | `$XDG_DATA_HOME` or `$HOME/.local/share` | `/home/alice/.local/share` |
|
||||
| macOS | `$HOME/Library/Application Support` | `/Users/Alice/Library/Application Support` |
|
||||
| Windows | `%LOCALAPPDATA%` | `C:\Users\Alice\AppData\Local` |
|
||||
|
||||
- `_ZO_ECHO`
|
||||
- When set to 1, `z` will print the matched directory before navigating to
|
||||
it.
|
||||
- `_ZO_EXCLUDE_DIRS`
|
||||
- Excludes the specified directories from the database.
|
||||
- This is provided as a list of [globs][glob], separated by OS-specific
|
||||
characters:
|
||||
|
||||
| OS | Separator | Example |
|
||||
| ------------------- | --------- | ----------------------- |
|
||||
| Linux / macOS / BSD | `:` | `$HOME:$HOME/private/*` |
|
||||
| Windows | `;` | `$HOME;$HOME/private/*` |
|
||||
|
||||
- By default, this is set to `"$HOME"`.
|
||||
- `_ZO_FZF_OPTS`
|
||||
- Custom options to pass to [fzf] during interactive selection. See
|
||||
[`man fzf`][fzf-man] for the list of options.
|
||||
- `_ZO_MAXAGE`
|
||||
- Configures the [aging algorithm][algorithm-aging], which limits the maximum
|
||||
number of entries in the database.
|
||||
- By default, this is set to 10000.
|
||||
- `_ZO_RESOLVE_SYMLINKS`
|
||||
- When set to 1, `z` will resolve symlinks before adding directories to the
|
||||
database.
|
||||
|
||||
## Third-party integrations
|
||||
|
||||
| Application | Description | Plugin |
|
||||
| --------------------- | -------------------------------------------- | -------------------------- |
|
||||
| [aerc] | Email client | Natively supported |
|
||||
| [alfred] | macOS launcher | [alfred-zoxide] |
|
||||
| [clink] | Improved cmd.exe for Windows | [clink-zoxide] |
|
||||
| [emacs] | Text editor | [zoxide.el] |
|
||||
| [felix] | File manager | Natively supported |
|
||||
| [joshuto] | File manager | Natively supported |
|
||||
| [lf] | File manager | See the [wiki][lf-wiki] |
|
||||
| [nnn] | File manager | [nnn-autojump] |
|
||||
| [ranger] | File manager | [ranger-zoxide] |
|
||||
| [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-matching]: https://github.com/ajeetdsouza/zoxide/wiki/Algorithm#matching
|
||||
[alpine linux packages]: https://pkgs.alpinelinux.org/packages?name=zoxide
|
||||
[apt.cli.rs]: https://apt.cli.rs/
|
||||
[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/
|
||||
[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
|
||||
[crates.io-badge]: https://img.shields.io/crates/v/zoxide?logo=rust&logoColor=white&style=flat-square
|
||||
[crates.io]: https://crates.io/crates/zoxide
|
||||
[debian packages]: https://packages.debian.org/stable/admin/zoxide
|
||||
[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
|
||||
[downloads-badge]: https://img.shields.io/github/downloads/ajeetdsouza/zoxide/total?logo=github&logoColor=white&style=flat-square
|
||||
[dports]: https://github.com/DragonFlyBSD/DPorts/tree/master/sysutils/zoxide
|
||||
[emacs]: https://www.gnu.org/software/emacs/
|
||||
[fedora packages]: https://src.fedoraproject.org/rpms/rust-zoxide
|
||||
[felix]: https://github.com/kyoheiu/felix
|
||||
[freshports]: https://www.freshports.org/sysutils/zoxide/
|
||||
[fzf-installation]: https://github.com/junegunn/fzf#installation
|
||||
[fzf-man]: https://manpages.ubuntu.com/manpages/en/man1/fzf.1.html
|
||||
[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
|
||||
[guix]: https://packages.guix.gnu.org/packages/zoxide/
|
||||
[homebrew]: https://formulae.brew.sh/formula/zoxide
|
||||
[issues]: https://github.com/ajeetdsouza/zoxide/issues/new
|
||||
[joshuto]: https://github.com/kamiyaa/joshuto
|
||||
[lf]: https://github.com/gokcehan/lf
|
||||
[lf-wiki]: https://github.com/gokcehan/lf/wiki/Integrations#zoxide
|
||||
[linuxbrew]: https://formulae.brew.sh/formula-linux/zoxide
|
||||
[macports]: https://ports.macports.org/port/zoxide/summary
|
||||
[neovim]: https://github.com/neovim/neovim
|
||||
[nixpkgs]: https://github.com/NixOS/nixpkgs/blob/master/pkgs/by-name/zo/zoxide/package.nix
|
||||
[nnn-autojump]: https://github.com/jarun/nnn/blob/master/plugins/autojump
|
||||
[nnn]: https://github.com/jarun/nnn
|
||||
[opensuse factory]: https://build.opensuse.org/package/show/openSUSE:Factory/zoxide
|
||||
[pacstall packages]: https://pacstall.dev/packages/zoxide-deb
|
||||
[pkgsrc]: https://pkgsrc.se/sysutils/zoxide
|
||||
[ranger-zoxide]: https://github.com/jchook/ranger-zoxide
|
||||
[ranger]: https://github.com/ranger/ranger
|
||||
[raspbian packages]: https://archive.raspbian.org/raspbian/pool/main/r/rust-zoxide/
|
||||
[raycast]: https://www.raycast.com/
|
||||
[raycast-zoxide]: https://www.raycast.com/mrpunkin/raycast-zoxide
|
||||
[releases]: https://github.com/ajeetdsouza/zoxide/releases
|
||||
[rfm]: https://github.com/dsxmachina/rfm
|
||||
[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.nvim]: https://github.com/nvim-telescope/telescope.nvim
|
||||
[termux]: https://github.com/termux/termux-packages/tree/master/packages/zoxide
|
||||
[tmux-session-wizard]: https://github.com/27medkamal/tmux-session-wizard
|
||||
[tmux-sessionx]: https://github.com/omerxx/tmux-sessionx
|
||||
[tutorial]: contrib/tutorial.webp
|
||||
[vim]: https://github.com/vim/vim
|
||||
[void linux packages]: https://github.com/void-linux/void-packages/tree/master/srcpkgs/zoxide
|
||||
[wiki-env]: https://github.com/ajeetdsouza/zoxide/wiki/HOWTO:-set-environment-variables "HOWTO: set environment variables"
|
||||
[xplr]: https://github.com/sayanarijit/xplr
|
||||
[xxh-plugin-prerun-zoxide]: https://github.com/xxh/xxh-plugin-prerun-zoxide
|
||||
[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.vim]: https://github.com/nanotee/zoxide.vim
|
||||
[zoxide.xplr]: https://github.com/sayanarijit/zoxide.xplr
|
||||
[zsh-autocomplete]: https://github.com/marlonrichert/zsh-autocomplete
|
||||
|
|
44
build.rs
44
build.rs
|
@ -1,16 +1,36 @@
|
|||
use std::process::Command;
|
||||
#[path = "src/cmd/cmd.rs"]
|
||||
mod cmd;
|
||||
|
||||
fn main() {
|
||||
let git_describe = Command::new("git")
|
||||
.args(&["describe", "--tags", "--broken"])
|
||||
.output()
|
||||
.ok()
|
||||
.and_then(|proc| String::from_utf8(proc.stdout).ok());
|
||||
use std::{env, io};
|
||||
|
||||
let version_info = match git_describe {
|
||||
Some(description) if !description.is_empty() => description,
|
||||
_ => format!("v{}-unknown", env!("CARGO_PKG_VERSION")),
|
||||
};
|
||||
use clap::CommandFactory as _;
|
||||
use clap_complete::shells::{Bash, Elvish, Fish, PowerShell, Zsh};
|
||||
use clap_complete_fig::Fig;
|
||||
use clap_complete_nushell::Nushell;
|
||||
use cmd::Cmd;
|
||||
|
||||
println!("cargo:rustc-env=ZOXIDE_VERSION={}", version_info);
|
||||
fn main() -> io::Result<()> {
|
||||
// Since we are generating completions in the package directory, we need to
|
||||
// set this so that Cargo doesn't rebuild every time.
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
println!("cargo:rerun-if-changed=src/");
|
||||
println!("cargo:rerun-if-changed=templates/");
|
||||
println!("cargo:rerun-if-changed=tests/");
|
||||
generate_completions()
|
||||
}
|
||||
|
||||
fn generate_completions() -> io::Result<()> {
|
||||
const BIN_NAME: &str = env!("CARGO_PKG_NAME");
|
||||
const OUT_DIR: &str = "contrib/completions";
|
||||
let cmd = &mut Cmd::command();
|
||||
|
||||
clap_complete::generate_to(Bash, cmd, BIN_NAME, OUT_DIR)?;
|
||||
clap_complete::generate_to(Elvish, cmd, BIN_NAME, OUT_DIR)?;
|
||||
clap_complete::generate_to(Fig, cmd, BIN_NAME, OUT_DIR)?;
|
||||
clap_complete::generate_to(Fish, cmd, BIN_NAME, OUT_DIR)?;
|
||||
clap_complete::generate_to(Nushell, cmd, BIN_NAME, OUT_DIR)?;
|
||||
clap_complete::generate_to(PowerShell, cmd, BIN_NAME, OUT_DIR)?;
|
||||
clap_complete::generate_to(Zsh, cmd, BIN_NAME, OUT_DIR)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
# completions
|
||||
|
||||
Shell completions for zoxide, generated by [clap]. Since clap is in beta, these
|
||||
completions should not be treated as stable.
|
||||
|
||||
[clap]: https://github.com/clap-rs/clap
|
|
@ -0,0 +1,225 @@
|
|||
#compdef zoxide
|
||||
|
||||
autoload -U is-at-least
|
||||
|
||||
_zoxide() {
|
||||
typeset -A opt_args
|
||||
typeset -a _arguments_options
|
||||
local ret=1
|
||||
|
||||
if is-at-least 5.2; then
|
||||
_arguments_options=(-s -S -C)
|
||||
else
|
||||
_arguments_options=(-s -C)
|
||||
fi
|
||||
|
||||
local context curcontext="$curcontext" state line
|
||||
_arguments "${_arguments_options[@]}" : \
|
||||
'-h[Print help]' \
|
||||
'--help[Print help]' \
|
||||
'-V[Print version]' \
|
||||
'--version[Print version]' \
|
||||
":: :_zoxide_commands" \
|
||||
"*::: :->zoxide" \
|
||||
&& ret=0
|
||||
case $state in
|
||||
(zoxide)
|
||||
words=($line[1] "${words[@]}")
|
||||
(( CURRENT += 1 ))
|
||||
curcontext="${curcontext%:*:*}:zoxide-command-$line[1]:"
|
||||
case $line[1] in
|
||||
(add)
|
||||
_arguments "${_arguments_options[@]}" : \
|
||||
'-s+[The rank to increment the entry if it exists or initialize it with if it doesn'\''t]:SCORE:_default' \
|
||||
'--score=[The rank to increment the entry if it exists or initialize it with if it doesn'\''t]:SCORE:_default' \
|
||||
'-h[Print help]' \
|
||||
'--help[Print help]' \
|
||||
'-V[Print version]' \
|
||||
'--version[Print version]' \
|
||||
'*::paths:_files -/' \
|
||||
&& ret=0
|
||||
;;
|
||||
(edit)
|
||||
_arguments "${_arguments_options[@]}" : \
|
||||
'-h[Print help]' \
|
||||
'--help[Print help]' \
|
||||
'-V[Print version]' \
|
||||
'--version[Print version]' \
|
||||
":: :_zoxide__edit_commands" \
|
||||
"*::: :->edit" \
|
||||
&& ret=0
|
||||
|
||||
case $state in
|
||||
(edit)
|
||||
words=($line[1] "${words[@]}")
|
||||
(( CURRENT += 1 ))
|
||||
curcontext="${curcontext%:*:*}:zoxide-edit-command-$line[1]:"
|
||||
case $line[1] in
|
||||
(decrement)
|
||||
_arguments "${_arguments_options[@]}" : \
|
||||
'-h[Print help]' \
|
||||
'--help[Print help]' \
|
||||
'-V[Print version]' \
|
||||
'--version[Print version]' \
|
||||
':path:_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)
|
||||
_arguments "${_arguments_options[@]}" : \
|
||||
'--from=[Application to import from]:FROM:(autojump z)' \
|
||||
'--merge[Merge into existing database]' \
|
||||
'-h[Print help]' \
|
||||
'--help[Print help]' \
|
||||
'-V[Print version]' \
|
||||
'--version[Print version]' \
|
||||
':path:_files' \
|
||||
&& ret=0
|
||||
;;
|
||||
(init)
|
||||
_arguments "${_arguments_options[@]}" : \
|
||||
'--cmd=[Changes the prefix of the \`z\` and \`zi\` commands]:CMD:_default' \
|
||||
'--hook=[Changes how often zoxide increments a directory'\''s score]:HOOK:(none prompt pwd)' \
|
||||
'--no-cmd[Prevents zoxide from defining the \`z\` and \`zi\` commands]' \
|
||||
'-h[Print help]' \
|
||||
'--help[Print help]' \
|
||||
'-V[Print version]' \
|
||||
'--version[Print version]' \
|
||||
':shell:(bash elvish fish nushell posix powershell tcsh xonsh zsh)' \
|
||||
&& ret=0
|
||||
;;
|
||||
(query)
|
||||
_arguments "${_arguments_options[@]}" : \
|
||||
'--exclude=[Exclude the current directory]:path:_files -/' \
|
||||
'--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)--interactive[Use interactive selection]' \
|
||||
'(-i --interactive)-l[List all matching directories]' \
|
||||
'(-i --interactive)--list[List all matching directories]' \
|
||||
'-s[Print score with results]' \
|
||||
'--score[Print score with results]' \
|
||||
'-h[Print help]' \
|
||||
'--help[Print help]' \
|
||||
'-V[Print version]' \
|
||||
'--version[Print version]' \
|
||||
'*::keywords:_default' \
|
||||
&& ret=0
|
||||
;;
|
||||
(remove)
|
||||
_arguments "${_arguments_options[@]}" : \
|
||||
'-h[Print help]' \
|
||||
'--help[Print help]' \
|
||||
'-V[Print version]' \
|
||||
'--version[Print version]' \
|
||||
'*::paths:_files -/' \
|
||||
&& ret=0
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
(( $+functions[_zoxide_commands] )) ||
|
||||
_zoxide_commands() {
|
||||
local commands; commands=(
|
||||
'add:Add a new directory or increment its rank' \
|
||||
'edit:Edit the database' \
|
||||
'import:Import entries from another application' \
|
||||
'init:Generate shell configuration' \
|
||||
'query:Search for a directory in the database' \
|
||||
'remove:Remove a directory from the database' \
|
||||
)
|
||||
_describe -t commands 'zoxide commands' commands "$@"
|
||||
}
|
||||
(( $+functions[_zoxide__add_commands] )) ||
|
||||
_zoxide__add_commands() {
|
||||
local 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] )) ||
|
||||
_zoxide__import_commands() {
|
||||
local commands; commands=()
|
||||
_describe -t commands 'zoxide import commands' commands "$@"
|
||||
}
|
||||
(( $+functions[_zoxide__init_commands] )) ||
|
||||
_zoxide__init_commands() {
|
||||
local commands; commands=()
|
||||
_describe -t commands 'zoxide init commands' commands "$@"
|
||||
}
|
||||
(( $+functions[_zoxide__query_commands] )) ||
|
||||
_zoxide__query_commands() {
|
||||
local commands; commands=()
|
||||
_describe -t commands 'zoxide query commands' commands "$@"
|
||||
}
|
||||
(( $+functions[_zoxide__remove_commands] )) ||
|
||||
_zoxide__remove_commands() {
|
||||
local commands; commands=()
|
||||
_describe -t commands 'zoxide remove commands' commands "$@"
|
||||
}
|
||||
|
||||
if [ "$funcstack[1]" = "_zoxide" ]; then
|
||||
_zoxide "$@"
|
||||
else
|
||||
compdef _zoxide zoxide
|
||||
fi
|
|
@ -0,0 +1,131 @@
|
|||
|
||||
using namespace System.Management.Automation
|
||||
using namespace System.Management.Automation.Language
|
||||
|
||||
Register-ArgumentCompleter -Native -CommandName 'zoxide' -ScriptBlock {
|
||||
param($wordToComplete, $commandAst, $cursorPosition)
|
||||
|
||||
$commandElements = $commandAst.CommandElements
|
||||
$command = @(
|
||||
'zoxide'
|
||||
for ($i = 1; $i -lt $commandElements.Count; $i++) {
|
||||
$element = $commandElements[$i]
|
||||
if ($element -isnot [StringConstantExpressionAst] -or
|
||||
$element.StringConstantType -ne [StringConstantType]::BareWord -or
|
||||
$element.Value.StartsWith('-') -or
|
||||
$element.Value -eq $wordToComplete) {
|
||||
break
|
||||
}
|
||||
$element.Value
|
||||
}) -join ';'
|
||||
|
||||
$completions = @(switch ($command) {
|
||||
'zoxide' {
|
||||
[CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')
|
||||
[CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')
|
||||
[CompletionResult]::new('-V', '-V ', [CompletionResultType]::ParameterName, 'Print version')
|
||||
[CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'Print version')
|
||||
[CompletionResult]::new('add', 'add', [CompletionResultType]::ParameterValue, 'Add a new directory or increment its rank')
|
||||
[CompletionResult]::new('edit', 'edit', [CompletionResultType]::ParameterValue, 'Edit the database')
|
||||
[CompletionResult]::new('import', 'import', [CompletionResultType]::ParameterValue, 'Import entries from another application')
|
||||
[CompletionResult]::new('init', 'init', [CompletionResultType]::ParameterValue, 'Generate shell configuration')
|
||||
[CompletionResult]::new('query', 'query', [CompletionResultType]::ParameterValue, 'Search for a directory in the database')
|
||||
[CompletionResult]::new('remove', 'remove', [CompletionResultType]::ParameterValue, 'Remove a directory from the database')
|
||||
break
|
||||
}
|
||||
'zoxide;add' {
|
||||
[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('--score', '--score', [CompletionResultType]::ParameterName, 'The rank to increment the entry if it exists or initialize it with if it doesn''t')
|
||||
[CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')
|
||||
[CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')
|
||||
[CompletionResult]::new('-V', '-V ', [CompletionResultType]::ParameterName, 'Print version')
|
||||
[CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'Print version')
|
||||
break
|
||||
}
|
||||
'zoxide;edit' {
|
||||
[CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')
|
||||
[CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')
|
||||
[CompletionResult]::new('-V', '-V ', [CompletionResultType]::ParameterName, 'Print version')
|
||||
[CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'Print version')
|
||||
[CompletionResult]::new('decrement', 'decrement', [CompletionResultType]::ParameterValue, 'decrement')
|
||||
[CompletionResult]::new('delete', 'delete', [CompletionResultType]::ParameterValue, 'delete')
|
||||
[CompletionResult]::new('increment', 'increment', [CompletionResultType]::ParameterValue, 'increment')
|
||||
[CompletionResult]::new('reload', 'reload', [CompletionResultType]::ParameterValue, 'reload')
|
||||
break
|
||||
}
|
||||
'zoxide;edit;decrement' {
|
||||
[CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')
|
||||
[CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')
|
||||
[CompletionResult]::new('-V', '-V ', [CompletionResultType]::ParameterName, 'Print version')
|
||||
[CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'Print version')
|
||||
break
|
||||
}
|
||||
'zoxide;edit;delete' {
|
||||
[CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')
|
||||
[CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')
|
||||
[CompletionResult]::new('-V', '-V ', [CompletionResultType]::ParameterName, 'Print version')
|
||||
[CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'Print version')
|
||||
break
|
||||
}
|
||||
'zoxide;edit;increment' {
|
||||
[CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')
|
||||
[CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')
|
||||
[CompletionResult]::new('-V', '-V ', [CompletionResultType]::ParameterName, 'Print version')
|
||||
[CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'Print version')
|
||||
break
|
||||
}
|
||||
'zoxide;edit;reload' {
|
||||
[CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')
|
||||
[CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')
|
||||
[CompletionResult]::new('-V', '-V ', [CompletionResultType]::ParameterName, 'Print version')
|
||||
[CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'Print version')
|
||||
break
|
||||
}
|
||||
'zoxide;import' {
|
||||
[CompletionResult]::new('--from', '--from', [CompletionResultType]::ParameterName, 'Application to import from')
|
||||
[CompletionResult]::new('--merge', '--merge', [CompletionResultType]::ParameterName, 'Merge into existing database')
|
||||
[CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')
|
||||
[CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')
|
||||
[CompletionResult]::new('-V', '-V ', [CompletionResultType]::ParameterName, 'Print version')
|
||||
[CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'Print version')
|
||||
break
|
||||
}
|
||||
'zoxide;init' {
|
||||
[CompletionResult]::new('--cmd', '--cmd', [CompletionResultType]::ParameterName, 'Changes the prefix of the `z` and `zi` commands')
|
||||
[CompletionResult]::new('--hook', '--hook', [CompletionResultType]::ParameterName, 'Changes how often zoxide increments a directory''s score')
|
||||
[CompletionResult]::new('--no-cmd', '--no-cmd', [CompletionResultType]::ParameterName, 'Prevents zoxide from defining the `z` and `zi` commands')
|
||||
[CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')
|
||||
[CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')
|
||||
[CompletionResult]::new('-V', '-V ', [CompletionResultType]::ParameterName, 'Print version')
|
||||
[CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'Print version')
|
||||
break
|
||||
}
|
||||
'zoxide;query' {
|
||||
[CompletionResult]::new('--exclude', '--exclude', [CompletionResultType]::ParameterName, 'Exclude the current directory')
|
||||
[CompletionResult]::new('--base-dir', '--base-dir', [CompletionResultType]::ParameterName, 'Only search within this directory')
|
||||
[CompletionResult]::new('-a', '-a', [CompletionResultType]::ParameterName, 'Show unavailable directories')
|
||||
[CompletionResult]::new('--all', '--all', [CompletionResultType]::ParameterName, 'Show unavailable directories')
|
||||
[CompletionResult]::new('-i', '-i', [CompletionResultType]::ParameterName, 'Use interactive selection')
|
||||
[CompletionResult]::new('--interactive', '--interactive', [CompletionResultType]::ParameterName, 'Use interactive selection')
|
||||
[CompletionResult]::new('-l', '-l', [CompletionResultType]::ParameterName, 'List all matching directories')
|
||||
[CompletionResult]::new('--list', '--list', [CompletionResultType]::ParameterName, 'List all matching directories')
|
||||
[CompletionResult]::new('-s', '-s', [CompletionResultType]::ParameterName, 'Print score with results')
|
||||
[CompletionResult]::new('--score', '--score', [CompletionResultType]::ParameterName, 'Print score with results')
|
||||
[CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')
|
||||
[CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')
|
||||
[CompletionResult]::new('-V', '-V ', [CompletionResultType]::ParameterName, 'Print version')
|
||||
[CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'Print version')
|
||||
break
|
||||
}
|
||||
'zoxide;remove' {
|
||||
[CompletionResult]::new('-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
|
||||
}
|
||||
})
|
||||
|
||||
$completions.Where{ $_.CompletionText -like "$wordToComplete*" } |
|
||||
Sort-Object -Property ListItemText
|
||||
}
|
|
@ -0,0 +1,250 @@
|
|||
_zoxide() {
|
||||
local i cur prev opts cmd
|
||||
COMPREPLY=()
|
||||
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
|
||||
cur="$2"
|
||||
else
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
fi
|
||||
prev="$3"
|
||||
cmd=""
|
||||
opts=""
|
||||
|
||||
for i in "${COMP_WORDS[@]:0:COMP_CWORD}"
|
||||
do
|
||||
case "${cmd},${i}" in
|
||||
",$1")
|
||||
cmd="zoxide"
|
||||
;;
|
||||
zoxide,add)
|
||||
cmd="zoxide__add"
|
||||
;;
|
||||
zoxide,edit)
|
||||
cmd="zoxide__edit"
|
||||
;;
|
||||
zoxide,import)
|
||||
cmd="zoxide__import"
|
||||
;;
|
||||
zoxide,init)
|
||||
cmd="zoxide__init"
|
||||
;;
|
||||
zoxide,query)
|
||||
cmd="zoxide__query"
|
||||
;;
|
||||
zoxide,remove)
|
||||
cmd="zoxide__remove"
|
||||
;;
|
||||
zoxide__edit,decrement)
|
||||
cmd="zoxide__edit__decrement"
|
||||
;;
|
||||
zoxide__edit,delete)
|
||||
cmd="zoxide__edit__delete"
|
||||
;;
|
||||
zoxide__edit,increment)
|
||||
cmd="zoxide__edit__increment"
|
||||
;;
|
||||
zoxide__edit,reload)
|
||||
cmd="zoxide__edit__reload"
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
case "${cmd}" in
|
||||
zoxide)
|
||||
opts="-h -V --help --version add edit import init query remove"
|
||||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
fi
|
||||
case "${prev}" in
|
||||
*)
|
||||
COMPREPLY=()
|
||||
;;
|
||||
esac
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
;;
|
||||
zoxide__add)
|
||||
opts="-s -h -V --score --help --version <PATHS>..."
|
||||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
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
|
||||
*)
|
||||
COMPREPLY=()
|
||||
;;
|
||||
esac
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
;;
|
||||
zoxide__import)
|
||||
opts="-h -V --from --merge --help --version <PATH>"
|
||||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
fi
|
||||
case "${prev}" in
|
||||
--from)
|
||||
COMPREPLY=($(compgen -W "autojump z" -- "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
COMPREPLY=()
|
||||
;;
|
||||
esac
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
;;
|
||||
zoxide__init)
|
||||
opts="-h -V --no-cmd --cmd --hook --help --version bash elvish fish nushell posix powershell tcsh xonsh zsh"
|
||||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
fi
|
||||
case "${prev}" in
|
||||
--cmd)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
--hook)
|
||||
COMPREPLY=($(compgen -W "none prompt pwd" -- "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
COMPREPLY=()
|
||||
;;
|
||||
esac
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
;;
|
||||
zoxide__query)
|
||||
opts="-a -i -l -s -h -V --all --interactive --list --score --exclude --base-dir --help --version [KEYWORDS]..."
|
||||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
fi
|
||||
case "${prev}" in
|
||||
--exclude)
|
||||
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
|
||||
;;
|
||||
*)
|
||||
COMPREPLY=()
|
||||
;;
|
||||
esac
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
;;
|
||||
zoxide__remove)
|
||||
opts="-h -V --help --version [PATHS]..."
|
||||
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
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
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
|
|
@ -0,0 +1,115 @@
|
|||
|
||||
use builtin;
|
||||
use str;
|
||||
|
||||
set edit:completion:arg-completer[zoxide] = {|@words|
|
||||
fn spaces {|n|
|
||||
builtin:repeat $n ' ' | str:join ''
|
||||
}
|
||||
fn cand {|text desc|
|
||||
edit:complex-candidate $text &display=$text' '(spaces (- 14 (wcswidth $text)))$desc
|
||||
}
|
||||
var command = 'zoxide'
|
||||
for word $words[1..-1] {
|
||||
if (str:has-prefix $word '-') {
|
||||
break
|
||||
}
|
||||
set command = $command';'$word
|
||||
}
|
||||
var completions = [
|
||||
&'zoxide'= {
|
||||
cand -h 'Print help'
|
||||
cand --help 'Print help'
|
||||
cand -V 'Print version'
|
||||
cand --version 'Print version'
|
||||
cand add 'Add a new directory or increment its rank'
|
||||
cand edit 'Edit the database'
|
||||
cand import 'Import entries from another application'
|
||||
cand init 'Generate shell configuration'
|
||||
cand query 'Search for a directory in the database'
|
||||
cand remove 'Remove a directory from the database'
|
||||
}
|
||||
&'zoxide;add'= {
|
||||
cand -s 'The rank to increment the entry if it exists or initialize it with if it doesn''t'
|
||||
cand --score 'The rank to increment the entry if it exists or initialize it with if it doesn''t'
|
||||
cand -h 'Print help'
|
||||
cand --help 'Print help'
|
||||
cand -V 'Print version'
|
||||
cand --version 'Print version'
|
||||
}
|
||||
&'zoxide;edit'= {
|
||||
cand -h 'Print help'
|
||||
cand --help 'Print help'
|
||||
cand -V 'Print version'
|
||||
cand --version 'Print version'
|
||||
cand decrement 'decrement'
|
||||
cand delete 'delete'
|
||||
cand increment 'increment'
|
||||
cand reload 'reload'
|
||||
}
|
||||
&'zoxide;edit;decrement'= {
|
||||
cand -h 'Print help'
|
||||
cand --help 'Print help'
|
||||
cand -V 'Print version'
|
||||
cand --version 'Print version'
|
||||
}
|
||||
&'zoxide;edit;delete'= {
|
||||
cand -h 'Print help'
|
||||
cand --help 'Print help'
|
||||
cand -V 'Print version'
|
||||
cand --version 'Print version'
|
||||
}
|
||||
&'zoxide;edit;increment'= {
|
||||
cand -h 'Print help'
|
||||
cand --help 'Print help'
|
||||
cand -V 'Print version'
|
||||
cand --version 'Print version'
|
||||
}
|
||||
&'zoxide;edit;reload'= {
|
||||
cand -h 'Print help'
|
||||
cand --help 'Print help'
|
||||
cand -V 'Print version'
|
||||
cand --version 'Print version'
|
||||
}
|
||||
&'zoxide;import'= {
|
||||
cand --from 'Application to import from'
|
||||
cand --merge 'Merge into existing database'
|
||||
cand -h 'Print help'
|
||||
cand --help 'Print help'
|
||||
cand -V 'Print version'
|
||||
cand --version 'Print version'
|
||||
}
|
||||
&'zoxide;init'= {
|
||||
cand --cmd 'Changes the prefix of the `z` and `zi` commands'
|
||||
cand --hook 'Changes how often zoxide increments a directory''s score'
|
||||
cand --no-cmd 'Prevents zoxide from defining the `z` and `zi` commands'
|
||||
cand -h 'Print help'
|
||||
cand --help 'Print help'
|
||||
cand -V 'Print version'
|
||||
cand --version 'Print version'
|
||||
}
|
||||
&'zoxide;query'= {
|
||||
cand --exclude 'Exclude the current directory'
|
||||
cand --base-dir 'Only search within this directory'
|
||||
cand -a 'Show unavailable directories'
|
||||
cand --all 'Show unavailable directories'
|
||||
cand -i 'Use interactive selection'
|
||||
cand --interactive 'Use interactive selection'
|
||||
cand -l 'List all matching directories'
|
||||
cand --list 'List all matching directories'
|
||||
cand -s 'Print score with results'
|
||||
cand --score 'Print score with results'
|
||||
cand -h 'Print help'
|
||||
cand --help 'Print help'
|
||||
cand -V 'Print version'
|
||||
cand --version 'Print version'
|
||||
}
|
||||
&'zoxide;remove'= {
|
||||
cand -h 'Print help'
|
||||
cand --help 'Print help'
|
||||
cand -V 'Print version'
|
||||
cand --version 'Print version'
|
||||
}
|
||||
]
|
||||
$completions[$command]
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
# Print an optspec for argparse to handle cmd's options that are independent of any subcommand.
|
||||
function __fish_zoxide_global_optspecs
|
||||
string join \n h/help V/version
|
||||
end
|
||||
|
||||
function __fish_zoxide_needs_command
|
||||
# Figure out if the current invocation already has a command.
|
||||
set -l cmd (commandline -opc)
|
||||
set -e cmd[1]
|
||||
argparse -s (__fish_zoxide_global_optspecs) -- $cmd 2>/dev/null
|
||||
or return
|
||||
if set -q argv[1]
|
||||
# Also print the command, so this can be used to figure out what it is.
|
||||
echo $argv[1]
|
||||
return 1
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
function __fish_zoxide_using_subcommand
|
||||
set -l cmd (__fish_zoxide_needs_command)
|
||||
test -z "$cmd"
|
||||
and return 1
|
||||
contains -- $cmd[1] $argv
|
||||
end
|
||||
|
||||
complete -c zoxide -n "__fish_zoxide_needs_command" -s h -l help -d 'Print help'
|
||||
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'
|
|
@ -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 *
|
|
@ -0,0 +1,299 @@
|
|||
const completion: Fig.Spec = {
|
||||
name: "zoxide",
|
||||
description: "A smarter cd command for your terminal",
|
||||
subcommands: [
|
||||
{
|
||||
name: "add",
|
||||
description: "Add a new directory or increment its rank",
|
||||
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"],
|
||||
description: "Print help",
|
||||
},
|
||||
{
|
||||
name: ["-V", "--version"],
|
||||
description: "Print version",
|
||||
},
|
||||
],
|
||||
args: {
|
||||
name: "paths",
|
||||
isVariadic: true,
|
||||
template: "folders",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "edit",
|
||||
description: "Edit the database",
|
||||
subcommands: [
|
||||
{
|
||||
name: "decrement",
|
||||
hidden: true,
|
||||
options: [
|
||||
{
|
||||
name: ["-h", "--help"],
|
||||
description: "Print help",
|
||||
},
|
||||
{
|
||||
name: ["-V", "--version"],
|
||||
description: "Print version",
|
||||
},
|
||||
],
|
||||
args: {
|
||||
name: "path",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "delete",
|
||||
hidden: true,
|
||||
options: [
|
||||
{
|
||||
name: ["-h", "--help"],
|
||||
description: "Print help",
|
||||
},
|
||||
{
|
||||
name: ["-V", "--version"],
|
||||
description: "Print version",
|
||||
},
|
||||
],
|
||||
args: {
|
||||
name: "path",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "increment",
|
||||
hidden: true,
|
||||
options: [
|
||||
{
|
||||
name: ["-h", "--help"],
|
||||
description: "Print help",
|
||||
},
|
||||
{
|
||||
name: ["-V", "--version"],
|
||||
description: "Print version",
|
||||
},
|
||||
],
|
||||
args: {
|
||||
name: "path",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "reload",
|
||||
hidden: true,
|
||||
options: [
|
||||
{
|
||||
name: ["-h", "--help"],
|
||||
description: "Print help",
|
||||
},
|
||||
{
|
||||
name: ["-V", "--version"],
|
||||
description: "Print version",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
options: [
|
||||
{
|
||||
name: ["-h", "--help"],
|
||||
description: "Print help",
|
||||
},
|
||||
{
|
||||
name: ["-V", "--version"],
|
||||
description: "Print version",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "import",
|
||||
description: "Import entries from another application",
|
||||
options: [
|
||||
{
|
||||
name: "--from",
|
||||
description: "Application to import from",
|
||||
isRepeatable: true,
|
||||
args: {
|
||||
name: "from",
|
||||
suggestions: [
|
||||
"autojump",
|
||||
"z",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "--merge",
|
||||
description: "Merge into existing database",
|
||||
},
|
||||
{
|
||||
name: ["-h", "--help"],
|
||||
description: "Print help",
|
||||
},
|
||||
{
|
||||
name: ["-V", "--version"],
|
||||
description: "Print version",
|
||||
},
|
||||
],
|
||||
args: {
|
||||
name: "path",
|
||||
template: "filepaths",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "init",
|
||||
description: "Generate shell configuration",
|
||||
options: [
|
||||
{
|
||||
name: "--cmd",
|
||||
description: "Changes the prefix of the `z` and `zi` commands",
|
||||
isRepeatable: true,
|
||||
args: {
|
||||
name: "cmd",
|
||||
isOptional: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "--hook",
|
||||
description: "Changes how often zoxide increments a directory's score",
|
||||
isRepeatable: true,
|
||||
args: {
|
||||
name: "hook",
|
||||
isOptional: true,
|
||||
suggestions: [
|
||||
"none",
|
||||
"prompt",
|
||||
"pwd",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "--no-cmd",
|
||||
description: "Prevents zoxide from defining the `z` and `zi` commands",
|
||||
},
|
||||
{
|
||||
name: ["-h", "--help"],
|
||||
description: "Print help",
|
||||
},
|
||||
{
|
||||
name: ["-V", "--version"],
|
||||
description: "Print version",
|
||||
},
|
||||
],
|
||||
args: {
|
||||
name: "shell",
|
||||
suggestions: [
|
||||
"bash",
|
||||
"elvish",
|
||||
"fish",
|
||||
"nushell",
|
||||
"posix",
|
||||
"powershell",
|
||||
"tcsh",
|
||||
"xonsh",
|
||||
"zsh",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "query",
|
||||
description: "Search for a directory in the database",
|
||||
options: [
|
||||
{
|
||||
name: "--exclude",
|
||||
description: "Exclude the current directory",
|
||||
isRepeatable: true,
|
||||
args: {
|
||||
name: "exclude",
|
||||
isOptional: true,
|
||||
template: "folders",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "--base-dir",
|
||||
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"],
|
||||
description: "Use interactive selection",
|
||||
exclusiveOn: [
|
||||
"-l",
|
||||
"--list",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: ["-l", "--list"],
|
||||
description: "List all matching directories",
|
||||
exclusiveOn: [
|
||||
"-i",
|
||||
"--interactive",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: ["-s", "--score"],
|
||||
description: "Print score with results",
|
||||
},
|
||||
{
|
||||
name: ["-h", "--help"],
|
||||
description: "Print help",
|
||||
},
|
||||
{
|
||||
name: ["-V", "--version"],
|
||||
description: "Print version",
|
||||
},
|
||||
],
|
||||
args: {
|
||||
name: "keywords",
|
||||
isVariadic: true,
|
||||
isOptional: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "remove",
|
||||
description: "Remove a directory from the database",
|
||||
options: [
|
||||
{
|
||||
name: ["-h", "--help"],
|
||||
description: "Print help",
|
||||
},
|
||||
{
|
||||
name: ["-V", "--version"],
|
||||
description: "Print version",
|
||||
},
|
||||
],
|
||||
args: {
|
||||
name: "paths",
|
||||
isVariadic: true,
|
||||
isOptional: true,
|
||||
template: "folders",
|
||||
},
|
||||
},
|
||||
],
|
||||
options: [
|
||||
{
|
||||
name: ["-h", "--help"],
|
||||
description: "Print help",
|
||||
},
|
||||
{
|
||||
name: ["-V", "--version"],
|
||||
description: "Print version",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default completion;
|
|
@ -1,46 +0,0 @@
|
|||
import os.path
|
||||
import ranger.api
|
||||
import ranger.core.fm
|
||||
import ranger.ext.signals
|
||||
import subprocess
|
||||
|
||||
hook_init_old = ranger.api.hook_init
|
||||
|
||||
|
||||
def hook_init(fm: ranger.core.fm.FM):
|
||||
def zoxide_add(signal: ranger.ext.signals.Signal):
|
||||
path = signal.new.path
|
||||
process = subprocess.Popen(["zoxide", "add", path])
|
||||
process.wait()
|
||||
|
||||
fm.signal_bind("cd", zoxide_add)
|
||||
return hook_init_old(fm)
|
||||
|
||||
|
||||
ranger.api.hook_init = hook_init
|
||||
|
||||
|
||||
class z(ranger.api.commands.Command):
|
||||
def execute(self):
|
||||
try:
|
||||
zoxide = subprocess.Popen(
|
||||
["zoxide", "query"] + self.args[1:],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
stdout, stderr = zoxide.communicate()
|
||||
|
||||
if zoxide.returncode == 0:
|
||||
output = stdout.decode("utf-8").strip()
|
||||
if output:
|
||||
self.fm.notify(output)
|
||||
if os.path.isdir(output):
|
||||
self.fm.cd(output)
|
||||
else:
|
||||
self.fm.notify("zoxide: unexpected exit", bad=True)
|
||||
else:
|
||||
output = stderr.decode("utf-8").strip() or "zoxide: unknown error"
|
||||
self.fm.notify(output, bad=True)
|
||||
|
||||
except Exception as e:
|
||||
self.fm.notify(e, bad=True)
|
Binary file not shown.
After Width: | Height: | Size: 107 KiB |
|
@ -1 +1,5 @@
|
|||
zoxide init fish | source
|
||||
if command -sq zoxide
|
||||
zoxide init fish | source
|
||||
else
|
||||
echo 'zoxide: command not found, please install it from https://github.com/ajeetdsouza/zoxide'
|
||||
end
|
||||
|
|
551
install.sh
551
install.sh
|
@ -1,143 +1,466 @@
|
|||
#!/bin/sh
|
||||
# shellcheck shell=dash
|
||||
# shellcheck disable=SC3043 # Assume `local` extension
|
||||
|
||||
set -u
|
||||
|
||||
usage() {
|
||||
cat 1>&2 <<EOF
|
||||
The installer for zoxide
|
||||
|
||||
USAGE
|
||||
zoxide-install
|
||||
EOF
|
||||
}
|
||||
# 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() {
|
||||
need_cmd uname
|
||||
need_cmd curl
|
||||
need_cmd grep
|
||||
need_cmd cut
|
||||
need_cmd xargs
|
||||
need_cmd chmod
|
||||
install
|
||||
# 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
|
||||
}
|
||||
|
||||
install() {
|
||||
local _ostype _cputype _clibtype _target _cargobuild _install_path
|
||||
_ostype="$(uname -s)"
|
||||
_cputype="$(uname -m)"
|
||||
_cargobuild="no"
|
||||
_install_path="/usr/local/bin"
|
||||
# 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"
|
||||
|
||||
case $_cputype in
|
||||
x86_64 | x86-64 | amd64)
|
||||
_cputype="x86_64"
|
||||
_clibtype="musl"
|
||||
;;
|
||||
*)
|
||||
warning "No binaries are available for your CPU architecture ($_cputype)"
|
||||
_clibtype="gnu"
|
||||
_cargobuild="yes"
|
||||
;;
|
||||
esac
|
||||
BIN_DIR="${BIN_DIR_DEFAULT}"
|
||||
MAN_DIR="${MAN_DIR_DEFAULT}"
|
||||
SUDO="${SUDO_DEFAULT}"
|
||||
|
||||
case $_ostype in
|
||||
Linux)
|
||||
_ostype=unknown-linux-$_clibtype
|
||||
;;
|
||||
Darwin)
|
||||
_ostype=apple-darwin
|
||||
;;
|
||||
*)
|
||||
warning "No binaries are available for your operating system ($_ostype)"
|
||||
_cargobuild="yes"
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ $_cargobuild = "yes" ]; then
|
||||
cargo_build
|
||||
exit 0
|
||||
fi
|
||||
|
||||
_target="$_cputype-$_ostype"
|
||||
warning "Detected target: $_target"
|
||||
|
||||
success "Downloading zoxide..."
|
||||
|
||||
## Downloading the binaries
|
||||
ensure rm -rf "zoxide-$_target"
|
||||
curl -s https://api.github.com/repos/ajeetdsouza/zoxide/releases/latest | grep "browser_download_url" | cut -d '"' -f 4 | grep "$_target" | xargs -n 1 curl -LJO
|
||||
|
||||
ensure mv "zoxide-$_target" "zoxide_bin"
|
||||
|
||||
ensure sudo mv zoxide_bin "$_install_path/zoxide"
|
||||
ensure chmod +x "$_install_path/zoxide"
|
||||
|
||||
success "zoxide is installed!"
|
||||
info "Please ensure that $_install_path is added to your \$PATH."
|
||||
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
|
||||
}
|
||||
|
||||
success() {
|
||||
printf "\033[32m%s\033[0m\n" "$1" >&1
|
||||
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"
|
||||
}
|
||||
|
||||
info() {
|
||||
printf "%s\n" "$1" >&1
|
||||
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}"
|
||||
}
|
||||
|
||||
warning() {
|
||||
printf "\033[33m%s\033[0m\n" "$1" >&2
|
||||
try_sudo() {
|
||||
if "$@" >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
need_sudo
|
||||
"${SUDO}" "$@"
|
||||
}
|
||||
|
||||
error() {
|
||||
printf "\033[31;1m%s\033[0m\n" "$1" >&2
|
||||
exit 1
|
||||
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
|
||||
}
|
||||
|
||||
cmd_chk() {
|
||||
command -v "$1" >/dev/null 2>&1
|
||||
# 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}"
|
||||
}
|
||||
|
||||
## Ensures that the command executes without error
|
||||
ensure() {
|
||||
if ! "$@"; then error "command failed: $*"; fi
|
||||
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 ! cmd_chk "$1"; then
|
||||
error "need $1 (command not found)"
|
||||
fi
|
||||
if ! check_cmd "$1"; then
|
||||
err "need '$1' (command not found)"
|
||||
fi
|
||||
}
|
||||
|
||||
prompt_confirm() {
|
||||
if [ ! -t 1 ]; then
|
||||
error "Unable to run interactively. Please execute this script using interactive shell"
|
||||
fi
|
||||
|
||||
while true; do
|
||||
read -rp "Is this okay? (y/N): " _choice
|
||||
_choice=$(echo "$_choice" | tr '[:upper:]' '[:lower:]')
|
||||
|
||||
case "$_choice" in
|
||||
y | yes) break ;;
|
||||
n | no) error "Operation aborted" ;;
|
||||
esac
|
||||
done
|
||||
check_cmd() {
|
||||
command -v -- "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
cargo_build() {
|
||||
success "Compiling from source..."
|
||||
|
||||
if ! cmd_chk "cargo"; then
|
||||
success "Cargo will be installed."
|
||||
prompt_confirm
|
||||
|
||||
ensure curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
|
||||
# shellcheck source=/dev/null
|
||||
. "$HOME/.cargo/env"
|
||||
fi
|
||||
|
||||
RUSTFLAGS="-C target-cpu=native" ensure cargo install zoxide
|
||||
# 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
|
||||
}
|
||||
|
||||
main "$@" || exit 1
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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}}
|
|
@ -0,0 +1,24 @@
|
|||
.TH "ZOXIDE" "1" "2021-04-12" "" "zoxide"
|
||||
.SH NAME
|
||||
\fBzoxide-add\fR - add a new directory or increment its rank
|
||||
.SH SYNOPSIS
|
||||
.B zoxide add [PATHS]
|
||||
.SH DESCRIPTION
|
||||
If the directory is not already in the database, this command creates a new
|
||||
entry for it with a default score of 1, otherwise, it increments the existing
|
||||
score by 1. It then sets the last updated field of the entry to the current
|
||||
time. After this, it runs the \fBAGING\fR algorithm on the database. See
|
||||
\fBzoxide\fR(1) for more about the algorithm.
|
||||
.sp
|
||||
If you'd like to prevent a directory from being added to the database, see the
|
||||
\fB_ZO_EXCLUDE_DIRS\fR environment variable in \fBzoxide\fR(1).
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.B -h, --help
|
||||
Print help information.
|
||||
.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
|
|
@ -0,0 +1,31 @@
|
|||
.TH "ZOXIDE" "1" "2021-04-12" "" "zoxide"
|
||||
.SH NAME
|
||||
\fBzoxide-import\fR - import data from other tools
|
||||
.SH SYNOPSIS
|
||||
.B zoxide import PATH --from FORMAT [OPTIONS]
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.B --from FORMAT
|
||||
The format of the database being imported:
|
||||
.TS
|
||||
tab(|);
|
||||
l l.
|
||||
\fBautojump\fR
|
||||
\fBz\fR|(for \fBfasd\fR, \fBz\fR, \fBz.lua\fR, or \fBzsh-z\fR)
|
||||
.TE
|
||||
.sp
|
||||
Note: zoxide only imports paths from autojump, since its matching
|
||||
algorithm is too different to import the scores.
|
||||
.TP
|
||||
.B -h, --help
|
||||
Print help information.
|
||||
.TP
|
||||
.B --merge
|
||||
By default, the import fails if the current database is not already empty. This
|
||||
option merges imported data into the existing database.
|
||||
.SH REPORTING BUGS
|
||||
For any issues, feature requests, or questions, please visit:
|
||||
.sp
|
||||
\fBhttps://github.com/ajeetdsouza/zoxide/issues\fR
|
||||
.SH AUTHOR
|
||||
Ajeet D'Souza \fB<98ajeet@gmail.com>\fR
|
|
@ -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
|
|
@ -0,0 +1,33 @@
|
|||
.TH "ZOXIDE" "1" "2021-04-12" "" "zoxide"
|
||||
.SH NAME
|
||||
\fBzoxide-query\fR - search for a directory in the database
|
||||
.SH SYNOPSIS
|
||||
.B zoxide query [KEYWORDS] [OPTIONS]
|
||||
.SH DESCRIPTION
|
||||
Query the database for paths matching the keywords. The exact \fBMATCHING\fR
|
||||
algorithm is described in \fBzoxide\fR(1).
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.B --all
|
||||
Show deleted directories.
|
||||
.TP
|
||||
.B --exclude PATH
|
||||
Exclude a path from query results.
|
||||
.TP
|
||||
.B -h, --help
|
||||
Print help information.
|
||||
.TP
|
||||
.B -i, --interactive
|
||||
Use interactive selection. This option requires \fBfzf\fR(1).
|
||||
.TP
|
||||
.B -l, --list
|
||||
List all results, rather than just the one with the highest frecency.
|
||||
.TP
|
||||
.B -s, --score
|
||||
Print the calculated score as well as the matched path.
|
||||
.SH REPORTING BUGS
|
||||
For any issues, feature requests, or questions, please visit:
|
||||
.sp
|
||||
\fBhttps://github.com/ajeetdsouza/zoxide/issues\fR
|
||||
.SH AUTHOR
|
||||
Ajeet D'Souza \fB<98ajeet@gmail.com>\fR
|
|
@ -0,0 +1,18 @@
|
|||
.TH "ZOXIDE" "1" "2021-04-12" "" "zoxide"
|
||||
.SH NAME
|
||||
\fBzoxide-remove\fR - remove a directory from the database
|
||||
.SH SYNOPSIS
|
||||
.B zoxide remove [PATHS] [OPTIONS]
|
||||
.SH DESCRIPTION
|
||||
If you'd like to permanently exclude a directory from the database, see the
|
||||
\fB_ZO_EXCLUDE_DIRS\fR environment variable in \fBzoxide\fR(1).
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.B -h, --help
|
||||
Print help information.
|
||||
.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
|
|
@ -0,0 +1,134 @@
|
|||
.TH "ZOXIDE" "1" "2021-04-12" "" "zoxide"
|
||||
.SH NAME
|
||||
\fBzoxide\fR - a smarter cd command
|
||||
.SH SYNOPSIS
|
||||
.B zoxide SUBCOMMAND [OPTIONS]
|
||||
.SH DESCRIPTION
|
||||
zoxide is a smarter cd command for your terminal. It keeps track of the
|
||||
directories you use most frequently, and uses a ranking algorithm to navigate
|
||||
to the best match.
|
||||
.SH USAGE
|
||||
.nf
|
||||
z foo # cd into highest ranked directory matching foo
|
||||
z foo bar # cd into highest ranked directory matching foo and bar
|
||||
z foo / # cd into a subdirectory starting with foo
|
||||
.sp
|
||||
z ~/foo # z also works like a regular cd command
|
||||
z foo/ # cd into relative path
|
||||
z .. # cd one level up
|
||||
z - # cd into previous directory
|
||||
.sp
|
||||
zi foo # cd with interactive selection (using fzf)
|
||||
.sp
|
||||
z foo<SPACE><TAB> # show interactive completions (bash 4.4+/fish/zsh only)
|
||||
.fi
|
||||
.SH SUBCOMMANDS
|
||||
.TP
|
||||
\fBzoxide-add\fR(1)
|
||||
Add a new directory to the database, or increment its rank.
|
||||
.TP
|
||||
\fBzoxide-import\fR(1)
|
||||
Import entries from another application.
|
||||
.TP
|
||||
\fBzoxide-init\fR(1)
|
||||
Generate shell configuration.
|
||||
.TP
|
||||
\fBzoxide-query\fR(1)
|
||||
Search for a directory in the database.
|
||||
.TP
|
||||
\fBzoxide-remove\fR(1)
|
||||
Remove a directory from the database.
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.B -h, --help
|
||||
Print help information.
|
||||
.TP
|
||||
.B -V, --version
|
||||
Print version information.
|
||||
.SH ENVIRONMENT VARIABLES
|
||||
Environment variables can be used for configuration. They must be set before
|
||||
\fBzoxide-init\fR(1) is called.
|
||||
.TP
|
||||
.B _ZO_DATA_DIR
|
||||
Specifies the directory in which the database is stored. The default value
|
||||
varies across OSes:
|
||||
.TS
|
||||
tab(|);
|
||||
l l.
|
||||
\fBOS|Path\fR
|
||||
\fBLinux/BSD\fR|T{
|
||||
\fB$XDG_DATA_HOME\fR or \fB$HOME/.local/share\fR, eg.
|
||||
\fB/home/alice/.local/share\fR
|
||||
T}
|
||||
\fBmacOS\fR|T{
|
||||
\fB$HOME/Library/Application Support\fR, eg.
|
||||
\fB/Users/Alice/Library/Application Support\fR
|
||||
T}
|
||||
\fBWindows\fR|T{
|
||||
\fB%LOCALAPPDATA%\fR, eg. \fBC:\\Users\\Alice\\AppData\\Local\fR
|
||||
T}
|
||||
.TE
|
||||
.TP
|
||||
.B _ZO_ECHO
|
||||
When set to 1, \fBz\fR will print the matched directory before navigating to it.
|
||||
.TP
|
||||
.B _ZO_EXCLUDE_DIRS
|
||||
Prevents the specified directories from being added to the database. This is
|
||||
provided as a list of globs, separated by OS-specific characters:
|
||||
.TS
|
||||
tab(|);
|
||||
l l.
|
||||
\fBOS|Separator\fR
|
||||
\fBLinux/macOS/BSD\fR|T{
|
||||
\fB:\fR, eg. \fB$HOME:$HOME/private/*\fR
|
||||
T}
|
||||
\fBWindows\fR|\fB;\fR, eg. \fB$HOME;$HOME/private/*\fR
|
||||
.TE
|
||||
.sp
|
||||
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.
|
||||
.TP
|
||||
.B _ZO_FZF_OPTS
|
||||
Custom options to pass to \fBfzf\fR(1) during interactive selection. See the
|
||||
manpage for the full list of options.
|
||||
.TP
|
||||
.B _ZO_MAXAGE
|
||||
Configures the aging algorithm, which limits the maximum number of entries in
|
||||
the database. By default, this is set to 10000.
|
||||
.TP
|
||||
.B _ZO_RESOLVE_SYMLINKS
|
||||
When set to 1, \fBz\fR will resolve symlinks before adding directories to
|
||||
the database.
|
||||
.SH ALGORITHM
|
||||
.TP
|
||||
.B AGING
|
||||
zoxide uses a parameter called \fB_ZO_MAXAGE\fR to limit the number of entries
|
||||
in the database based on usage patterns. If the total \fBFRECENCY\fR of the
|
||||
directories in the database exceeds this value, we divide each directory's
|
||||
score by a factor \fBk\fR - such that the new total becomes ~90% of
|
||||
\fB_ZO_MAXAGE\fR. Thereafter, if the new score of any directory falls below
|
||||
1, it is removed from the database.
|
||||
.sp
|
||||
Theoretically, the maximum number of directories in the database is
|
||||
\fB4 * _ZO_MAXAGE\fR, although it is lower in practice.
|
||||
.TP
|
||||
.B FRECENCY
|
||||
Each directory in zoxide is given a score, starting with 1 the first time
|
||||
it is accessed. Every subsequent access increases the score by 1. When a
|
||||
query is made, we calculate frecency based on the last time the directory was
|
||||
accessed:
|
||||
.TS
|
||||
tab(|);
|
||||
l l.
|
||||
\fBLast access time\fR|\fBFrecency\fR
|
||||
Within the last hour|score * 4
|
||||
Within the last day|score * 2
|
||||
Within the last week|score / 2
|
||||
Otherwise|score / 4
|
||||
.TE
|
||||
.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
|
|
@ -0,0 +1,8 @@
|
|||
group_imports = "StdExternalCrate"
|
||||
imports_granularity = "Module"
|
||||
newline_style = "Native"
|
||||
use_field_init_shorthand = true
|
||||
use_small_heuristics = "Max"
|
||||
use_try_shorthand = true
|
||||
wrap_comments = true
|
||||
style_edition = "2024"
|
|
@ -0,0 +1,58 @@
|
|||
let
|
||||
pkgs = import (builtins.fetchTarball
|
||||
"https://github.com/NixOS/nixpkgs/archive/ec9ef366451af88284d7dfd18ee017b7e86a0710.tar.gz") {
|
||||
overlays = [ rust ];
|
||||
};
|
||||
rust = import (builtins.fetchTarball
|
||||
"https://github.com/oxalica/rust-overlay/archive/026e8fedefd6b167d92ed04b195c658d95ffc7a5.tar.gz");
|
||||
|
||||
rust-nightly =
|
||||
pkgs.rust-bin.selectLatestNightlyWith (toolchain: toolchain.minimal);
|
||||
cargo-udeps = pkgs.writeShellScriptBin "cargo-udeps" ''
|
||||
export RUSTC="${rust-nightly}/bin/rustc";
|
||||
export CARGO="${rust-nightly}/bin/cargo";
|
||||
exec "${pkgs.cargo-udeps}/bin/cargo-udeps" "$@"
|
||||
'';
|
||||
in pkgs.mkShell {
|
||||
buildInputs = [
|
||||
# Rust
|
||||
(pkgs.rust-bin.selectLatestNightlyWith (toolchain: toolchain.rustfmt))
|
||||
pkgs.rust-bin.stable.latest.default
|
||||
|
||||
# Shells
|
||||
pkgs.bash
|
||||
pkgs.dash
|
||||
pkgs.elvish
|
||||
pkgs.fish
|
||||
pkgs.ksh
|
||||
pkgs.nushell
|
||||
pkgs.powershell
|
||||
pkgs.tcsh
|
||||
pkgs.xonsh
|
||||
pkgs.zsh
|
||||
|
||||
# Tools
|
||||
cargo-udeps
|
||||
pkgs.cargo-msrv
|
||||
pkgs.cargo-nextest
|
||||
pkgs.cargo-udeps
|
||||
pkgs.just
|
||||
pkgs.mandoc
|
||||
pkgs.nixfmt
|
||||
pkgs.nodePackages.markdownlint-cli
|
||||
pkgs.python3Packages.black
|
||||
pkgs.python3Packages.mypy
|
||||
pkgs.python3Packages.pylint
|
||||
pkgs.shellcheck
|
||||
pkgs.shfmt
|
||||
pkgs.yamlfmt
|
||||
|
||||
# Dependencies
|
||||
pkgs.cacert
|
||||
pkgs.fzf
|
||||
pkgs.git
|
||||
pkgs.libiconv
|
||||
];
|
||||
|
||||
CARGO_TARGET_DIR = "target_nix";
|
||||
}
|
|
@ -1,49 +1,46 @@
|
|||
use super::Cmd;
|
||||
use crate::config;
|
||||
use crate::util;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::store::Store;
|
||||
use anyhow::Result;
|
||||
use clap::Clap;
|
||||
use anyhow::{Result, bail};
|
||||
|
||||
use std::path::PathBuf;
|
||||
use crate::cmd::{Add, Run};
|
||||
use crate::db::Database;
|
||||
use crate::{config, util};
|
||||
|
||||
/// Add a new directory or increment its rank
|
||||
#[derive(Clap, Debug)]
|
||||
pub struct Add {
|
||||
path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl Cmd for Add {
|
||||
impl Run for Add {
|
||||
fn run(&self) -> Result<()> {
|
||||
let path = match &self.path {
|
||||
Some(path) => {
|
||||
if config::zo_resolve_symlinks() {
|
||||
util::canonicalize(&path)
|
||||
} else {
|
||||
util::resolve_path(&path)
|
||||
}
|
||||
}
|
||||
None => util::current_dir(),
|
||||
}?;
|
||||
// These characters can't be printed cleanly to a single line, so they can cause
|
||||
// confusion when writing to stdout.
|
||||
const EXCLUDE_CHARS: &[char] = &['\n', '\r'];
|
||||
|
||||
if config::zo_exclude_dirs()?
|
||||
.iter()
|
||||
.any(|pattern| pattern.matches_path(&path))
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let path = util::path_to_str(&path)?;
|
||||
let exclude_dirs = config::exclude_dirs()?;
|
||||
let max_age = config::maxage()?;
|
||||
let now = util::current_time()?;
|
||||
|
||||
let data_dir = config::zo_data_dir()?;
|
||||
let max_age = config::zo_maxage()?;
|
||||
let mut db = Database::open()?;
|
||||
|
||||
let mut store = Store::open(&data_dir)?;
|
||||
store.add(path, now);
|
||||
store.age(max_age);
|
||||
for path in &self.paths {
|
||||
let path =
|
||||
if config::resolve_symlinks() { util::canonicalize } else { util::resolve_path }(
|
||||
path,
|
||||
)?;
|
||||
let path = util::path_to_str(&path)?;
|
||||
|
||||
Ok(())
|
||||
// Ignore path if it contains unsupported characters, or if it's in the exclude
|
||||
// list.
|
||||
if path.contains(EXCLUDE_CHARS) || exclude_dirs.iter().any(|glob| glob.matches(path)) {
|
||||
continue;
|
||||
}
|
||||
if !Path::new(path).is_dir() {
|
||||
bail!("not a directory: {path}");
|
||||
}
|
||||
|
||||
let by = self.score.unwrap_or(1.0);
|
||||
db.add_update(path, by, now);
|
||||
}
|
||||
|
||||
if db.dirty() {
|
||||
db.age(max_age);
|
||||
}
|
||||
db.save()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>,
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -1,51 +1,166 @@
|
|||
use super::Cmd;
|
||||
use crate::config;
|
||||
use crate::import::{Autojump, Import as _, Z};
|
||||
use crate::util;
|
||||
use std::fs;
|
||||
|
||||
use crate::store::Store;
|
||||
use anyhow::{bail, Result};
|
||||
use clap::{ArgEnum, Clap};
|
||||
use anyhow::{Context, Result, bail};
|
||||
|
||||
use std::path::PathBuf;
|
||||
use crate::cmd::{Import, ImportFrom, Run};
|
||||
use crate::db::Database;
|
||||
|
||||
/// Import entries from another database
|
||||
#[derive(Clap, Debug)]
|
||||
pub struct Import {
|
||||
path: PathBuf,
|
||||
|
||||
/// Application to import from
|
||||
#[clap(arg_enum, long, default_value = "z")]
|
||||
from: From,
|
||||
|
||||
/// Merge into existing database
|
||||
#[clap(long)]
|
||||
merge: bool,
|
||||
}
|
||||
|
||||
impl Cmd for Import {
|
||||
impl Run for Import {
|
||||
fn run(&self) -> Result<()> {
|
||||
let data_dir = config::zo_data_dir()?;
|
||||
let buffer = fs::read_to_string(&self.path).with_context(|| {
|
||||
format!("could not open database for importing: {}", &self.path.display())
|
||||
})?;
|
||||
|
||||
let mut store = Store::open(&data_dir)?;
|
||||
if !self.merge && !store.dirs.is_empty() {
|
||||
bail!("zoxide database is not empty, specify --merge to continue anyway")
|
||||
let mut db = Database::open()?;
|
||||
if !self.merge && !db.dirs().is_empty() {
|
||||
bail!("current database is not empty, specify --merge to continue anyway");
|
||||
}
|
||||
|
||||
let resolve_symlinks = config::zo_resolve_symlinks();
|
||||
match self.from {
|
||||
From::Autojump => Autojump {
|
||||
resolve_symlinks,
|
||||
now: util::current_time()?,
|
||||
}
|
||||
.import(&mut store, &self.path),
|
||||
From::Z => Z { resolve_symlinks }.import(&mut store, &self.path),
|
||||
ImportFrom::Autojump => import_autojump(&mut db, &buffer),
|
||||
ImportFrom::Z => import_z(&mut db, &buffer),
|
||||
}
|
||||
.context("import error")?;
|
||||
|
||||
db.save()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(ArgEnum, Debug)]
|
||||
enum From {
|
||||
Autojump,
|
||||
Z,
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
103
src/cmd/init.rs
103
src/cmd/init.rs
|
@ -1,95 +1,32 @@
|
|||
use super::Cmd;
|
||||
use crate::config;
|
||||
use crate::shell::{self, Hook, Opts};
|
||||
use std::io::{self, Write};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use askama::Template;
|
||||
use clap::{ArgEnum, Clap};
|
||||
use once_cell::sync::OnceCell;
|
||||
|
||||
/// Generates shell configuration
|
||||
#[derive(Clap, Debug)]
|
||||
#[clap(after_help(env_help()))]
|
||||
pub struct Init {
|
||||
#[clap(arg_enum)]
|
||||
shell: Shell,
|
||||
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};
|
||||
|
||||
/// Prevents zoxide from defining any commands
|
||||
#[clap(long)]
|
||||
no_aliases: bool,
|
||||
|
||||
/// Renames the 'z' command and corresponding aliases
|
||||
#[clap(long, default_value = "z")]
|
||||
cmd: String,
|
||||
|
||||
/// Chooses event upon which an entry is added to the database
|
||||
#[clap(arg_enum, long, default_value = "pwd")]
|
||||
hook: Hook,
|
||||
}
|
||||
|
||||
impl Cmd for Init {
|
||||
impl Run for Init {
|
||||
fn run(&self) -> Result<()> {
|
||||
let cmd = if self.no_aliases {
|
||||
None
|
||||
} else {
|
||||
Some(self.cmd.as_str())
|
||||
};
|
||||
|
||||
let echo = config::zo_echo();
|
||||
let resolve_symlinks = config::zo_resolve_symlinks();
|
||||
|
||||
let opts = &Opts {
|
||||
cmd,
|
||||
hook: self.hook,
|
||||
echo,
|
||||
resolve_symlinks,
|
||||
};
|
||||
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 {
|
||||
Shell::Bash => shell::Bash(opts).render(),
|
||||
Shell::Fish => shell::Fish(opts).render(),
|
||||
Shell::Posix => shell::Posix(opts).render(),
|
||||
Shell::Powershell => shell::PowerShell(opts).render(),
|
||||
Shell::Xonsh => shell::Xonsh(opts).render(),
|
||||
Shell::Zsh => shell::Zsh(opts).render(),
|
||||
InitShell::Bash => Bash(opts).render(),
|
||||
InitShell::Elvish => Elvish(opts).render(),
|
||||
InitShell::Fish => Fish(opts).render(),
|
||||
InitShell::Nushell => Nushell(opts).render(),
|
||||
InitShell::Posix => Posix(opts).render(),
|
||||
InitShell::Powershell => Powershell(opts).render(),
|
||||
InitShell::Tcsh => Tcsh(opts).render(),
|
||||
InitShell::Xonsh => Xonsh(opts).render(),
|
||||
InitShell::Zsh => Zsh(opts).render(),
|
||||
}
|
||||
.context("could not render template")?;
|
||||
println!("{}", source);
|
||||
|
||||
Ok(())
|
||||
writeln!(io::stdout(), "{source}").pipe_exit("stdout")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(ArgEnum, Debug)]
|
||||
enum Shell {
|
||||
Bash,
|
||||
Fish,
|
||||
Posix,
|
||||
Powershell,
|
||||
Xonsh,
|
||||
Zsh,
|
||||
}
|
||||
|
||||
fn env_help() -> &'static str {
|
||||
static ENV_HELP: OnceCell<String> = OnceCell::new();
|
||||
ENV_HELP.get_or_init(|| {
|
||||
const PATH_SPLIT_SEPARATOR: u8 = if cfg!(any(target_os = "redox", target_os = "windows")) {
|
||||
b';'
|
||||
} else {
|
||||
b':'
|
||||
};
|
||||
|
||||
format!(
|
||||
"\
|
||||
ENVIRONMENT VARIABLES:
|
||||
_ZO_DATA_DIR Path for zoxide data files
|
||||
[current: {data_dir}]
|
||||
_ZO_ECHO Prints the matched directory before navigating to it when set to 1
|
||||
_ZO_EXCLUDE_DIRS List of directory globs to be excluded, separated by '{path_split_separator}'
|
||||
_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",
|
||||
data_dir=config::zo_data_dir().unwrap_or_else(|_| "none".into()).display(),
|
||||
path_split_separator=PATH_SPLIT_SEPARATOR as char)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,40 +1,28 @@
|
|||
mod add;
|
||||
mod cmd;
|
||||
mod edit;
|
||||
mod import;
|
||||
mod init;
|
||||
mod query;
|
||||
mod remove;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::{AppSettings, Clap};
|
||||
|
||||
pub use add::Add;
|
||||
pub use import::Import;
|
||||
pub use init::Init;
|
||||
pub use query::Query;
|
||||
pub use remove::Remove;
|
||||
pub use crate::cmd::cmd::*;
|
||||
|
||||
pub trait Cmd {
|
||||
pub trait Run {
|
||||
fn run(&self) -> Result<()>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clap)]
|
||||
#[clap(about, author, global_setting(AppSettings::GlobalVersion), global_setting(AppSettings::VersionlessSubcommands), version = env!("ZOXIDE_VERSION"))]
|
||||
pub enum App {
|
||||
Add(Add),
|
||||
Import(Import),
|
||||
Init(Init),
|
||||
Query(Query),
|
||||
Remove(Remove),
|
||||
}
|
||||
|
||||
impl Cmd for App {
|
||||
impl Run for Cmd {
|
||||
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(),
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
184
src/cmd/query.rs
184
src/cmd/query.rs
|
@ -1,77 +1,121 @@
|
|||
use super::Cmd;
|
||||
use crate::config;
|
||||
use crate::fzf::Fzf;
|
||||
use crate::util;
|
||||
|
||||
use crate::store::{self, Store};
|
||||
use anyhow::{Context, Result};
|
||||
use clap::Clap;
|
||||
|
||||
use std::io::{self, Write};
|
||||
|
||||
/// Searches for a directory
|
||||
#[derive(Clap, Debug)]
|
||||
pub struct Query {
|
||||
keywords: Vec<String>,
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
/// Lists all matching directories
|
||||
#[clap(long, short, conflicts_with = "list")]
|
||||
interactive: bool,
|
||||
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};
|
||||
|
||||
/// Lists all matching directories
|
||||
#[clap(long, short, conflicts_with = "interactive")]
|
||||
list: bool,
|
||||
|
||||
/// Prints score with results
|
||||
#[clap(long, short)]
|
||||
score: bool,
|
||||
}
|
||||
|
||||
impl Cmd for Query {
|
||||
impl Run for Query {
|
||||
fn run(&self) -> Result<()> {
|
||||
let data_dir = config::zo_data_dir()?;
|
||||
let mut store = Store::open(&data_dir)?;
|
||||
|
||||
let query = store::Query::new(&self.keywords);
|
||||
let now = util::current_time()?;
|
||||
|
||||
let mut matches = store.iter_matches(&query, now);
|
||||
|
||||
if self.interactive {
|
||||
let mut fzf = Fzf::new()?;
|
||||
let handle = fzf.stdin();
|
||||
for dir in matches {
|
||||
writeln!(handle, "{}", dir.display_score(now)).context("could not write to 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();
|
||||
for dir in matches {
|
||||
if self.score {
|
||||
writeln!(handle, "{}", dir.display_score(now))
|
||||
} else {
|
||||
writeln!(handle, "{}", dir.display())
|
||||
}
|
||||
.unwrap()
|
||||
}
|
||||
} else {
|
||||
let dir = matches.next().context("no match found")?;
|
||||
if self.score {
|
||||
println!("{}", dir.display_score(now))
|
||||
} else {
|
||||
println!("{}", dir.display())
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,61 +1,23 @@
|
|||
use super::Cmd;
|
||||
use crate::config;
|
||||
use crate::fzf::Fzf;
|
||||
use crate::store::Query;
|
||||
use crate::store::Store;
|
||||
use anyhow::{Result, bail};
|
||||
|
||||
use crate::cmd::{Remove, Run};
|
||||
use crate::db::Database;
|
||||
use crate::util;
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
use clap::Clap;
|
||||
|
||||
use std::io::Write;
|
||||
|
||||
/// Removes a directory
|
||||
#[derive(Clap, Debug)]
|
||||
pub struct Remove {
|
||||
#[clap(conflicts_with = "path", long, short, value_name = "keywords")]
|
||||
interactive: Option<Vec<String>>,
|
||||
#[clap(
|
||||
conflicts_with = "interactive",
|
||||
required_unless_present = "interactive"
|
||||
)]
|
||||
path: Option<String>,
|
||||
}
|
||||
|
||||
impl Cmd for Remove {
|
||||
impl Run for Remove {
|
||||
fn run(&self) -> Result<()> {
|
||||
let data_dir = config::zo_data_dir()?;
|
||||
let mut store = Store::open(&data_dir)?;
|
||||
let mut db = Database::open()?;
|
||||
|
||||
let selection;
|
||||
let path = match &self.interactive {
|
||||
Some(keywords) => {
|
||||
let query = Query::new(keywords);
|
||||
let now = util::current_time()?;
|
||||
|
||||
let mut fzf = Fzf::new()?;
|
||||
let handle = fzf.stdin();
|
||||
for dir in store.iter_matches(&query, now) {
|
||||
writeln!(handle, "{}", dir.display_score(now))
|
||||
.context("could not write to fzf")?;
|
||||
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}")
|
||||
}
|
||||
|
||||
selection = fzf.wait_select()?;
|
||||
selection
|
||||
.get(5..selection.len().saturating_sub(1))
|
||||
.context("fzf returned invalid output")?
|
||||
}
|
||||
None => self.path.as_ref().unwrap(),
|
||||
};
|
||||
|
||||
if !store.remove(path) {
|
||||
let path = util::resolve_path(&path)?;
|
||||
let path = util::path_to_str(&path)?;
|
||||
if !store.remove(path) {
|
||||
bail!("path not found in store: {}", &path)
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
db.save()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,70 +1,62 @@
|
|||
use crate::store::Rank;
|
||||
use anyhow::{bail, Context, Result};
|
||||
use dirs_next as dirs;
|
||||
|
||||
use std::env;
|
||||
use std::ffi::OsString;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub fn zo_data_dir() -> Result<PathBuf> {
|
||||
let data_dir = match env::var_os("_ZO_DATA_DIR") {
|
||||
Some(data_osstr) => PathBuf::from(data_osstr),
|
||||
None => match dirs::data_local_dir() {
|
||||
Some(mut data_dir) => {
|
||||
data_dir.push("zoxide");
|
||||
data_dir
|
||||
}
|
||||
None => bail!("could not find database directory, please set _ZO_DATA_DIR manually"),
|
||||
},
|
||||
use anyhow::{Context, Result, ensure};
|
||||
use glob::Pattern;
|
||||
|
||||
use crate::db::Rank;
|
||||
|
||||
pub fn data_dir() -> Result<PathBuf> {
|
||||
let dir = match env::var_os("_ZO_DATA_DIR") {
|
||||
Some(path) => PathBuf::from(path),
|
||||
None => dirs::data_local_dir()
|
||||
.context("could not find data directory, please set _ZO_DATA_DIR manually")?
|
||||
.join("zoxide"),
|
||||
};
|
||||
|
||||
Ok(data_dir)
|
||||
ensure!(dir.is_absolute(), "_ZO_DATA_DIR must be an absolute path");
|
||||
Ok(dir)
|
||||
}
|
||||
|
||||
pub fn zo_echo() -> bool {
|
||||
match env::var_os("_ZO_ECHO") {
|
||||
Some(var) => var == "1",
|
||||
None => false,
|
||||
}
|
||||
pub fn echo() -> bool {
|
||||
env::var_os("_ZO_ECHO").is_some_and(|var| var == "1")
|
||||
}
|
||||
|
||||
pub fn zo_exclude_dirs() -> Result<Vec<glob::Pattern>> {
|
||||
pub fn exclude_dirs() -> Result<Vec<Pattern>> {
|
||||
match env::var_os("_ZO_EXCLUDE_DIRS") {
|
||||
Some(dirs_osstr) => env::split_paths(&dirs_osstr)
|
||||
Some(paths) => env::split_paths(&paths)
|
||||
.map(|path| {
|
||||
let pattern = path
|
||||
.to_str()
|
||||
.context("invalid unicode in _ZO_EXCLUDE_DIRS")?;
|
||||
glob::Pattern::new(&pattern)
|
||||
.with_context(|| format!("invalid glob in _ZO_EXCLUDE_DIRS: {}", pattern))
|
||||
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 => Ok(Vec::new()),
|
||||
None => {
|
||||
let pattern = (|| {
|
||||
let home = dirs::home_dir()?;
|
||||
let home = Pattern::escape(home.to_str()?);
|
||||
Pattern::new(&home).ok()
|
||||
})();
|
||||
Ok(pattern.into_iter().collect())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn zo_fzf_opts() -> Option<OsString> {
|
||||
pub fn fzf_opts() -> Option<OsString> {
|
||||
env::var_os("_ZO_FZF_OPTS")
|
||||
}
|
||||
|
||||
pub fn zo_maxage() -> Result<Rank> {
|
||||
match env::var_os("_ZO_MAXAGE") {
|
||||
Some(maxage_osstr) => {
|
||||
let maxage_str = maxage_osstr
|
||||
.to_str()
|
||||
.context("invalid unicode in _ZO_MAXAGE")?;
|
||||
let maxage = maxage_str.parse::<u64>().with_context(|| {
|
||||
format!("unable to parse _ZO_MAXAGE as integer: {}", maxage_str)
|
||||
})?;
|
||||
Ok(maxage as Rank)
|
||||
}
|
||||
None => Ok(10000.0),
|
||||
}
|
||||
pub fn maxage() -> Result<Rank> {
|
||||
env::var_os("_ZO_MAXAGE").map_or(Ok(10_000.0), |maxage| {
|
||||
let maxage = maxage.to_str().context("invalid unicode in _ZO_MAXAGE")?;
|
||||
let maxage = maxage
|
||||
.parse::<u32>()
|
||||
.with_context(|| format!("unable to parse _ZO_MAXAGE as integer: {maxage}"))?;
|
||||
Ok(maxage as Rank)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn zo_resolve_symlinks() -> bool {
|
||||
match env::var_os("_ZO_RESOLVE_SYMLINKS") {
|
||||
Some(var) => var == "1",
|
||||
None => false,
|
||||
}
|
||||
pub fn resolve_symlinks() -> bool {
|
||||
env::var_os("_ZO_RESOLVE_SYMLINKS").is_some_and(|var| var == "1")
|
||||
}
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
use std::borrow::Cow;
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::util::{DAY, HOUR, WEEK};
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct Dir<'a> {
|
||||
#[serde(borrow)]
|
||||
pub path: Cow<'a, str>,
|
||||
pub rank: Rank,
|
||||
pub last_accessed: Epoch,
|
||||
}
|
||||
|
||||
impl Dir<'_> {
|
||||
pub fn display(&self) -> DirDisplay<'_> {
|
||||
DirDisplay::new(self)
|
||||
}
|
||||
|
||||
pub fn score(&self, now: Epoch) -> Rank {
|
||||
// The older the entry, the lesser its importance.
|
||||
let duration = now.saturating_sub(self.last_accessed);
|
||||
if duration < HOUR {
|
||||
self.rank * 4.0
|
||||
} else if duration < DAY {
|
||||
self.rank * 2.0
|
||||
} else if duration < WEEK {
|
||||
self.rank * 0.5
|
||||
} else {
|
||||
self.rank * 0.25
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DirDisplay<'a> {
|
||||
dir: &'a Dir<'a>,
|
||||
now: Option<Epoch>,
|
||||
separator: char,
|
||||
}
|
||||
|
||||
impl<'a> DirDisplay<'a> {
|
||||
fn new(dir: &'a Dir) -> Self {
|
||||
Self { dir, separator: ' ', now: None }
|
||||
}
|
||||
|
||||
pub fn with_score(mut self, now: Epoch) -> Self {
|
||||
self.now = Some(now);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_separator(mut self, separator: char) -> Self {
|
||||
self.separator = separator;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for DirDisplay<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
if let Some(now) = self.now {
|
||||
let score = self.dir.score(now).clamp(0.0, 9999.0);
|
||||
write!(f, "{score:>6.1}{}", self.separator)?;
|
||||
}
|
||||
write!(f, "{}", self.dir.path)
|
||||
}
|
||||
}
|
||||
|
||||
pub type Rank = f64;
|
||||
pub type Epoch = u64;
|
|
@ -0,0 +1,287 @@
|
|||
mod dir;
|
||||
mod stream;
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{fs, io};
|
||||
|
||||
use anyhow::{Context, Result, bail};
|
||||
use bincode::Options;
|
||||
use ouroboros::self_referencing;
|
||||
|
||||
pub use crate::db::dir::{Dir, Epoch, Rank};
|
||||
pub use crate::db::stream::{Stream, StreamOptions};
|
||||
use crate::{config, util};
|
||||
|
||||
#[self_referencing]
|
||||
pub struct Database {
|
||||
path: PathBuf,
|
||||
bytes: Vec<u8>,
|
||||
#[borrows(bytes)]
|
||||
#[covariant]
|
||||
pub dirs: Vec<Dir<'this>>,
|
||||
dirty: bool,
|
||||
}
|
||||
|
||||
impl Database {
|
||||
const VERSION: u32 = 3;
|
||||
|
||||
pub fn open() -> Result<Self> {
|
||||
let data_dir = config::data_dir()?;
|
||||
Self::open_dir(data_dir)
|
||||
}
|
||||
|
||||
pub fn open_dir(data_dir: impl AsRef<Path>) -> Result<Self> {
|
||||
let data_dir = data_dir.as_ref();
|
||||
let path = data_dir.join("db.zo");
|
||||
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<()> {
|
||||
// Only write to disk if the database is modified.
|
||||
if !self.dirty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let bytes = Self::serialize(self.dirs())?;
|
||||
util::write(self.borrow_path(), bytes).context("could not write to database")?;
|
||||
self.with_dirty_mut(|dirty| *dirty = false);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Increments the rank of a directory, or creates it if it does not exist.
|
||||
pub fn add(&mut self, path: impl AsRef<str> + Into<String>, by: Rank, now: Epoch) {
|
||||
self.with_dirs_mut(|dirs| match dirs.iter_mut().find(|dir| dir.path == path.as_ref()) {
|
||||
Some(dir) => dir.rank = (dir.rank + by).max(0.0),
|
||||
None => {
|
||||
dirs.push(Dir { path: path.into().into(), rank: by.max(0.0), last_accessed: now })
|
||||
}
|
||||
});
|
||||
self.with_dirty_mut(|dirty| *dirty = 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) {
|
||||
// Sort by path, so that equal paths are next to each other.
|
||||
self.sort_by_path();
|
||||
|
||||
let mut dirty = false;
|
||||
self.with_dirs_mut(|dirs| {
|
||||
for idx in (1..dirs.len()).rev() {
|
||||
// Check if curr_dir and next_dir have equal paths.
|
||||
let curr_dir = &dirs[idx];
|
||||
let next_dir = &dirs[idx - 1];
|
||||
if next_dir.path != curr_dir.path {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
// Deserialize sections.
|
||||
let version = deserializer.deserialize(bytes_version)?;
|
||||
let dirs = match version {
|
||||
Self::VERSION => {
|
||||
deserializer.deserialize(bytes_dirs).context("could not deserialize database")?
|
||||
}
|
||||
version => {
|
||||
bail!("unsupported version (got {version}, supports {})", Self::VERSION)
|
||||
}
|
||||
};
|
||||
|
||||
Ok(dirs)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn add() {
|
||||
let data_dir = tempfile::tempdir().unwrap();
|
||||
let path = if cfg!(windows) { r"C:\foo\bar" } else { "/foo/bar" };
|
||||
let now = 946684800;
|
||||
|
||||
{
|
||||
let mut db = Database::open_dir(data_dir.path()).unwrap();
|
||||
db.add(path, 1.0, now);
|
||||
db.add(path, 1.0, now);
|
||||
db.save().unwrap();
|
||||
}
|
||||
|
||||
{
|
||||
let db = Database::open_dir(data_dir.path()).unwrap();
|
||||
assert_eq!(db.dirs().len(), 1);
|
||||
|
||||
let dir = &db.dirs()[0];
|
||||
assert_eq!(dir.path, path);
|
||||
assert!((dir.rank - 2.0).abs() < 0.01);
|
||||
assert_eq!(dir.last_accessed, now);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove() {
|
||||
let data_dir = tempfile::tempdir().unwrap();
|
||||
let path = if cfg!(windows) { r"C:\foo\bar" } else { "/foo/bar" };
|
||||
let now = 946684800;
|
||||
|
||||
{
|
||||
let mut db = Database::open_dir(data_dir.path()).unwrap();
|
||||
db.add(path, 1.0, now);
|
||||
db.save().unwrap();
|
||||
}
|
||||
|
||||
{
|
||||
let mut db = Database::open_dir(data_dir.path()).unwrap();
|
||||
assert!(db.remove(path));
|
||||
db.save().unwrap();
|
||||
}
|
||||
|
||||
{
|
||||
let mut db = Database::open_dir(data_dir.path()).unwrap();
|
||||
assert!(db.dirs().is_empty());
|
||||
assert!(!db.remove(path));
|
||||
db.save().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,211 @@
|
|||
use std::iter::Rev;
|
||||
use std::ops::Range;
|
||||
use std::path::Path;
|
||||
use std::{fs, path};
|
||||
|
||||
use glob::Pattern;
|
||||
|
||||
use crate::db::{Database, Dir, Epoch};
|
||||
use crate::util::{self, MONTH};
|
||||
|
||||
pub struct Stream<'a> {
|
||||
db: &'a mut Database,
|
||||
idxs: Rev<Range<usize>>,
|
||||
options: StreamOptions,
|
||||
}
|
||||
|
||||
impl<'a> Stream<'a> {
|
||||
pub fn new(db: &'a mut Database, options: StreamOptions) -> Self {
|
||||
db.sort_by_score(options.now);
|
||||
let idxs = (0..db.dirs().len()).rev();
|
||||
Stream { db, idxs, options }
|
||||
}
|
||||
|
||||
pub fn next(&mut self) -> Option<&Dir<'_>> {
|
||||
while let Some(idx) = self.idxs.next() {
|
||||
let dir = &self.db.dirs()[idx];
|
||||
|
||||
if !self.filter_by_keywords(&dir.path) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if !self.filter_by_base_dir(&dir.path) {
|
||||
continue;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
let dir = &self.db.dirs()[idx];
|
||||
return Some(dir);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn filter_by_base_dir(&self, path: &str) -> bool {
|
||||
match &self.options.base_dir {
|
||||
Some(base_dir) => Path::new(path).starts_with(base_dir),
|
||||
None => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn filter_by_exclude(&self, path: &str) -> bool {
|
||||
!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,
|
||||
None => return true,
|
||||
};
|
||||
|
||||
let path = util::to_lowercase(path);
|
||||
let mut path = path.as_str();
|
||||
match path.rfind(keywords_last) {
|
||||
Some(idx) => {
|
||||
if path[idx + keywords_last.len()..].contains(path::is_separator) {
|
||||
return false;
|
||||
}
|
||||
path = &path[..idx];
|
||||
}
|
||||
None => return false,
|
||||
}
|
||||
|
||||
for keyword in keywords.iter().rev() {
|
||||
match path.rfind(keyword) {
|
||||
Some(idx) => path = &path[..idx],
|
||||
None => return false,
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
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)]
|
||||
mod tests {
|
||||
use std::path::PathBuf;
|
||||
|
||||
use rstest::rstest;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[rstest]
|
||||
// Case normalization
|
||||
#[case(&["fOo", "bAr"], "/foo/bar", true)]
|
||||
// Last component
|
||||
#[case(&["ba"], "/foo/bar", true)]
|
||||
#[case(&["fo"], "/foo/bar", false)]
|
||||
// Slash as suffix
|
||||
#[case(&["foo/"], "/foo", false)]
|
||||
#[case(&["foo/"], "/foo/bar", true)]
|
||||
#[case(&["foo/"], "/foo/bar/baz", false)]
|
||||
#[case(&["foo", "/"], "/foo", false)]
|
||||
#[case(&["foo", "/"], "/foo/bar", true)]
|
||||
#[case(&["foo", "/"], "/foo/bar/baz", true)]
|
||||
// Split components
|
||||
#[case(&["/", "fo", "/", "ar"], "/foo/bar", true)]
|
||||
#[case(&["oo/ba"], "/foo/bar", true)]
|
||||
// Overlap
|
||||
#[case(&["foo", "o", "bar"], "/foo/bar", false)]
|
||||
#[case(&["/foo/", "/bar"], "/foo/bar", false)]
|
||||
#[case(&["/foo/", "/bar"], "/foo/baz/bar", true)]
|
||||
fn query(#[case] keywords: &[&str], #[case] path: &str, #[case] is_match: bool) {
|
||||
let db = &mut Database::new(PathBuf::new(), Vec::new(), |_| Vec::new(), false);
|
||||
let options = StreamOptions::new(0).with_keywords(keywords.iter());
|
||||
let stream = Stream::new(db, options);
|
||||
assert_eq!(is_match, stream.filter_by_keywords(path));
|
||||
}
|
||||
}
|
21
src/error.rs
21
src/error.rs
|
@ -1,12 +1,29 @@
|
|||
use std::fmt::{self, Display, Formatter};
|
||||
use std::io;
|
||||
|
||||
use anyhow::{Context, Result, bail};
|
||||
|
||||
/// Custom error type for early exit.
|
||||
#[derive(Debug)]
|
||||
pub struct SilentExit {
|
||||
pub code: i32,
|
||||
pub code: u8,
|
||||
}
|
||||
|
||||
impl Display for SilentExit {
|
||||
fn fmt(&self, _: &mut Formatter) -> fmt::Result {
|
||||
fn fmt(&self, _: &mut Formatter<'_>) -> fmt::Result {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait BrokenPipeHandler {
|
||||
fn pipe_exit(self, device: &str) -> Result<()>;
|
||||
}
|
||||
|
||||
impl BrokenPipeHandler for io::Result<()> {
|
||||
fn pipe_exit(self, device: &str) -> Result<()> {
|
||||
match self {
|
||||
Err(e) if e.kind() == io::ErrorKind::BrokenPipe => bail!(SilentExit { code: 0 }),
|
||||
result => result.with_context(|| format!("could not write to {device}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
57
src/fzf.rs
57
src/fzf.rs
|
@ -1,57 +0,0 @@
|
|||
use crate::config;
|
||||
use crate::error::SilentExit;
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
|
||||
use std::process::{Child, ChildStdin, Command, Stdio};
|
||||
|
||||
pub struct Fzf {
|
||||
child: Child,
|
||||
}
|
||||
|
||||
impl Fzf {
|
||||
pub fn new() -> Result<Self> {
|
||||
let mut command = Command::new("fzf");
|
||||
command
|
||||
.arg("-n2..")
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped());
|
||||
|
||||
if let Some(fzf_opts) = config::zo_fzf_opts() {
|
||||
command.env("FZF_DEFAULT_OPTS", fzf_opts);
|
||||
}
|
||||
|
||||
Ok(Fzf {
|
||||
child: command.spawn().context("could not launch fzf")?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn stdin(&mut self) -> &mut ChildStdin {
|
||||
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"),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
use super::Import;
|
||||
|
||||
use crate::store::{Dir, Epoch, Store};
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
pub struct Autojump {
|
||||
pub resolve_symlinks: bool,
|
||||
pub now: Epoch,
|
||||
}
|
||||
|
||||
impl Import for Autojump {
|
||||
fn import<P: AsRef<Path>>(&self, store: &mut Store, path: P) -> Result<()> {
|
||||
let path = path.as_ref();
|
||||
let buffer = fs::read_to_string(path)
|
||||
.with_context(|| format!("could not open autojump database: {}", path.display()))?;
|
||||
|
||||
let mut entries = Vec::new();
|
||||
for (idx, line) in buffer.lines().enumerate() {
|
||||
(|| -> Result<()> {
|
||||
if line.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let (rank, path) = (|| {
|
||||
let mut split = line.splitn(2, '\t');
|
||||
let rank = split.next()?;
|
||||
let path = split.next()?;
|
||||
Some((rank, path))
|
||||
})()
|
||||
.with_context(|| format!("invalid entry: {}", line))?;
|
||||
|
||||
let rank = rank
|
||||
.parse::<f64>()
|
||||
.with_context(|| format!("invalid rank: {}", rank))?;
|
||||
|
||||
entries.push((path, rank));
|
||||
Ok(())
|
||||
})()
|
||||
.with_context(|| format!("line {}: error reading from autojump database", idx + 1))?;
|
||||
}
|
||||
|
||||
let rank_sum = entries.iter().map(|(_, rank)| rank).sum::<f64>();
|
||||
for (path, rank) in entries.iter() {
|
||||
if store
|
||||
.dirs
|
||||
.iter_mut()
|
||||
.find(|dir| &dir.path == path)
|
||||
.is_none()
|
||||
{
|
||||
store.dirs.push(Dir {
|
||||
path: path.to_string(),
|
||||
rank: rank / rank_sum,
|
||||
last_accessed: self.now,
|
||||
});
|
||||
store.modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
mod autojump;
|
||||
mod z;
|
||||
|
||||
use crate::store::Store;
|
||||
use anyhow::Result;
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
pub use autojump::Autojump;
|
||||
pub use z::Z;
|
||||
|
||||
pub trait Import {
|
||||
fn import<P: AsRef<Path>>(&self, store: &mut Store, path: P) -> Result<()>;
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
use super::Import;
|
||||
|
||||
use crate::store::{Dir, Store};
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::{BufRead, BufReader};
|
||||
use std::path::Path;
|
||||
|
||||
pub struct Z {
|
||||
pub resolve_symlinks: bool,
|
||||
}
|
||||
|
||||
impl Import for Z {
|
||||
fn import<P: AsRef<Path>>(&self, store: &mut Store, path: P) -> Result<()> {
|
||||
let file = File::open(path).context("could not open z database")?;
|
||||
let reader = BufReader::new(file);
|
||||
|
||||
for (idx, line) in reader.lines().enumerate() {
|
||||
(|| -> Result<()> {
|
||||
let line = line?;
|
||||
if line.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let (path, rank, last_accessed) = (|| {
|
||||
let mut split = line.rsplitn(3, '|');
|
||||
let last_accessed = split.next()?;
|
||||
let rank = split.next()?;
|
||||
let path = split.next()?;
|
||||
Some((path, rank, last_accessed))
|
||||
})()
|
||||
.with_context(|| format!("invalid entry: {}", line))?;
|
||||
|
||||
let rank = rank
|
||||
.parse()
|
||||
.with_context(|| format!("invalid rank: {}", rank))?;
|
||||
|
||||
let last_accessed = last_accessed
|
||||
.parse()
|
||||
.with_context(|| format!("invalid epoch: {}", last_accessed))?;
|
||||
|
||||
match store.dirs.iter_mut().find(|dir| dir.path == path) {
|
||||
Some(dir) => {
|
||||
dir.rank += rank;
|
||||
dir.last_accessed = dir.last_accessed.max(last_accessed);
|
||||
}
|
||||
None => store.dirs.push(Dir {
|
||||
path: path.to_string(),
|
||||
rank,
|
||||
last_accessed,
|
||||
}),
|
||||
}
|
||||
store.modified = true;
|
||||
|
||||
Ok(())
|
||||
})()
|
||||
.with_context(|| format!("line {}: error reading from z database", idx + 1))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
40
src/main.rs
40
src/main.rs
|
@ -1,26 +1,34 @@
|
|||
#![allow(clippy::single_component_path_imports)]
|
||||
|
||||
mod cmd;
|
||||
mod config;
|
||||
mod db;
|
||||
mod error;
|
||||
mod fzf;
|
||||
mod import;
|
||||
mod shell;
|
||||
mod store;
|
||||
mod util;
|
||||
|
||||
use crate::cmd::{App, Cmd};
|
||||
use std::env;
|
||||
use std::io::{self, Write};
|
||||
use std::process::ExitCode;
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
use crate::cmd::{Cmd, Run};
|
||||
use crate::error::SilentExit;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Clap;
|
||||
pub fn main() -> ExitCode {
|
||||
// Forcibly disable backtraces.
|
||||
unsafe { env::remove_var("RUST_LIB_BACKTRACE") };
|
||||
unsafe { env::remove_var("RUST_BACKTRACE") };
|
||||
|
||||
use std::process;
|
||||
|
||||
pub fn main() -> Result<()> {
|
||||
App::parse()
|
||||
.run()
|
||||
.map_err(|e| match e.downcast::<SilentExit>() {
|
||||
Ok(SilentExit { code }) => process::exit(code),
|
||||
// TODO: change the error prefix to `zoxide:`
|
||||
Err(e) => e,
|
||||
})
|
||||
match Cmd::parse().run() {
|
||||
Ok(()) => ExitCode::SUCCESS,
|
||||
Err(e) => match e.downcast::<SilentExit>() {
|
||||
Ok(SilentExit { code }) => code.into(),
|
||||
Err(e) => {
|
||||
_ = writeln!(io::stderr(), "zoxide: {e:?}");
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
455
src/shell.rs
455
src/shell.rs
|
@ -1,17 +1,13 @@
|
|||
use clap::ArgEnum;
|
||||
use crate::cmd::InitHook;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct Opts<'a> {
|
||||
pub cmd: Option<&'a str>,
|
||||
pub hook: Hook,
|
||||
pub hook: InitHook,
|
||||
pub echo: bool,
|
||||
pub resolve_symlinks: bool,
|
||||
}
|
||||
|
||||
impl Opts<'_> {
|
||||
pub const DEVNULL: &'static str = if cfg!(windows) { "NUL" } else { "/dev/null" };
|
||||
}
|
||||
|
||||
macro_rules! make_template {
|
||||
($name:ident, $path:expr) => {
|
||||
#[derive(::std::fmt::Debug, ::askama::Template)]
|
||||
|
@ -28,208 +24,323 @@ macro_rules! make_template {
|
|||
}
|
||||
|
||||
make_template!(Bash, "bash.txt");
|
||||
make_template!(Elvish, "elvish.txt");
|
||||
make_template!(Fish, "fish.txt");
|
||||
make_template!(Nushell, "nushell.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!(Zsh, "zsh.txt");
|
||||
|
||||
#[derive(ArgEnum, Clone, Copy, Debug, PartialEq)]
|
||||
pub enum Hook {
|
||||
None,
|
||||
Prompt,
|
||||
Pwd,
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[cfg(feature = "nix-dev")]
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use askama::Template;
|
||||
use assert_cmd::Command;
|
||||
use once_cell::sync::OnceCell;
|
||||
use rstest::rstest;
|
||||
use rstest_reuse::{apply, template};
|
||||
|
||||
fn opts() -> &'static [Opts<'static>] {
|
||||
static OPTS: OnceCell<Vec<Opts>> = OnceCell::new();
|
||||
OPTS.get_or_init(|| {
|
||||
const BOOLS: &[bool] = &[false, true];
|
||||
const HOOKS: &[Hook] = &[Hook::None, Hook::Prompt, Hook::Pwd];
|
||||
const CMDS: &[Option<&str>] = &[None, Some("z")];
|
||||
use super::*;
|
||||
|
||||
let mut opts = Vec::new();
|
||||
for &echo in BOOLS {
|
||||
for &resolve_symlinks in BOOLS {
|
||||
for &hook in HOOKS {
|
||||
for &cmd in CMDS {
|
||||
let opt = Opts {
|
||||
echo,
|
||||
resolve_symlinks,
|
||||
hook,
|
||||
cmd,
|
||||
};
|
||||
opts.push(opt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
opts
|
||||
})
|
||||
#[template]
|
||||
#[rstest]
|
||||
fn opts(
|
||||
#[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,
|
||||
) {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bash() {
|
||||
for opts in opts() {
|
||||
let source = Bash(opts).render().unwrap();
|
||||
Command::new("bash")
|
||||
.args(&["-c", &source])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
#[apply(opts)]
|
||||
fn bash_bash(cmd: Option<&str>, hook: InitHook, echo: bool, resolve_symlinks: bool) {
|
||||
let opts = Opts { cmd, hook, echo, resolve_symlinks };
|
||||
let source = Bash(&opts).render().unwrap();
|
||||
Command::new("bash")
|
||||
.args(["--noprofile", "--norc", "-e", "-u", "-o", "pipefail", "-c", &source])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
}
|
||||
|
||||
#[apply(opts)]
|
||||
fn bash_shellcheck(cmd: Option<&str>, hook: InitHook, echo: bool, resolve_symlinks: bool) {
|
||||
let opts = Opts { cmd, hook, echo, resolve_symlinks };
|
||||
let source = Bash(&opts).render().unwrap();
|
||||
|
||||
Command::new("shellcheck")
|
||||
.args(["--enable=all", "-"])
|
||||
.write_stdin(source)
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
}
|
||||
|
||||
#[apply(opts)]
|
||||
fn bash_shfmt(cmd: Option<&str>, hook: InitHook, echo: bool, resolve_symlinks: bool) {
|
||||
let opts = Opts { cmd, hook, echo, resolve_symlinks };
|
||||
let mut source = Bash(&opts).render().unwrap();
|
||||
source.push('\n');
|
||||
|
||||
Command::new("shfmt")
|
||||
.args(["--diff", "--indent=4", "--language-dialect=bash", "--simplify", "-"])
|
||||
.write_stdin(source)
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
}
|
||||
|
||||
#[apply(opts)]
|
||||
fn elvish_elvish(cmd: Option<&str>, hook: InitHook, echo: bool, resolve_symlinks: bool) {
|
||||
let opts = Opts { cmd, hook, echo, resolve_symlinks };
|
||||
let mut source = String::new();
|
||||
|
||||
// Filter out lines using edit:*, since those functions are only available in
|
||||
// the interactive editor.
|
||||
for line in Elvish(&opts).render().unwrap().lines().filter(|line| !line.contains("edit:")) {
|
||||
source.push_str(line);
|
||||
source.push('\n');
|
||||
}
|
||||
|
||||
Command::new("elvish")
|
||||
.args(["-c", &source, "-norc"])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
}
|
||||
|
||||
#[apply(opts)]
|
||||
fn fish_no_builtin_abbr(cmd: Option<&str>, hook: InitHook, echo: bool, resolve_symlinks: bool) {
|
||||
let opts = Opts { cmd, hook, echo, resolve_symlinks };
|
||||
let source = Fish(&opts).render().unwrap();
|
||||
assert!(
|
||||
!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 source = Fish(&opts).render().unwrap();
|
||||
|
||||
let tempdir = tempfile::tempdir().unwrap();
|
||||
let tempdir = tempdir.path().to_str().unwrap();
|
||||
|
||||
Command::new("fish")
|
||||
.env("HOME", tempdir)
|
||||
.args(["--command", &source, "--no-config", "--private"])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
}
|
||||
|
||||
#[apply(opts)]
|
||||
fn fish_fishindent(cmd: Option<&str>, hook: InitHook, echo: bool, resolve_symlinks: bool) {
|
||||
let opts = Opts { cmd, hook, echo, resolve_symlinks };
|
||||
let mut source = Fish(&opts).render().unwrap();
|
||||
source.push('\n');
|
||||
|
||||
let tempdir = tempfile::tempdir().unwrap();
|
||||
let tempdir = tempdir.path().to_str().unwrap();
|
||||
|
||||
Command::new("fish_indent")
|
||||
.env("HOME", tempdir)
|
||||
.write_stdin(source.to_string())
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(source)
|
||||
.stderr("");
|
||||
}
|
||||
|
||||
#[apply(opts)]
|
||||
fn nushell_nushell(cmd: Option<&str>, hook: InitHook, echo: bool, resolve_symlinks: bool) {
|
||||
let opts = Opts { cmd, hook, echo, resolve_symlinks };
|
||||
let source = Nushell(&opts).render().unwrap();
|
||||
|
||||
let tempdir = tempfile::tempdir().unwrap();
|
||||
let tempdir = tempdir.path();
|
||||
|
||||
let assert = Command::new("nu")
|
||||
.env("HOME", tempdir)
|
||||
.args(["--commands", &source])
|
||||
.assert()
|
||||
.success()
|
||||
.stderr("");
|
||||
|
||||
if opts.hook != InitHook::Pwd {
|
||||
assert.stdout("");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bash_posix() {
|
||||
for opts in opts() {
|
||||
let source = Posix(opts).render().unwrap();
|
||||
let assert = Command::new("bash")
|
||||
.args(&["--posix", "-c", &source])
|
||||
.assert()
|
||||
.success()
|
||||
.stderr("");
|
||||
#[apply(opts)]
|
||||
fn posix_bash(cmd: Option<&str>, hook: InitHook, echo: bool, resolve_symlinks: bool) {
|
||||
let opts = Opts { cmd, hook, echo, resolve_symlinks };
|
||||
let source = Posix(&opts).render().unwrap();
|
||||
|
||||
if opts.hook != Hook::Pwd {
|
||||
assert.stdout("");
|
||||
}
|
||||
let assert = Command::new("bash")
|
||||
.args(["--posix", "--noprofile", "--norc", "-e", "-u", "-o", "pipefail", "-c", &source])
|
||||
.assert()
|
||||
.success()
|
||||
.stderr("");
|
||||
if opts.hook != InitHook::Pwd {
|
||||
assert.stdout("");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dash() {
|
||||
for opts in opts() {
|
||||
let source = Posix(opts).render().unwrap();
|
||||
let assert = Command::new("bash")
|
||||
.args(&["--posix", "-c", &source])
|
||||
.assert()
|
||||
.success()
|
||||
.stderr("");
|
||||
#[apply(opts)]
|
||||
fn posix_dash(cmd: Option<&str>, hook: InitHook, echo: bool, resolve_symlinks: bool) {
|
||||
let opts = Opts { cmd, hook, echo, resolve_symlinks };
|
||||
let source = Posix(&opts).render().unwrap();
|
||||
|
||||
if opts.hook != Hook::Pwd {
|
||||
assert.stdout("");
|
||||
}
|
||||
let assert =
|
||||
Command::new("dash").args(["-e", "-u", "-c", &source]).assert().success().stderr("");
|
||||
if opts.hook != InitHook::Pwd {
|
||||
assert.stdout("");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fish() {
|
||||
for opts in opts() {
|
||||
let source = Fish(opts).render().unwrap();
|
||||
Command::new("fish")
|
||||
.args(&["-c", &source])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
}
|
||||
#[apply(opts)]
|
||||
fn posix_shellcheck(cmd: Option<&str>, hook: InitHook, echo: bool, resolve_symlinks: bool) {
|
||||
let opts = Opts { cmd, hook, echo, resolve_symlinks };
|
||||
let source = Posix(&opts).render().unwrap();
|
||||
|
||||
Command::new("shellcheck")
|
||||
.args(["--enable=all", "-"])
|
||||
.write_stdin(source)
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pwsh() {
|
||||
for opts in opts() {
|
||||
let source = PowerShell(opts).render().unwrap();
|
||||
Command::new("pwsh")
|
||||
.args(&["-c", &source])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
}
|
||||
#[apply(opts)]
|
||||
fn posix_shfmt(cmd: Option<&str>, hook: InitHook, echo: bool, resolve_symlinks: bool) {
|
||||
let opts = Opts { cmd, hook, echo, resolve_symlinks };
|
||||
let mut source = Posix(&opts).render().unwrap();
|
||||
source.push('\n');
|
||||
|
||||
Command::new("shfmt")
|
||||
.args(["--diff", "--indent=4", "--language-dialect=posix", "--simplify", "-"])
|
||||
.write_stdin(source)
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_shellcheck_bash() {
|
||||
for opts in opts() {
|
||||
let source = Bash(opts).render().unwrap();
|
||||
Command::new("shellcheck")
|
||||
.args(&["--shell", "bash", "-"])
|
||||
.write_stdin(source.as_bytes())
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
}
|
||||
#[apply(opts)]
|
||||
fn powershell_pwsh(cmd: Option<&str>, hook: InitHook, echo: bool, resolve_symlinks: bool) {
|
||||
let opts = Opts { cmd, hook, echo, resolve_symlinks };
|
||||
let mut source = "Set-StrictMode -Version latest\n".to_string();
|
||||
Powershell(&opts).render_into(&mut source).unwrap();
|
||||
|
||||
Command::new("pwsh")
|
||||
.args(["-NoLogo", "-NonInteractive", "-NoProfile", "-Command", &source])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_shellcheck_sh() {
|
||||
for opts in opts() {
|
||||
let source = Posix(opts).render().unwrap();
|
||||
Command::new("shellcheck")
|
||||
.args(&["--shell", "sh", "-"])
|
||||
.write_stdin(source.as_bytes())
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
}
|
||||
#[apply(opts)]
|
||||
fn tcsh_tcsh(cmd: Option<&str>, hook: InitHook, echo: bool, resolve_symlinks: bool) {
|
||||
let opts = Opts { cmd, hook, echo, resolve_symlinks };
|
||||
let source = Tcsh(&opts).render().unwrap();
|
||||
|
||||
Command::new("tcsh")
|
||||
.args(["-e", "-f", "-s"])
|
||||
.write_stdin(source)
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_shfmt_bash() {
|
||||
for opts in opts() {
|
||||
let source = Bash(opts).render().unwrap();
|
||||
Command::new("shfmt")
|
||||
.args(&["-d", "-s", "-ln", "bash", "-i", "4", "-ci", "-"])
|
||||
.write_stdin(source.as_bytes())
|
||||
.write_stdin(b"\n".as_ref())
|
||||
.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 mut source = Xonsh(&opts).render().unwrap();
|
||||
source.push('\n');
|
||||
|
||||
Command::new("black")
|
||||
.args(["--check", "--diff", "-"])
|
||||
.write_stdin(source)
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_shfmt_posix() {
|
||||
for opts in opts() {
|
||||
let source = Posix(opts).render().unwrap();
|
||||
Command::new("shfmt")
|
||||
.args(&["-d", "-s", "-ln", "posix", "-i", "4", "-ci", "-"])
|
||||
.write_stdin(source.as_bytes())
|
||||
.write_stdin(b"\n".as_ref())
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
}
|
||||
#[apply(opts)]
|
||||
fn xonsh_mypy(cmd: Option<&str>, hook: InitHook, echo: bool, resolve_symlinks: bool) {
|
||||
let opts = Opts { cmd, hook, echo, resolve_symlinks };
|
||||
let source = Xonsh(&opts).render().unwrap();
|
||||
|
||||
Command::new("mypy").args(["--command", &source, "--strict"]).assert().success().stderr("");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_xonsh() {
|
||||
for opts in opts() {
|
||||
let source = Xonsh(opts).render().unwrap();
|
||||
Command::new("xonsh")
|
||||
.args(&["-c", &source])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
}
|
||||
#[apply(opts)]
|
||||
fn xonsh_pylint(cmd: Option<&str>, hook: InitHook, echo: bool, resolve_symlinks: bool) {
|
||||
let opts = Opts { cmd, hook, echo, resolve_symlinks };
|
||||
let mut source = Xonsh(&opts).render().unwrap();
|
||||
source.push('\n');
|
||||
|
||||
Command::new("pylint")
|
||||
.args(["--from-stdin", "--persistent=n", "zoxide"])
|
||||
.write_stdin(source)
|
||||
.assert()
|
||||
.success()
|
||||
.stderr("");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_zsh() {
|
||||
for opts in opts() {
|
||||
let source = Zsh(opts).render().unwrap();
|
||||
Command::new("zsh")
|
||||
.args(&["-c", &source])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
}
|
||||
#[apply(opts)]
|
||||
fn xonsh_xonsh(cmd: Option<&str>, hook: InitHook, echo: bool, resolve_symlinks: bool) {
|
||||
let opts = Opts { cmd, hook, echo, resolve_symlinks };
|
||||
let source = Xonsh(&opts).render().unwrap();
|
||||
|
||||
let tempdir = tempfile::tempdir().unwrap();
|
||||
let tempdir = tempdir.path().to_str().unwrap();
|
||||
|
||||
Command::new("xonsh")
|
||||
.args(["-c", &source, "--no-rc"])
|
||||
.env("HOME", tempdir)
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
}
|
||||
|
||||
#[apply(opts)]
|
||||
fn zsh_shellcheck(cmd: Option<&str>, hook: InitHook, echo: bool, resolve_symlinks: bool) {
|
||||
let opts = Opts { cmd, hook, echo, resolve_symlinks };
|
||||
let source = Zsh(&opts).render().unwrap();
|
||||
|
||||
// ShellCheck doesn't support zsh yet: https://github.com/koalaman/shellcheck/issues/809
|
||||
Command::new("shellcheck")
|
||||
.args(["--enable=all", "-"])
|
||||
.write_stdin(source)
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
}
|
||||
|
||||
#[apply(opts)]
|
||||
fn zsh_zsh(cmd: Option<&str>, hook: InitHook, echo: bool, resolve_symlinks: bool) {
|
||||
let opts = Opts { cmd, hook, echo, resolve_symlinks };
|
||||
let source = Zsh(&opts).render().unwrap();
|
||||
|
||||
Command::new("zsh")
|
||||
.args(["-e", "-u", "-o", "pipefail", "--no-globalrcs", "--no-rcs", "-c", &source])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,73 +0,0 @@
|
|||
use super::{Epoch, Query, Rank};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Dir {
|
||||
pub path: String,
|
||||
pub rank: Rank,
|
||||
pub last_accessed: Epoch,
|
||||
}
|
||||
|
||||
impl Dir {
|
||||
pub fn is_match(&self, query: &Query) -> bool {
|
||||
query.matches(&self.path) && Path::new(&self.path).is_dir()
|
||||
}
|
||||
|
||||
pub fn get_score(&self, now: Epoch) -> Rank {
|
||||
const HOUR: Epoch = 60 * 60;
|
||||
const DAY: Epoch = 24 * HOUR;
|
||||
const WEEK: Epoch = 7 * DAY;
|
||||
|
||||
let duration = now.saturating_sub(self.last_accessed);
|
||||
if duration < HOUR {
|
||||
self.rank * 4.0
|
||||
} else if duration < DAY {
|
||||
self.rank * 2.0
|
||||
} else if duration < WEEK {
|
||||
self.rank * 0.5
|
||||
} else {
|
||||
self.rank * 0.25
|
||||
}
|
||||
}
|
||||
|
||||
pub fn display(&self) -> DirDisplay {
|
||||
DirDisplay { dir: self }
|
||||
}
|
||||
|
||||
pub fn display_score(&self, now: Epoch) -> DirDisplayScore {
|
||||
DirDisplayScore { dir: self, now }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DirDisplay<'a> {
|
||||
dir: &'a Dir,
|
||||
}
|
||||
|
||||
impl Display for DirDisplay<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.dir.path)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DirDisplayScore<'a> {
|
||||
dir: &'a Dir,
|
||||
now: Epoch,
|
||||
}
|
||||
|
||||
impl Display for DirDisplayScore<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
let score = self.dir.get_score(self.now);
|
||||
let score = if score > 9999.0 {
|
||||
9999
|
||||
} else if score > 0.0 {
|
||||
score as _
|
||||
} else {
|
||||
0
|
||||
};
|
||||
write!(f, "{:>4} {}", score, self.dir.path)
|
||||
}
|
||||
}
|
296
src/store/mod.rs
296
src/store/mod.rs
|
@ -1,296 +0,0 @@
|
|||
mod dir;
|
||||
mod query;
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
use bincode::Options;
|
||||
use ordered_float::OrderedFloat;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tempfile::{NamedTempFile, PersistError};
|
||||
|
||||
use std::cmp::Reverse;
|
||||
use std::fs;
|
||||
use std::io::{self, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub use dir::Dir;
|
||||
pub use query::Query;
|
||||
|
||||
pub type Rank = f64;
|
||||
pub type Epoch = u64;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Store {
|
||||
pub dirs: Vec<Dir>,
|
||||
pub modified: bool,
|
||||
data_dir: PathBuf,
|
||||
}
|
||||
|
||||
impl Store {
|
||||
pub const CURRENT_VERSION: StoreVersion = StoreVersion(3);
|
||||
const MAX_SIZE: u64 = 8 * 1024 * 1024; // 8 MiB
|
||||
|
||||
pub fn open<P: Into<PathBuf>>(data_dir: P) -> Result<Store> {
|
||||
let data_dir = data_dir.into();
|
||||
let path = Self::get_path(&data_dir);
|
||||
|
||||
let buffer = match fs::read(&path) {
|
||||
Ok(buffer) => buffer,
|
||||
Err(e) if e.kind() == io::ErrorKind::NotFound => {
|
||||
fs::create_dir_all(&data_dir).with_context(|| {
|
||||
format!("unable to create data directory: {}", path.display())
|
||||
})?;
|
||||
return Ok(Store {
|
||||
dirs: Vec::new(),
|
||||
modified: false,
|
||||
data_dir,
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
Err(e).with_context(|| format!("could not read from store: {}", path.display()))?
|
||||
}
|
||||
};
|
||||
|
||||
let deserializer = &mut bincode::options()
|
||||
.with_fixint_encoding()
|
||||
.with_limit(Self::MAX_SIZE);
|
||||
|
||||
let version_size = deserializer
|
||||
.serialized_size(&Self::CURRENT_VERSION)
|
||||
.unwrap() as _;
|
||||
|
||||
if buffer.len() < version_size {
|
||||
bail!("data store may be corrupted: {}", path.display());
|
||||
}
|
||||
|
||||
let (buffer_version, buffer_dirs) = buffer.split_at(version_size);
|
||||
|
||||
let version = deserializer
|
||||
.deserialize(buffer_version)
|
||||
.with_context(|| format!("could not deserialize store version: {}", path.display()))?;
|
||||
|
||||
let dirs = match version {
|
||||
Self::CURRENT_VERSION => deserializer
|
||||
.deserialize(buffer_dirs)
|
||||
.with_context(|| format!("could not deserialize store: {}", path.display()))?,
|
||||
version => bail!(
|
||||
"unsupported store version, got={}, supported={}: {}",
|
||||
version.0,
|
||||
Self::CURRENT_VERSION.0,
|
||||
path.display()
|
||||
),
|
||||
};
|
||||
|
||||
Ok(Store {
|
||||
dirs,
|
||||
modified: false,
|
||||
data_dir,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn save(&mut self) -> Result<()> {
|
||||
if !self.modified {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let (buffer, buffer_size) = (|| -> bincode::Result<_> {
|
||||
let version_size = bincode::serialized_size(&Self::CURRENT_VERSION)?;
|
||||
let dirs_size = bincode::serialized_size(&self.dirs)?;
|
||||
|
||||
let buffer_size = version_size + dirs_size;
|
||||
let mut buffer = Vec::with_capacity(buffer_size as _);
|
||||
|
||||
bincode::serialize_into(&mut buffer, &Self::CURRENT_VERSION)?;
|
||||
bincode::serialize_into(&mut buffer, &self.dirs)?;
|
||||
|
||||
Ok((buffer, buffer_size))
|
||||
})()
|
||||
.context("could not serialize store")?;
|
||||
|
||||
let mut file = NamedTempFile::new_in(&self.data_dir).with_context(|| {
|
||||
format!(
|
||||
"could not create temporary store in: {}",
|
||||
self.data_dir.display()
|
||||
)
|
||||
})?;
|
||||
|
||||
let _ = file.as_file().set_len(buffer_size);
|
||||
file.write_all(&buffer).with_context(|| {
|
||||
format!(
|
||||
"could not write to temporary store: {}",
|
||||
file.path().display()
|
||||
)
|
||||
})?;
|
||||
|
||||
let path = Self::get_path(&self.data_dir);
|
||||
persist(file, &path)
|
||||
.with_context(|| format!("could not replace store: {}", path.display()))?;
|
||||
|
||||
self.modified = false;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add<S: AsRef<str>>(&mut self, path: S, now: Epoch) {
|
||||
let path = path.as_ref();
|
||||
debug_assert!(Path::new(path).is_absolute());
|
||||
|
||||
match self.dirs.iter_mut().find(|dir| dir.path == path) {
|
||||
None => self.dirs.push(Dir {
|
||||
path: path.into(),
|
||||
last_accessed: now,
|
||||
rank: 1.0,
|
||||
}),
|
||||
Some(dir) => {
|
||||
dir.last_accessed = now;
|
||||
dir.rank += 1.0;
|
||||
}
|
||||
};
|
||||
|
||||
self.modified = true;
|
||||
}
|
||||
|
||||
pub fn iter_matches<'a>(
|
||||
&'a mut self,
|
||||
query: &'a Query,
|
||||
now: Epoch,
|
||||
) -> impl DoubleEndedIterator<Item = &'a Dir> {
|
||||
self.dirs
|
||||
.sort_unstable_by_key(|dir| Reverse(OrderedFloat(dir.get_score(now))));
|
||||
self.dirs.iter().filter(move |dir| dir.is_match(&query))
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
fn get_path<P: AsRef<Path>>(data_dir: P) -> PathBuf {
|
||||
data_dir.as_ref().join("db.zo")
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Store {
|
||||
fn drop(&mut self) {
|
||||
if let Err(e) = self.save() {
|
||||
println!("Error: {}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub struct StoreVersion(pub u32);
|
||||
|
||||
#[cfg(windows)]
|
||||
fn persist<P: AsRef<Path>>(mut file: NamedTempFile, path: P) -> Result<(), PersistError> {
|
||||
use rand::distributions::{Distribution, Uniform};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
// 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(rand::thread_rng);
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn persist<P: AsRef<Path>>(file: NamedTempFile, path: P) -> Result<(), PersistError> {
|
||||
file.persist(&path)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_add() {
|
||||
let path = if cfg!(windows) {
|
||||
r"C:\foo\bar"
|
||||
} else {
|
||||
"/foo/bar"
|
||||
};
|
||||
let now = 946684800;
|
||||
|
||||
let data_dir = tempfile::tempdir().unwrap();
|
||||
{
|
||||
let mut store = Store::open(data_dir.path()).unwrap();
|
||||
store.add(path, now);
|
||||
store.add(path, now);
|
||||
}
|
||||
{
|
||||
let store = Store::open(data_dir.path()).unwrap();
|
||||
assert_eq!(store.dirs.len(), 1);
|
||||
|
||||
let dir = &store.dirs[0];
|
||||
assert_eq!(dir.path, path);
|
||||
assert_eq!(dir.last_accessed, now);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove() {
|
||||
let path = if cfg!(windows) {
|
||||
r"C:\foo\bar"
|
||||
} else {
|
||||
"/foo/bar"
|
||||
};
|
||||
let now = 946684800;
|
||||
|
||||
let data_dir = tempfile::tempdir().unwrap();
|
||||
{
|
||||
let mut store = Store::open(data_dir.path()).unwrap();
|
||||
store.add(path, now);
|
||||
}
|
||||
{
|
||||
let mut store = Store::open(data_dir.path()).unwrap();
|
||||
assert!(store.remove(path));
|
||||
}
|
||||
{
|
||||
let mut store = Store::open(data_dir.path()).unwrap();
|
||||
assert!(store.dirs.is_empty());
|
||||
assert!(!store.remove(path));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
use std::path::Path;
|
||||
|
||||
pub struct Query(Vec<String>);
|
||||
|
||||
impl Query {
|
||||
pub fn new<I, S>(keywords: I) -> Query
|
||||
where
|
||||
I: IntoIterator<Item = S>,
|
||||
S: AsRef<str>,
|
||||
{
|
||||
Query(
|
||||
keywords
|
||||
.into_iter()
|
||||
.map(|s: S| s.as_ref().to_lowercase())
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn keywords(&self) -> &[String] {
|
||||
&self.0
|
||||
}
|
||||
|
||||
pub fn matches<S: AsRef<str>>(&self, path: S) -> bool {
|
||||
let path = path.as_ref().to_lowercase();
|
||||
let keywords = self.keywords();
|
||||
|
||||
let get_filenames = || {
|
||||
let query_name = Path::new(keywords.last()?).file_name()?.to_str().unwrap();
|
||||
let dir_name = Path::new(&path).file_name()?.to_str().unwrap();
|
||||
Some((query_name, dir_name))
|
||||
};
|
||||
|
||||
if let Some((query_name, dir_name)) = get_filenames() {
|
||||
if !dir_name.contains(query_name) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
let mut subpath = path.as_str();
|
||||
|
||||
for keyword in keywords.iter() {
|
||||
match subpath.find(keyword) {
|
||||
Some(idx) => subpath = &subpath[idx + keyword.len()..],
|
||||
None => return false,
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Query;
|
||||
|
||||
#[test]
|
||||
fn test_query_normalization() {
|
||||
assert!(Query::new(&["fOo", "bAr"]).matches("/foo/bar"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_query_filename() {
|
||||
assert!(Query::new(&["ba"]).matches("/foo/bar"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_query_not_filename() {
|
||||
assert!(!Query::new(&["fo"]).matches("/foo/bar"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_query_not_filename_slash() {
|
||||
assert!(!Query::new(&["foo/"]).matches("/foo/bar"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_query_path_separator() {
|
||||
assert!(Query::new(&["/", "fo", "/", "ar"]).matches("/foo/bar"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_query_path_separator_between() {
|
||||
assert!(Query::new(&["oo/ba"]).matches("/foo/bar"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_query_overlap_text() {
|
||||
assert!(!Query::new(&["foo", "o", "bar"]).matches("/foo/bar"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_query_overlap_slash() {
|
||||
assert!(!Query::new(&["/foo/", "/bar"]).matches("/foo/bar"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_query_consecutive_slash() {
|
||||
assert!(Query::new(&["/foo/", "/baz"]).matches("/foo/bar/baz"));
|
||||
}
|
||||
}
|
315
src/util.rs
315
src/util.rs
|
@ -1,13 +1,252 @@
|
|||
use crate::store::Epoch;
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
|
||||
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::process::{Child, Command, Stdio};
|
||||
use std::time::SystemTime;
|
||||
use std::{env, mem};
|
||||
|
||||
pub fn canonicalize<P: AsRef<Path>>(path: &P) -> Result<PathBuf> {
|
||||
dunce::canonicalize(path)
|
||||
#[cfg(windows)]
|
||||
use anyhow::anyhow;
|
||||
use anyhow::{Context, Result, bail};
|
||||
|
||||
use crate::db::{Dir, Epoch};
|
||||
use crate::error::SilentExit;
|
||||
|
||||
pub const SECOND: Epoch = 1;
|
||||
pub const MINUTE: Epoch = 60 * SECOND;
|
||||
pub const HOUR: Epoch = 60 * MINUTE;
|
||||
pub const DAY: Epoch = 24 * HOUR;
|
||||
pub const WEEK: Epoch = 7 * DAY;
|
||||
pub const MONTH: Epoch = 30 * DAY;
|
||||
|
||||
pub struct Fzf(Command);
|
||||
|
||||
impl Fzf {
|
||||
const ERR_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()))
|
||||
}
|
||||
|
||||
|
@ -24,18 +263,14 @@ pub fn current_time() -> Result<Epoch> {
|
|||
Ok(current_time)
|
||||
}
|
||||
|
||||
pub fn path_to_str<P: AsRef<Path>>(path: &P) -> Result<&str> {
|
||||
pub fn path_to_str(path: &impl AsRef<Path>) -> Result<&str> {
|
||||
let path = path.as_ref();
|
||||
path.to_str()
|
||||
.with_context(|| format!("invalid unicode in path: {}", path.display()))
|
||||
path.to_str().with_context(|| format!("invalid unicode in path: {}", path.display()))
|
||||
}
|
||||
|
||||
/// Resolves the absolute version of a path.
|
||||
///
|
||||
/// If path is already absolute, the path is still processed to be cleaned, as it can contained ".." or "." (or other)
|
||||
/// character.
|
||||
/// If path is relative, use the current directory to build the absolute path.
|
||||
pub fn resolve_path<P: AsRef<Path>>(path: &P) -> Result<PathBuf> {
|
||||
/// Returns the absolute version of a path. Like
|
||||
/// [`std::path::Path::canonicalize`], but doesn't resolve symlinks.
|
||||
pub fn resolve_path(path: impl AsRef<Path>) -> Result<PathBuf> {
|
||||
let path = path.as_ref();
|
||||
let base_path;
|
||||
|
||||
|
@ -46,7 +281,7 @@ pub fn resolve_path<P: AsRef<Path>>(path: &P) -> Result<PathBuf> {
|
|||
if cfg!(windows) {
|
||||
use std::path::Prefix;
|
||||
|
||||
fn get_drive_letter<P: AsRef<Path>>(path: P) -> Option<u8> {
|
||||
fn get_drive_letter(path: impl AsRef<Path>) -> Option<u8> {
|
||||
let path = path.as_ref();
|
||||
let mut components = path.components();
|
||||
|
||||
|
@ -83,16 +318,13 @@ pub fn resolve_path<P: AsRef<Path>>(path: &P) -> Result<PathBuf> {
|
|||
Some(Component::Prefix(prefix)) => match prefix.kind() {
|
||||
Prefix::Disk(drive_letter) => {
|
||||
let disk = components.next().unwrap();
|
||||
match components.peek() {
|
||||
Some(Component::RootDir) => {
|
||||
let root = components.next().unwrap();
|
||||
stack.push(disk);
|
||||
stack.push(root);
|
||||
}
|
||||
_ => {
|
||||
base_path = get_drive_relative(drive_letter)?;
|
||||
stack.extend(base_path.components());
|
||||
}
|
||||
if components.peek() == Some(&Component::RootDir) {
|
||||
let root = components.next().unwrap();
|
||||
stack.push(disk);
|
||||
stack.push(root);
|
||||
} else {
|
||||
base_path = get_drive_relative(drive_letter)?;
|
||||
stack.extend(base_path.components());
|
||||
}
|
||||
}
|
||||
Prefix::VerbatimDisk(drive_letter) => {
|
||||
|
@ -121,23 +353,18 @@ pub fn resolve_path<P: AsRef<Path>>(path: &P) -> Result<PathBuf> {
|
|||
stack.extend(base_path.components());
|
||||
}
|
||||
}
|
||||
} else if components.peek() == Some(&Component::RootDir) {
|
||||
let root = components.next().unwrap();
|
||||
stack.push(root);
|
||||
} else {
|
||||
match components.peek() {
|
||||
Some(Component::RootDir) => {
|
||||
let root = components.next().unwrap();
|
||||
stack.push(root);
|
||||
}
|
||||
_ => {
|
||||
base_path = current_dir()?;
|
||||
stack.extend(base_path.components());
|
||||
}
|
||||
}
|
||||
base_path = current_dir()?;
|
||||
stack.extend(base_path.components());
|
||||
}
|
||||
|
||||
for component in components {
|
||||
match component {
|
||||
Component::Normal(_) => stack.push(component),
|
||||
Component::CurDir => (),
|
||||
Component::CurDir => {}
|
||||
Component::ParentDir => {
|
||||
if stack.last() != Some(&Component::RootDir) {
|
||||
stack.pop();
|
||||
|
@ -147,9 +374,11 @@ pub fn resolve_path<P: AsRef<Path>>(path: &P) -> Result<PathBuf> {
|
|||
}
|
||||
}
|
||||
|
||||
let result = stack.iter().collect::<PathBuf>();
|
||||
if !result.is_dir() {
|
||||
bail!("could not resolve path: {}", result.display());
|
||||
}
|
||||
Ok(result)
|
||||
Ok(stack.iter().collect())
|
||||
}
|
||||
|
||||
/// Convert a string to lowercase, with a fast path for ASCII strings.
|
||||
pub fn to_lowercase(s: impl AsRef<str>) -> String {
|
||||
let s = s.as_ref();
|
||||
if s.is_ascii() { s.to_ascii_lowercase() } else { s.to_lowercase() }
|
||||
}
|
||||
|
|
|
@ -1,175 +1,198 @@
|
|||
{%- let SECTION = "# =============================================================================\n#" -%}
|
||||
{%- let NOT_CONFIGURED = "# -- not configured --" -%}
|
||||
{%- let section = "# =============================================================================\n#" -%}
|
||||
{%- let not_configured = "# -- not configured --" -%}
|
||||
|
||||
{{ SECTION }}
|
||||
# shellcheck shell=bash
|
||||
|
||||
{{ section }}
|
||||
# Utility functions for zoxide.
|
||||
#
|
||||
|
||||
# pwd based on the value of _ZO_RESOLVE_SYMLINKS.
|
||||
function __zoxide_pwd() {
|
||||
{%- if resolve_symlinks %}
|
||||
pwd -P
|
||||
{%- if cfg!(windows) %}
|
||||
\command cygpath -w "$(\builtin pwd -P)"
|
||||
{%- else if resolve_symlinks %}
|
||||
\builtin pwd -P
|
||||
{%- else %}
|
||||
pwd -L
|
||||
\builtin pwd -L
|
||||
{%- endif %}
|
||||
}
|
||||
|
||||
# cd + custom logic based on the value of _ZO_ECHO.
|
||||
function __zoxide_cd() {
|
||||
# shellcheck disable=SC2164
|
||||
cd "$@" {%- if echo %} && __zoxide_pwd {%- endif %}
|
||||
\builtin cd -- "$@" {%- if echo %} && __zoxide_pwd {%- endif %}
|
||||
}
|
||||
|
||||
{{ SECTION }}
|
||||
{{ section }}
|
||||
# Hook configuration for zoxide.
|
||||
#
|
||||
{%- if hook != InitHook::None %}
|
||||
|
||||
# Hook to add new entries to the database.
|
||||
{%- match hook %}
|
||||
{%- when Hook::None %}
|
||||
{{ NOT_CONFIGURED }}
|
||||
|
||||
{%- when Hook::Prompt %}
|
||||
{%- if hook == InitHook::Prompt %}
|
||||
function __zoxide_hook() {
|
||||
zoxide add "$(__zoxide_pwd)"
|
||||
\builtin local -r retval="$?"
|
||||
# shellcheck disable=SC2312
|
||||
\command zoxide add -- "$(__zoxide_pwd)"
|
||||
return "${retval}"
|
||||
}
|
||||
|
||||
{%- when Hook::Pwd %}
|
||||
{%- else if hook == InitHook::Pwd %}
|
||||
__zoxide_oldpwd="$(__zoxide_pwd)"
|
||||
|
||||
function __zoxide_hook() {
|
||||
local -r __zoxide_pwd_tmp="$(__zoxide_pwd)"
|
||||
if [ -z "$__zoxide_pwd_old" ]; then
|
||||
__zoxide_pwd_old="$__zoxide_pwd_tmp"
|
||||
elif [ "$__zoxide_pwd_old" != "$__zoxide_pwd_tmp" ]; then
|
||||
__zoxide_pwd_old="$__zoxide_pwd_tmp"
|
||||
zoxide add "$__zoxide_pwd_old"
|
||||
\builtin local -r retval="$?"
|
||||
\builtin local pwd_tmp
|
||||
pwd_tmp="$(__zoxide_pwd)"
|
||||
if [[ ${__zoxide_oldpwd} != "${pwd_tmp}" ]]; then
|
||||
__zoxide_oldpwd="${pwd_tmp}"
|
||||
\command zoxide add -- "${__zoxide_oldpwd}"
|
||||
fi
|
||||
return "${retval}"
|
||||
}
|
||||
{%- endmatch %}
|
||||
{%- endif %}
|
||||
|
||||
# Initialize hook.
|
||||
{%- if hook == Hook::None %}
|
||||
{{ NOT_CONFIGURED }}
|
||||
|
||||
{%- else %}
|
||||
case "$PROMPT_COMMAND" in
|
||||
*__zoxide_hook*) ;;
|
||||
*) PROMPT_COMMAND="${PROMPT_COMMAND:+${PROMPT_COMMAND};}__zoxide_hook" ;;
|
||||
esac
|
||||
if [[ ${PROMPT_COMMAND:=} != *'__zoxide_hook'* ]]; then
|
||||
PROMPT_COMMAND="__zoxide_hook;${PROMPT_COMMAND#;}"
|
||||
fi
|
||||
|
||||
{%- endif %}
|
||||
|
||||
{{ SECTION }}
|
||||
# When using zoxide with --no-aliases, alias these internal functions as
|
||||
# desired.
|
||||
# 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 }}
|
||||
# When using zoxide with --no-cmd, alias these internal functions as desired.
|
||||
#
|
||||
|
||||
__zoxide_z_prefix='z#'
|
||||
|
||||
# Jump to a directory using only keywords.
|
||||
function __zoxide_z() {
|
||||
if [ "$#" -eq 0 ]; then
|
||||
__zoxide_doctor
|
||||
|
||||
# shellcheck disable=SC2199
|
||||
if [[ $# -eq 0 ]]; then
|
||||
__zoxide_cd ~
|
||||
elif [ "$#" -eq 1 ] && [ "$1" = '-' ]; then
|
||||
if [ -n "$OLDPWD" ]; then
|
||||
__zoxide_cd "$OLDPWD"
|
||||
else
|
||||
echo "zoxide: \\$OLDPWD is not set"
|
||||
return 1
|
||||
fi
|
||||
elif [ "$#" -eq 1 ] && [ -d "$1" ]; then
|
||||
elif [[ $# -eq 1 && $1 == '-' ]]; then
|
||||
__zoxide_cd "${OLDPWD}"
|
||||
elif [[ $# -eq 1 && -d $1 ]]; then
|
||||
__zoxide_cd "$1"
|
||||
elif [[ $# -eq 2 && $1 == '--' ]]; then
|
||||
__zoxide_cd "$2"
|
||||
elif [[ ${@: -1} == "${__zoxide_z_prefix}"?* ]]; then
|
||||
# shellcheck disable=SC2124
|
||||
\builtin local result="${@: -1}"
|
||||
__zoxide_cd "{{ "${result:${#__zoxide_z_prefix}}" }}"
|
||||
else
|
||||
local __zoxide_result
|
||||
__zoxide_result="$(zoxide query -- "$@")" && __zoxide_cd "$__zoxide_result"
|
||||
\builtin local result
|
||||
# shellcheck disable=SC2312
|
||||
result="$(\command zoxide query --exclude "$(__zoxide_pwd)" -- "$@")" &&
|
||||
__zoxide_cd "${result}"
|
||||
fi
|
||||
}
|
||||
|
||||
# Jump to a directory using interactive search.
|
||||
function __zoxide_zi() {
|
||||
local __zoxide_result
|
||||
__zoxide_result="$(zoxide query -i -- "$@")" && __zoxide_cd "$__zoxide_result"
|
||||
__zoxide_doctor
|
||||
\builtin local result
|
||||
result="$(\command zoxide query --interactive -- "$@")" && __zoxide_cd "${result}"
|
||||
}
|
||||
|
||||
# Add a new entry to the database.
|
||||
function __zoxide_za() {
|
||||
zoxide add "$@"
|
||||
}
|
||||
|
||||
# Query an entry from the database using only keywords.
|
||||
function __zoxide_zq() {
|
||||
zoxide query "$@"
|
||||
}
|
||||
|
||||
# Query an entry from the database using interactive selection.
|
||||
function __zoxide_zqi() {
|
||||
zoxide query -i "$@"
|
||||
}
|
||||
|
||||
# Remove an entry from the database using the exact path.
|
||||
function __zoxide_zr() {
|
||||
zoxide remove "$@"
|
||||
}
|
||||
|
||||
# Remove an entry from the database using interactive selection.
|
||||
function __zoxide_zri() {
|
||||
zoxide remove -i "$@"
|
||||
}
|
||||
|
||||
{{ SECTION }}
|
||||
# Convenient aliases for zoxide. Disable these using --no-aliases.
|
||||
{{ section }}
|
||||
# Commands for zoxide. Disable these using --no-cmd.
|
||||
#
|
||||
|
||||
{%- match cmd %}
|
||||
{%- when Some with (cmd) %}
|
||||
|
||||
# Remove definitions.
|
||||
function __zoxide_unset() {
|
||||
# shellcheck disable=SC1001
|
||||
\unset -f "$@" &>{{ Opts::DEVNULL }}
|
||||
# shellcheck disable=SC1001
|
||||
\unset -v "$@" &>{{ Opts::DEVNULL }}
|
||||
}
|
||||
|
||||
__zoxide_unset '{{cmd}}'
|
||||
\builtin unalias {{cmd}} &>/dev/null || \builtin true
|
||||
function {{cmd}}() {
|
||||
__zoxide_z "$@"
|
||||
}
|
||||
|
||||
__zoxide_unset '{{cmd}}i'
|
||||
\builtin unalias {{cmd}}i &>/dev/null || \builtin true
|
||||
function {{cmd}}i() {
|
||||
__zoxide_zi "$@"
|
||||
}
|
||||
|
||||
__zoxide_unset '{{cmd}}a'
|
||||
function {{cmd}}a() {
|
||||
__zoxide_za "$@"
|
||||
}
|
||||
# Load completions.
|
||||
# - Bash 4.4+ is required to use `@Q`.
|
||||
# - Completions require line editing. Since Bash supports only two modes of
|
||||
# line editing (`vim` and `emacs`), we check if either them is enabled.
|
||||
# - Completions don't work on `dumb` terminals.
|
||||
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
|
||||
|
||||
__zoxide_unset '{{cmd}}q'
|
||||
function {{cmd}}q() {
|
||||
__zoxide_zq "$@"
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
__zoxide_unset '{{cmd}}qi'
|
||||
function {{cmd}}qi() {
|
||||
__zoxide_zqi "$@"
|
||||
}
|
||||
function __zoxide_z_complete() {
|
||||
# Only show completions when the cursor is at the end of the line.
|
||||
[[ {{ "${#COMP_WORDS[@]}" }} -eq $((COMP_CWORD + 1)) ]] || return
|
||||
|
||||
__zoxide_unset '{{cmd}}r'
|
||||
function {{cmd}}r() {
|
||||
__zoxide_zr "$@"
|
||||
}
|
||||
# If there is only one argument, use `cd` completions.
|
||||
if [[ {{ "${#COMP_WORDS[@]}" }} -eq 2 ]]; then
|
||||
\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.
|
||||
elif [[ -z ${COMP_WORDS[-1]} ]]; then
|
||||
# shellcheck disable=SC2312
|
||||
__zoxide_result="$(\command zoxide query --exclude "$(__zoxide_pwd)" --interactive -- "{{ "${COMP_WORDS[@]:1:${#COMP_WORDS[@]}-2}" }}")" && {
|
||||
# In case the terminal does not respond to \e[5n or another
|
||||
# mechanism steals the response, it is still worth completing
|
||||
# the directory in the command line.
|
||||
COMPREPLY=("${__zoxide_z_prefix}${__zoxide_result}/")
|
||||
|
||||
__zoxide_unset '{{cmd}}ri'
|
||||
function {{cmd}}ri() {
|
||||
__zoxide_zri "$@"
|
||||
}
|
||||
# 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
|
||||
}
|
||||
|
||||
\builtin complete -F __zoxide_z_complete -o filenames -- {{cmd}}
|
||||
\builtin complete -r {{cmd}}i &>/dev/null || \builtin true
|
||||
fi
|
||||
|
||||
{%- when None %}
|
||||
{{ NOT_CONFIGURED }}
|
||||
|
||||
{{ not_configured }}
|
||||
|
||||
{%- endmatch %}
|
||||
|
||||
{{ SECTION }}
|
||||
# To initialize zoxide with bash, add the following line to your bash
|
||||
# configuration file (usually ~/.bashrc):
|
||||
{{ section }}
|
||||
# To initialize zoxide, add this to your shell configuration file (usually ~/.bashrc):
|
||||
#
|
||||
# eval "$(zoxide init bash)"
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
{%- let section = "# =============================================================================\n#" -%}
|
||||
{%- let not_configured = "# -- not configured --" -%}
|
||||
|
||||
use builtin
|
||||
use path
|
||||
|
||||
{{ section }}
|
||||
# Utility functions for zoxide.
|
||||
#
|
||||
|
||||
# cd + custom logic based on the value of _ZO_ECHO.
|
||||
fn __zoxide_cd {|path|
|
||||
builtin:cd $path
|
||||
{%- if echo %}
|
||||
builtin:echo $pwd
|
||||
{%- endif %}
|
||||
}
|
||||
|
||||
{{ section }}
|
||||
# Hook configuration for zoxide.
|
||||
#
|
||||
|
||||
# Initialize hook to track previous directory.
|
||||
var oldpwd = $builtin:pwd
|
||||
set builtin:before-chdir = [$@builtin:before-chdir {|_| set oldpwd = $builtin:pwd }]
|
||||
|
||||
# Initialize hook to add directories to zoxide.
|
||||
{%- if hook == InitHook::None %}
|
||||
{{ not_configured }}
|
||||
|
||||
{%- else %}
|
||||
if (builtin:not (builtin:eq $E:__zoxide_shlvl $E:SHLVL)) {
|
||||
set E:__zoxide_shlvl = $E:SHLVL
|
||||
{%- if hook == InitHook::Prompt %}
|
||||
set edit:before-readline = [$@edit:before-readline {|| zoxide add -- $pwd }]
|
||||
{%- else if hook == InitHook::Pwd %}
|
||||
set builtin:after-chdir = [$@builtin:after-chdir {|_| zoxide add -- $pwd }]
|
||||
{%- endif %}
|
||||
}
|
||||
|
||||
{%- endif %}
|
||||
|
||||
{{ section }}
|
||||
# When using zoxide with --no-cmd, alias these internal functions as desired.
|
||||
#
|
||||
|
||||
# Jump to a directory using only keywords.
|
||||
fn __zoxide_z {|@rest|
|
||||
if (builtin:eq [] $rest) {
|
||||
__zoxide_cd ~
|
||||
} elif (builtin:eq [-] $rest) {
|
||||
__zoxide_cd $oldpwd
|
||||
} elif (and ('builtin:==' (builtin:count $rest) 1) (path:is-dir &follow-symlink=$true $rest[0])) {
|
||||
__zoxide_cd $rest[0]
|
||||
} else {
|
||||
var path
|
||||
try {
|
||||
set path = (zoxide query --exclude $pwd -- $@rest)
|
||||
} catch {
|
||||
} else {
|
||||
__zoxide_cd $path
|
||||
}
|
||||
}
|
||||
}
|
||||
edit:add-var __zoxide_z~ $__zoxide_z~
|
||||
|
||||
# Jump to a directory using interactive search.
|
||||
fn __zoxide_zi {|@rest|
|
||||
var path
|
||||
try {
|
||||
set path = (zoxide query --interactive -- $@rest)
|
||||
} catch {
|
||||
} else {
|
||||
__zoxide_cd $path
|
||||
}
|
||||
}
|
||||
edit:add-var __zoxide_zi~ $__zoxide_zi~
|
||||
|
||||
{{ section }}
|
||||
# Commands for zoxide. Disable these using --no-cmd.
|
||||
#
|
||||
|
||||
{%- match cmd %}
|
||||
{%- when Some with (cmd) %}
|
||||
|
||||
edit:add-var {{cmd}}~ $__zoxide_z~
|
||||
edit:add-var {{cmd}}i~ $__zoxide_zi~
|
||||
|
||||
# Load completions.
|
||||
{#-
|
||||
zoxide-based completions are currently not possible, because Elvish only prints
|
||||
a completion if the current token is a prefix of it.
|
||||
#}
|
||||
fn __zoxide_z_complete {|@rest|
|
||||
if (!= (builtin:count $rest) 2) {
|
||||
builtin:return
|
||||
}
|
||||
edit:complete-filename $rest[1] |
|
||||
builtin:each {|completion|
|
||||
var dir = $completion[stem]
|
||||
if (path:is-dir $dir) {
|
||||
builtin:put $dir
|
||||
}
|
||||
}
|
||||
}
|
||||
set edit:completion:arg-completer[{{cmd}}] = $__zoxide_z_complete~
|
||||
|
||||
{%- when None %}
|
||||
|
||||
{{ not_configured }}
|
||||
|
||||
{%- endmatch %}
|
||||
|
||||
{{ section }}
|
||||
# To initialize zoxide, add this to your configuration (usually
|
||||
# ~/.elvish/rc.elv):
|
||||
#
|
||||
# eval (zoxide init elvish | slurp)
|
||||
#
|
||||
# Note: zoxide only supports elvish v0.18.0 and above.
|
|
@ -1,156 +1,131 @@
|
|||
{%- let SECTION = "# =============================================================================\n#" -%}
|
||||
{%- let NOT_CONFIGURED = "# -- not configured --" -%}
|
||||
{%- let section = "# =============================================================================\n#" -%}
|
||||
{%- let not_configured = "# -- not configured --" -%}
|
||||
|
||||
{{ SECTION }}
|
||||
{{ section }}
|
||||
# Utility functions for zoxide.
|
||||
#
|
||||
|
||||
# Remove definitions.
|
||||
|
||||
# pwd based on the value of _ZO_RESOLVE_SYMLINKS.
|
||||
function __zoxide_pwd
|
||||
{%- if resolve_symlinks %}
|
||||
pwd -P
|
||||
{%- if cfg!(windows) %}
|
||||
command cygpath -w (builtin pwd -P)
|
||||
{%- else if resolve_symlinks %}
|
||||
builtin pwd -P
|
||||
{%- else %}
|
||||
pwd -L
|
||||
builtin pwd -L
|
||||
{%- endif %}
|
||||
end
|
||||
|
||||
# A copy of fish's internal cd function. This makes it possible to use
|
||||
# `alias cd=z` without causing an infinite loop.
|
||||
if ! builtin functions --query __zoxide_cd_internal
|
||||
string replace --regex -- '^function cd\s' 'function __zoxide_cd_internal ' <$__fish_data_dir/functions/cd.fish | source
|
||||
end
|
||||
|
||||
# cd + custom logic based on the value of _ZO_ECHO.
|
||||
function __zoxide_cd
|
||||
cd $argv
|
||||
{%- if echo %}
|
||||
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 %}
|
||||
and __zoxide_pwd
|
||||
{%- endif %}
|
||||
and commandline -f repaint
|
||||
{%- endif %}
|
||||
end
|
||||
|
||||
{{ SECTION }}
|
||||
{{ section }}
|
||||
# Hook configuration for zoxide.
|
||||
#
|
||||
|
||||
{% if hook == InitHook::None -%}
|
||||
{{ not_configured }}
|
||||
|
||||
{%- else -%}
|
||||
# Initialize hook to add new entries to the database.
|
||||
{%- match hook %}
|
||||
{%- when Hook::None %}
|
||||
function __zoxide_hook
|
||||
|
||||
{%- when Hook::Prompt %}
|
||||
{%- if hook == InitHook::Prompt %}
|
||||
function __zoxide_hook --on-event fish_prompt
|
||||
|
||||
{%- when Hook::Pwd %}
|
||||
{%- else if hook == InitHook::Pwd %}
|
||||
function __zoxide_hook --on-variable PWD
|
||||
|
||||
{%- endmatch %}
|
||||
zoxide add (__zoxide_pwd)
|
||||
{%- endif %}
|
||||
test -z "$fish_private_mode"
|
||||
and command zoxide add -- (__zoxide_pwd)
|
||||
end
|
||||
|
||||
{{ SECTION }}
|
||||
# When using zoxide with --no-aliases, alias these internal functions as
|
||||
# desired.
|
||||
{%- endif %}
|
||||
|
||||
{{ section }}
|
||||
# When using zoxide with --no-cmd, alias these internal functions as desired.
|
||||
#
|
||||
|
||||
# Jump to a directory using only keywords.
|
||||
function __zoxide_z
|
||||
set argc (count $argv)
|
||||
set -l argc (builtin count $argv)
|
||||
if test $argc -eq 0
|
||||
__zoxide_cd $HOME
|
||||
else if begin; test $argc -eq 1; and test $argv[1] = '-'; end
|
||||
else if test "$argv" = -
|
||||
__zoxide_cd -
|
||||
else if begin; test $argc -eq 1; and test -d $argv[1]; end
|
||||
else if test $argc -eq 1 -a -d $argv[1]
|
||||
__zoxide_cd $argv[1]
|
||||
else if test $argc -eq 2 -a $argv[1] = --
|
||||
__zoxide_cd -- $argv[2]
|
||||
else
|
||||
set -l __zoxide_result (zoxide query -- $argv)
|
||||
and __zoxide_cd $__zoxide_result
|
||||
set -l result (command zoxide query --exclude (__zoxide_pwd) -- $argv)
|
||||
and __zoxide_cd $result
|
||||
end
|
||||
end
|
||||
|
||||
# Completions.
|
||||
function __zoxide_z_complete
|
||||
set -l tokens (builtin commandline --current-process --tokenize)
|
||||
set -l curr_tokens (builtin commandline --cut-at-cursor --current-process --tokenize)
|
||||
|
||||
if test (builtin count $tokens) -le 2 -a (builtin count $curr_tokens) -eq 1
|
||||
# If there are < 2 arguments, use `cd` completions.
|
||||
complete --do-complete "'' "(builtin commandline --cut-at-cursor --current-token) | string match --regex -- '.*/$'
|
||||
else if test (builtin count $tokens) -eq (builtin count $curr_tokens)
|
||||
# If the last argument is empty, use interactive selection.
|
||||
set -l query $tokens[2..-1]
|
||||
set -l result (command zoxide query --exclude (__zoxide_pwd) --interactive -- $query)
|
||||
and __zoxide_cd $result
|
||||
and builtin commandline --function cancel-commandline repaint
|
||||
end
|
||||
end
|
||||
complete --command __zoxide_z --no-files --arguments '(__zoxide_z_complete)'
|
||||
|
||||
# Jump to a directory using interactive search.
|
||||
function __zoxide_zi
|
||||
set -l __zoxide_result (zoxide query -i -- $argv)
|
||||
and __zoxide_cd $__zoxide_result
|
||||
set -l result (command zoxide query --interactive -- $argv)
|
||||
and __zoxide_cd $result
|
||||
end
|
||||
|
||||
# Add a new entry to the database.
|
||||
function __zoxide_za
|
||||
zoxide add $argv
|
||||
end
|
||||
|
||||
# Query an entry from the database using only keywords.
|
||||
function __zoxide_zq
|
||||
zoxide query $argv
|
||||
end
|
||||
|
||||
# Query an entry from the database using interactive selection.
|
||||
function __zoxide_zqi
|
||||
zoxide query -i $argv
|
||||
end
|
||||
|
||||
# Remove an entry from the database using the exact path.
|
||||
function __zoxide_zr
|
||||
zoxide remove $argv
|
||||
end
|
||||
|
||||
# Remove an entry from the database using interactive selection.
|
||||
function __zoxide_zri
|
||||
zoxide remove -i $argv
|
||||
end
|
||||
|
||||
{{ SECTION }}
|
||||
# Convenient aliases for zoxide. Disable these using --no-aliases.
|
||||
{{ section }}
|
||||
# Commands for zoxide. Disable these using --no-cmd.
|
||||
#
|
||||
|
||||
{%- match cmd %}
|
||||
{%- when Some with (cmd) %}
|
||||
|
||||
# Remove definitions.
|
||||
function __zoxide_unset
|
||||
set --erase $argv > {{ Opts::DEVNULL }} 2>&1
|
||||
abbr --erase $argv > {{ Opts::DEVNULL }} 2>&1
|
||||
functions --erase $argv > {{ Opts::DEVNULL }} 2>&1
|
||||
end
|
||||
abbr --erase {{cmd}} &>/dev/null
|
||||
alias {{cmd}}=__zoxide_z
|
||||
|
||||
__zoxide_unset '{{cmd}}'
|
||||
function {{cmd}}
|
||||
__zoxide_z $argv
|
||||
end
|
||||
|
||||
__zoxide_unset '{{cmd}}i'
|
||||
function {{cmd}}i
|
||||
__zoxide_zi $argv
|
||||
end
|
||||
|
||||
__zoxide_unset '{{cmd}}a'
|
||||
function {{cmd}}a
|
||||
__zoxide_za $argv
|
||||
end
|
||||
|
||||
__zoxide_unset '{{cmd}}q'
|
||||
function {{cmd}}q
|
||||
__zoxide_zq $argv
|
||||
end
|
||||
|
||||
__zoxide_unset '{{cmd}}qi'
|
||||
function {{cmd}}qi
|
||||
__zoxide_zqi $argv
|
||||
end
|
||||
|
||||
__zoxide_unset '{{cmd}}r'
|
||||
function {{cmd}}r
|
||||
__zoxide_zr $argv
|
||||
end
|
||||
|
||||
__zoxide_unset '{{cmd}}ri'
|
||||
function {{cmd}}ri
|
||||
__zoxide_zri $argv
|
||||
end
|
||||
abbr --erase {{cmd}}i &>/dev/null
|
||||
alias {{cmd}}i=__zoxide_zi
|
||||
|
||||
{%- when None %}
|
||||
{{ NOT_CONFIGURED }}
|
||||
|
||||
{{ not_configured }}
|
||||
|
||||
{%- endmatch %}
|
||||
|
||||
{{ SECTION }}
|
||||
# To initialize zoxide with fish, add the following line to your fish
|
||||
# configuration file (usually ~/.config/fish/config.fish):
|
||||
{{ section }}
|
||||
# To initialize zoxide, add this to your configuration (usually
|
||||
# ~/.config/fish/config.fish):
|
||||
#
|
||||
# zoxide init fish | source
|
||||
# zoxide init fish | source
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
{%- let section = "# =============================================================================\n#" -%}
|
||||
{%- let not_configured = "# -- not configured --" -%}
|
||||
|
||||
# Code generated by zoxide. DO NOT EDIT.
|
||||
|
||||
{{ section }}
|
||||
# Hook configuration for zoxide.
|
||||
#
|
||||
|
||||
{% if hook == InitHook::None -%}
|
||||
{{ not_configured }}
|
||||
|
||||
{%- else -%}
|
||||
# Initialize hook to add new entries to the database.
|
||||
export-env {
|
||||
{%- 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 %}
|
||||
}
|
||||
|
||||
{%- endif %}
|
||||
|
||||
{{ section }}
|
||||
# When using zoxide with --no-cmd, alias these internal functions as desired.
|
||||
#
|
||||
|
||||
# Jump to a directory using only keywords.
|
||||
def --env --wrapped __zoxide_z [...rest: string] {
|
||||
let path = match $rest {
|
||||
[] => {'~'},
|
||||
[ '-' ] => {'-'},
|
||||
[ $arg ] if ($arg | path expand | path type) == 'dir' => {$arg}
|
||||
_ => {
|
||||
^zoxide query --exclude $env.PWD -- ...$rest | str trim -r -c "\n"
|
||||
}
|
||||
}
|
||||
cd $path
|
||||
{%- if echo %}
|
||||
echo $env.PWD
|
||||
{%- endif %}
|
||||
}
|
||||
|
||||
# Jump to a directory using interactive search.
|
||||
def --env --wrapped __zoxide_zi [...rest:string] {
|
||||
cd $'(^zoxide query --interactive -- ...$rest | str trim -r -c "\n")'
|
||||
{%- if echo %}
|
||||
echo $env.PWD
|
||||
{%- endif %}
|
||||
}
|
||||
|
||||
{{ 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 }}
|
||||
# Add this to your env file (find it by running `$nu.env-path` in Nushell):
|
||||
#
|
||||
# zoxide init nushell | save -f ~/.zoxide.nu
|
||||
#
|
||||
# Now, add this to the end of your config file (find it by running
|
||||
# `$nu.config-path` in Nushell):
|
||||
#
|
||||
# source ~/.zoxide.nu
|
||||
#
|
||||
# Note: zoxide only supports Nushell v0.89.0+.
|
|
@ -1,176 +1,135 @@
|
|||
{%- let SECTION = "# =============================================================================\n#" -%}
|
||||
{%- let NOT_CONFIGURED = "# -- not configured --" -%}
|
||||
{%- let section = "# =============================================================================\n#" -%}
|
||||
{%- let not_configured = "# -- not configured --" -%}
|
||||
|
||||
{% if hook == Hook::Pwd -%}
|
||||
echo "\
|
||||
zoxide: PWD hooks are not supported on POSIX shells.
|
||||
Use '--hook prompt' when initializing zoxide."
|
||||
{%- endif %}
|
||||
# shellcheck shell=sh
|
||||
|
||||
{{ SECTION }}
|
||||
{{ section }}
|
||||
# Utility functions for zoxide.
|
||||
#
|
||||
|
||||
# pwd based on the value of _ZO_RESOLVE_SYMLINKS.
|
||||
__zoxide_pwd() {
|
||||
{%- if resolve_symlinks %}
|
||||
pwd -P
|
||||
{%- if cfg!(windows) %}
|
||||
\command cygpath -w "$(\builtin pwd -P)"
|
||||
{%- else if resolve_symlinks %}
|
||||
\command pwd -P
|
||||
{%- else %}
|
||||
pwd -L
|
||||
\command pwd -L
|
||||
{%- endif %}
|
||||
}
|
||||
|
||||
# cd + custom logic based on the value of _ZO_ECHO.
|
||||
__zoxide_cd() {
|
||||
# shellcheck disable=SC2164
|
||||
cd "$@" {%- if echo %} && __zoxide_pwd {%- endif %}
|
||||
\command cd "$@" {%- if echo %} && __zoxide_pwd {%- endif %}
|
||||
}
|
||||
|
||||
{{ SECTION }}
|
||||
{{ section }}
|
||||
# Hook configuration for zoxide.
|
||||
#
|
||||
|
||||
# Hook to add new entries to the database.
|
||||
{%- match hook %}
|
||||
{%- when Hook::None %}
|
||||
{{ NOT_CONFIGURED }}
|
||||
{% match hook %}
|
||||
{%- when InitHook::None -%}
|
||||
{{ not_configured }}
|
||||
|
||||
{%- when Hook::Prompt %}
|
||||
{%- when InitHook::Prompt -%}
|
||||
# Hook to add new entries to the database.
|
||||
__zoxide_hook() {
|
||||
zoxide add "$(__zoxide_pwd)"
|
||||
\command zoxide add -- "$(__zoxide_pwd || \builtin true)"
|
||||
}
|
||||
|
||||
{%- when Hook::Pwd %}
|
||||
{{ NOT_CONFIGURED }}
|
||||
|
||||
{%- endmatch %}
|
||||
|
||||
# Initialize hook.
|
||||
{%- match hook %}
|
||||
{%- when Hook::None %}
|
||||
{{ NOT_CONFIGURED }}
|
||||
if [ "${PS1:=}" = "${PS1#*\$(__zoxide_hook)}" ]; then
|
||||
PS1="${PS1}\$(__zoxide_hook)"
|
||||
fi
|
||||
|
||||
{%- when Hook::Prompt %}
|
||||
case "$PS1" in
|
||||
*\$\(__zoxide_hook\)*) ;;
|
||||
*) PS1="${PS1}\$(__zoxide_hook)" ;;
|
||||
esac
|
||||
# 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
|
||||
|
||||
{%- when Hook::Pwd %}
|
||||
{{ NOT_CONFIGURED }}
|
||||
_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 -%}
|
||||
\command printf "%s\n%s\n" \
|
||||
"zoxide: PWD hooks are not supported on POSIX shells." \
|
||||
" Use 'zoxide init posix --hook prompt' instead."
|
||||
|
||||
{%- endmatch %}
|
||||
|
||||
{{ SECTION }}
|
||||
# When using zoxide with --no-aliases, alias these internal functions as
|
||||
# desired.
|
||||
{{ section }}
|
||||
# When using zoxide with --no-cmd, alias these internal functions as desired.
|
||||
#
|
||||
|
||||
# Jump to a directory using only keywords.
|
||||
__zoxide_z() {
|
||||
__zoxide_doctor
|
||||
|
||||
if [ "$#" -eq 0 ]; then
|
||||
__zoxide_cd ~
|
||||
elif [ "$#" -eq 1 ] && [ "$1" = '-' ]; then
|
||||
if [ -n "$OLDPWD" ]; then
|
||||
__zoxide_cd "$OLDPWD"
|
||||
if [ -n "${OLDPWD}" ]; then
|
||||
__zoxide_cd "${OLDPWD}"
|
||||
else
|
||||
echo "zoxide: \\$OLDPWD is not set"
|
||||
# shellcheck disable=SC2016
|
||||
\command printf 'zoxide: $OLDPWD is not set'
|
||||
return 1
|
||||
fi
|
||||
elif [ "$#" -eq 1 ] && [ -d "$1" ]; then
|
||||
elif [ "$#" -eq 1 ] && [ -d "$1" ]; then
|
||||
__zoxide_cd "$1"
|
||||
else
|
||||
__zoxide_result="$(zoxide query -- "$@")" && __zoxide_cd "$__zoxide_result"
|
||||
__zoxide_result="$(\command zoxide query --exclude "$(__zoxide_pwd || \builtin true)" -- "$@")" &&
|
||||
__zoxide_cd "${__zoxide_result}"
|
||||
fi
|
||||
}
|
||||
|
||||
# Jump to a directory using interactive search.
|
||||
__zoxide_zi() {
|
||||
__zoxide_result="$(zoxide query -i -- "$@")" && __zoxide_cd "$__zoxide_result"
|
||||
__zoxide_doctor
|
||||
__zoxide_result="$(\command zoxide query --interactive -- "$@")" && __zoxide_cd "${__zoxide_result}"
|
||||
}
|
||||
|
||||
# Add a new entry to the database.
|
||||
__zoxide_za() {
|
||||
zoxide add "$@"
|
||||
}
|
||||
|
||||
# Query an entry from the database using only keywords.
|
||||
__zoxide_zq() {
|
||||
zoxide query "$@"
|
||||
}
|
||||
|
||||
# Query an entry from the database using interactive selection.
|
||||
__zoxide_zqi() {
|
||||
zoxide query -i "$@"
|
||||
}
|
||||
|
||||
# Remove an entry from the database using the exact path.
|
||||
__zoxide_zr() {
|
||||
zoxide remove "$@"
|
||||
}
|
||||
|
||||
# Remove an entry from the database using interactive selection.
|
||||
__zoxide_zri() {
|
||||
zoxide remove -i "$@"
|
||||
}
|
||||
|
||||
{{ SECTION }}
|
||||
# Convenient aliases for zoxide. Disable these using --no-aliases.
|
||||
{{ section }}
|
||||
# Commands for zoxide. Disable these using --no-cmd.
|
||||
#
|
||||
|
||||
{%- match cmd %}
|
||||
{%- when Some with (cmd) %}
|
||||
|
||||
# Remove definitions.
|
||||
__zoxide_unset() {
|
||||
# shellcheck disable=SC1001
|
||||
\unset -f "$@" >{{ Opts::DEVNULL }} 2>&1
|
||||
# shellcheck disable=SC1001
|
||||
\unset -v "$@" >{{ Opts::DEVNULL }} 2>&1
|
||||
}
|
||||
|
||||
__zoxide_unset '{{cmd}}'
|
||||
\command unalias {{cmd}} >/dev/null 2>&1 || \true
|
||||
{{cmd}}() {
|
||||
__zoxide_z "$@"
|
||||
}
|
||||
|
||||
__zoxide_unset '{{cmd}}i'
|
||||
\command unalias {{cmd}}i >/dev/null 2>&1 || \true
|
||||
{{cmd}}i() {
|
||||
__zoxide_zi "$@"
|
||||
}
|
||||
|
||||
__zoxide_unset '{{cmd}}a'
|
||||
{{cmd}}a() {
|
||||
__zoxide_za "$@"
|
||||
}
|
||||
|
||||
__zoxide_unset '{{cmd}}q'
|
||||
{{cmd}}q() {
|
||||
__zoxide_zq "$@"
|
||||
}
|
||||
|
||||
__zoxide_unset '{{cmd}}qi'
|
||||
{{cmd}}qi() {
|
||||
__zoxide_zqi "$@"
|
||||
}
|
||||
|
||||
__zoxide_unset '{{cmd}}r'
|
||||
{{cmd}}r() {
|
||||
__zoxide_zr "$@"
|
||||
}
|
||||
|
||||
__zoxide_unset '{{cmd}}ri'
|
||||
{{cmd}}ri() {
|
||||
__zoxide_zri "$@"
|
||||
}
|
||||
|
||||
{%- when None %}
|
||||
{{ NOT_CONFIGURED }}
|
||||
|
||||
{{ not_configured }}
|
||||
|
||||
{%- endmatch %}
|
||||
|
||||
{{ SECTION }}
|
||||
# To initialize zoxide with your POSIX shell, add the following line to your
|
||||
# shell configuration file:
|
||||
{{ section }}
|
||||
# To initialize zoxide, add this to your configuration:
|
||||
#
|
||||
# eval "$(zoxide init posix --hook prompt)"
|
||||
|
|
|
@ -1,139 +1,157 @@
|
|||
{%- let SECTION = "# =============================================================================\n#" -%}
|
||||
{%- let NOT_CONFIGURED = "# -- not configured --" -%}
|
||||
{%- let section = "# =============================================================================\n#" -%}
|
||||
{%- let not_configured = "# -- not configured --" -%}
|
||||
|
||||
{{ SECTION }}
|
||||
{{ section }}
|
||||
# Utility functions for zoxide.
|
||||
#
|
||||
|
||||
# pwd based on the value of _ZO_RESOLVE_SYMLINKS.
|
||||
function __zoxide_pwd {
|
||||
$(Get-Location).Path
|
||||
# Call zoxide binary, returning the output as UTF-8.
|
||||
function global:__zoxide_bin {
|
||||
$encoding = [Console]::OutputEncoding
|
||||
try {
|
||||
[Console]::OutputEncoding = [System.Text.Utf8Encoding]::new()
|
||||
$result = zoxide @args
|
||||
return $result
|
||||
} finally {
|
||||
[Console]::OutputEncoding = $encoding
|
||||
}
|
||||
}
|
||||
|
||||
# pwd based on zoxide's format.
|
||||
function global:__zoxide_pwd {
|
||||
$cwd = Get-Location
|
||||
if ($cwd.Provider.Name -eq "FileSystem") {
|
||||
$cwd.ProviderPath
|
||||
}
|
||||
}
|
||||
|
||||
# cd + custom logic based on the value of _ZO_ECHO.
|
||||
function __zoxide_cd($dir) {
|
||||
Set-Location $dir -ea Stop
|
||||
{%- if echo %}
|
||||
__zoxide_pwd
|
||||
{%- endif %}
|
||||
function global:__zoxide_cd($dir, $literal) {
|
||||
$dir = if ($literal) {
|
||||
Set-Location -LiteralPath $dir -Passthru -ErrorAction Stop
|
||||
} else {
|
||||
if ($dir -eq '-' -and ($PSVersionTable.PSVersion -lt 6.1)) {
|
||||
Write-Error "cd - is not supported below PowerShell 6.1. Please upgrade your version of PowerShell."
|
||||
}
|
||||
elseif ($dir -eq '+' -and ($PSVersionTable.PSVersion -lt 6.2)) {
|
||||
Write-Error "cd + is not supported below PowerShell 6.2. Please upgrade your version of PowerShell."
|
||||
}
|
||||
else {
|
||||
Set-Location -Path $dir -Passthru -ErrorAction Stop
|
||||
}
|
||||
}
|
||||
{%- if echo %}
|
||||
Write-Output $dir.Path
|
||||
{%- endif %}
|
||||
}
|
||||
|
||||
{{ SECTION }}
|
||||
{{ section }}
|
||||
# Hook configuration for zoxide.
|
||||
#
|
||||
|
||||
{% if hook == InitHook::None -%}
|
||||
{{ not_configured }}
|
||||
|
||||
{%- else -%}
|
||||
{#-
|
||||
Initialize $__zoxide_hooked if it does not exist. Removing this will cause an
|
||||
unset variable error in StrictMode.
|
||||
-#}
|
||||
{%- if hook == InitHook::Prompt -%}
|
||||
# Hook to add new entries to the database.
|
||||
function __zoxide_hook {
|
||||
zoxide add $(__zoxide_pwd)
|
||||
function global:__zoxide_hook {
|
||||
$result = __zoxide_pwd
|
||||
if ($null -ne $result) {
|
||||
zoxide add "--" $result
|
||||
}
|
||||
}
|
||||
{%- else if hook == InitHook::Pwd -%}
|
||||
# Hook to add new entries to the database.
|
||||
$global:__zoxide_oldpwd = __zoxide_pwd
|
||||
function global:__zoxide_hook {
|
||||
$result = __zoxide_pwd
|
||||
if ($result -ne $global:__zoxide_oldpwd) {
|
||||
if ($null -ne $result) {
|
||||
zoxide add "--" $result
|
||||
}
|
||||
$global:__zoxide_oldpwd = $result
|
||||
}
|
||||
}
|
||||
{%- endif %}
|
||||
|
||||
# Initialize hook.
|
||||
{%- match hook %}
|
||||
{%- when Hook::None %}
|
||||
{{ NOT_CONFIGURED }}
|
||||
$global:__zoxide_hooked = (Get-Variable __zoxide_hooked -ErrorAction Ignore -ValueOnly)
|
||||
if ($global:__zoxide_hooked -ne 1) {
|
||||
$global:__zoxide_hooked = 1
|
||||
$global:__zoxide_prompt_old = $function:prompt
|
||||
|
||||
{%- when Hook::Prompt %}
|
||||
$PreZoxidePrompt = $function:prompt
|
||||
function prompt {
|
||||
$null = __zoxide_hook
|
||||
& $PreZoxidePrompt
|
||||
}
|
||||
|
||||
{%- when Hook::Pwd %}
|
||||
if ($PSVersionTable.PSVersion.Major -ge 6) {
|
||||
$ExecutionContext.InvokeCommand.LocationChangedAction = {
|
||||
function global:prompt {
|
||||
if ($null -ne $__zoxide_prompt_old) {
|
||||
& $__zoxide_prompt_old
|
||||
}
|
||||
$null = __zoxide_hook
|
||||
}
|
||||
} else {
|
||||
Write-Error "`
|
||||
zoxide: PWD hooks are not supported below PowerShell 6.
|
||||
Use '--hook prompt' when initializing zoxide."
|
||||
}
|
||||
{%- endif %}
|
||||
|
||||
{%- endmatch %}
|
||||
|
||||
{{ SECTION }}
|
||||
# When using zoxide with --no-aliases, alias these internal functions as
|
||||
# desired.
|
||||
{{ section }}
|
||||
# When using zoxide with --no-cmd, alias these internal functions as desired.
|
||||
#
|
||||
|
||||
# Jump to a directory using only keywords.
|
||||
function __zoxide_z {
|
||||
function global:__zoxide_z {
|
||||
if ($args.Length -eq 0) {
|
||||
__zoxide_cd ~
|
||||
__zoxide_cd ~ $true
|
||||
}
|
||||
elseif ($args.Length -eq 1 -and $args[0] -eq '-') {
|
||||
__zoxide_cd -
|
||||
elseif ($args.Length -eq 1 -and ($args[0] -eq '-' -or $args[0] -eq '+')) {
|
||||
__zoxide_cd $args[0] $false
|
||||
}
|
||||
elseif ($args.Length -eq 1 -and ( Test-Path $args[0] -PathType Container) ) {
|
||||
__zoxide_cd $args[0]
|
||||
elseif ($args.Length -eq 1 -and (Test-Path -PathType Container -LiteralPath $args[0])) {
|
||||
__zoxide_cd $args[0] $true
|
||||
}
|
||||
elseif ($args.Length -eq 1 -and (Test-Path -PathType Container -Path $args[0] )) {
|
||||
__zoxide_cd $args[0] $false
|
||||
}
|
||||
else {
|
||||
$__zoxide_result = zoxide query -- @args
|
||||
$result = __zoxide_pwd
|
||||
if ($null -ne $result) {
|
||||
$result = __zoxide_bin query --exclude $result "--" @args
|
||||
}
|
||||
else {
|
||||
$result = __zoxide_bin query "--" @args
|
||||
}
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
__zoxide_cd $__zoxide_result
|
||||
__zoxide_cd $result $true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Jump to a directory using interactive search.
|
||||
function __zoxide_zi {
|
||||
$__zoxide_result = zoxide query -i -- @args
|
||||
function global:__zoxide_zi {
|
||||
$result = __zoxide_bin query -i "--" @args
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
__zoxide_cd $__zoxide_result
|
||||
__zoxide_cd $result $true
|
||||
}
|
||||
}
|
||||
|
||||
# Add a new entry to the database.
|
||||
function __zoxide_za {
|
||||
zoxide add @args
|
||||
}
|
||||
|
||||
# Query an entry from the database using only keywords.
|
||||
function __zoxide_zq {
|
||||
zoxide query @args
|
||||
}
|
||||
|
||||
# Query an entry from the database using interactive selection.
|
||||
function __zoxide_zqi {
|
||||
zoxide query -i @args
|
||||
}
|
||||
|
||||
# Remove an entry from the database using the exact path.
|
||||
function __zoxide_zr {
|
||||
zoxide remove @args
|
||||
}
|
||||
|
||||
# Remove an entry from the database using interactive selection.
|
||||
function __zoxide_zri {
|
||||
zoxide remove -i @args
|
||||
}
|
||||
|
||||
{{ SECTION }}
|
||||
# Convenient aliases for zoxide. Disable these using --no-aliases.
|
||||
{{ section }}
|
||||
# Commands for zoxide. Disable these using --no-cmd.
|
||||
#
|
||||
|
||||
{%- match cmd %}
|
||||
{%- when Some with (cmd) %}
|
||||
|
||||
Set-Alias {{cmd}} __zoxide_z
|
||||
Set-Alias {{cmd}}i __zoxide_zi
|
||||
|
||||
Set-Alias {{cmd}}a __zoxide_za
|
||||
|
||||
Set-Alias {{cmd}}q __zoxide_zq
|
||||
Set-Alias {{cmd}}qi __zoxide_zqi
|
||||
|
||||
Set-Alias {{cmd}}r __zoxide_zr
|
||||
Set-Alias {{cmd}}ri __zoxide_zri
|
||||
Set-Alias -Name {{cmd}} -Value __zoxide_z -Option AllScope -Scope Global -Force
|
||||
Set-Alias -Name {{cmd}}i -Value __zoxide_zi -Option AllScope -Scope Global -Force
|
||||
|
||||
{%- when None %}
|
||||
{{ NOT_CONFIGURED }}
|
||||
|
||||
{{ not_configured }}
|
||||
|
||||
{%- endmatch %}
|
||||
|
||||
{{ SECTION }}
|
||||
# To initialize zoxide with PowerShell, add the following line to your
|
||||
# PowerShell configuration file (the location is stored in $profile):
|
||||
{{ section }}
|
||||
# To initialize zoxide, add this to your configuration (find it by running
|
||||
# `echo $profile` in PowerShell):
|
||||
#
|
||||
# Invoke-Expression (& { $hook = if ($PSVersionTable.PSVersion.Major -ge 6) { 'pwd' } else { 'prompt' } (zoxide init powershell --hook $hook) -join "`n" })
|
||||
# Invoke-Expression (& { (zoxide init powershell | Out-String) })
|
||||
|
|
|
@ -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
|
|
@ -1,135 +1,177 @@
|
|||
{%- let SECTION = "# =============================================================================\n#" -%}
|
||||
{%- let NOT_CONFIGURED = "# -- not configured --" -%}
|
||||
{%- let section = "# =============================================================================\n#" -%}
|
||||
{%- let not_configured = "# -- not configured --" -%}
|
||||
|
||||
{%- if resolve_symlinks -%}
|
||||
# pylint: disable=missing-module-docstring
|
||||
|
||||
import builtins # pylint: disable=unused-import
|
||||
import os
|
||||
{%- endif %}
|
||||
import os.path
|
||||
import subprocess
|
||||
import sys
|
||||
import typing
|
||||
|
||||
from subprocess import CalledProcessError
|
||||
import xonsh.dirstack # type: ignore # pylint: disable=import-error
|
||||
import xonsh.environ # type: ignore # pylint: disable=import-error
|
||||
|
||||
{{ SECTION }}
|
||||
{{ section }}
|
||||
# Utility functions for zoxide.
|
||||
#
|
||||
|
||||
# pwd based on the value of _ZO_RESOLVE_SYMLINKS.
|
||||
|
||||
def __zoxide_bin() -> str:
|
||||
"""Finds and returns the location of the zoxide binary."""
|
||||
zoxide = typing.cast(str, xonsh.environ.locate_binary("zoxide"))
|
||||
if zoxide is None:
|
||||
zoxide = "zoxide"
|
||||
return zoxide
|
||||
|
||||
|
||||
def __zoxide_env() -> dict[str, str]:
|
||||
"""Returns the current environment."""
|
||||
return builtins.__xonsh__.env.detype() # type: ignore # pylint:disable=no-member
|
||||
|
||||
|
||||
def __zoxide_pwd() -> str:
|
||||
"""pwd based on the value of _ZO_RESOLVE_SYMLINKS."""
|
||||
{%- if resolve_symlinks %}
|
||||
return os.getcwd()
|
||||
pwd = os.getcwd()
|
||||
{%- else %}
|
||||
return $PWD
|
||||
pwd = __zoxide_env().get("PWD")
|
||||
if pwd is None:
|
||||
raise RuntimeError("$PWD not found")
|
||||
{%- endif %}
|
||||
return pwd
|
||||
|
||||
|
||||
def __zoxide_cd(path: str | bytes | None = None) -> None:
|
||||
"""cd + custom logic based on the value of _ZO_ECHO."""
|
||||
if path is None:
|
||||
args = []
|
||||
elif isinstance(path, bytes):
|
||||
args = [path.decode("utf-8")]
|
||||
else:
|
||||
args = [path]
|
||||
_, exc, _ = xonsh.dirstack.cd(args)
|
||||
if exc is not None:
|
||||
raise RuntimeError(exc)
|
||||
{%- if echo %}
|
||||
print(__zoxide_pwd())
|
||||
{%- endif %}
|
||||
|
||||
# cd + custom logic based on the value of _ZO_ECHO.
|
||||
def __zoxide_cd(path: str):
|
||||
cd @(path) {%- if echo %} and print(__zoxide_pwd()) {%- endif %}
|
||||
|
||||
{{ SECTION }}
|
||||
class ZoxideSilentException(Exception):
|
||||
"""Exit without complaining."""
|
||||
|
||||
|
||||
def __zoxide_errhandler(
|
||||
func: typing.Callable[[list[str]], None],
|
||||
) -> typing.Callable[[list[str]], int]:
|
||||
"""Print exception and exit with error code 1."""
|
||||
|
||||
def wrapper(args: list[str]) -> int:
|
||||
try:
|
||||
func(args)
|
||||
return 0
|
||||
except ZoxideSilentException:
|
||||
return 1
|
||||
except Exception as exc: # pylint: disable=broad-except
|
||||
print(f"zoxide: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
{{ section }}
|
||||
# Hook configuration for zoxide.
|
||||
#
|
||||
|
||||
{% if hook == InitHook::None -%}
|
||||
{{ not_configured }}
|
||||
|
||||
{%- else -%}
|
||||
# Initialize hook to add new entries to the database.
|
||||
{%- match hook %}
|
||||
{%- when Hook::None %}
|
||||
{{ NOT_CONFIGURED }}
|
||||
if "__zoxide_hook" not in globals():
|
||||
{% if hook == InitHook::Prompt %}
|
||||
@builtins.events.on_post_prompt # type: ignore # pylint:disable=no-member
|
||||
{%- else if hook == InitHook::Pwd %}
|
||||
@builtins.events.on_chdir # type: ignore # pylint:disable=no-member
|
||||
{%- endif %}
|
||||
def __zoxide_hook(**_kwargs: typing.Any) -> None:
|
||||
"""Hook to add new entries to the database."""
|
||||
pwd = __zoxide_pwd()
|
||||
zoxide = __zoxide_bin()
|
||||
subprocess.run(
|
||||
[zoxide, "add", "--", pwd],
|
||||
check=False,
|
||||
env=__zoxide_env(),
|
||||
)
|
||||
{% endif %}
|
||||
|
||||
{%- when Hook::Prompt %}
|
||||
@events.on_post_prompt
|
||||
|
||||
{%- when Hook::Pwd %}
|
||||
@events.on_chdir
|
||||
|
||||
{%- endmatch %}
|
||||
def __zoxide_hook(**kwargs):
|
||||
zoxide add @(__zoxide_pwd())
|
||||
|
||||
{{ SECTION }}
|
||||
# When using zoxide with --no-aliases, alias these internal functions as
|
||||
# desired.
|
||||
{{ section }}
|
||||
# When using zoxide with --no-cmd, alias these internal functions as desired.
|
||||
#
|
||||
|
||||
# Jump to a directory using only keywords.
|
||||
def __zoxide_z(keywords: [str]):
|
||||
if keywords == []:
|
||||
__zoxide_cd($HOME)
|
||||
elif keywords == ['-']:
|
||||
__zoxide_cd('-')
|
||||
elif len(keywords) == 1 and os.path.isdir(keywords[0]):
|
||||
__zoxide_cd(keywords[0])
|
||||
|
||||
@__zoxide_errhandler
|
||||
def __zoxide_z(args: list[str]) -> None:
|
||||
"""Jump to a directory using only keywords."""
|
||||
if args == []:
|
||||
__zoxide_cd()
|
||||
elif args == ["-"]:
|
||||
__zoxide_cd("-")
|
||||
elif len(args) == 1 and os.path.isdir(args[0]):
|
||||
__zoxide_cd(args[0])
|
||||
else:
|
||||
try:
|
||||
__zoxide_cmd = subprocess.run(["zoxide", "query", "--"] + keywords, check=True, stdout=subprocess.PIPE)
|
||||
except CalledProcessError as e:
|
||||
return e.returncode
|
||||
zoxide = __zoxide_bin()
|
||||
cmd = subprocess.run(
|
||||
[zoxide, "query", "--exclude", __zoxide_pwd(), "--"] + args,
|
||||
check=True,
|
||||
env=__zoxide_env(),
|
||||
stdout=subprocess.PIPE,
|
||||
)
|
||||
except subprocess.CalledProcessError as exc:
|
||||
raise ZoxideSilentException() from exc
|
||||
|
||||
try:
|
||||
__zoxide_result = __zoxide_cmd.stdout[:-1].decode("utf-8")
|
||||
except UnicodeDecodeError:
|
||||
print(f"zoxide: invalid unicode in result: {__zoxide_result}", file=sys.stderr)
|
||||
return 1
|
||||
result = cmd.stdout[:-1]
|
||||
__zoxide_cd(result)
|
||||
|
||||
__zoxide_cd(__zoxide_result)
|
||||
|
||||
# Jump to a directory using interactive search.
|
||||
def __zoxide_zi(keywords: [str]):
|
||||
@__zoxide_errhandler
|
||||
def __zoxide_zi(args: list[str]) -> None:
|
||||
"""Jump to a directory using interactive search."""
|
||||
try:
|
||||
__zoxide_cmd = subprocess.run(["zoxide", "query", "-i", "--"] + keywords, check=True, stdout=subprocess.PIPE)
|
||||
except CalledProcessError as e:
|
||||
return e.returncode
|
||||
zoxide = __zoxide_bin()
|
||||
cmd = subprocess.run(
|
||||
[zoxide, "query", "-i", "--"] + args,
|
||||
check=True,
|
||||
env=__zoxide_env(),
|
||||
stdout=subprocess.PIPE,
|
||||
)
|
||||
except subprocess.CalledProcessError as exc:
|
||||
raise ZoxideSilentException() from exc
|
||||
|
||||
try:
|
||||
__zoxide_result = __zoxide_cmd.stdout[:-1].decode("utf-8")
|
||||
except UnicodeDecodeError:
|
||||
print(f"zoxide: invalid unicode in result: {__zoxide_result}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
__zoxide_cd(__zoxide_result)
|
||||
result = cmd.stdout[:-1]
|
||||
__zoxide_cd(result)
|
||||
|
||||
|
||||
# Add a new entry to the database.
|
||||
def __zoxide_za(args: [str]):
|
||||
zoxide add @(args)
|
||||
|
||||
# Query an entry from the database using only keywords.
|
||||
def __zoxide_zq(args: [str]):
|
||||
zoxide query @(args)
|
||||
|
||||
# Query an entry from the database using interactive selection.
|
||||
def __zoxide_zqi(args: [str]):
|
||||
zoxide query -i @(args)
|
||||
|
||||
# Remove an entry from the database using the exact path.
|
||||
def __zoxide_zr(args: [str]):
|
||||
zoxide remove @(args)
|
||||
|
||||
# Remove an entry from the database using interactive selection.
|
||||
def __zoxide_zri(args: [str]):
|
||||
zoxide remove -i @(args)
|
||||
|
||||
{{ SECTION }}
|
||||
# Convenient aliases for zoxide. Disable these using --no-aliases.
|
||||
{{ section }}
|
||||
# Commands for zoxide. Disable these using --no-cmd.
|
||||
#
|
||||
|
||||
{%- match cmd %}
|
||||
{%- when Some with (cmd) %}
|
||||
|
||||
aliases['{{cmd}}'] = __zoxide_z
|
||||
aliases['{{cmd}}i'] = __zoxide_zi
|
||||
aliases['{{cmd}}a'] = __zoxide_za
|
||||
aliases['{{cmd}}q'] = __zoxide_zq
|
||||
aliases['{{cmd}}qi'] = __zoxide_zqi
|
||||
aliases['{{cmd}}r'] = __zoxide_zr
|
||||
aliases['{{cmd}}ri'] = __zoxide_zri
|
||||
builtins.aliases["{{cmd}}"] = __zoxide_z # type: ignore # pylint:disable=no-member
|
||||
builtins.aliases["{{cmd}}i"] = __zoxide_zi # type: ignore # pylint:disable=no-member
|
||||
|
||||
{%- when None %}
|
||||
{{ NOT_CONFIGURED }}
|
||||
|
||||
{{ not_configured }}
|
||||
|
||||
{%- endmatch %}
|
||||
|
||||
{{ SECTION }}
|
||||
# To initialize zoxide with xonsh, add the following line to your xonsh
|
||||
# configuration file (usually ~/.xonshrc):
|
||||
{{ section }}
|
||||
# To initialize zoxide, add this to your configuration (usually ~/.xonshrc):
|
||||
#
|
||||
# execx($(zoxide init xonsh), 'exec', __xonsh__.ctx, filename='zoxide')
|
||||
|
|
|
@ -1,159 +1,179 @@
|
|||
{%- let SECTION = "# =============================================================================\n#" -%}
|
||||
{%- let NOT_CONFIGURED = "# -- not configured --" -%}
|
||||
{%- let section = "# =============================================================================\n#" -%}
|
||||
{%- let not_configured = "# -- not configured --" -%}
|
||||
|
||||
{{ SECTION }}
|
||||
# shellcheck shell=bash
|
||||
|
||||
{{ section }}
|
||||
# Utility functions for zoxide.
|
||||
#
|
||||
|
||||
# pwd based on the value of _ZO_RESOLVE_SYMLINKS.
|
||||
function __zoxide_pwd() {
|
||||
{%- if resolve_symlinks %}
|
||||
pwd -P
|
||||
{%- if cfg!(windows) %}
|
||||
\command cygpath -w "$(\builtin pwd -P)"
|
||||
{%- else if resolve_symlinks %}
|
||||
\builtin pwd -P
|
||||
{%- else %}
|
||||
pwd -L
|
||||
\builtin pwd -L
|
||||
{%- endif %}
|
||||
}
|
||||
|
||||
# cd + custom logic based on the value of _ZO_ECHO.
|
||||
function __zoxide_cd() {
|
||||
cd "$@" {%- if echo %} && __zoxide_pwd {%- endif %}
|
||||
# shellcheck disable=SC2164
|
||||
\builtin cd -- "$@" {%- if echo %} && __zoxide_pwd {%- endif %}
|
||||
}
|
||||
|
||||
{{ SECTION }}
|
||||
{{ section }}
|
||||
# Hook configuration for zoxide.
|
||||
#
|
||||
|
||||
# Hook to add new entries to the database.
|
||||
function __zoxide_hook() {
|
||||
zoxide add "$(__zoxide_pwd)"
|
||||
# shellcheck disable=SC2312
|
||||
\command zoxide add -- "$(__zoxide_pwd)"
|
||||
}
|
||||
|
||||
# Initialize hook.
|
||||
{%- match hook %}
|
||||
{%- when Hook::None %}
|
||||
{{ NOT_CONFIGURED }}
|
||||
\builtin typeset -ga precmd_functions
|
||||
\builtin typeset -ga chpwd_functions
|
||||
# shellcheck disable=SC2034,SC2296
|
||||
precmd_functions=("${(@)precmd_functions:#__zoxide_hook}")
|
||||
# shellcheck disable=SC2034,SC2296
|
||||
chpwd_functions=("${(@)chpwd_functions:#__zoxide_hook}")
|
||||
|
||||
{%- when Hook::Prompt %}
|
||||
[[ -n "${precmd_functions[(r)__zoxide_hook]}" ]] || {
|
||||
precmd_functions+=(__zoxide_hook)
|
||||
{%- if hook == InitHook::Prompt %}
|
||||
precmd_functions+=(__zoxide_hook)
|
||||
{%- else if hook == InitHook::Pwd %}
|
||||
chpwd_functions+=(__zoxide_hook)
|
||||
{%- 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 %}
|
||||
}
|
||||
|
||||
{%- when Hook::Pwd %}
|
||||
chpwd_functions=(${chpwd_functions[@]} "__zoxide_hook")
|
||||
|
||||
{%- endmatch %}
|
||||
|
||||
{{ SECTION }}
|
||||
# When using zoxide with --no-aliases, alias these internal functions as
|
||||
# desired.
|
||||
{{ section }}
|
||||
# When using zoxide with --no-cmd, alias these internal functions as desired.
|
||||
#
|
||||
|
||||
# Jump to a directory using only keywords.
|
||||
function __zoxide_z() {
|
||||
if [ "$#" -eq 0 ]; then
|
||||
__zoxide_doctor
|
||||
if [[ "$#" -eq 0 ]]; then
|
||||
__zoxide_cd ~
|
||||
elif [ "$#" -eq 1 ] && [ "$1" = '-' ]; then
|
||||
if [ -n "$OLDPWD" ]; then
|
||||
__zoxide_cd "$OLDPWD"
|
||||
else
|
||||
echo "zoxide: \\$OLDPWD is not set"
|
||||
return 1
|
||||
fi
|
||||
elif [ "$#" -eq 1 ] && [ -d "$1" ]; then
|
||||
elif [[ "$#" -eq 1 ]] && { [[ -d "$1" ]] || [[ "$1" = '-' ]] || [[ "$1" =~ ^[-+][0-9]+$ ]]; }; then
|
||||
__zoxide_cd "$1"
|
||||
elif [[ "$#" -eq 2 ]] && [[ "$1" = "--" ]]; then
|
||||
__zoxide_cd "$2"
|
||||
else
|
||||
local __zoxide_result
|
||||
__zoxide_result="$(zoxide query -- "$@")" && __zoxide_cd "$__zoxide_result"
|
||||
\builtin local result
|
||||
# shellcheck disable=SC2312
|
||||
result="$(\command zoxide query --exclude "$(__zoxide_pwd)" -- "$@")" && __zoxide_cd "${result}"
|
||||
fi
|
||||
}
|
||||
|
||||
# Jump to a directory using interactive search.
|
||||
function __zoxide_zi() {
|
||||
local __zoxide_result
|
||||
__zoxide_result="$(zoxide query -i -- "$@")" && __zoxide_cd "$__zoxide_result"
|
||||
__zoxide_doctor
|
||||
\builtin local result
|
||||
result="$(\command zoxide query --interactive -- "$@")" && __zoxide_cd "${result}"
|
||||
}
|
||||
|
||||
# Add a new entry to the database.
|
||||
function __zoxide_za() {
|
||||
zoxide add "$@"
|
||||
}
|
||||
|
||||
# Query an entry from the database using only keywords.
|
||||
function __zoxide_zq() {
|
||||
zoxide query "$@"
|
||||
}
|
||||
|
||||
# Query an entry from the database using interactive selection.
|
||||
function __zoxide_zqi() {
|
||||
zoxide query -i "$@"
|
||||
}
|
||||
|
||||
# Remove an entry from the database using the exact path.
|
||||
function __zoxide_zr() {
|
||||
zoxide remove "$@"
|
||||
}
|
||||
|
||||
# Remove an entry from the database using interactive selection.
|
||||
function __zoxide_zri() {
|
||||
zoxide remove -i "$@"
|
||||
}
|
||||
|
||||
{{ SECTION }}
|
||||
# Convenient aliases for zoxide. Disable these using --no-aliases.
|
||||
{{ section }}
|
||||
# Commands for zoxide. Disable these using --no-cmd.
|
||||
#
|
||||
|
||||
{%- match cmd %}
|
||||
{%- when Some with (cmd) %}
|
||||
|
||||
# Remove definitions.
|
||||
function __zoxide_unset() {
|
||||
\unalias "$@" &>{{ Opts::DEVNULL }}
|
||||
\unfunction "$@" &>{{ Opts::DEVNULL }}
|
||||
\unset "$@" &>{{ Opts::DEVNULL }}
|
||||
}
|
||||
|
||||
__zoxide_unset '{{cmd}}'
|
||||
function {{cmd}}() {
|
||||
function {{ cmd }}() {
|
||||
__zoxide_z "$@"
|
||||
}
|
||||
|
||||
__zoxide_unset '{{cmd}}i'
|
||||
function {{cmd}}i() {
|
||||
function {{ cmd }}i() {
|
||||
__zoxide_zi "$@"
|
||||
}
|
||||
|
||||
__zoxide_unset '{{cmd}}a'
|
||||
function {{cmd}}a() {
|
||||
__zoxide_za "$@"
|
||||
}
|
||||
|
||||
__zoxide_unset '{{cmd}}q'
|
||||
function {{cmd}}q() {
|
||||
__zoxide_zq "$@"
|
||||
}
|
||||
|
||||
__zoxide_unset '{{cmd}}qi'
|
||||
function {{cmd}}qi() {
|
||||
__zoxide_zqi "$@"
|
||||
}
|
||||
|
||||
__zoxide_unset '{{cmd}}r'
|
||||
function {{cmd}}r() {
|
||||
__zoxide_zr "$@"
|
||||
}
|
||||
|
||||
__zoxide_unset '{{cmd}}ri'
|
||||
function {{cmd}}ri() {
|
||||
__zoxide_zri "$@"
|
||||
}
|
||||
|
||||
{%- when None %}
|
||||
{{ NOT_CONFIGURED }}
|
||||
|
||||
{{ not_configured }}
|
||||
|
||||
{%- endmatch %}
|
||||
|
||||
{{ SECTION }}
|
||||
# To initialize zoxide with zsh, add the following line to your zsh
|
||||
# configuration file (usually ~/.zshrc):
|
||||
# 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 }}
|
||||
# To initialize zoxide, add this to your shell configuration file (usually ~/.zshrc):
|
||||
#
|
||||
# eval "$(zoxide init zsh)"
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
//! Test clap generated completions.
|
||||
#![cfg(feature = "nix-dev")]
|
||||
|
||||
use assert_cmd::Command;
|
||||
|
||||
#[test]
|
||||
fn completions_bash() {
|
||||
let source = include_str!("../contrib/completions/zoxide.bash");
|
||||
Command::new("bash")
|
||||
.args(["--noprofile", "--norc", "-c", source])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
}
|
||||
|
||||
// Elvish: the completions file uses editor commands to add completions to the
|
||||
// shell. However, Elvish does not support running editor commands from a
|
||||
// script, so we can't create a test for this. See: https://github.com/elves/elvish/issues/1299
|
||||
|
||||
#[test]
|
||||
fn completions_fish() {
|
||||
let source = include_str!("../contrib/completions/zoxide.fish");
|
||||
let tempdir = tempfile::tempdir().unwrap();
|
||||
let tempdir = tempdir.path().to_str().unwrap();
|
||||
|
||||
Command::new("fish")
|
||||
.env("HOME", tempdir)
|
||||
.args(["--command", source, "--private"])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completions_powershell() {
|
||||
let source = include_str!("../contrib/completions/_zoxide.ps1");
|
||||
Command::new("pwsh")
|
||||
.args(["-NoLogo", "-NonInteractive", "-NoProfile", "-Command", source])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completions_zsh() {
|
||||
let source = r#"
|
||||
set -eu
|
||||
completions='./contrib/completions'
|
||||
test -d "$completions"
|
||||
fpath=("$completions" $fpath)
|
||||
autoload -Uz compinit
|
||||
compinit -u
|
||||
"#;
|
||||
|
||||
Command::new("zsh").args(["-c", source, "--no-rcs"]).assert().success().stdout("").stderr("");
|
||||
}
|
|
@ -1 +1,5 @@
|
|||
eval "$(zoxide init zsh)"
|
||||
if (( $+commands[zoxide] )); then
|
||||
eval "$(zoxide init zsh)"
|
||||
else
|
||||
echo 'zoxide: command not found, please install it from https://github.com/ajeetdsouza/zoxide'
|
||||
fi
|
||||
|
|
Loading…
Reference in New Issue