Compare commits
331 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 |
|
@ -1,2 +0,0 @@
|
|||
[advisories]
|
||||
ignore = ["RUSTSEC-2020-0095"]
|
|
@ -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
|
|
@ -3,22 +3,55 @@ 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: ci-${{ matrix.os }}
|
||||
name: ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
os: [ubuntu-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/install@v0.1
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions-rs/toolchain@v1
|
||||
if: ${{ matrix.os == 'windows-latest' }}
|
||||
with:
|
||||
crate: cargo-audit
|
||||
version: latest
|
||||
- uses: cachix/install-nix-action@v12
|
||||
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=https://github.com/NixOS/nixpkgs/archive/20.09.tar.gz
|
||||
- run: make test
|
||||
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,44 +1,56 @@
|
|||
name: release
|
||||
on:
|
||||
push:
|
||||
tags: ["v*"]
|
||||
branches: [main]
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
permissions:
|
||||
contents: write
|
||||
jobs:
|
||||
build:
|
||||
name: build-${{ matrix.target }}
|
||||
release:
|
||||
name: ${{ matrix.target }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target:
|
||||
- aarch64-pc-windows-msvc
|
||||
- aarch64-unknown-linux-musl
|
||||
- armv7-unknown-linux-musleabihf
|
||||
- x86_64-apple-darwin
|
||||
- x86_64-pc-windows-msvc
|
||||
- x86_64-unknown-linux-musl
|
||||
include:
|
||||
- target: aarch64-pc-windows-msvc
|
||||
os: windows-latest
|
||||
use-cross: false
|
||||
- target: aarch64-unknown-linux-musl
|
||||
os: ubuntu-latest
|
||||
use-cross: true
|
||||
- target: armv7-unknown-linux-musleabihf
|
||||
os: ubuntu-latest
|
||||
use-cross: true
|
||||
- target: x86_64-apple-darwin
|
||||
os: macos-latest
|
||||
use-cross: false
|
||||
- target: x86_64-pc-windows-msvc
|
||||
os: windows-latest
|
||||
use-cross: false
|
||||
- target: x86_64-unknown-linux-musl
|
||||
os: ubuntu-latest
|
||||
use-cross: true
|
||||
- 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:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 1
|
||||
fetch-depth: 0
|
||||
- name: Get version
|
||||
id: get_version
|
||||
uses: SebRollen/toml-action@v1.2.0
|
||||
with:
|
||||
file: Cargo.toml
|
||||
field: package.version
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
|
@ -46,52 +58,64 @@ jobs:
|
|||
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: --color=always --release --target=${{ matrix.target }}
|
||||
use-cross: ${{ matrix.use-cross }}
|
||||
- name: Build archive
|
||||
shell: bash
|
||||
args: --release --locked --target=${{ matrix.target }} --color=always --verbose
|
||||
use-cross: ${{ runner.os == 'Linux' }}
|
||||
- name: Install cargo-deb
|
||||
if: ${{ matrix.deb == true }}
|
||||
uses: actions-rs/install@v0.1
|
||||
with:
|
||||
crate: cargo-deb
|
||||
- name: Build deb
|
||||
if: ${{ matrix.deb == true }}
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: deb
|
||||
args: --no-build --no-strip --output=. --target=${{ matrix.target }}
|
||||
- name: Package (*nix)
|
||||
if: runner.os != 'Windows'
|
||||
run: |
|
||||
# Build archive
|
||||
tmpdir="zoxide-${{ matrix.target }}"
|
||||
mkdir "$tmpdir/"
|
||||
cp -r {man,CHANGELOG.md,LICENSE,README.md} "$tmpdir/"
|
||||
if [[ "${{ matrix.target }}" = *"windows"* ]]; then
|
||||
asset="$tmpdir.zip"
|
||||
cp "target/${{ matrix.target }}/release/zoxide.exe" "$tmpdir/"
|
||||
7z a -mm=Deflate -mfb=258 -mpass=15 -r "$asset" "./$tmpdir/*"
|
||||
else
|
||||
asset="$tmpdir.tar.gz"
|
||||
cp "target/${{ matrix.target }}/release/zoxide" "$tmpdir/"
|
||||
tar -cv "$tmpdir/" | gzip --best > "$asset"
|
||||
fi
|
||||
echo "ASSET=$asset" >> $GITHUB_ENV
|
||||
- name: Upload archive
|
||||
uses: actions/upload-artifact@v2
|
||||
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: ${{ env.ASSET }}
|
||||
path: ${{ env.ASSET }}
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build]
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- name: Download archives
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
path: artifact
|
||||
- name: Show downloaded files
|
||||
run: ls -lRh artifact/
|
||||
name: ${{ matrix.target }}
|
||||
path: |
|
||||
*.deb
|
||||
*.tar.gz
|
||||
*.zip
|
||||
- name: Create release
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: |
|
||||
github.ref == 'refs/heads/main' && startsWith(github.event.head_commit.message, 'chore(release)')
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
draft: true
|
||||
files: artifact/*/*
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
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 }}
|
|
@ -5,6 +5,7 @@
|
|||
# Compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
target_nix/
|
||||
|
||||
# Backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
|
350
CHANGELOG.md
350
CHANGELOG.md
|
@ -1,3 +1,5 @@
|
|||
<!-- markdownlint-disable-file MD024 -->
|
||||
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
@ -5,6 +7,287 @@ 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
|
||||
|
@ -17,7 +300,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
- Auto-generated shell completions.
|
||||
- `zoxide query --all` for listing deleted directories.
|
||||
- Lazy deletion for removed directories that have not been accessed in > 90 days.
|
||||
- Lazy deletion for removed directories that have not been accessed in > 90
|
||||
days.
|
||||
- Nushell: support for 0.32.0+.
|
||||
|
||||
### Fixed
|
||||
|
@ -43,8 +327,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
### Fixed
|
||||
|
||||
- `cd -` on fish shells.
|
||||
- `__zoxide_hook` no longer changes value of `$?` within `$PROMPT_COMMAND` on bash.
|
||||
- `cd -` on Fish shells.
|
||||
- `__zoxide_hook` no longer changes value of `$?` within `$PROMPT_COMMAND` on
|
||||
Bash.
|
||||
|
||||
### Removed
|
||||
|
||||
|
@ -73,16 +358,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
### Removed
|
||||
|
||||
- Aliases: `za`, `zq`, `zqi`, `zr`, `zri`. These are trivial aliases to zoxide that can easily be defined manually, and aren't very useful to most users.
|
||||
- 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
|
||||
|
@ -91,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
|
||||
|
||||
|
@ -114,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
|
||||
|
||||
|
@ -125,7 +413,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
### Added
|
||||
|
||||
- Support for powershell.
|
||||
- Support for PowerShell.
|
||||
|
||||
### Removed
|
||||
|
||||
|
@ -143,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`.
|
||||
|
@ -153,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
|
||||
|
||||
|
@ -164,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
|
||||
|
||||
|
@ -175,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
|
||||
|
@ -186,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
|
||||
|
||||
|
@ -206,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 bash.
|
||||
- Support for Bash.
|
||||
- `migrate` subcommand to allow users to migrate from `z`.
|
||||
|
||||
### Fixed
|
||||
|
@ -220,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
|
||||
|
||||
|
@ -246,8 +534,28 @@ 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 zsh.
|
||||
- Add support for Zsh.
|
||||
|
||||
[0.9.8]: https://github.com/ajeetdsouza/zoxide/compare/v0.9.7...v0.9.8
|
||||
[0.9.7]: https://github.com/ajeetdsouza/zoxide/compare/v0.9.6...v0.9.7
|
||||
[0.9.6]: https://github.com/ajeetdsouza/zoxide/compare/v0.9.5...v0.9.6
|
||||
[0.9.5]: https://github.com/ajeetdsouza/zoxide/compare/v0.9.4...v0.9.5
|
||||
[0.9.4]: https://github.com/ajeetdsouza/zoxide/compare/v0.9.3...v0.9.4
|
||||
[0.9.3]: https://github.com/ajeetdsouza/zoxide/compare/v0.9.2...v0.9.3
|
||||
[0.9.2]: https://github.com/ajeetdsouza/zoxide/compare/v0.9.1...v0.9.2
|
||||
[0.9.1]: https://github.com/ajeetdsouza/zoxide/compare/v0.9.0...v0.9.1
|
||||
[0.9.0]: https://github.com/ajeetdsouza/zoxide/compare/v0.8.3...v0.9.0
|
||||
[0.8.3]: https://github.com/ajeetdsouza/zoxide/compare/v0.8.2...v0.8.3
|
||||
[0.8.2]: https://github.com/ajeetdsouza/zoxide/compare/v0.8.1...v0.8.2
|
||||
[0.8.1]: https://github.com/ajeetdsouza/zoxide/compare/v0.8.0...v0.8.1
|
||||
[0.8.0]: https://github.com/ajeetdsouza/zoxide/compare/v0.7.9...v0.8.0
|
||||
[0.7.9]: https://github.com/ajeetdsouza/zoxide/compare/v0.7.8...v0.7.9
|
||||
[0.7.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
|
||||
|
|
File diff suppressed because it is too large
Load Diff
130
Cargo.toml
130
Cargo.toml
|
@ -1,58 +1,114 @@
|
|||
[package]
|
||||
name = "zoxide"
|
||||
version = "0.7.2"
|
||||
authors = ["Ajeet D'Souza <98ajeet@gmail.com>"]
|
||||
edition = "2018"
|
||||
description = "A smarter cd command for your terminal"
|
||||
repository = "https://github.com/ajeetdsouza/zoxide"
|
||||
license = "MIT"
|
||||
keywords = ["cli"]
|
||||
categories = ["command-line-utilities", "filesystem"]
|
||||
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 = "2.0.0"
|
||||
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"
|
||||
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"
|
||||
|
||||
[dev-dependencies]
|
||||
assert_cmd = "1.0.1"
|
||||
rstest = "0.10.0"
|
||||
which = "7.0.3"
|
||||
|
||||
[build-dependencies]
|
||||
clap = "3.0.0-beta.2"
|
||||
clap_generate = "3.0.0-beta.2"
|
||||
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 = []
|
||||
# Adds tests for code generated by `zoxide init`.
|
||||
# This requires the following external programs available in $PATH:
|
||||
# - bash
|
||||
# - black: <https://github.com/psf/black>
|
||||
# - dash
|
||||
# - elvish: <https://github.com/elves/elvish>
|
||||
# - fish: <https://github.com/fish-shell/fish-shell>
|
||||
# - mypy: <https://github.com/python/mypy>
|
||||
# - nushell: <https://github.com/nushell/nushell>
|
||||
# - powershell: <https://github.com/PowerShell/PowerShell>
|
||||
# - pylint: <https://github.com/PyCQA/pylint>
|
||||
# - shellcheck: <https://github.com/koalaman/shellcheck>
|
||||
# - shfmt: <https://github.com/mvdan/sh>
|
||||
# - xonsh: <https://github.com/xonsh/xonsh>
|
||||
# - zsh: <https://github.com/zsh-users/zsh>
|
||||
# Since most users are unlikely to have installed all of the above, these tests
|
||||
# are disabled by default.
|
||||
shell_tests = []
|
||||
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"]
|
39
Makefile
39
Makefile
|
@ -1,39 +0,0 @@
|
|||
ifeq ($(CI), true)
|
||||
ci_color_always := --color=always
|
||||
endif
|
||||
|
||||
ifeq ($(OS), Windows_NT)
|
||||
NIX := false
|
||||
else
|
||||
NIX := true
|
||||
endif
|
||||
|
||||
.PHONY: build clean install test uninstall
|
||||
|
||||
build:
|
||||
cargo build $(ci_color_always)
|
||||
|
||||
clean:
|
||||
cargo clean $(ci_color_always)
|
||||
|
||||
install:
|
||||
cargo install --path=. $(ci_color_always)
|
||||
|
||||
ifeq ($(NIX), true)
|
||||
test:
|
||||
nix-shell --pure --run 'cargo fmt -- --check --files-with-diff $(ci_color_always)'
|
||||
nix-shell --pure --run 'cargo check --all-features $(ci_color_always)'
|
||||
nix-shell --pure --run 'cargo clippy --all-features $(ci_color_always) -- --deny warnings --deny clippy::all'
|
||||
nix-shell --pure --run 'cargo test --all-features --no-fail-fast $(ci_color_always)'
|
||||
nix-shell --pure --run 'cargo audit --deny warnings $(ci_color_always)'
|
||||
else
|
||||
test:
|
||||
cargo fmt -- --check --files-with-diff $(ci_color_always)
|
||||
cargo check --all-features $(ci_color_always)
|
||||
cargo clippy --all-features $(ci_color_always) -- --deny warnings --deny clippy::all
|
||||
cargo test --no-fail-fast $(ci_color_always)
|
||||
cargo audit --deny warnings $(ci_color_always)
|
||||
endif
|
||||
|
||||
uninstall:
|
||||
cargo uninstall $(ci_color_always)
|
611
README.md
611
README.md
|
@ -1,292 +1,573 @@
|
|||
# `zoxide`
|
||||
<!-- markdownlint-configure-file {
|
||||
"MD013": {
|
||||
"code_blocks": false,
|
||||
"tables": false
|
||||
},
|
||||
"MD033": false,
|
||||
"MD041": false
|
||||
} -->
|
||||
|
||||
> A smarter cd command for your terminal
|
||||
<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
|
||||
|
||||
[![crates.io][crates.io-badge]][crates.io]
|
||||
[![Releases][releases-badge]][releases]
|
||||
[![Downloads][downloads-badge]][releases]
|
||||
[![Built with Nix][builtwithnix-badge]][builtwithnix]
|
||||
|
||||
`zoxide` is a blazing fast replacement for your `cd` command, inspired by
|
||||
`z` and `z.lua`. It keeps track of the directories you use most
|
||||
frequently, and uses a ranking algorithm to navigate to the best match.
|
||||
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.<br />
|
||||
zoxide works on all major shells.
|
||||
|
||||
[Getting started](#getting-started) •
|
||||
[Installation](#installation) •
|
||||
[Configuration](#configuration) •
|
||||
[Integrations](#third-party-integrations)
|
||||
|
||||
</div>
|
||||
|
||||
## Getting started
|
||||
|
||||
![Tutorial][tutorial]
|
||||
|
||||
## Examples
|
||||
|
||||
```sh
|
||||
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 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
|
||||
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)
|
||||
zi foo # cd with interactive selection (using fzf)
|
||||
|
||||
z foo<SPACE><TAB> # show interactive completions (zoxide v0.8.0+, bash 4.4+/fish/zsh only)
|
||||
```
|
||||
|
||||
Read more about the matching algorithm [here][algorithm-matching].
|
||||
|
||||
## Getting started
|
||||
## Installation
|
||||
|
||||
### Step 1: Install `zoxide`
|
||||
zoxide can be installed in 4 easy steps:
|
||||
|
||||
`zoxide` works across all major platforms. If your distribution isn't included
|
||||
in the list below, you can download the binary from the [Releases] page and
|
||||
copy it to your `$PATH`.
|
||||
1. **Install binary**
|
||||
|
||||
#### On Linux
|
||||
zoxide runs on most major platforms. If your platform isn't listed below,
|
||||
please [open an issue][issues].
|
||||
|
||||
| Distribution | Repository | Instructions |
|
||||
| ------------------ | ----------------------- | ------------------------------------------------------------------------------------------------------ |
|
||||
| ***Any*** | **[crates.io]** | `cargo install zoxide` |
|
||||
| *Any* | [conda-forge] | `conda install -c conda-forge zoxide` |
|
||||
| *Any* | [Linuxbrew] | `brew install zoxide` |
|
||||
| Alpine Linux 3.13+ | [Alpine Linux Packages] | `apk add zoxide` |
|
||||
| Arch Linux | [AUR] | `yay -Sy zoxide-bin` |
|
||||
| CentOS 7+ | [Copr] | `dnf copr enable atim/zoxide` <br /> `dnf install zoxide` |
|
||||
| Debian Testing | [Debian Packages] | `apt install zoxide` |
|
||||
| Devuan 4.0+ | [Devuan Packages] | `apt install zoxide` |
|
||||
| Fedora 32+ | [Fedora Packages] | `dnf install zoxide` |
|
||||
| Gentoo | [dm9pZCAq Overlay] | `eselect repository enable dm9pZCAq` <br /> `emerge --sync dm9pZCAq` <br /> `emerge app-shells/zoxide` |
|
||||
| NixOS | [nixpkgs] | `nix-env -iA nixpkgs.zoxide` |
|
||||
| Parrot OS | | `apt install zoxide` |
|
||||
| Ubuntu 21.04+ | [Ubuntu Packages] | `apt install zoxide` |
|
||||
| Void Linux | [Void Linux Packages] | `xbps-install -S zoxide` |
|
||||
<details>
|
||||
<summary>Linux / WSL</summary>
|
||||
|
||||
#### On macOS
|
||||
> 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` |
|
||||
|
||||
| Repository | Instructions |
|
||||
| --------------- | ------------------------------------- |
|
||||
| **[crates.io]** | `cargo install zoxide` |
|
||||
| [conda-forge] | `conda install -c conda-forge zoxide` |
|
||||
| [Homebrew] | `brew install zoxide` |
|
||||
| [MacPorts] | `port install zoxide` |
|
||||
</details>
|
||||
|
||||
#### On Windows
|
||||
<details>
|
||||
<summary>macOS</summary>
|
||||
|
||||
| Repository | Instructions |
|
||||
| --------------- | ------------------------------------- |
|
||||
| **[crates.io]** | `cargo install zoxide` |
|
||||
| [Chocolatey] | `choco install zoxide` |
|
||||
| [conda-forge] | `conda install -c conda-forge zoxide` |
|
||||
| [Scoop] | `scoop 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 BSD
|
||||
</details>
|
||||
|
||||
| Distribution | Repository | Instructions |
|
||||
| ------------- | --------------- | ---------------------- |
|
||||
| ***Any*** | **[crates.io]** | `cargo install zoxide` |
|
||||
| DragonFly BSD | [DPorts] | `pkg install zoxide` |
|
||||
| FreeBSD | [FreshPorts] | `pkg install zoxide` |
|
||||
| NetBSD | [pkgsrc] | `pkgin install zoxide` |
|
||||
<details>
|
||||
<summary>Windows</summary>
|
||||
|
||||
#### On Android
|
||||
> 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
|
||||
> ```
|
||||
|
||||
| Repository | Instructions |
|
||||
| ---------- | -------------------- |
|
||||
| [Termux] | `pkg install zoxide` |
|
||||
</details>
|
||||
|
||||
### Step 2: Install `fzf` (optional)
|
||||
<details>
|
||||
<summary>BSD</summary>
|
||||
|
||||
[`fzf`][fzf] is a command-line fuzzy finder, used by zoxide for interactive
|
||||
selection. Installation instructions can be found [here][fzf-installation].
|
||||
> 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
|
||||
> ```
|
||||
|
||||
### Step 3: Add `zoxide` to your shell
|
||||
</details>
|
||||
|
||||
If you currently use `z`, `z.lua`, or `zsh-z`, you may want to first import
|
||||
your existing entries into `zoxide`:
|
||||
<details>
|
||||
<summary>Android</summary>
|
||||
|
||||
```sh
|
||||
zoxide import --from z path/to/db
|
||||
```
|
||||
> 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
|
||||
> ```
|
||||
|
||||
Alternatively, for `autojump` (note that scores are not imported):
|
||||
</details>
|
||||
|
||||
```sh
|
||||
zoxide import --from autojump path/to/db
|
||||
```
|
||||
2. **Setup zoxide on your shell**
|
||||
|
||||
#### `bash`
|
||||
To start using zoxide, add it to your shell.
|
||||
|
||||
Add this to your configuration (usually `~/.bashrc`):
|
||||
<details>
|
||||
<summary>Bash</summary>
|
||||
|
||||
```sh
|
||||
eval "$(zoxide init bash)"
|
||||
```
|
||||
> Add this to the <ins>**end**</ins> of your config file (usually `~/.bashrc`):
|
||||
>
|
||||
> ```sh
|
||||
> eval "$(zoxide init bash)"
|
||||
> ```
|
||||
|
||||
#### `elvish`
|
||||
</details>
|
||||
|
||||
Add this to your configuration (usually `~/.elvish/rc.elv`):
|
||||
<details>
|
||||
<summary>Elvish</summary>
|
||||
|
||||
```sh
|
||||
eval (zoxide init elvish | slurp)
|
||||
```
|
||||
> 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.
|
||||
|
||||
#### `fish`
|
||||
</details>
|
||||
|
||||
Add this to your configuration (usually `~/.config/fish/config.fish`):
|
||||
<details>
|
||||
<summary>Fish</summary>
|
||||
|
||||
```fish
|
||||
zoxide init fish | source
|
||||
```
|
||||
> Add this to the <ins>**end**</ins> of your config file (usually
|
||||
> `~/.config/fish/config.fish`):
|
||||
>
|
||||
> ```sh
|
||||
> zoxide init fish | source
|
||||
> ```
|
||||
|
||||
#### `nushell 0.32.0+`
|
||||
</details>
|
||||
|
||||
Initialize zoxide's Nushell script:
|
||||
<details>
|
||||
<summary>Nushell</summary>
|
||||
|
||||
```sh
|
||||
zoxide init nushell --hook prompt | save ~/.zoxide.nu
|
||||
```
|
||||
> 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 this to your configuration (usually `~/.config/nu/config.toml`):
|
||||
</details>
|
||||
|
||||
```toml
|
||||
prompt = "__zoxide_hook;__zoxide_prompt"
|
||||
startup = ["zoxide init nushell --hook prompt | save ~/.zoxide.nu", "source ~/.zoxide.nu"]
|
||||
```
|
||||
<details>
|
||||
<summary>PowerShell</summary>
|
||||
|
||||
You can replace `__zoxide_prompt` with a custom prompt.
|
||||
> 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) })
|
||||
> ```
|
||||
|
||||
#### `powershell`
|
||||
</details>
|
||||
|
||||
Add this to your configuration (the location is stored in `$profile`):
|
||||
<details>
|
||||
<summary>Tcsh</summary>
|
||||
|
||||
```powershell
|
||||
Invoke-Expression (& {
|
||||
$hook = if ($PSVersionTable.PSVersion.Major -lt 6) { 'prompt' } else { 'pwd' }
|
||||
(zoxide init --hook $hook powershell) -join "`n"
|
||||
})
|
||||
```
|
||||
> Add this to the <ins>**end**</ins> of your config file (usually `~/.tcshrc`):
|
||||
>
|
||||
> ```sh
|
||||
> zoxide init tcsh > ~/.zoxide.tcsh
|
||||
> source ~/.zoxide.tcsh
|
||||
> ```
|
||||
|
||||
#### `xonsh`
|
||||
</details>
|
||||
|
||||
Add this to your configuration (usually `~/.xonshrc`):
|
||||
<details>
|
||||
<summary>Xonsh</summary>
|
||||
|
||||
```python
|
||||
execx($(zoxide init xonsh), 'exec', __xonsh__.ctx, filename='zoxide')
|
||||
```
|
||||
> Add this to the <ins>**end**</ins> of your config file (usually `~/.xonshrc`):
|
||||
>
|
||||
> ```python
|
||||
> execx($(zoxide init xonsh), 'exec', __xonsh__.ctx, filename='zoxide')
|
||||
> ```
|
||||
|
||||
#### `zsh`
|
||||
</details>
|
||||
|
||||
Add this to your configuration (usually `~/.zshrc`):
|
||||
<details>
|
||||
<summary>Zsh</summary>
|
||||
|
||||
```sh
|
||||
eval "$(zoxide init 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`.
|
||||
|
||||
#### Any POSIX shell
|
||||
</details>
|
||||
|
||||
Add this to your configuration:
|
||||
<details>
|
||||
<summary>Any POSIX shell</summary>
|
||||
|
||||
```sh
|
||||
eval "$(zoxide init posix --hook prompt)"
|
||||
```
|
||||
> 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`: changes the prefix of predefined aliases (`z`, `zi`).
|
||||
- e.g. `--cmd j` would change the aliases to `j` and `ji` respectively.
|
||||
- `--hook <HOOK>`: change how often zoxide increments a directory's score:
|
||||
- `none`: never automatically add directories to zoxide.
|
||||
- `prompt`: add the current directory to zoxide at every shell prompt.
|
||||
- `pwd`: whenever the user changes directories, add the new directory to
|
||||
zoxide.
|
||||
- `--no-aliases`: don't define extra aliases (`z`, `zi`).
|
||||
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 use them elsewhere.
|
||||
`__zoxide_zi`, should you choose to redefine them.
|
||||
|
||||
### Environment variables
|
||||
|
||||
Be sure to set these before calling `zoxide init`.
|
||||
Environment variables[^2] can be used for configuration. They must be set before
|
||||
`zoxide init` is called.
|
||||
|
||||
- `_ZO_DATA_DIR`
|
||||
- Specifies the directory in which zoxide should store its database.
|
||||
- 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 | `{FOLDERID_RoamingAppData}` | `C:\Users\Alice\AppData\Roaming` |
|
||||
| Windows | `%LOCALAPPDATA%` | `C:\Users\Alice\AppData\Local` |
|
||||
|
||||
- `_ZO_ECHO`
|
||||
- When set to `1`, `z` will print the matched directory before navigating to
|
||||
- When set to 1, `z` will print the matched directory before navigating to
|
||||
it.
|
||||
- `_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`][fzf]. See `man fzf` for the list of
|
||||
options.
|
||||
- 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`.
|
||||
- By default, this is set to 10000.
|
||||
- `_ZO_RESOLVE_SYMLINKS`
|
||||
- When set to `1`, `z` will resolve symlinks before adding directories to the
|
||||
- When set to 1, `z` will resolve symlinks before adding directories to the
|
||||
database.
|
||||
|
||||
## Third-party integrations
|
||||
|
||||
- [`nnn`][nnn] is a terminal file manager. You can use `zoxide` for navigation
|
||||
with the official [`autojump`][nnn-autojump] plugin.
|
||||
- [`ranger`][ranger] is a terminal file manager. You can use `zoxide` for
|
||||
navigation with the [`ranger-zoxide`][ranger-zoxide] plugin.
|
||||
- [`telescope.nvim`][telescope-nvim] is a fuzzy finder for `neovim`. You can
|
||||
use it with `zoxide` via the [`telescope-zoxide`][telescope-zoxide] plugin.
|
||||
- [`vim`][vim] / [`neovim`][neovim]. You can use `zoxide` for navigation with
|
||||
the [`zoxide.vim`][zoxide-vim] plugin.
|
||||
- [`xxh`][xxh] transports your shell configuration over SSH. You can use
|
||||
`zoxide` over SSH via the [`xxh-plugin-prerun-zoxide`][xxh-zoxide] plugin.
|
||||
- [`zsh-autocomplete`][zsh-autocomplete] adds realtime completions to `zsh`. It
|
||||
supports `zoxide` out of the box.
|
||||
| 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
|
||||
[aur]: https://aur.archlinux.org/packages/zoxide-bin
|
||||
[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
|
||||
[copr]: https://copr.fedorainfracloud.org/coprs/atim/zoxide/
|
||||
[crates.io-badge]: https://img.shields.io/crates/v/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/testing/admin/zoxide
|
||||
[debian packages]: https://packages.debian.org/stable/admin/zoxide
|
||||
[exherbo packages]: https://gitlab.exherbo.org/exherbo/rust/-/tree/master/packages/sys-apps/zoxide
|
||||
[devuan packages]: https://pkginfo.devuan.org/cgi-bin/package-query.html?c=package&q=zoxide
|
||||
[dm9pzcaq overlay]: https://github.com/gentoo-mirror/dm9pZCAq
|
||||
[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/tools/misc/zoxide/default.nix
|
||||
[nixpkgs]: https://github.com/NixOS/nixpkgs/blob/master/pkgs/by-name/zo/zoxide/package.nix
|
||||
[nnn-autojump]: https://github.com/jarun/nnn/blob/master/plugins/autojump
|
||||
[nnn]: 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
|
||||
[releases-badge]: https://img.shields.io/github/v/release/ajeetdsouza/zoxide
|
||||
[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
|
||||
[telescope-nvim]: https://github.com/nvim-telescope/telescope.nvim
|
||||
[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
|
||||
[ubuntu packages]: https://packages.ubuntu.com/hirsute/zoxide
|
||||
[vim]: https://github.com/vim/vim
|
||||
[void linux packages]: https://github.com/void-linux/void-packages/tree/master/srcpkgs/zoxide
|
||||
[xxh-zoxide]: https://github.com/xxh/xxh-plugin-prerun-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
|
||||
[zoxide-vim]: https://github.com/nanotee/zoxide.vim
|
||||
[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
|
||||
|
|
79
build.rs
79
build.rs
|
@ -1,55 +1,36 @@
|
|||
use std::env;
|
||||
use std::process::Command;
|
||||
#[path = "src/cmd/cmd.rs"]
|
||||
mod cmd;
|
||||
|
||||
fn git_version() -> Option<String> {
|
||||
let mut git = Command::new("git");
|
||||
git.args(&["describe", "--tags", "--broken"]);
|
||||
use std::{env, io};
|
||||
|
||||
let output = git.output().ok()?;
|
||||
if !output.status.success() || output.stdout.is_empty() || !output.stderr.is_empty() {
|
||||
return None;
|
||||
}
|
||||
String::from_utf8(output.stdout).ok()
|
||||
}
|
||||
|
||||
fn crate_version() -> String {
|
||||
// unwrap is safe here, since Cargo will always supply this variable.
|
||||
format!("v{}", env::var("CARGO_PKG_VERSION").unwrap())
|
||||
}
|
||||
|
||||
fn generate_completions() {
|
||||
#[path = "src/app/_app.rs"]
|
||||
mod app;
|
||||
|
||||
use app::App;
|
||||
use clap::IntoApp;
|
||||
use clap_generate::generate_to;
|
||||
use clap_generate::generators::{Bash, Elvish, Fish, PowerShell, Zsh};
|
||||
|
||||
let app = &mut App::into_app();
|
||||
let bin_name = &env::var("CARGO_PKG_NAME").unwrap();
|
||||
let out_dir = "contrib/completions";
|
||||
|
||||
generate_to::<Bash, _, _>(app, bin_name, out_dir);
|
||||
generate_to::<Elvish, _, _>(app, bin_name, out_dir);
|
||||
generate_to::<Fish, _, _>(app, bin_name, out_dir);
|
||||
generate_to::<PowerShell, _, _>(app, bin_name, out_dir);
|
||||
generate_to::<Zsh, _, _>(app, bin_name, out_dir);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Packaged releases of zoxide almost always use the source tarball
|
||||
// provided by GitHub, which does not include the `.git` folder. Since this
|
||||
// feature is only useful for development, we can silently fall back to
|
||||
// using the crate version.
|
||||
let version = git_version().unwrap_or_else(crate_version);
|
||||
println!("cargo:rustc-env=ZOXIDE_VERSION={}", 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;
|
||||
|
||||
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=src");
|
||||
println!("cargo:rerun-if-changed=templates");
|
||||
println!("cargo:rerun-if-changed=tests");
|
||||
|
||||
generate_completions();
|
||||
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(())
|
||||
}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
# completions
|
||||
|
||||
Shell completions for zoxide, auto-generated by [`clap`][clap].
|
||||
|
||||
Since `clap` itself is in beta at the moment, these completions should not be
|
||||
treated as stable either.
|
||||
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
|
||||
|
|
|
@ -14,11 +14,11 @@ _zoxide() {
|
|||
fi
|
||||
|
||||
local context curcontext="$curcontext" state line
|
||||
_arguments "${_arguments_options[@]}" \
|
||||
'-h[Prints help information]' \
|
||||
'--help[Prints help information]' \
|
||||
'-V[Prints version information]' \
|
||||
'--version[Prints version information]' \
|
||||
_arguments "${_arguments_options[@]}" : \
|
||||
'-h[Print help]' \
|
||||
'--help[Print help]' \
|
||||
'-V[Print version]' \
|
||||
'--version[Print version]' \
|
||||
":: :_zoxide_commands" \
|
||||
"*::: :->zoxide" \
|
||||
&& ret=0
|
||||
|
@ -29,53 +29,120 @@ _zoxide() {
|
|||
curcontext="${curcontext%:*:*}:zoxide-command-$line[1]:"
|
||||
case $line[1] in
|
||||
(add)
|
||||
_arguments "${_arguments_options[@]}" \
|
||||
'-h[Prints help information]' \
|
||||
'--help[Prints help information]' \
|
||||
':path:_files -/' \
|
||||
_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]: :(autojump z)' \
|
||||
_arguments "${_arguments_options[@]}" : \
|
||||
'--from=[Application to import from]:FROM:(autojump z)' \
|
||||
'--merge[Merge into existing database]' \
|
||||
'-h[Prints help information]' \
|
||||
'--help[Prints help information]' \
|
||||
'-h[Print help]' \
|
||||
'--help[Print help]' \
|
||||
'-V[Print version]' \
|
||||
'--version[Print version]' \
|
||||
':path:_files' \
|
||||
&& ret=0
|
||||
;;
|
||||
(init)
|
||||
_arguments "${_arguments_options[@]}" \
|
||||
'--cmd=[Renames the '\''z'\'' command and corresponding aliases]' \
|
||||
'--hook=[Chooses event upon which an entry is added to the database]: :(none prompt pwd)' \
|
||||
'--no-aliases[Prevents zoxide from defining any commands]' \
|
||||
'-h[Prints help information]' \
|
||||
'--help[Prints help information]' \
|
||||
':shell:(bash elvish fish nushell posix powershell xonsh zsh)' \
|
||||
_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 a path from results]: :_files -/' \
|
||||
'--all[Show deleted directories]' \
|
||||
_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]' \
|
||||
'(-i --interactive)-s[Print score with results]' \
|
||||
'(-i --interactive)--score[Print score with results]' \
|
||||
'-h[Prints help information]' \
|
||||
'--help[Prints help information]' \
|
||||
'*::keywords:' \
|
||||
'-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[@]}" \
|
||||
'()*-i+[]' \
|
||||
'()*--interactive=[]' \
|
||||
'-h[Prints help information]' \
|
||||
'--help[Prints help information]' \
|
||||
'::path:_files -/' \
|
||||
_arguments "${_arguments_options[@]}" : \
|
||||
'-h[Print help]' \
|
||||
'--help[Print help]' \
|
||||
'-V[Print version]' \
|
||||
'--version[Print version]' \
|
||||
'*::paths:_files -/' \
|
||||
&& ret=0
|
||||
;;
|
||||
esac
|
||||
|
@ -86,48 +153,73 @@ esac
|
|||
(( $+functions[_zoxide_commands] )) ||
|
||||
_zoxide_commands() {
|
||||
local commands; commands=(
|
||||
"add:Add a new directory or increment its rank" \
|
||||
"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" \
|
||||
'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=(
|
||||
|
||||
)
|
||||
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=(
|
||||
|
||||
)
|
||||
local commands; commands=()
|
||||
_describe -t commands 'zoxide import commands' commands "$@"
|
||||
}
|
||||
(( $+functions[_zoxide__init_commands] )) ||
|
||||
_zoxide__init_commands() {
|
||||
local commands; commands=(
|
||||
|
||||
)
|
||||
local commands; commands=()
|
||||
_describe -t commands 'zoxide init commands' commands "$@"
|
||||
}
|
||||
(( $+functions[_zoxide__query_commands] )) ||
|
||||
_zoxide__query_commands() {
|
||||
local commands; commands=(
|
||||
|
||||
)
|
||||
local commands; commands=()
|
||||
_describe -t commands 'zoxide query commands' commands "$@"
|
||||
}
|
||||
(( $+functions[_zoxide__remove_commands] )) ||
|
||||
_zoxide__remove_commands() {
|
||||
local commands; commands=(
|
||||
|
||||
)
|
||||
local commands; commands=()
|
||||
_describe -t commands 'zoxide remove commands' commands "$@"
|
||||
}
|
||||
|
||||
_zoxide "$@"
|
||||
if [ "$funcstack[1]" = "_zoxide" ]; then
|
||||
_zoxide "$@"
|
||||
else
|
||||
compdef _zoxide zoxide
|
||||
fi
|
||||
|
|
|
@ -12,7 +12,8 @@ Register-ArgumentCompleter -Native -CommandName 'zoxide' -ScriptBlock {
|
|||
$element = $commandElements[$i]
|
||||
if ($element -isnot [StringConstantExpressionAst] -or
|
||||
$element.StringConstantType -ne [StringConstantType]::BareWord -or
|
||||
$element.Value.StartsWith('-')) {
|
||||
$element.Value.StartsWith('-') -or
|
||||
$element.Value -eq $wordToComplete) {
|
||||
break
|
||||
}
|
||||
$element.Value
|
||||
|
@ -20,11 +21,12 @@ Register-ArgumentCompleter -Native -CommandName 'zoxide' -ScriptBlock {
|
|||
|
||||
$completions = @(switch ($command) {
|
||||
'zoxide' {
|
||||
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Prints help information')
|
||||
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information')
|
||||
[CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Prints version information')
|
||||
[CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Prints version information')
|
||||
[CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')
|
||||
[CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')
|
||||
[CompletionResult]::new('-V', '-V ', [CompletionResultType]::ParameterName, 'Print version')
|
||||
[CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'Print version')
|
||||
[CompletionResult]::new('add', 'add', [CompletionResultType]::ParameterValue, 'Add a new directory or increment its rank')
|
||||
[CompletionResult]::new('edit', 'edit', [CompletionResultType]::ParameterValue, 'Edit the database')
|
||||
[CompletionResult]::new('import', 'import', [CompletionResultType]::ParameterValue, 'Import entries from another application')
|
||||
[CompletionResult]::new('init', 'init', [CompletionResultType]::ParameterValue, 'Generate shell configuration')
|
||||
[CompletionResult]::new('query', 'query', [CompletionResultType]::ParameterValue, 'Search for a directory in the database')
|
||||
|
@ -32,43 +34,94 @@ Register-ArgumentCompleter -Native -CommandName 'zoxide' -ScriptBlock {
|
|||
break
|
||||
}
|
||||
'zoxide;add' {
|
||||
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Prints help information')
|
||||
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information')
|
||||
[CompletionResult]::new('-s', '-s', [CompletionResultType]::ParameterName, 'The rank to increment the entry if it exists or initialize it with if it doesn''t')
|
||||
[CompletionResult]::new('--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, 'Prints help information')
|
||||
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information')
|
||||
[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, 'Renames the ''z'' command and corresponding aliases')
|
||||
[CompletionResult]::new('--hook', 'hook', [CompletionResultType]::ParameterName, 'Chooses event upon which an entry is added to the database')
|
||||
[CompletionResult]::new('--no-aliases', 'no-aliases', [CompletionResultType]::ParameterName, 'Prevents zoxide from defining any commands')
|
||||
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Prints help information')
|
||||
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information')
|
||||
[CompletionResult]::new('--cmd', '--cmd', [CompletionResultType]::ParameterName, 'Changes the prefix of the `z` and `zi` commands')
|
||||
[CompletionResult]::new('--hook', '--hook', [CompletionResultType]::ParameterName, 'Changes how often zoxide increments a directory''s score')
|
||||
[CompletionResult]::new('--no-cmd', '--no-cmd', [CompletionResultType]::ParameterName, 'Prevents zoxide from defining the `z` and `zi` commands')
|
||||
[CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')
|
||||
[CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')
|
||||
[CompletionResult]::new('-V', '-V ', [CompletionResultType]::ParameterName, 'Print version')
|
||||
[CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'Print version')
|
||||
break
|
||||
}
|
||||
'zoxide;query' {
|
||||
[CompletionResult]::new('--exclude', 'exclude', [CompletionResultType]::ParameterName, 'Exclude a path from results')
|
||||
[CompletionResult]::new('--all', 'all', [CompletionResultType]::ParameterName, 'Show deleted directories')
|
||||
[CompletionResult]::new('-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, 'Prints help information')
|
||||
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information')
|
||||
[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('-i', 'i', [CompletionResultType]::ParameterName, 'i')
|
||||
[CompletionResult]::new('--interactive', 'interactive', [CompletionResultType]::ParameterName, 'interactive')
|
||||
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Prints help information')
|
||||
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information')
|
||||
[CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')
|
||||
[CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')
|
||||
[CompletionResult]::new('-V', '-V ', [CompletionResultType]::ParameterName, 'Print version')
|
||||
[CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'Print version')
|
||||
break
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,32 +1,50 @@
|
|||
_zoxide() {
|
||||
local i cur prev opts cmds
|
||||
local i cur prev opts cmd
|
||||
COMPREPLY=()
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||
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[@]}
|
||||
for i in "${COMP_WORDS[@]:0:COMP_CWORD}"
|
||||
do
|
||||
case "${i}" in
|
||||
zoxide)
|
||||
case "${cmd},${i}" in
|
||||
",$1")
|
||||
cmd="zoxide"
|
||||
;;
|
||||
|
||||
add)
|
||||
cmd+="__add"
|
||||
zoxide,add)
|
||||
cmd="zoxide__add"
|
||||
;;
|
||||
import)
|
||||
cmd+="__import"
|
||||
zoxide,edit)
|
||||
cmd="zoxide__edit"
|
||||
;;
|
||||
init)
|
||||
cmd+="__init"
|
||||
zoxide,import)
|
||||
cmd="zoxide__import"
|
||||
;;
|
||||
query)
|
||||
cmd+="__query"
|
||||
zoxide,init)
|
||||
cmd="zoxide__init"
|
||||
;;
|
||||
remove)
|
||||
cmd+="__remove"
|
||||
zoxide,query)
|
||||
cmd="zoxide__query"
|
||||
;;
|
||||
zoxide,remove)
|
||||
cmd="zoxide__remove"
|
||||
;;
|
||||
zoxide__edit,decrement)
|
||||
cmd="zoxide__edit__decrement"
|
||||
;;
|
||||
zoxide__edit,delete)
|
||||
cmd="zoxide__edit__delete"
|
||||
;;
|
||||
zoxide__edit,increment)
|
||||
cmd="zoxide__edit__increment"
|
||||
;;
|
||||
zoxide__edit,reload)
|
||||
cmd="zoxide__edit__reload"
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
|
@ -35,13 +53,12 @@ _zoxide() {
|
|||
|
||||
case "${cmd}" in
|
||||
zoxide)
|
||||
opts=" -h -V --help --version add import init query remove"
|
||||
opts="-h -V --help --version add edit import init query remove"
|
||||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
fi
|
||||
case "${prev}" in
|
||||
|
||||
*)
|
||||
COMPREPLY=()
|
||||
;;
|
||||
|
@ -49,15 +66,91 @@ _zoxide() {
|
|||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
;;
|
||||
|
||||
zoxide__add)
|
||||
opts=" -h --help <path> "
|
||||
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=()
|
||||
;;
|
||||
|
@ -66,13 +159,12 @@ _zoxide() {
|
|||
return 0
|
||||
;;
|
||||
zoxide__import)
|
||||
opts=" -h --from --merge --help <path> "
|
||||
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
|
||||
|
@ -85,13 +177,12 @@ _zoxide() {
|
|||
return 0
|
||||
;;
|
||||
zoxide__init)
|
||||
opts=" -h --no-aliases --cmd --hook --help <shell> "
|
||||
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
|
||||
|
@ -108,15 +199,24 @@ _zoxide() {
|
|||
return 0
|
||||
;;
|
||||
zoxide__query)
|
||||
opts=" -i -l -s -h --all --interactive --list --score --exclude --help <keywords>... "
|
||||
opts="-a -i -l -s -h -V --all --interactive --list --score --exclude --base-dir --help --version [KEYWORDS]..."
|
||||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
fi
|
||||
case "${prev}" in
|
||||
|
||||
--exclude)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
COMPREPLY=()
|
||||
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
|
||||
compopt -o plusdirs
|
||||
fi
|
||||
return 0
|
||||
;;
|
||||
--base-dir)
|
||||
COMPREPLY=()
|
||||
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
|
||||
compopt -o plusdirs
|
||||
fi
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
|
@ -127,21 +227,12 @@ _zoxide() {
|
|||
return 0
|
||||
;;
|
||||
zoxide__remove)
|
||||
opts=" -i -h --interactive --help <path> "
|
||||
opts="-h -V --help --version [PATHS]..."
|
||||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
fi
|
||||
case "${prev}" in
|
||||
|
||||
--interactive)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
-i)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
COMPREPLY=()
|
||||
;;
|
||||
|
@ -152,4 +243,8 @@ _zoxide() {
|
|||
esac
|
||||
}
|
||||
|
||||
complete -F _zoxide -o bashdefault -o default zoxide
|
||||
if [[ "${BASH_VERSINFO[0]}" -eq 4 && "${BASH_VERSINFO[1]}" -ge 4 || "${BASH_VERSINFO[0]}" -gt 4 ]]; then
|
||||
complete -F _zoxide -o nosort -o bashdefault -o default zoxide
|
||||
else
|
||||
complete -F _zoxide -o bashdefault -o default zoxide
|
||||
fi
|
||||
|
|
|
@ -1,64 +1,114 @@
|
|||
|
||||
edit:completion:arg-completer[zoxide] = [@words]{
|
||||
fn spaces [n]{
|
||||
repeat $n ' ' | joins ''
|
||||
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-suffix=' '(spaces (- 14 (wcswidth $text)))$desc
|
||||
fn cand {|text desc|
|
||||
edit:complex-candidate $text &display=$text' '(spaces (- 14 (wcswidth $text)))$desc
|
||||
}
|
||||
command = 'zoxide'
|
||||
for word $words[1:-1] {
|
||||
if (has-prefix $word '-') {
|
||||
var command = 'zoxide'
|
||||
for word $words[1..-1] {
|
||||
if (str:has-prefix $word '-') {
|
||||
break
|
||||
}
|
||||
command = $command';'$word
|
||||
set command = $command';'$word
|
||||
}
|
||||
completions = [
|
||||
var completions = [
|
||||
&'zoxide'= {
|
||||
cand -h 'Prints help information'
|
||||
cand --help 'Prints help information'
|
||||
cand -V 'Prints version information'
|
||||
cand --version 'Prints version information'
|
||||
cand -h 'Print help'
|
||||
cand --help 'Print help'
|
||||
cand -V 'Print version'
|
||||
cand --version 'Print version'
|
||||
cand add 'Add a new directory or increment its rank'
|
||||
cand edit 'Edit the database'
|
||||
cand import 'Import entries from another application'
|
||||
cand init 'Generate shell configuration'
|
||||
cand query 'Search for a directory in the database'
|
||||
cand remove 'Remove a directory from the database'
|
||||
}
|
||||
&'zoxide;add'= {
|
||||
cand -h 'Prints help information'
|
||||
cand --help 'Prints help information'
|
||||
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 'Prints help information'
|
||||
cand --help 'Prints help information'
|
||||
cand -h 'Print help'
|
||||
cand --help 'Print help'
|
||||
cand -V 'Print version'
|
||||
cand --version 'Print version'
|
||||
}
|
||||
&'zoxide;init'= {
|
||||
cand --cmd 'Renames the ''z'' command and corresponding aliases'
|
||||
cand --hook 'Chooses event upon which an entry is added to the database'
|
||||
cand --no-aliases 'Prevents zoxide from defining any commands'
|
||||
cand -h 'Prints help information'
|
||||
cand --help 'Prints help information'
|
||||
cand --cmd 'Changes the prefix of the `z` and `zi` commands'
|
||||
cand --hook 'Changes how often zoxide increments a directory''s score'
|
||||
cand --no-cmd 'Prevents zoxide from defining the `z` and `zi` commands'
|
||||
cand -h 'Print help'
|
||||
cand --help 'Print help'
|
||||
cand -V 'Print version'
|
||||
cand --version 'Print version'
|
||||
}
|
||||
&'zoxide;query'= {
|
||||
cand --exclude 'Exclude a path from results'
|
||||
cand --all 'Show deleted directories'
|
||||
cand --exclude 'Exclude the current directory'
|
||||
cand --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 'Prints help information'
|
||||
cand --help 'Prints help information'
|
||||
cand -h 'Print help'
|
||||
cand --help 'Print help'
|
||||
cand -V 'Print version'
|
||||
cand --version 'Print version'
|
||||
}
|
||||
&'zoxide;remove'= {
|
||||
cand -i 'i'
|
||||
cand --interactive 'interactive'
|
||||
cand -h 'Prints help information'
|
||||
cand --help 'Prints help information'
|
||||
cand -h 'Print help'
|
||||
cand --help 'Print help'
|
||||
cand -V 'Print version'
|
||||
cand --version 'Print version'
|
||||
}
|
||||
]
|
||||
$completions[$command]
|
||||
|
|
|
@ -1,28 +1,73 @@
|
|||
complete -c zoxide -n "__fish_use_subcommand" -s h -l help -d 'Prints help information'
|
||||
complete -c zoxide -n "__fish_use_subcommand" -s V -l version -d 'Prints version information'
|
||||
complete -c zoxide -n "__fish_use_subcommand" -f -a "add" -d 'Add a new directory or increment its rank'
|
||||
complete -c zoxide -n "__fish_use_subcommand" -f -a "import" -d 'Import entries from another application'
|
||||
complete -c zoxide -n "__fish_use_subcommand" -f -a "init" -d 'Generate shell configuration'
|
||||
complete -c zoxide -n "__fish_use_subcommand" -f -a "query" -d 'Search for a directory in the database'
|
||||
complete -c zoxide -n "__fish_use_subcommand" -f -a "remove" -d 'Remove a directory from the database'
|
||||
complete -c zoxide -n "__fish_seen_subcommand_from add" -r -f -a "(__fish_complete_directories)"
|
||||
complete -c zoxide -n "__fish_seen_subcommand_from add" -s h -l help -d 'Prints help information'
|
||||
complete -c zoxide -n "__fish_seen_subcommand_from import" -r -F
|
||||
complete -c zoxide -n "__fish_seen_subcommand_from import" -l from -d 'Application to import from' -r -f -a "autojump z"
|
||||
complete -c zoxide -n "__fish_seen_subcommand_from import" -l merge -d 'Merge into existing database'
|
||||
complete -c zoxide -n "__fish_seen_subcommand_from import" -s h -l help -d 'Prints help information'
|
||||
complete -c zoxide -n "__fish_seen_subcommand_from init" -r -f -a "bash elvish fish nushell posix powershell xonsh zsh"
|
||||
complete -c zoxide -n "__fish_seen_subcommand_from init" -l cmd -d 'Renames the \'z\' command and corresponding aliases' -r
|
||||
complete -c zoxide -n "__fish_seen_subcommand_from init" -l hook -d 'Chooses event upon which an entry is added to the database' -r -f -a "none prompt pwd"
|
||||
complete -c zoxide -n "__fish_seen_subcommand_from init" -l no-aliases -d 'Prevents zoxide from defining any commands'
|
||||
complete -c zoxide -n "__fish_seen_subcommand_from init" -s h -l help -d 'Prints help information'
|
||||
complete -c zoxide -n "__fish_seen_subcommand_from query" -r
|
||||
complete -c zoxide -n "__fish_seen_subcommand_from query" -l exclude -d 'Exclude a path from results' -r -f -a "(__fish_complete_directories)"
|
||||
complete -c zoxide -n "__fish_seen_subcommand_from query" -l all -d 'Show deleted directories'
|
||||
complete -c zoxide -n "__fish_seen_subcommand_from query" -s i -l interactive -d 'Use interactive selection'
|
||||
complete -c zoxide -n "__fish_seen_subcommand_from query" -s l -l list -d 'List all matching directories'
|
||||
complete -c zoxide -n "__fish_seen_subcommand_from query" -s s -l score -d 'Print score with results'
|
||||
complete -c zoxide -n "__fish_seen_subcommand_from query" -s h -l help -d 'Prints help information'
|
||||
complete -c zoxide -n "__fish_seen_subcommand_from remove" -s i -l interactive -r
|
||||
complete -c zoxide -n "__fish_seen_subcommand_from remove" -r -f -a "(__fish_complete_directories)"
|
||||
complete -c zoxide -n "__fish_seen_subcommand_from remove" -s h -l help -d 'Prints help information'
|
||||
# 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;
|
Binary file not shown.
Before Width: | Height: | Size: 269 KiB 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
|
||||
|
|
|
@ -0,0 +1,466 @@
|
|||
#!/bin/sh
|
||||
# shellcheck shell=dash
|
||||
# shellcheck disable=SC3043 # Assume `local` extension
|
||||
|
||||
# The official zoxide installer.
|
||||
#
|
||||
# It runs on Unix shells like {a,ba,da,k,z}sh. It uses the common `local`
|
||||
# extension. Note: Most shells limit `local` to 1 var per line, contra bash.
|
||||
|
||||
main() {
|
||||
# The version of ksh93 that ships with many illumos systems does not support the "local"
|
||||
# extension. Print a message rather than fail in subtle ways later on:
|
||||
if [ "${KSH_VERSION-}" = 'Version JM 93t+ 2010-03-05' ]; then
|
||||
err 'the installer does not work with this ksh93 version; please try bash'
|
||||
fi
|
||||
|
||||
set -u
|
||||
|
||||
parse_args "$@"
|
||||
|
||||
local _arch
|
||||
_arch="${ARCH:-$(ensure get_architecture)}"
|
||||
assert_nz "${_arch}" "arch"
|
||||
echo "Detected architecture: ${_arch}"
|
||||
|
||||
local _bin_name
|
||||
case "${_arch}" in
|
||||
*windows*) _bin_name="zoxide.exe" ;;
|
||||
*) _bin_name="zoxide" ;;
|
||||
esac
|
||||
|
||||
# Create and enter a temporary directory.
|
||||
local _tmp_dir
|
||||
_tmp_dir="$(mktemp -d)" || err "mktemp: could not create temporary directory"
|
||||
cd "${_tmp_dir}" || err "cd: failed to enter directory: ${_tmp_dir}"
|
||||
|
||||
# Download and extract zoxide.
|
||||
local _package
|
||||
_package="$(ensure download_zoxide "${_arch}")"
|
||||
assert_nz "${_package}" "package"
|
||||
echo "Downloaded package: ${_package}"
|
||||
case "${_package}" in
|
||||
*.tar.gz)
|
||||
need_cmd tar
|
||||
ensure tar -xf "${_package}"
|
||||
;;
|
||||
*.zip)
|
||||
need_cmd unzip
|
||||
ensure unzip -oq "${_package}"
|
||||
;;
|
||||
*)
|
||||
err "unsupported package format: ${_package}"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Install binary.
|
||||
ensure try_sudo mkdir -p -- "${BIN_DIR}"
|
||||
ensure try_sudo cp -- "${_bin_name}" "${BIN_DIR}/${_bin_name}"
|
||||
ensure try_sudo chmod +x "${BIN_DIR}/${_bin_name}"
|
||||
echo "Installed zoxide to ${BIN_DIR}"
|
||||
|
||||
# Install manpages.
|
||||
ensure try_sudo mkdir -p -- "${MAN_DIR}/man1"
|
||||
ensure try_sudo cp -- "man/man1/"* "${MAN_DIR}/man1/"
|
||||
echo "Installed manpages to ${MAN_DIR}"
|
||||
|
||||
# Print success message and check $PATH.
|
||||
echo ""
|
||||
echo "zoxide is installed!"
|
||||
if ! echo ":${PATH}:" | grep -Fq ":${BIN_DIR}:"; then
|
||||
echo "Note: ${BIN_DIR} is not on your \$PATH. zoxide will not work unless it is added to \$PATH."
|
||||
fi
|
||||
}
|
||||
|
||||
# Parse the arguments passed and set variables accordingly.
|
||||
parse_args() {
|
||||
BIN_DIR_DEFAULT="${HOME}/.local/bin"
|
||||
MAN_DIR_DEFAULT="${HOME}/.local/share/man"
|
||||
SUDO_DEFAULT="sudo"
|
||||
|
||||
BIN_DIR="${BIN_DIR_DEFAULT}"
|
||||
MAN_DIR="${MAN_DIR_DEFAULT}"
|
||||
SUDO="${SUDO_DEFAULT}"
|
||||
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
--arch) ARCH="$2" && shift 2 ;;
|
||||
--arch=*) ARCH="${1#*=}" && shift 1 ;;
|
||||
--bin-dir) BIN_DIR="$2" && shift 2 ;;
|
||||
--bin-dir=*) BIN_DIR="${1#*=}" && shift 1 ;;
|
||||
--man-dir) MAN_DIR="$2" && shift 2 ;;
|
||||
--man-dir=*) MAN_DIR="${1#*=}" && shift 1 ;;
|
||||
--sudo) SUDO="$2" && shift 2 ;;
|
||||
--sudo=*) SUDO="${1#*=}" && shift 1 ;;
|
||||
-h | --help) usage && exit 0 ;;
|
||||
*) err "Unknown option: $1" ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
usage() {
|
||||
# heredocs are not defined in POSIX.
|
||||
local _text_heading _text_reset
|
||||
_text_heading="$(tput bold || true 2>/dev/null)$(tput smul || true 2>/dev/null)"
|
||||
_text_reset="$(tput sgr0 || true 2>/dev/null)"
|
||||
|
||||
local _arch
|
||||
_arch="$(get_architecture || true)"
|
||||
|
||||
echo "\
|
||||
${_text_heading}zoxide installer${_text_reset}
|
||||
Ajeet D'Souza <98ajeet@gmail.com>
|
||||
https://github.com/ajeetdsouza/zoxide
|
||||
|
||||
Fetches and installs zoxide. If zoxide is already installed, it will be updated to the latest version.
|
||||
|
||||
${_text_heading}Usage:${_text_reset}
|
||||
install.sh [OPTIONS]
|
||||
|
||||
${_text_heading}Options:${_text_reset}
|
||||
--arch Override the architecture identified by the installer [current: ${_arch}]
|
||||
--bin-dir Override the installation directory [default: ${BIN_DIR_DEFAULT}]
|
||||
--man-dir Override the manpage installation directory [default: ${MAN_DIR_DEFAULT}]
|
||||
--sudo Override the command used to elevate to root privileges [default: ${SUDO_DEFAULT}]
|
||||
-h, --help Print help"
|
||||
}
|
||||
|
||||
download_zoxide() {
|
||||
local _arch="$1"
|
||||
|
||||
if check_cmd curl; then
|
||||
_dld=curl
|
||||
elif check_cmd wget; then
|
||||
_dld=wget
|
||||
else
|
||||
need_cmd 'curl or wget'
|
||||
fi
|
||||
need_cmd grep
|
||||
|
||||
local _releases_url="https://api.github.com/repos/ajeetdsouza/zoxide/releases/latest"
|
||||
local _releases
|
||||
case "${_dld}" in
|
||||
curl) _releases="$(curl -sL "${_releases_url}")" ||
|
||||
err "curl: failed to download ${_releases_url}" ;;
|
||||
wget) _releases="$(wget -qO- "${_releases_url}")" ||
|
||||
err "wget: failed to download ${_releases_url}" ;;
|
||||
*) err "unsupported downloader: ${_dld}" ;;
|
||||
esac
|
||||
(echo "${_releases}" | grep -q 'API rate limit exceeded') &&
|
||||
err "you have exceeded GitHub's API rate limit. Please try again later, or use a different installation method: https://github.com/ajeetdsouza/zoxide/#installation"
|
||||
|
||||
local _package_url
|
||||
_package_url="$(echo "${_releases}" | grep "browser_download_url" | cut -d '"' -f 4 | grep -- "${_arch}")" ||
|
||||
err "zoxide has not yet been packaged for your architecture (${_arch}), please file an issue: https://github.com/ajeetdsouza/zoxide/issues"
|
||||
|
||||
local _ext
|
||||
case "${_package_url}" in
|
||||
*.tar.gz) _ext="tar.gz" ;;
|
||||
*.zip) _ext="zip" ;;
|
||||
*) err "unsupported package format: ${_package_url}" ;;
|
||||
esac
|
||||
|
||||
local _package="zoxide.${_ext}"
|
||||
case "${_dld}" in
|
||||
curl) _releases="$(curl -sLo "${_package}" "${_package_url}")" || err "curl: failed to download ${_package_url}" ;;
|
||||
wget) _releases="$(wget -qO "${_package}" "${_package_url}")" || err "wget: failed to download ${_package_url}" ;;
|
||||
*) err "unsupported downloader: ${_dld}" ;;
|
||||
esac
|
||||
|
||||
echo "${_package}"
|
||||
}
|
||||
|
||||
try_sudo() {
|
||||
if "$@" >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
need_sudo
|
||||
"${SUDO}" "$@"
|
||||
}
|
||||
|
||||
need_sudo() {
|
||||
if ! check_cmd "${SUDO}"; then
|
||||
err "\
|
||||
could not find the command \`${SUDO}\` needed to get permissions for install.
|
||||
|
||||
If you are on Windows, please run your shell as an administrator, then rerun this script.
|
||||
Otherwise, please run this script as root, or install \`sudo\`."
|
||||
fi
|
||||
|
||||
if ! "${SUDO}" -v; then
|
||||
err "sudo permissions not granted, aborting installation"
|
||||
fi
|
||||
}
|
||||
|
||||
# The below functions have been extracted with minor modifications from the
|
||||
# Rustup install script:
|
||||
#
|
||||
# https://github.com/rust-lang/rustup/blob/4c1289b2c3f3702783900934a38d7c5f912af787/rustup-init.sh
|
||||
|
||||
get_architecture() {
|
||||
local _ostype _cputype _bitness _arch _clibtype
|
||||
_ostype="$(uname -s)"
|
||||
_cputype="$(uname -m)"
|
||||
_clibtype="musl"
|
||||
|
||||
if [ "${_ostype}" = Linux ]; then
|
||||
if [ "$(uname -o || true)" = Android ]; then
|
||||
_ostype=Android
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "${_ostype}" = Darwin ] && [ "${_cputype}" = i386 ]; then
|
||||
# Darwin `uname -m` lies
|
||||
if sysctl hw.optional.x86_64 | grep -q ': 1'; then
|
||||
_cputype=x86_64
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "${_ostype}" = SunOS ]; then
|
||||
# Both Solaris and illumos presently announce as "SunOS" in "uname -s"
|
||||
# so use "uname -o" to disambiguate. We use the full path to the
|
||||
# system uname in case the user has coreutils uname first in PATH,
|
||||
# which has historically sometimes printed the wrong value here.
|
||||
if [ "$(/usr/bin/uname -o || true)" = illumos ]; then
|
||||
_ostype=illumos
|
||||
fi
|
||||
|
||||
# illumos systems have multi-arch userlands, and "uname -m" reports the
|
||||
# machine hardware name; e.g., "i86pc" on both 32- and 64-bit x86
|
||||
# systems. Check for the native (widest) instruction set on the
|
||||
# running kernel:
|
||||
if [ "${_cputype}" = i86pc ]; then
|
||||
_cputype="$(isainfo -n)"
|
||||
fi
|
||||
fi
|
||||
|
||||
case "${_ostype}" in
|
||||
Android)
|
||||
_ostype=linux-android
|
||||
;;
|
||||
Linux)
|
||||
check_proc
|
||||
_ostype=unknown-linux-${_clibtype}
|
||||
_bitness=$(get_bitness)
|
||||
;;
|
||||
FreeBSD)
|
||||
_ostype=unknown-freebsd
|
||||
;;
|
||||
NetBSD)
|
||||
_ostype=unknown-netbsd
|
||||
;;
|
||||
DragonFly)
|
||||
_ostype=unknown-dragonfly
|
||||
;;
|
||||
Darwin)
|
||||
_ostype=apple-darwin
|
||||
;;
|
||||
illumos)
|
||||
_ostype=unknown-illumos
|
||||
;;
|
||||
MINGW* | MSYS* | CYGWIN* | Windows_NT)
|
||||
_ostype=pc-windows-msvc
|
||||
;;
|
||||
*)
|
||||
err "unrecognized OS type: ${_ostype}"
|
||||
;;
|
||||
esac
|
||||
|
||||
case "${_cputype}" in
|
||||
i386 | i486 | i686 | i786 | x86)
|
||||
_cputype=i686
|
||||
;;
|
||||
xscale | arm)
|
||||
_cputype=arm
|
||||
if [ "${_ostype}" = "linux-android" ]; then
|
||||
_ostype=linux-androideabi
|
||||
fi
|
||||
;;
|
||||
armv6l)
|
||||
_cputype=arm
|
||||
if [ "${_ostype}" = "linux-android" ]; then
|
||||
_ostype=linux-androideabi
|
||||
else
|
||||
_ostype="${_ostype}eabihf"
|
||||
fi
|
||||
;;
|
||||
armv7l | armv8l)
|
||||
_cputype=armv7
|
||||
if [ "${_ostype}" = "linux-android" ]; then
|
||||
_ostype=linux-androideabi
|
||||
else
|
||||
_ostype="${_ostype}eabihf"
|
||||
fi
|
||||
;;
|
||||
aarch64 | arm64)
|
||||
_cputype=aarch64
|
||||
;;
|
||||
x86_64 | x86-64 | x64 | amd64)
|
||||
_cputype=x86_64
|
||||
;;
|
||||
mips)
|
||||
_cputype=$(get_endianness mips '' el)
|
||||
;;
|
||||
mips64)
|
||||
if [ "${_bitness}" -eq 64 ]; then
|
||||
# only n64 ABI is supported for now
|
||||
_ostype="${_ostype}abi64"
|
||||
_cputype=$(get_endianness mips64 '' el)
|
||||
fi
|
||||
;;
|
||||
ppc)
|
||||
_cputype=powerpc
|
||||
;;
|
||||
ppc64)
|
||||
_cputype=powerpc64
|
||||
;;
|
||||
ppc64le)
|
||||
_cputype=powerpc64le
|
||||
;;
|
||||
s390x)
|
||||
_cputype=s390x
|
||||
;;
|
||||
riscv64)
|
||||
_cputype=riscv64gc
|
||||
;;
|
||||
*)
|
||||
err "unknown CPU type: ${_cputype}"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Detect 64-bit linux with 32-bit userland
|
||||
if [ "${_ostype}" = unknown-linux-musl ] && [ "${_bitness}" -eq 32 ]; then
|
||||
case ${_cputype} in
|
||||
x86_64)
|
||||
# 32-bit executable for amd64 = x32
|
||||
if is_host_amd64_elf; then {
|
||||
err "x32 userland is unsupported"
|
||||
}; else
|
||||
_cputype=i686
|
||||
fi
|
||||
;;
|
||||
mips64)
|
||||
_cputype=$(get_endianness mips '' el)
|
||||
;;
|
||||
powerpc64)
|
||||
_cputype=powerpc
|
||||
;;
|
||||
aarch64)
|
||||
_cputype=armv7
|
||||
if [ "${_ostype}" = "linux-android" ]; then
|
||||
_ostype=linux-androideabi
|
||||
else
|
||||
_ostype="${_ostype}eabihf"
|
||||
fi
|
||||
;;
|
||||
riscv64gc)
|
||||
err "riscv64 with 32-bit userland unsupported"
|
||||
;;
|
||||
*) ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Detect armv7 but without the CPU features Rust needs in that build,
|
||||
# and fall back to arm.
|
||||
# See https://github.com/rust-lang/rustup.rs/issues/587.
|
||||
if [ "${_ostype}" = "unknown-linux-musleabihf" ] && [ "${_cputype}" = armv7 ]; then
|
||||
if ensure grep '^Features' /proc/cpuinfo | grep -q -v neon; then
|
||||
# At least one processor does not have NEON.
|
||||
_cputype=arm
|
||||
fi
|
||||
fi
|
||||
|
||||
_arch="${_cputype}-${_ostype}"
|
||||
echo "${_arch}"
|
||||
}
|
||||
|
||||
get_bitness() {
|
||||
need_cmd head
|
||||
# Architecture detection without dependencies beyond coreutils.
|
||||
# ELF files start out "\x7fELF", and the following byte is
|
||||
# 0x01 for 32-bit and
|
||||
# 0x02 for 64-bit.
|
||||
# The printf builtin on some shells like dash only supports octal
|
||||
# escape sequences, so we use those.
|
||||
local _current_exe_head
|
||||
_current_exe_head=$(head -c 5 /proc/self/exe)
|
||||
if [ "${_current_exe_head}" = "$(printf '\177ELF\001')" ]; then
|
||||
echo 32
|
||||
elif [ "${_current_exe_head}" = "$(printf '\177ELF\002')" ]; then
|
||||
echo 64
|
||||
else
|
||||
err "unknown platform bitness"
|
||||
fi
|
||||
}
|
||||
|
||||
get_endianness() {
|
||||
local cputype="$1"
|
||||
local suffix_eb="$2"
|
||||
local suffix_el="$3"
|
||||
|
||||
# detect endianness without od/hexdump, like get_bitness() does.
|
||||
need_cmd head
|
||||
need_cmd tail
|
||||
|
||||
local _current_exe_endianness
|
||||
_current_exe_endianness="$(head -c 6 /proc/self/exe | tail -c 1)"
|
||||
if [ "${_current_exe_endianness}" = "$(printf '\001')" ]; then
|
||||
echo "${cputype}${suffix_el}"
|
||||
elif [ "${_current_exe_endianness}" = "$(printf '\002')" ]; then
|
||||
echo "${cputype}${suffix_eb}"
|
||||
else
|
||||
err "unknown platform endianness"
|
||||
fi
|
||||
}
|
||||
|
||||
is_host_amd64_elf() {
|
||||
need_cmd head
|
||||
need_cmd tail
|
||||
# ELF e_machine detection without dependencies beyond coreutils.
|
||||
# Two-byte field at offset 0x12 indicates the CPU,
|
||||
# but we're interested in it being 0x3E to indicate amd64, or not that.
|
||||
local _current_exe_machine
|
||||
_current_exe_machine=$(head -c 19 /proc/self/exe | tail -c 1)
|
||||
[ "${_current_exe_machine}" = "$(printf '\076')" ]
|
||||
}
|
||||
|
||||
check_proc() {
|
||||
# Check for /proc by looking for the /proc/self/exe link.
|
||||
# This is only run on Linux.
|
||||
if ! test -L /proc/self/exe; then
|
||||
err "unable to find /proc/self/exe. Is /proc mounted? Installation cannot proceed without /proc."
|
||||
fi
|
||||
}
|
||||
|
||||
need_cmd() {
|
||||
if ! check_cmd "$1"; then
|
||||
err "need '$1' (command not found)"
|
||||
fi
|
||||
}
|
||||
|
||||
check_cmd() {
|
||||
command -v -- "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# Run a command that should never fail. If the command fails execution
|
||||
# will immediately terminate with an error showing the failing
|
||||
# command.
|
||||
ensure() {
|
||||
if ! "$@"; then err "command failed: $*"; fi
|
||||
}
|
||||
|
||||
assert_nz() {
|
||||
if [ -z "$1" ]; then err "found empty string: $2"; fi
|
||||
}
|
||||
|
||||
err() {
|
||||
echo "Error: $1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# This is put in braces to ensure that the script does not run until it is
|
||||
# downloaded completely.
|
||||
{
|
||||
main "$@" || exit 1
|
||||
}
|
|
@ -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
|
|
@ -1,20 +1,20 @@
|
|||
.TH "zoxide-import" "1" "2021-04-12" "zoxide" "zoxide"
|
||||
.TH "ZOXIDE" "1" "2021-04-12" "" "zoxide"
|
||||
.SH NAME
|
||||
zoxide-import - import data from other tools
|
||||
\fBzoxide-import\fR - import data from other tools
|
||||
.SH SYNOPSIS
|
||||
.B zoxide import \fIPATH --from FORMAT [OPTIONS]\fR
|
||||
.B zoxide import PATH --from FORMAT [OPTIONS]
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.B --from \fIFORMAT\fR
|
||||
.B --from FORMAT
|
||||
The format of the database being imported:
|
||||
.TS
|
||||
tab(|);
|
||||
l l.
|
||||
\fIautojump\fR
|
||||
\fIz\fR|For \fIz\fR, \fIz.lua\fR, or \fIzsh-z\fR.
|
||||
\fBautojump\fR
|
||||
\fBz\fR|(for \fBfasd\fR, \fBz\fR, \fBz.lua\fR, or \fBzsh-z\fR)
|
||||
.TE
|
||||
.sp
|
||||
\fBNOTE\fR: zoxide only imports paths from autojump, since its matching
|
||||
Note: zoxide only imports paths from autojump, since its matching
|
||||
algorithm is too different to import the scores.
|
||||
.TP
|
||||
.B -h, --help
|
||||
|
@ -26,6 +26,6 @@ option merges imported data into the existing database.
|
|||
.SH REPORTING BUGS
|
||||
For any issues, feature requests, or questions, please visit:
|
||||
.sp
|
||||
\fIhttps://github.com/ajeetdsouza/zoxide/issues\fR
|
||||
\fBhttps://github.com/ajeetdsouza/zoxide/issues\fR
|
||||
.SH AUTHOR
|
||||
Ajeet D'Souza <\fI98ajeet@gmail.com\fR>
|
||||
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
|
|
@ -1,8 +1,8 @@
|
|||
.TH "zoxide-query" "1" "2021-04-12" "zoxide" "zoxide"
|
||||
.TH "ZOXIDE" "1" "2021-04-12" "" "zoxide"
|
||||
.SH NAME
|
||||
zoxide-query - search for a directory in the database
|
||||
\fBzoxide-query\fR - search for a directory in the database
|
||||
.SH SYNOPSIS
|
||||
.B zoxide query \fI[KEYWORDS] [OPTIONS]\fR
|
||||
.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).
|
||||
|
@ -10,14 +10,15 @@ algorithm is described in \fBzoxide\fR(1).
|
|||
.TP
|
||||
.B --all
|
||||
Show deleted directories.
|
||||
.B --exclude \fIPATH\fR
|
||||
.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 fzf.
|
||||
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.
|
||||
|
@ -27,6 +28,6 @@ Print the calculated score as well as the matched path.
|
|||
.SH REPORTING BUGS
|
||||
For any issues, feature requests, or questions, please visit:
|
||||
.sp
|
||||
\fIhttps://github.com/ajeetdsouza/zoxide/issues\fR
|
||||
\fBhttps://github.com/ajeetdsouza/zoxide/issues\fR
|
||||
.SH AUTHOR
|
||||
Ajeet D'Souza <\fI98ajeet@gmail.com\fR>
|
||||
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
|
|
@ -1,24 +0,0 @@
|
|||
.TH "zoxide-add" "1" "2021-04-12" "zoxide" "zoxide"
|
||||
.SH NAME
|
||||
zoxide-add - add a new directory or increment its rank
|
||||
.SH SYNOPSIS
|
||||
.B zoxide add \fIPATH\fR
|
||||
.SH DESCRIPTION
|
||||
If the directory is not already in the database, this command creates a new
|
||||
entry for it with a default score of \fI1\fR, otherwise, it increments the
|
||||
existing score by \fI1\fR. 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
|
||||
\fIhttps://github.com/ajeetdsouza/zoxide/issues\fR
|
||||
.SH AUTHOR
|
||||
Ajeet D'Souza <\fI98ajeet@gmail.com\fR>
|
|
@ -1,106 +0,0 @@
|
|||
.TH "zoxide-init" "1" "2021-04-12" "zoxide" "zoxide"
|
||||
.SH NAME
|
||||
zoxide-init - generate shell configuration for zoxide
|
||||
.SH SYNOPSIS
|
||||
.B zoxide init \fISHELL [OPTIONS]\fR
|
||||
.SH DESCRIPTION
|
||||
To initialize zoxide on your shell:
|
||||
.TP
|
||||
.B bash
|
||||
Add this to your configuration (usually \fI~/.bashrc\fR):
|
||||
.sp
|
||||
.nf
|
||||
\fBeval "$(zoxide init bash)"\fR
|
||||
.fi
|
||||
.TP
|
||||
.B elvish
|
||||
Add this to your configuration (usually \fI~/.elvish/rc.elv\fR):
|
||||
.sp
|
||||
.nf
|
||||
\fBeval $(zoxide init elvish | slurp)\fR
|
||||
.fi
|
||||
.TP
|
||||
.B fish
|
||||
Add this to your configuration (usually \fI~/.config/fish/config.fish\fR):
|
||||
.sp
|
||||
.nf
|
||||
\fBzoxide init fish | source\fR
|
||||
.fi
|
||||
.TP
|
||||
.B nushell
|
||||
Create a Nushell script:
|
||||
.sp
|
||||
.nf
|
||||
\fBzoxide init nushell --hook prompt | save ~/.zoxide.nu\fR
|
||||
.fi
|
||||
.sp
|
||||
Add this to your configuration (usually \fI~/.config/nu/config.toml\fR):
|
||||
.sp
|
||||
.nf
|
||||
\fBprompt = "__zoxide_hook;__zoxide_prompt"\fR
|
||||
\fBstartup = ["zoxide init nushell --hook prompt | save ~/.zoxide.nu", "source ~/.zoxide.nu"]\fR
|
||||
.fi
|
||||
.sp
|
||||
You can replace \fB__zoxide_prompt\fR with a custom prompt.
|
||||
.TP
|
||||
.B powershell
|
||||
Add this to your configuration (the location is stored in \fI$profile\fR):
|
||||
.sp
|
||||
.nf
|
||||
\fBInvoke-Expression (& {
|
||||
$hook = if ($PSVersionTable.PSVersion.Major -lt 6) { 'prompt' } else { 'pwd' }
|
||||
(zoxide init --hook $hook powershell) -join "`n"
|
||||
})\fR
|
||||
.fi
|
||||
.TP
|
||||
.B xonsh
|
||||
Add this to your configuration (usually \fI~/.xonshrc\fR):
|
||||
.sp
|
||||
.nf
|
||||
\fBexecx($(zoxide init xonsh), 'exec', __xonsh__.ctx, filename='zoxide')\fR
|
||||
.fi
|
||||
.TP
|
||||
.B zsh
|
||||
Add this to your configuration (usually \fI~/.zshrc\fR):
|
||||
.sp
|
||||
.nf
|
||||
\fBeval "$(zoxide init zsh)"\fR
|
||||
.fi
|
||||
.TP
|
||||
.B Any POSIX shell
|
||||
.sp
|
||||
Add this to your configuration:
|
||||
.sp
|
||||
.nf
|
||||
\fBeval "$(zoxide init posix --hook prompt)"\fR
|
||||
.fi
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.B --cmd
|
||||
Changes the prefix of predefined aliases (\fBz\fR, \fBzi\fR).
|
||||
.br
|
||||
e.g. --cmd j would change the aliases to j and ji respectively.
|
||||
.TP
|
||||
.B -h, --help
|
||||
Print help information.
|
||||
.TP
|
||||
.B --hook \fIHOOK\fR
|
||||
Changes how often zoxide increments a directory's score:
|
||||
.TS
|
||||
tab(|);
|
||||
l l.
|
||||
\fInone\fR|Never
|
||||
\fIprompt\fR|At every shell prompt
|
||||
\fIpwd\fR|Whenever the directory is changed
|
||||
.TE
|
||||
.TP
|
||||
.B --no-aliases
|
||||
Don't define extra aliases (\fBz\fR, \fBzi\fR). These functions will still be
|
||||
available in your shell as \fB__zoxide_z\fR and \fB__zoxide_zi\fR, should you
|
||||
choose to redefine them.
|
||||
.SH REPORTING BUGS
|
||||
For any issues, feature requests, or questions, please visit:
|
||||
.sp
|
||||
\fIhttps://github.com/ajeetdsouza/zoxide/issues\fR
|
||||
.SH AUTHOR
|
||||
Ajeet D'Souza <\fI98ajeet@gmail.com\fR>
|
|
@ -1,21 +0,0 @@
|
|||
.TH "zoxide-remove" "1" "2021-04-12" "zoxide" "zoxide"
|
||||
.SH NAME
|
||||
zoxide-remove - remove a directory from the database
|
||||
.SH SYNOPSIS
|
||||
.B zoxide remove \fIPATH [OPTIONS]\fR
|
||||
.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.
|
||||
.TP
|
||||
.B -i, --interactive \fI[KEYWORDS]\fR
|
||||
Use interactive selection. This option requires fzf.
|
||||
.SH REPORTING BUGS
|
||||
For any issues, feature requests, or questions, please visit:
|
||||
.sp
|
||||
\fIhttps://github.com/ajeetdsouza/zoxide/issues\fR
|
||||
.SH AUTHOR
|
||||
Ajeet D'Souza <\fI98ajeet@gmail.com\fR>
|
152
man/zoxide.1
152
man/zoxide.1
|
@ -1,152 +0,0 @@
|
|||
.TH "zoxide" "1" "2021-04-12" "zoxide" "zoxide"
|
||||
.SH NAME
|
||||
zoxide - a smarter cd command
|
||||
.SH SYNOPSIS
|
||||
.B zoxide \fISUBCOMMAND [OPTIONS]\fR
|
||||
.SH DESCRIPTION
|
||||
zoxide is a smarter replacement for your cd command. It keeps track of the
|
||||
directories you use most frequently, and uses a ranking algorithm to navigate
|
||||
to the best match.
|
||||
.SH USAGE
|
||||
.nf
|
||||
\fBz\fR \fIfoo\fR # cd into highest ranked directory matching foo
|
||||
\fBz\fR \fIfoo bar\fR # cd into highest ranked directory matching foo and bar
|
||||
.sp
|
||||
\fBz\fR \fI~/foo\fR # z also works like a regular cd command
|
||||
\fBz\fR \fIfoo/\fR # cd into relative path
|
||||
\fBz\fR \fI..\fR # cd one level up
|
||||
\fBz\fR \fI-\fR # cd into previous directory
|
||||
.sp
|
||||
\fBzi\fR \fIfoo\fR # cd with interactive selection (using fzf)
|
||||
.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 must be set before calling \fBzoxide init\fR.
|
||||
.TP
|
||||
.B _ZO_DATA_DIR
|
||||
Specifies the directory in which zoxide should store its database. The default
|
||||
value varies across OSes:
|
||||
.TS
|
||||
tab(|);
|
||||
l l.
|
||||
\fBOS|Path\fR
|
||||
Linux/BSD|T{
|
||||
\fI$XDG_DATA_HOME\fR or \fI$HOME/.local/share\fR
|
||||
.br
|
||||
eg. /home/alice/.local/share
|
||||
T}
|
||||
macOS|T{
|
||||
\fI$HOME/Library/Application Support\fR
|
||||
.br
|
||||
eg. /Users/Alice/Library/Application Support
|
||||
T}
|
||||
Windows|T{
|
||||
\fI{FOLDERID_RoamingAppData}\fR
|
||||
.br
|
||||
eg. C:\\Users\\Alice\\AppData\\Roaming
|
||||
T}
|
||||
.TE
|
||||
.TP
|
||||
.B _ZO_ECHO
|
||||
When set to \fI1\fR, z 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
|
||||
Linux/macOS/BSD|T{
|
||||
\fI:\fR eg. $HOME:$HOME/private/*
|
||||
T}
|
||||
Windows|\fI;\fR eg. $HOME;$HOME/private/*
|
||||
.TE
|
||||
.sp
|
||||
By default, this is set to \fI"$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 fzf. See \fBman fzf\fR for the 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 \fI10000\fR.
|
||||
.TP
|
||||
.B _ZO_RESOLVE_SYMLINKS
|
||||
When set to \fI1\fR, z 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 \fIk\fR such that the new total becomes ~90% of
|
||||
\fB_ZO_MAXAGE\fR. Thereafter, if the new score of any directory falls below
|
||||
\fI1\fR, it is removed from the database.
|
||||
.sp
|
||||
Theoretically, the maximum number of directories in the database is
|
||||
\fI4 * _ZO_MAXAGE\fR, although it is lower in practice.
|
||||
.TP
|
||||
.B FRECENCY
|
||||
Each directory in zoxide is given a score, starting with \fI1\fR the first time
|
||||
it is accessed. Every subsequent access increases the score by \fI1\fR. 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|\fIscore * 4\fR
|
||||
Within the last day|\fIscore * 2\fR
|
||||
Within the last week|\fIscore / 2\fR
|
||||
Otherwise|\fIscore / 4\fR
|
||||
.TE
|
||||
.TP
|
||||
.B MATCHING
|
||||
zoxide uses a simple, predictable algorithm for resolving queries:
|
||||
.sp
|
||||
* All matching is case-insensitive.
|
||||
\fBzoxide query\fR \fIfoo\fR matches \fI/foo\fR as well as \fI/FOO\fR.
|
||||
.sp
|
||||
* All terms must be present (including slashes) within the path, in order.
|
||||
\fBzoxide query\fR \fIfo ba\fR matches \fI/foo/bar\fR, but not \fI/bar/foo\fR.
|
||||
\fBzoxide query\fR \fIfo / ba\fR matches \fI/foo/bar\fR, but not \fI/foobar\fR.
|
||||
.sp
|
||||
* The last component of the last keyword must match the last component of the path.
|
||||
\fBzoxide query\fR \fIbar\fR matches \fI/foo/bar\fR, but not \fI/bar/foo\fR.
|
||||
\fBzoxide query\fR \fIfoo/bar\fR (last component: \fIbar\fR) matches \fI/foo/bar\fR, but not \fI/foo/bar/baz\fR.
|
||||
.sp
|
||||
* The path must exist. Deleted directories are filtered out.
|
||||
.sp
|
||||
* Matches are returned in descending order of \fBFRECENCY\fR.
|
||||
.SH REPORTING BUGS
|
||||
For any issues, feature requests, or questions, please visit:
|
||||
.sp
|
||||
\fIhttps://github.com/ajeetdsouza/zoxide/issues\fR
|
||||
.SH AUTHOR
|
||||
Ajeet D'Souza <\fI98ajeet@gmail.com\fR>
|
|
@ -1,7 +1,8 @@
|
|||
# group_imports = "StdExternalCrate"
|
||||
# imports_granularity = "Module"
|
||||
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
|
||||
wrap_comments = true
|
||||
style_edition = "2024"
|
||||
|
|
66
shell.nix
66
shell.nix
|
@ -1,28 +1,58 @@
|
|||
let
|
||||
pkgs = import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/20.09.tar.gz") {};
|
||||
pkgs-master = import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/ebe28430ba2d6d0e5562bf69c4afe907645fac61.tar.gz") {};
|
||||
pkgs-python = pkgs-master.python3.withPackages (pkgs: [ pkgs.black pkgs.mypy pkgs.pylint ]);
|
||||
in
|
||||
pkgs.mkShell {
|
||||
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 = [
|
||||
pkgs-master.cargo-audit
|
||||
pkgs-master.elvish
|
||||
pkgs-master.nushell
|
||||
pkgs-master.shellcheck
|
||||
pkgs-master.shfmt
|
||||
pkgs-python
|
||||
# Rust
|
||||
(pkgs.rust-bin.selectLatestNightlyWith (toolchain: toolchain.rustfmt))
|
||||
pkgs.rust-bin.stable.latest.default
|
||||
|
||||
# Shells
|
||||
pkgs.bash
|
||||
pkgs.cargo
|
||||
pkgs.clippy
|
||||
pkgs.dash
|
||||
pkgs.elvish
|
||||
pkgs.fish
|
||||
pkgs.fzf
|
||||
pkgs.git
|
||||
pkgs.ksh
|
||||
pkgs.nushell
|
||||
pkgs.powershell
|
||||
pkgs.rustc
|
||||
pkgs.rustfmt
|
||||
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
|
||||
];
|
||||
RUST_BACKTRACE = 1;
|
||||
|
||||
CARGO_TARGET_DIR = "target_nix";
|
||||
}
|
||||
|
|
137
src/app/_app.rs
137
src/app/_app.rs
|
@ -1,137 +0,0 @@
|
|||
use clap::{AppSettings, ArgEnum, Clap, ValueHint};
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
const ENV_HELP: &str = "ENVIRONMENT VARIABLES:
|
||||
_ZO_DATA_DIR Path for zoxide data files
|
||||
_ZO_ECHO Prints the matched directory before navigating to it when set to 1
|
||||
_ZO_EXCLUDE_DIRS List of directory globs to be excluded
|
||||
_ZO_FZF_OPTS Custom flags to pass to fzf
|
||||
_ZO_MAXAGE Maximum total age after which entries start getting deleted
|
||||
_ZO_RESOLVE_SYMLINKS Resolve symlinks when storing paths";
|
||||
|
||||
#[derive(Debug, Clap)]
|
||||
#[clap(
|
||||
bin_name = env!("CARGO_PKG_NAME"),
|
||||
about,
|
||||
author,
|
||||
after_help = ENV_HELP,
|
||||
global_setting(AppSettings::ColoredHelp),
|
||||
global_setting(AppSettings::DisableHelpSubcommand),
|
||||
global_setting(AppSettings::GlobalVersion),
|
||||
global_setting(AppSettings::VersionlessSubcommands),
|
||||
version = option_env!("ZOXIDE_VERSION").unwrap_or_default()
|
||||
)]
|
||||
pub enum App {
|
||||
Add(Add),
|
||||
Import(Import),
|
||||
Init(Init),
|
||||
Query(Query),
|
||||
Remove(Remove),
|
||||
}
|
||||
|
||||
/// Add a new directory or increment its rank
|
||||
#[derive(Clap, Debug)]
|
||||
pub struct Add {
|
||||
#[clap(value_hint = ValueHint::DirPath)]
|
||||
pub path: PathBuf,
|
||||
}
|
||||
|
||||
/// Import entries from another application
|
||||
#[derive(Clap, Debug)]
|
||||
pub struct Import {
|
||||
#[clap(value_hint = ValueHint::FilePath)]
|
||||
pub path: PathBuf,
|
||||
|
||||
/// Application to import from
|
||||
#[clap(arg_enum, long)]
|
||||
pub from: ImportFrom,
|
||||
|
||||
/// Merge into existing database
|
||||
#[clap(long)]
|
||||
pub merge: bool,
|
||||
}
|
||||
|
||||
#[derive(ArgEnum, Debug)]
|
||||
pub enum ImportFrom {
|
||||
Autojump,
|
||||
Z,
|
||||
}
|
||||
|
||||
/// Generate shell configuration
|
||||
#[derive(Clap, Debug)]
|
||||
pub struct Init {
|
||||
#[clap(arg_enum)]
|
||||
pub shell: InitShell,
|
||||
|
||||
/// Prevents zoxide from defining any commands
|
||||
#[clap(long)]
|
||||
pub no_aliases: bool,
|
||||
|
||||
/// Renames the 'z' command and corresponding aliases
|
||||
#[clap(long, default_value = "z")]
|
||||
pub cmd: String,
|
||||
|
||||
/// Chooses event upon which an entry is added to the database
|
||||
#[clap(arg_enum, long, default_value = "pwd")]
|
||||
pub hook: InitHook,
|
||||
}
|
||||
|
||||
#[derive(ArgEnum, Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum InitHook {
|
||||
None,
|
||||
Prompt,
|
||||
Pwd,
|
||||
}
|
||||
|
||||
#[derive(ArgEnum, Debug)]
|
||||
pub enum InitShell {
|
||||
Bash,
|
||||
Elvish,
|
||||
Fish,
|
||||
Nushell,
|
||||
Posix,
|
||||
Powershell,
|
||||
Xonsh,
|
||||
Zsh,
|
||||
}
|
||||
|
||||
/// Search for a directory in the database
|
||||
#[derive(Clap, Debug)]
|
||||
pub struct Query {
|
||||
pub keywords: Vec<String>,
|
||||
|
||||
/// Show deleted directories
|
||||
#[clap(long)]
|
||||
pub all: bool,
|
||||
|
||||
/// Use interactive selection
|
||||
#[clap(long, short, conflicts_with = "list")]
|
||||
pub interactive: bool,
|
||||
|
||||
/// List all matching directories
|
||||
#[clap(long, short, conflicts_with = "interactive")]
|
||||
pub list: bool,
|
||||
|
||||
/// Print score with results
|
||||
#[clap(long, short, conflicts_with = "interactive")]
|
||||
pub score: bool,
|
||||
|
||||
/// Exclude a path from results
|
||||
#[clap(long, value_hint = ValueHint::DirPath, value_name = "path")]
|
||||
pub exclude: Option<String>,
|
||||
}
|
||||
|
||||
/// Remove a directory from the database
|
||||
#[derive(Clap, Debug)]
|
||||
pub struct Remove {
|
||||
// Use interactive selection
|
||||
#[clap(conflicts_with = "path", long, short, value_name = "keywords")]
|
||||
pub interactive: Option<Vec<String>>,
|
||||
#[clap(
|
||||
conflicts_with = "interactive",
|
||||
required_unless_present = "interactive",
|
||||
value_hint = ValueHint::DirPath
|
||||
)]
|
||||
pub path: Option<String>,
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
use crate::app::{Add, Run};
|
||||
use crate::config;
|
||||
use crate::db::DatabaseFile;
|
||||
use crate::util;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
impl Run for Add {
|
||||
fn run(&self) -> Result<()> {
|
||||
let path = if config::resolve_symlinks() {
|
||||
util::canonicalize(&self.path)
|
||||
} else {
|
||||
util::resolve_path(&self.path)
|
||||
}?;
|
||||
let path = util::path_to_str(&path)?;
|
||||
let now = util::current_time()?;
|
||||
|
||||
// These characters can't be printed cleanly to a single line, so they
|
||||
// can cause confusion when writing to fzf / stdout.
|
||||
const EXCLUDE_CHARS: &[char] = &['\n', '\r'];
|
||||
let mut exclude_dirs = config::exclude_dirs()?.into_iter();
|
||||
if exclude_dirs.any(|pattern| pattern.matches(path)) || path.contains(EXCLUDE_CHARS) {
|
||||
return Ok(());
|
||||
}
|
||||
if !Path::new(path).is_dir() {
|
||||
bail!("not a directory: {}", path);
|
||||
}
|
||||
|
||||
let data_dir = config::data_dir()?;
|
||||
let max_age = config::maxage()?;
|
||||
|
||||
let mut db = DatabaseFile::new(data_dir);
|
||||
let mut db = db.open()?;
|
||||
|
||||
db.add(path, now);
|
||||
db.age(max_age);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,175 +0,0 @@
|
|||
use crate::app::{Import, ImportFrom, Run};
|
||||
use crate::config;
|
||||
use crate::db::{Database, DatabaseFile, Dir};
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
|
||||
use std::fs;
|
||||
|
||||
impl Run for Import {
|
||||
fn run(&self) -> Result<()> {
|
||||
let buffer = &fs::read_to_string(&self.path).with_context(|| {
|
||||
format!("could not open database for importing: {}", &self.path.display())
|
||||
})?;
|
||||
|
||||
let data_dir = config::data_dir()?;
|
||||
let mut db = DatabaseFile::new(data_dir);
|
||||
let db = &mut db.open()?;
|
||||
if !self.merge && !db.dirs.is_empty() {
|
||||
bail!("current database is not empty, specify --merge to continue anyway");
|
||||
}
|
||||
|
||||
match self.from {
|
||||
ImportFrom::Autojump => from_autojump(db, buffer),
|
||||
ImportFrom::Z => from_z(db, buffer),
|
||||
}
|
||||
.context("import error")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn from_autojump<'a>(db: &mut Database<'a>, buffer: &'a str) -> Result<()> {
|
||||
for line in buffer.lines() {
|
||||
if line.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let mut split = line.splitn(2, '\t');
|
||||
|
||||
let rank = split.next().with_context(|| format!("invalid entry: {}", line))?;
|
||||
let mut rank = rank.parse::<f64>().with_context(|| format!("invalid rank: {}", rank))?;
|
||||
// Normalize the rank using a sigmoid function. Don't import actual
|
||||
// ranks from autojump, since its scoring algorithm is very different,
|
||||
// and might take a while to get normalized.
|
||||
rank = sigmoid(rank);
|
||||
|
||||
let path = split.next().with_context(|| format!("invalid entry: {}", line))?;
|
||||
|
||||
db.dirs.push(Dir { path: path.into(), rank, last_accessed: 0 });
|
||||
db.modified = true;
|
||||
}
|
||||
|
||||
if db.modified {
|
||||
db.dedup();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn from_z<'a>(db: &mut Database<'a>, buffer: &'a str) -> Result<()> {
|
||||
for line in buffer.lines() {
|
||||
if line.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let mut split = line.rsplitn(3, '|');
|
||||
|
||||
let last_accessed = split.next().with_context(|| format!("invalid entry: {}", line))?;
|
||||
let last_accessed =
|
||||
last_accessed.parse().with_context(|| format!("invalid epoch: {}", last_accessed))?;
|
||||
|
||||
let rank = split.next().with_context(|| format!("invalid entry: {}", line))?;
|
||||
let rank = rank.parse().with_context(|| format!("invalid rank: {}", rank))?;
|
||||
|
||||
let path = split.next().with_context(|| format!("invalid entry: {}", line))?;
|
||||
|
||||
db.dirs.push(Dir { path: path.into(), rank, last_accessed });
|
||||
db.modified = true;
|
||||
}
|
||||
|
||||
if db.modified {
|
||||
db.dedup();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn sigmoid(x: f64) -> f64 {
|
||||
1.0 / (1.0 + (-x).exp())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::sigmoid;
|
||||
use crate::db::{Database, Dir};
|
||||
|
||||
#[test]
|
||||
fn from_autojump() {
|
||||
let buffer = r#"
|
||||
7.0 /baz
|
||||
2.0 /foo/bar
|
||||
5.0 /quux/quuz
|
||||
"#;
|
||||
|
||||
let dirs = vec![
|
||||
Dir { path: "/quux/quuz".into(), rank: 1.0, last_accessed: 100 },
|
||||
Dir { path: "/corge/grault/garply".into(), rank: 6.0, last_accessed: 600 },
|
||||
Dir { path: "/waldo/fred/plugh".into(), rank: 3.0, last_accessed: 300 },
|
||||
Dir { path: "/xyzzy/thud".into(), rank: 8.0, last_accessed: 800 },
|
||||
Dir { path: "/foo/bar".into(), rank: 9.0, last_accessed: 900 },
|
||||
];
|
||||
let data_dir = tempfile::tempdir().unwrap();
|
||||
let data_dir = &data_dir.path().to_path_buf();
|
||||
let mut db = Database { dirs: dirs.into(), modified: false, data_dir };
|
||||
|
||||
super::from_autojump(&mut db, buffer).unwrap();
|
||||
db.dirs.sort_by(|dir1, dir2| dir1.path.cmp(&dir2.path));
|
||||
println!("got: {:?}", &db.dirs.as_slice());
|
||||
|
||||
let exp = &[
|
||||
Dir { path: "/baz".into(), rank: sigmoid(7.0), last_accessed: 0 },
|
||||
Dir { path: "/corge/grault/garply".into(), rank: 6.0, last_accessed: 600 },
|
||||
Dir { path: "/foo/bar".into(), rank: 9.0 + sigmoid(2.0), last_accessed: 900 },
|
||||
Dir { path: "/quux/quuz".into(), rank: 1.0 + sigmoid(5.0), last_accessed: 100 },
|
||||
Dir { path: "/waldo/fred/plugh".into(), rank: 3.0, last_accessed: 300 },
|
||||
Dir { path: "/xyzzy/thud".into(), rank: 8.0, last_accessed: 800 },
|
||||
];
|
||||
println!("exp: {:?}", &exp);
|
||||
|
||||
for (dir1, dir2) in db.dirs.iter().zip(exp) {
|
||||
assert_eq!(dir1.path, dir2.path);
|
||||
assert!((dir1.rank - dir2.rank).abs() < 0.01);
|
||||
assert_eq!(dir1.last_accessed, dir2.last_accessed);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_z() {
|
||||
let buffer = r#"
|
||||
/baz|7|700
|
||||
/quux/quuz|4|400
|
||||
/foo/bar|2|200
|
||||
/quux/quuz|5|500
|
||||
"#;
|
||||
|
||||
let dirs = vec![
|
||||
Dir { path: "/quux/quuz".into(), rank: 1.0, last_accessed: 100 },
|
||||
Dir { path: "/corge/grault/garply".into(), rank: 6.0, last_accessed: 600 },
|
||||
Dir { path: "/waldo/fred/plugh".into(), rank: 3.0, last_accessed: 300 },
|
||||
Dir { path: "/xyzzy/thud".into(), rank: 8.0, last_accessed: 800 },
|
||||
Dir { path: "/foo/bar".into(), rank: 9.0, last_accessed: 900 },
|
||||
];
|
||||
let data_dir = tempfile::tempdir().unwrap();
|
||||
let data_dir = &data_dir.path().to_path_buf();
|
||||
let mut db = Database { dirs: dirs.into(), modified: false, data_dir };
|
||||
|
||||
super::from_z(&mut db, buffer).unwrap();
|
||||
db.dirs.sort_by(|dir1, dir2| dir1.path.cmp(&dir2.path));
|
||||
println!("got: {:?}", &db.dirs.as_slice());
|
||||
|
||||
let exp = &[
|
||||
Dir { path: "/baz".into(), rank: 7.0, last_accessed: 700 },
|
||||
Dir { path: "/corge/grault/garply".into(), rank: 6.0, last_accessed: 600 },
|
||||
Dir { path: "/foo/bar".into(), rank: 11.0, last_accessed: 900 },
|
||||
Dir { path: "/quux/quuz".into(), rank: 10.0, last_accessed: 500 },
|
||||
Dir { path: "/waldo/fred/plugh".into(), rank: 3.0, last_accessed: 300 },
|
||||
Dir { path: "/xyzzy/thud".into(), rank: 8.0, last_accessed: 800 },
|
||||
];
|
||||
println!("exp: {:?}", &exp);
|
||||
|
||||
for (dir1, dir2) in db.dirs.iter().zip(exp) {
|
||||
assert_eq!(dir1.path, dir2.path);
|
||||
assert!((dir1.rank - dir2.rank).abs() < 0.01);
|
||||
assert_eq!(dir1.last_accessed, dir2.last_accessed);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
use crate::app::{Init, InitShell, Run};
|
||||
use crate::config;
|
||||
use crate::error::BrokenPipeHandler;
|
||||
use crate::shell::{self, Opts};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use askama::Template;
|
||||
|
||||
use std::io::{self, Write};
|
||||
|
||||
impl Run for Init {
|
||||
fn run(&self) -> Result<()> {
|
||||
let cmd = if self.no_aliases { None } else { Some(self.cmd.as_str()) };
|
||||
|
||||
let echo = config::echo();
|
||||
let resolve_symlinks = config::resolve_symlinks();
|
||||
|
||||
let opts = &Opts { cmd, hook: self.hook, echo, resolve_symlinks };
|
||||
|
||||
let source = match self.shell {
|
||||
InitShell::Bash => shell::Bash(opts).render(),
|
||||
InitShell::Elvish => shell::Elvish(opts).render(),
|
||||
InitShell::Fish => shell::Fish(opts).render(),
|
||||
InitShell::Nushell => shell::Nushell(opts).render(),
|
||||
InitShell::Posix => shell::Posix(opts).render(),
|
||||
InitShell::Powershell => shell::Powershell(opts).render(),
|
||||
InitShell::Xonsh => shell::Xonsh(opts).render(),
|
||||
InitShell::Zsh => shell::Zsh(opts).render(),
|
||||
}
|
||||
.context("could not render template")?;
|
||||
writeln!(io::stdout(), "{}", source).pipe_exit("stdout")
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
mod _app;
|
||||
mod add;
|
||||
mod import;
|
||||
mod init;
|
||||
mod query;
|
||||
mod remove;
|
||||
|
||||
pub use crate::app::_app::*;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
pub trait Run {
|
||||
fn run(&self) -> Result<()>;
|
||||
}
|
||||
|
||||
impl Run for App {
|
||||
fn run(&self) -> Result<()> {
|
||||
match self {
|
||||
App::Add(cmd) => cmd.run(),
|
||||
App::Import(cmd) => cmd.run(),
|
||||
App::Init(cmd) => cmd.run(),
|
||||
App::Query(cmd) => cmd.run(),
|
||||
App::Remove(cmd) => cmd.run(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
use crate::app::{Query, Run};
|
||||
use crate::config;
|
||||
use crate::db::DatabaseFile;
|
||||
use crate::error::BrokenPipeHandler;
|
||||
use crate::fzf::Fzf;
|
||||
use crate::util;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
use std::io::{self, Write};
|
||||
|
||||
impl Run for Query {
|
||||
fn run(&self) -> Result<()> {
|
||||
let data_dir = config::data_dir()?;
|
||||
let mut db = DatabaseFile::new(data_dir);
|
||||
let mut db = db.open()?;
|
||||
let now = util::current_time()?;
|
||||
|
||||
let mut stream = db.stream(now).with_keywords(&self.keywords);
|
||||
if !self.all {
|
||||
let resolve_symlinks = config::resolve_symlinks();
|
||||
stream = stream.with_exists(resolve_symlinks);
|
||||
}
|
||||
if let Some(path) = &self.exclude {
|
||||
stream = stream.with_exclude(path);
|
||||
}
|
||||
|
||||
if self.interactive {
|
||||
let mut fzf = Fzf::new(false)?;
|
||||
while let Some(dir) = stream.next() {
|
||||
writeln!(fzf.stdin(), "{}", dir.display_score(now)).pipe_exit("fzf")?;
|
||||
}
|
||||
|
||||
let selection = fzf.wait_select()?;
|
||||
if self.score {
|
||||
print!("{}", selection);
|
||||
} else {
|
||||
let path = selection.get(5..).context("could not read selection from fzf")?;
|
||||
print!("{}", path)
|
||||
}
|
||||
} else if self.list {
|
||||
let stdout = io::stdout();
|
||||
let handle = &mut stdout.lock();
|
||||
while let Some(dir) = stream.next() {
|
||||
if self.score {
|
||||
writeln!(handle, "{}", dir.display_score(now))
|
||||
} else {
|
||||
writeln!(handle, "{}", dir.display())
|
||||
}
|
||||
.pipe_exit("stdout")?;
|
||||
}
|
||||
handle.flush().pipe_exit("stdout")?;
|
||||
} else {
|
||||
let dir = stream.next().context("no match found")?;
|
||||
if self.score {
|
||||
writeln!(io::stdout(), "{}", dir.display_score(now))
|
||||
} else {
|
||||
writeln!(io::stdout(), "{}", dir.display())
|
||||
}
|
||||
.pipe_exit("stdout")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
use crate::app::{Remove, Run};
|
||||
use crate::config;
|
||||
use crate::db::DatabaseFile;
|
||||
use crate::error::BrokenPipeHandler;
|
||||
use crate::fzf::Fzf;
|
||||
use crate::util;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
use std::io::Write;
|
||||
|
||||
impl Run for Remove {
|
||||
fn run(&self) -> Result<()> {
|
||||
let data_dir = config::data_dir()?;
|
||||
let mut db = DatabaseFile::new(data_dir);
|
||||
let mut db = db.open()?;
|
||||
|
||||
let selection;
|
||||
match &self.interactive {
|
||||
Some(keywords) => {
|
||||
let now = util::current_time()?;
|
||||
let mut stream = db.stream(now).with_keywords(keywords);
|
||||
|
||||
let mut fzf = Fzf::new(true)?;
|
||||
while let Some(dir) = stream.next() {
|
||||
writeln!(fzf.stdin(), "{}", dir.display_score(now)).pipe_exit("fzf")?;
|
||||
}
|
||||
|
||||
selection = fzf.wait_select()?;
|
||||
let paths = selection.lines().filter_map(|line| line.get(5..));
|
||||
let mut not_found = Vec::new();
|
||||
for path in paths {
|
||||
if !db.remove(&path) {
|
||||
not_found.push(path);
|
||||
}
|
||||
}
|
||||
|
||||
if !not_found.is_empty() {
|
||||
let mut err = "path not found in database:".to_string();
|
||||
for path in not_found {
|
||||
err.push_str("\n ");
|
||||
err.push_str(path.as_ref());
|
||||
}
|
||||
bail!(err);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// unwrap is safe here because path is required_unless_present = "interactive"
|
||||
let path = self.path.as_ref().unwrap();
|
||||
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) {
|
||||
bail!("path not found in database:\n {}", &path)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
use std::path::Path;
|
||||
|
||||
use anyhow::{Result, bail};
|
||||
|
||||
use crate::cmd::{Add, Run};
|
||||
use crate::db::Database;
|
||||
use crate::{config, util};
|
||||
|
||||
impl Run for Add {
|
||||
fn run(&self) -> Result<()> {
|
||||
// These characters can't be printed cleanly to a single line, so they can cause
|
||||
// confusion when writing to stdout.
|
||||
const EXCLUDE_CHARS: &[char] = &['\n', '\r'];
|
||||
|
||||
let exclude_dirs = config::exclude_dirs()?;
|
||||
let max_age = config::maxage()?;
|
||||
let now = util::current_time()?;
|
||||
|
||||
let mut db = Database::open()?;
|
||||
|
||||
for path in &self.paths {
|
||||
let path =
|
||||
if config::resolve_symlinks() { util::canonicalize } else { util::resolve_path }(
|
||||
path,
|
||||
)?;
|
||||
let path = util::path_to_str(&path)?;
|
||||
|
||||
// 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()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,166 @@
|
|||
use std::fs;
|
||||
|
||||
use anyhow::{Context, Result, bail};
|
||||
|
||||
use crate::cmd::{Import, ImportFrom, Run};
|
||||
use crate::db::Database;
|
||||
|
||||
impl Run for Import {
|
||||
fn run(&self) -> Result<()> {
|
||||
let buffer = fs::read_to_string(&self.path).with_context(|| {
|
||||
format!("could not open database for importing: {}", &self.path.display())
|
||||
})?;
|
||||
|
||||
let mut db = Database::open()?;
|
||||
if !self.merge && !db.dirs().is_empty() {
|
||||
bail!("current database is not empty, specify --merge to continue anyway");
|
||||
}
|
||||
|
||||
match self.from {
|
||||
ImportFrom::Autojump => import_autojump(&mut db, &buffer),
|
||||
ImportFrom::Z => import_z(&mut db, &buffer),
|
||||
}
|
||||
.context("import error")?;
|
||||
|
||||
db.save()
|
||||
}
|
||||
}
|
||||
|
||||
fn import_autojump(db: &mut Database, buffer: &str) -> Result<()> {
|
||||
for line in buffer.lines() {
|
||||
if line.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let (rank, path) =
|
||||
line.split_once('\t').with_context(|| format!("invalid entry: {line}"))?;
|
||||
|
||||
let mut rank = rank.parse::<f64>().with_context(|| format!("invalid rank: {rank}"))?;
|
||||
// Normalize the rank using a sigmoid function. Don't import actual ranks from
|
||||
// autojump, since its scoring algorithm is very different and might
|
||||
// take a while to get normalized.
|
||||
rank = sigmoid(rank);
|
||||
|
||||
db.add_unchecked(path, rank, 0);
|
||||
}
|
||||
|
||||
if db.dirty() {
|
||||
db.dedup();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn import_z(db: &mut Database, buffer: &str) -> Result<()> {
|
||||
for line in buffer.lines() {
|
||||
if line.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let mut split = line.rsplitn(3, '|');
|
||||
|
||||
let last_accessed = split.next().with_context(|| format!("invalid entry: {line}"))?;
|
||||
let last_accessed =
|
||||
last_accessed.parse().with_context(|| format!("invalid epoch: {last_accessed}"))?;
|
||||
|
||||
let rank = split.next().with_context(|| format!("invalid entry: {line}"))?;
|
||||
let rank = rank.parse().with_context(|| format!("invalid rank: {rank}"))?;
|
||||
|
||||
let path = split.next().with_context(|| format!("invalid entry: {line}"))?;
|
||||
|
||||
db.add_unchecked(path, rank, last_accessed);
|
||||
}
|
||||
|
||||
if db.dirty() {
|
||||
db.dedup();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn sigmoid(x: f64) -> f64 {
|
||||
1.0 / (1.0 + (-x).exp())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::db::Dir;
|
||||
|
||||
#[test]
|
||||
fn from_autojump() {
|
||||
let data_dir = tempfile::tempdir().unwrap();
|
||||
let mut db = Database::open_dir(data_dir.path()).unwrap();
|
||||
for (path, rank, last_accessed) in [
|
||||
("/quux/quuz", 1.0, 100),
|
||||
("/corge/grault/garply", 6.0, 600),
|
||||
("/waldo/fred/plugh", 3.0, 300),
|
||||
("/xyzzy/thud", 8.0, 800),
|
||||
("/foo/bar", 9.0, 900),
|
||||
] {
|
||||
db.add_unchecked(path, rank, last_accessed);
|
||||
}
|
||||
|
||||
let buffer = "\
|
||||
7.0 /baz
|
||||
2.0 /foo/bar
|
||||
5.0 /quux/quuz";
|
||||
import_autojump(&mut db, buffer).unwrap();
|
||||
|
||||
db.sort_by_path();
|
||||
println!("got: {:?}", &db.dirs());
|
||||
|
||||
let exp = [
|
||||
Dir { path: "/baz".into(), rank: sigmoid(7.0), last_accessed: 0 },
|
||||
Dir { path: "/corge/grault/garply".into(), rank: 6.0, last_accessed: 600 },
|
||||
Dir { path: "/foo/bar".into(), rank: 9.0 + sigmoid(2.0), last_accessed: 900 },
|
||||
Dir { path: "/quux/quuz".into(), rank: 1.0 + sigmoid(5.0), last_accessed: 100 },
|
||||
Dir { path: "/waldo/fred/plugh".into(), rank: 3.0, last_accessed: 300 },
|
||||
Dir { path: "/xyzzy/thud".into(), rank: 8.0, last_accessed: 800 },
|
||||
];
|
||||
println!("exp: {exp:?}");
|
||||
|
||||
for (dir1, dir2) in db.dirs().iter().zip(exp) {
|
||||
assert_eq!(dir1.path, dir2.path);
|
||||
assert!((dir1.rank - dir2.rank).abs() < 0.01);
|
||||
assert_eq!(dir1.last_accessed, dir2.last_accessed);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_z() {
|
||||
let data_dir = tempfile::tempdir().unwrap();
|
||||
let mut db = Database::open_dir(data_dir.path()).unwrap();
|
||||
for (path, rank, last_accessed) in [
|
||||
("/quux/quuz", 1.0, 100),
|
||||
("/corge/grault/garply", 6.0, 600),
|
||||
("/waldo/fred/plugh", 3.0, 300),
|
||||
("/xyzzy/thud", 8.0, 800),
|
||||
("/foo/bar", 9.0, 900),
|
||||
] {
|
||||
db.add_unchecked(path, rank, last_accessed);
|
||||
}
|
||||
|
||||
let buffer = "\
|
||||
/baz|7|700
|
||||
/quux/quuz|4|400
|
||||
/foo/bar|2|200
|
||||
/quux/quuz|5|500";
|
||||
import_z(&mut db, buffer).unwrap();
|
||||
|
||||
db.sort_by_path();
|
||||
println!("got: {:?}", &db.dirs());
|
||||
|
||||
let exp = [
|
||||
Dir { path: "/baz".into(), rank: 7.0, last_accessed: 700 },
|
||||
Dir { path: "/corge/grault/garply".into(), rank: 6.0, last_accessed: 600 },
|
||||
Dir { path: "/foo/bar".into(), rank: 11.0, last_accessed: 900 },
|
||||
Dir { path: "/quux/quuz".into(), rank: 10.0, last_accessed: 500 },
|
||||
Dir { path: "/waldo/fred/plugh".into(), rank: 3.0, last_accessed: 300 },
|
||||
Dir { path: "/xyzzy/thud".into(), rank: 8.0, last_accessed: 800 },
|
||||
];
|
||||
println!("exp: {exp:?}");
|
||||
|
||||
for (dir1, dir2) in db.dirs().iter().zip(exp) {
|
||||
assert_eq!(dir1.path, dir2.path);
|
||||
assert!((dir1.rank - dir2.rank).abs() < 0.01);
|
||||
assert_eq!(dir1.last_accessed, dir2.last_accessed);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
use std::io::{self, Write};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use askama::Template;
|
||||
|
||||
use crate::cmd::{Init, InitShell, Run};
|
||||
use crate::config;
|
||||
use crate::error::BrokenPipeHandler;
|
||||
use crate::shell::{Bash, Elvish, Fish, Nushell, Opts, Posix, Powershell, Tcsh, Xonsh, Zsh};
|
||||
|
||||
impl Run for Init {
|
||||
fn run(&self) -> Result<()> {
|
||||
let cmd = if self.no_cmd { None } else { Some(self.cmd.as_str()) };
|
||||
let echo = config::echo();
|
||||
let resolve_symlinks = config::resolve_symlinks();
|
||||
let opts = &Opts { cmd, hook: self.hook, echo, resolve_symlinks };
|
||||
|
||||
let source = match self.shell {
|
||||
InitShell::Bash => Bash(opts).render(),
|
||||
InitShell::Elvish => Elvish(opts).render(),
|
||||
InitShell::Fish => Fish(opts).render(),
|
||||
InitShell::Nushell => Nushell(opts).render(),
|
||||
InitShell::Posix => Posix(opts).render(),
|
||||
InitShell::Powershell => Powershell(opts).render(),
|
||||
InitShell::Tcsh => Tcsh(opts).render(),
|
||||
InitShell::Xonsh => Xonsh(opts).render(),
|
||||
InitShell::Zsh => Zsh(opts).render(),
|
||||
}
|
||||
.context("could not render template")?;
|
||||
writeln!(io::stdout(), "{source}").pipe_exit("stdout")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
mod add;
|
||||
mod cmd;
|
||||
mod edit;
|
||||
mod import;
|
||||
mod init;
|
||||
mod query;
|
||||
mod remove;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
pub use crate::cmd::cmd::*;
|
||||
|
||||
pub trait Run {
|
||||
fn run(&self) -> Result<()>;
|
||||
}
|
||||
|
||||
impl Run for Cmd {
|
||||
fn run(&self) -> Result<()> {
|
||||
match self {
|
||||
Cmd::Add(cmd) => cmd.run(),
|
||||
Cmd::Edit(cmd) => cmd.run(),
|
||||
Cmd::Import(cmd) => cmd.run(),
|
||||
Cmd::Init(cmd) => cmd.run(),
|
||||
Cmd::Query(cmd) => cmd.run(),
|
||||
Cmd::Remove(cmd) => cmd.run(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
use std::io::{self, Write};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
use crate::cmd::{Query, Run};
|
||||
use crate::config;
|
||||
use crate::db::{Database, Epoch, Stream, StreamOptions};
|
||||
use crate::error::BrokenPipeHandler;
|
||||
use crate::util::{self, Fzf, FzfChild};
|
||||
|
||||
impl Run for Query {
|
||||
fn run(&self) -> Result<()> {
|
||||
let mut db = crate::db::Database::open()?;
|
||||
self.query(&mut db).and(db.save())
|
||||
}
|
||||
}
|
||||
|
||||
impl Query {
|
||||
fn query(&self, db: &mut Database) -> Result<()> {
|
||||
let now = util::current_time()?;
|
||||
let mut stream = self.get_stream(db, now)?;
|
||||
|
||||
if self.interactive {
|
||||
self.query_interactive(&mut stream, now)
|
||||
} else if self.list {
|
||||
self.query_list(&mut stream, now)
|
||||
} else {
|
||||
self.query_first(&mut stream, now)
|
||||
}
|
||||
}
|
||||
|
||||
fn query_interactive(&self, stream: &mut Stream, now: Epoch) -> Result<()> {
|
||||
let mut fzf = Self::get_fzf()?;
|
||||
let selection = loop {
|
||||
match stream.next() {
|
||||
Some(dir) if Some(dir.path.as_ref()) == self.exclude.as_deref() => continue,
|
||||
Some(dir) => {
|
||||
if let Some(selection) = fzf.write(dir, now)? {
|
||||
break selection;
|
||||
}
|
||||
}
|
||||
None => break fzf.wait()?,
|
||||
}
|
||||
};
|
||||
|
||||
if self.score {
|
||||
print!("{selection}");
|
||||
} else {
|
||||
let path = selection.get(7..).context("could not read selection from fzf")?;
|
||||
print!("{path}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn query_list(&self, stream: &mut Stream, now: Epoch) -> Result<()> {
|
||||
let handle = &mut io::stdout().lock();
|
||||
while let Some(dir) = stream.next() {
|
||||
if Some(dir.path.as_ref()) == self.exclude.as_deref() {
|
||||
continue;
|
||||
}
|
||||
let dir = if self.score { dir.display().with_score(now) } else { dir.display() };
|
||||
writeln!(handle, "{dir}").pipe_exit("stdout")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn query_first(&self, stream: &mut Stream, now: Epoch) -> Result<()> {
|
||||
let handle = &mut io::stdout();
|
||||
|
||||
let mut dir = stream.next().context("no match found")?;
|
||||
while Some(dir.path.as_ref()) == self.exclude.as_deref() {
|
||||
dir = stream.next().context("you are already in the only match")?;
|
||||
}
|
||||
|
||||
let dir = if self.score { dir.display().with_score(now) } else { dir.display() };
|
||||
writeln!(handle, "{dir}").pipe_exit("stdout")
|
||||
}
|
||||
|
||||
fn get_stream<'a>(&self, db: &'a mut Database, now: Epoch) -> Result<Stream<'a>> {
|
||||
let mut options = StreamOptions::new(now)
|
||||
.with_keywords(self.keywords.iter().map(|s| s.as_str()))
|
||||
.with_exclude(config::exclude_dirs()?)
|
||||
.with_base_dir(self.base_dir.clone());
|
||||
if !self.all {
|
||||
let resolve_symlinks = config::resolve_symlinks();
|
||||
options = options.with_exists(true).with_resolve_symlinks(resolve_symlinks);
|
||||
}
|
||||
|
||||
let stream = Stream::new(db, options);
|
||||
Ok(stream)
|
||||
}
|
||||
|
||||
fn get_fzf() -> Result<FzfChild> {
|
||||
let mut fzf = Fzf::new()?;
|
||||
if let Some(fzf_opts) = config::fzf_opts() {
|
||||
fzf.env("FZF_DEFAULT_OPTS", fzf_opts)
|
||||
} else {
|
||||
fzf.args([
|
||||
// Search mode
|
||||
"--exact",
|
||||
// Search result
|
||||
"--no-sort",
|
||||
// Interface
|
||||
"--bind=ctrl-z:ignore,btab:up,tab:down",
|
||||
"--cycle",
|
||||
"--keep-right",
|
||||
// Layout
|
||||
"--border=sharp", // rounded edges don't display correctly on some terminals
|
||||
"--height=45%",
|
||||
"--info=inline",
|
||||
"--layout=reverse",
|
||||
// Display
|
||||
"--tabstop=1",
|
||||
// Scripting
|
||||
"--exit-0",
|
||||
])
|
||||
.enable_preview()
|
||||
}
|
||||
.spawn()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
use anyhow::{Result, bail};
|
||||
|
||||
use crate::cmd::{Remove, Run};
|
||||
use crate::db::Database;
|
||||
use crate::util;
|
||||
|
||||
impl Run for Remove {
|
||||
fn run(&self) -> Result<()> {
|
||||
let mut db = Database::open()?;
|
||||
|
||||
for path in &self.paths {
|
||||
if !db.remove(path) {
|
||||
let path_abs = util::resolve_path(path)?;
|
||||
let path_abs = util::path_to_str(&path_abs)?;
|
||||
if path_abs == path || !db.remove(path_abs) {
|
||||
bail!("path not found in database: {path}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
db.save()
|
||||
}
|
||||
}
|
|
@ -1,33 +1,26 @@
|
|||
use crate::db::Rank;
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
use dirs_next as dirs;
|
||||
use glob::Pattern;
|
||||
|
||||
use std::env;
|
||||
use std::ffi::OsString;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::{Context, Result, ensure};
|
||||
use glob::Pattern;
|
||||
|
||||
use crate::db::Rank;
|
||||
|
||||
pub fn data_dir() -> Result<PathBuf> {
|
||||
let path = match env::var_os("_ZO_DATA_DIR") {
|
||||
let dir = match env::var_os("_ZO_DATA_DIR") {
|
||||
Some(path) => PathBuf::from(path),
|
||||
None => match dirs::data_local_dir() {
|
||||
Some(mut path) => {
|
||||
path.push("zoxide");
|
||||
path
|
||||
}
|
||||
None => bail!("could not find data directory, please set _ZO_DATA_DIR manually"),
|
||||
},
|
||||
None => dirs::data_local_dir()
|
||||
.context("could not find data directory, please set _ZO_DATA_DIR manually")?
|
||||
.join("zoxide"),
|
||||
};
|
||||
|
||||
Ok(path)
|
||||
ensure!(dir.is_absolute(), "_ZO_DATA_DIR must be an absolute path");
|
||||
Ok(dir)
|
||||
}
|
||||
|
||||
pub fn echo() -> bool {
|
||||
match env::var_os("_ZO_ECHO") {
|
||||
Some(var) => var == "1",
|
||||
None => false,
|
||||
}
|
||||
env::var_os("_ZO_ECHO").is_some_and(|var| var == "1")
|
||||
}
|
||||
|
||||
pub fn exclude_dirs() -> Result<Vec<Pattern>> {
|
||||
|
@ -36,14 +29,13 @@ pub fn exclude_dirs() -> Result<Vec<Pattern>> {
|
|||
.map(|path| {
|
||||
let pattern = path.to_str().context("invalid unicode in _ZO_EXCLUDE_DIRS")?;
|
||||
Pattern::new(pattern)
|
||||
.with_context(|| format!("invalid glob in _ZO_EXCLUDE_DIRS: {}", pattern))
|
||||
.with_context(|| format!("invalid glob in _ZO_EXCLUDE_DIRS: {pattern}"))
|
||||
})
|
||||
.collect(),
|
||||
None => {
|
||||
let pattern = (|| {
|
||||
let home = dirs::home_dir()?;
|
||||
let home = home.to_str()?;
|
||||
let home = Pattern::escape(home);
|
||||
let home = Pattern::escape(home.to_str()?);
|
||||
Pattern::new(&home).ok()
|
||||
})();
|
||||
Ok(pattern.into_iter().collect())
|
||||
|
@ -56,21 +48,15 @@ pub fn fzf_opts() -> Option<OsString> {
|
|||
}
|
||||
|
||||
pub fn maxage() -> Result<Rank> {
|
||||
match env::var_os("_ZO_MAXAGE") {
|
||||
Some(maxage) => {
|
||||
let maxage = maxage.to_str().context("invalid unicode in _ZO_MAXAGE")?;
|
||||
let maxage = maxage
|
||||
.parse::<u64>()
|
||||
.with_context(|| format!("unable to parse _ZO_MAXAGE as integer: {}", maxage))?;
|
||||
Ok(maxage as Rank)
|
||||
}
|
||||
None => Ok(10000.0),
|
||||
}
|
||||
env::var_os("_ZO_MAXAGE").map_or(Ok(10_000.0), |maxage| {
|
||||
let maxage = maxage.to_str().context("invalid unicode in _ZO_MAXAGE")?;
|
||||
let maxage = maxage
|
||||
.parse::<u32>()
|
||||
.with_context(|| format!("unable to parse _ZO_MAXAGE as integer: {maxage}"))?;
|
||||
Ok(maxage as Rank)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn resolve_symlinks() -> bool {
|
||||
match env::var_os("_ZO_RESOLVE_SYMLINKS") {
|
||||
Some(var) => var == "1",
|
||||
None => false,
|
||||
}
|
||||
env::var_os("_ZO_RESOLVE_SYMLINKS").is_some_and(|var| var == "1")
|
||||
}
|
||||
|
|
154
src/db/dir.rs
154
src/db/dir.rs
|
@ -1,83 +1,9 @@
|
|||
use anyhow::{bail, Context, Result};
|
||||
use bincode::Options as _;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct DirList<'a>(#[serde(borrow)] pub Vec<Dir<'a>>);
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
impl DirList<'_> {
|
||||
const VERSION: u32 = 3;
|
||||
|
||||
pub fn new() -> DirList<'static> {
|
||||
DirList(Vec::new())
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<DirList> {
|
||||
// Assume a maximum size for the database. This prevents bincode from
|
||||
// throwing strange errors when it encounters invalid data.
|
||||
const MAX_SIZE: u64 = 32 << 20; // 32 MiB
|
||||
let deserializer = &mut bincode::options().with_fixint_encoding().with_limit(MAX_SIZE);
|
||||
|
||||
// Split bytes into sections.
|
||||
let version_size = deserializer.serialized_size(&Self::VERSION).unwrap() as _;
|
||||
if bytes.len() < version_size {
|
||||
bail!("could not deserialize database: corrupted data");
|
||||
}
|
||||
let (bytes_version, bytes_dirs) = bytes.split_at(version_size);
|
||||
|
||||
// Deserialize sections.
|
||||
(|| {
|
||||
let version = deserializer.deserialize(bytes_version)?;
|
||||
match version {
|
||||
Self::VERSION => Ok(deserializer.deserialize(bytes_dirs)?),
|
||||
version => {
|
||||
bail!("unsupported version (got {}, supports {})", version, Self::VERSION,)
|
||||
}
|
||||
}
|
||||
})()
|
||||
.context("could not deserialize database")
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>> {
|
||||
(|| -> bincode::Result<_> {
|
||||
// Preallocate buffer with combined size of sections.
|
||||
let version_size = bincode::serialized_size(&Self::VERSION)?;
|
||||
let dirs_size = bincode::serialized_size(&self)?;
|
||||
let buffer_size = version_size + dirs_size;
|
||||
let mut buffer = Vec::with_capacity(buffer_size as _);
|
||||
|
||||
// Serialize sections into buffer.
|
||||
bincode::serialize_into(&mut buffer, &Self::VERSION)?;
|
||||
bincode::serialize_into(&mut buffer, &self)?;
|
||||
Ok(buffer)
|
||||
})()
|
||||
.context("could not serialize database")
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Deref for DirList<'a> {
|
||||
type Target = Vec<Dir<'a>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> DerefMut for DirList<'a> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<Vec<Dir<'a>>> for DirList<'a> {
|
||||
fn from(dirs: Vec<Dir<'a>>) -> Self {
|
||||
DirList(dirs)
|
||||
}
|
||||
}
|
||||
use crate::util::{DAY, HOUR, WEEK};
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct Dir<'a> {
|
||||
|
@ -88,11 +14,11 @@ pub struct Dir<'a> {
|
|||
}
|
||||
|
||||
impl Dir<'_> {
|
||||
pub fn score(&self, now: Epoch) -> Rank {
|
||||
const HOUR: Epoch = 60 * 60;
|
||||
const DAY: Epoch = 24 * HOUR;
|
||||
const WEEK: Epoch = 7 * DAY;
|
||||
pub fn display(&self) -> DirDisplay<'_> {
|
||||
DirDisplay::new(self)
|
||||
}
|
||||
|
||||
pub fn score(&self, now: Epoch) -> Rank {
|
||||
// The older the entry, the lesser its importance.
|
||||
let duration = now.saturating_sub(self.last_accessed);
|
||||
if duration < HOUR {
|
||||
|
@ -105,63 +31,39 @@ impl Dir<'_> {
|
|||
self.rank * 0.25
|
||||
}
|
||||
}
|
||||
|
||||
pub fn display(&self) -> DirDisplay {
|
||||
DirDisplay { dir: self }
|
||||
}
|
||||
|
||||
pub fn display_score(&self, now: Epoch) -> DirDisplayScore {
|
||||
DirDisplayScore { dir: self, now }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DirDisplay<'a> {
|
||||
dir: &'a Dir<'a>,
|
||||
now: Option<Epoch>,
|
||||
separator: char,
|
||||
}
|
||||
|
||||
impl<'a> DirDisplay<'a> {
|
||||
fn new(dir: &'a Dir) -> Self {
|
||||
Self { dir, separator: ' ', now: None }
|
||||
}
|
||||
|
||||
pub fn with_score(mut self, now: Epoch) -> Self {
|
||||
self.now = Some(now);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_separator(mut self, separator: char) -> Self {
|
||||
self.separator = separator;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for DirDisplay<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
if let Some(now) = self.now {
|
||||
let score = self.dir.score(now).clamp(0.0, 9999.0);
|
||||
write!(f, "{score:>6.1}{}", self.separator)?;
|
||||
}
|
||||
write!(f, "{}", self.dir.path)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DirDisplayScore<'a> {
|
||||
dir: &'a Dir<'a>,
|
||||
now: Epoch,
|
||||
}
|
||||
|
||||
impl Display for DirDisplayScore<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
let score = self.dir.score(self.now);
|
||||
let score = if score > 9999.0 {
|
||||
9999
|
||||
} else if score > 0.0 {
|
||||
score as u32
|
||||
} else {
|
||||
0
|
||||
};
|
||||
write!(f, "{:>4} {}", score, self.dir.path)
|
||||
}
|
||||
}
|
||||
|
||||
pub type Rank = f64;
|
||||
pub type Epoch = u64;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{Dir, DirList};
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
#[test]
|
||||
fn zero_copy() {
|
||||
let dirs = DirList(vec![Dir { path: "/".into(), rank: 0.0, last_accessed: 0 }]);
|
||||
|
||||
let bytes = dirs.to_bytes().unwrap();
|
||||
let dirs = DirList::from_bytes(&bytes).unwrap();
|
||||
|
||||
for dir in dirs.iter() {
|
||||
assert!(matches!(dir.path, Cow::Borrowed(_)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
444
src/db/mod.rs
444
src/db/mod.rs
|
@ -1,217 +1,234 @@
|
|||
mod dir;
|
||||
mod stream;
|
||||
|
||||
pub use dir::{Dir, DirList, Epoch, Rank};
|
||||
pub use stream::Stream;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use tempfile::{NamedTempFile, PersistError};
|
||||
|
||||
use std::fs;
|
||||
use std::io::{self, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{fs, io};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Database<'file> {
|
||||
pub dirs: DirList<'file>,
|
||||
pub modified: bool,
|
||||
pub data_dir: &'file PathBuf,
|
||||
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<'file> Database<'file> {
|
||||
pub fn save(&mut self) -> Result<()> {
|
||||
if !self.modified {
|
||||
return Ok(());
|
||||
}
|
||||
impl Database {
|
||||
const VERSION: u32 = 3;
|
||||
|
||||
let buffer = self.dirs.to_bytes()?;
|
||||
let mut file = NamedTempFile::new_in(&self.data_dir).with_context(|| {
|
||||
format!("could not create temporary database in: {}", self.data_dir.display())
|
||||
})?;
|
||||
|
||||
// Preallocate enough space on the file, preventing copying later on.
|
||||
// This optimization may fail on some filesystems, but it is safe to
|
||||
// ignore it and proceed.
|
||||
let _ = file.as_file().set_len(buffer.len() as _);
|
||||
file.write_all(&buffer).with_context(|| {
|
||||
format!("could not write to temporary database: {}", file.path().display())
|
||||
})?;
|
||||
|
||||
let path = db_path(&self.data_dir);
|
||||
persist(file, &path)
|
||||
.with_context(|| format!("could not replace database: {}", path.display()))?;
|
||||
|
||||
self.modified = false;
|
||||
Ok(())
|
||||
pub fn open() -> Result<Self> {
|
||||
let data_dir = config::data_dir()?;
|
||||
Self::open_dir(data_dir)
|
||||
}
|
||||
|
||||
/// Adds a new directory or increments its rank. Also updates its last accessed time.
|
||||
pub fn add<S: AsRef<str>>(&mut self, path: S, now: Epoch) {
|
||||
let path = path.as_ref();
|
||||
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 self.dirs.iter_mut().find(|dir| dir.path == path) {
|
||||
None => {
|
||||
self.dirs.push(Dir { path: path.to_string().into(), last_accessed: now, rank: 1.0 })
|
||||
}
|
||||
Some(dir) => {
|
||||
dir.last_accessed = now;
|
||||
dir.rank += 1.0;
|
||||
}
|
||||
};
|
||||
|
||||
self.modified = true;
|
||||
}
|
||||
|
||||
pub fn dedup(&mut self) {
|
||||
// Sort by path, so that equal paths are next to each other.
|
||||
self.dirs.sort_by(|dir1, dir2| dir1.path.cmp(&dir2.path));
|
||||
|
||||
for idx in (1..self.dirs.len()).rev() {
|
||||
// Check if curr_dir and next_dir have equal paths.
|
||||
let curr_dir = &self.dirs[idx];
|
||||
let next_dir = &self.dirs[idx - 1];
|
||||
if next_dir.path != curr_dir.path {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Merge curr_dir's rank and last_accessed into next_dir.
|
||||
let rank = curr_dir.rank;
|
||||
let last_accessed = curr_dir.last_accessed;
|
||||
let next_dir = &mut self.dirs[idx - 1];
|
||||
next_dir.last_accessed = next_dir.last_accessed.max(last_accessed);
|
||||
next_dir.rank += rank;
|
||||
|
||||
// Delete curr_dir.
|
||||
self.dirs.swap_remove(idx);
|
||||
self.modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Streaming iterator for directories.
|
||||
pub fn stream(&mut self, now: Epoch) -> Stream<'_, 'file> {
|
||||
Stream::new(self, now)
|
||||
}
|
||||
|
||||
/// Removes the directory with `path` from the store.
|
||||
/// This does not preserve ordering, but is O(1).
|
||||
pub fn remove<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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Database<'_> {
|
||||
fn drop(&mut self) {
|
||||
// Since the error can't be properly handled here,
|
||||
// pretty-print it instead.
|
||||
if let Err(e) = self.save() {
|
||||
let _ = writeln!(io::stderr(), "zoxide: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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(())
|
||||
}
|
||||
|
||||
pub struct DatabaseFile {
|
||||
buffer: Vec<u8>,
|
||||
data_dir: PathBuf,
|
||||
}
|
||||
|
||||
impl DatabaseFile {
|
||||
pub fn new<P: Into<PathBuf>>(data_dir: P) -> Self {
|
||||
DatabaseFile { buffer: Vec::new(), data_dir: data_dir.into() }
|
||||
}
|
||||
|
||||
pub fn open(&mut self) -> Result<Database> {
|
||||
// Read the entire database to memory. For smaller files, this is
|
||||
// faster than mmap / streaming, and allows for zero-copy
|
||||
// deserialization.
|
||||
let path = db_path(&self.data_dir);
|
||||
match fs::read(&path) {
|
||||
Ok(buffer) => {
|
||||
self.buffer = buffer;
|
||||
let dirs = DirList::from_bytes(&self.buffer).with_context(|| {
|
||||
format!("could not deserialize database: {}", path.display())
|
||||
})?;
|
||||
Ok(Database { dirs, modified: false, data_dir: &self.data_dir })
|
||||
}
|
||||
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(&self.data_dir).with_context(|| {
|
||||
format!("unable to create data directory: {}", self.data_dir.display())
|
||||
// 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(Database { dirs: DirList::new(), modified: false, data_dir: &self.data_dir })
|
||||
Ok(Self::new(path, Vec::new(), |_| Vec::new(), false))
|
||||
}
|
||||
Err(e) => {
|
||||
Err(e).with_context(|| format!("could not read from database: {}", path.display()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn db_path<P: AsRef<Path>>(data_dir: P) -> PathBuf {
|
||||
const DB_FILENAME: &str = "db.zo";
|
||||
data_dir.as_ref().join(DB_FILENAME)
|
||||
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)]
|
||||
|
@ -220,48 +237,51 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn add() {
|
||||
let data_dir = tempfile::tempdir().unwrap();
|
||||
let path = if cfg!(windows) { r"C:\foo\bar" } else { "/foo/bar" };
|
||||
let now = 946684800;
|
||||
|
||||
let data_dir = tempfile::tempdir().unwrap();
|
||||
{
|
||||
let mut db = DatabaseFile::new(data_dir.path());
|
||||
let mut db = db.open().unwrap();
|
||||
db.add(path, now);
|
||||
db.add(path, now);
|
||||
let mut db = Database::open_dir(data_dir.path()).unwrap();
|
||||
db.add(path, 1.0, now);
|
||||
db.add(path, 1.0, now);
|
||||
db.save().unwrap();
|
||||
}
|
||||
{
|
||||
let mut db = DatabaseFile::new(data_dir.path());
|
||||
let db = db.open().unwrap();
|
||||
assert_eq!(db.dirs.len(), 1);
|
||||
|
||||
let dir = &db.dirs[0];
|
||||
{
|
||||
let db = Database::open_dir(data_dir.path()).unwrap();
|
||||
assert_eq!(db.dirs().len(), 1);
|
||||
|
||||
let dir = &db.dirs()[0];
|
||||
assert_eq!(dir.path, path);
|
||||
assert!((dir.rank - 2.0).abs() < 0.01);
|
||||
assert_eq!(dir.last_accessed, now);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove() {
|
||||
let data_dir = tempfile::tempdir().unwrap();
|
||||
let path = if cfg!(windows) { r"C:\foo\bar" } else { "/foo/bar" };
|
||||
let now = 946684800;
|
||||
|
||||
let data_dir = tempfile::tempdir().unwrap();
|
||||
{
|
||||
let mut db = DatabaseFile::new(data_dir.path());
|
||||
let mut db = db.open().unwrap();
|
||||
db.add(path, now);
|
||||
let mut db = Database::open_dir(data_dir.path()).unwrap();
|
||||
db.add(path, 1.0, now);
|
||||
db.save().unwrap();
|
||||
}
|
||||
|
||||
{
|
||||
let mut db = DatabaseFile::new(data_dir.path());
|
||||
let mut db = db.open().unwrap();
|
||||
let mut db = Database::open_dir(data_dir.path()).unwrap();
|
||||
assert!(db.remove(path));
|
||||
db.save().unwrap();
|
||||
}
|
||||
|
||||
{
|
||||
let mut db = DatabaseFile::new(data_dir.path());
|
||||
let mut db = db.open().unwrap();
|
||||
assert!(db.dirs.is_empty());
|
||||
let mut db = Database::open_dir(data_dir.path()).unwrap();
|
||||
assert!(db.dirs().is_empty());
|
||||
assert!(!db.remove(path));
|
||||
db.save().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
206
src/db/stream.rs
206
src/db/stream.rs
|
@ -1,100 +1,84 @@
|
|||
use super::{Database, Dir, Epoch};
|
||||
use crate::util;
|
||||
|
||||
use ordered_float::OrderedFloat;
|
||||
|
||||
use std::fs;
|
||||
use std::iter::Rev;
|
||||
use std::ops::Range;
|
||||
use std::path;
|
||||
use std::path::Path;
|
||||
use std::{fs, path};
|
||||
|
||||
pub struct Stream<'db, 'file> {
|
||||
db: &'db mut Database<'file>,
|
||||
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>>,
|
||||
|
||||
keywords: Vec<String>,
|
||||
|
||||
check_exists: bool,
|
||||
expire_below: Epoch,
|
||||
resolve_symlinks: bool,
|
||||
|
||||
exclude_path: Option<String>,
|
||||
options: StreamOptions,
|
||||
}
|
||||
|
||||
impl<'db, 'file> Stream<'db, 'file> {
|
||||
pub fn new(db: &'db mut Database<'file>, now: Epoch) -> Self {
|
||||
// Iterate in descending order of score.
|
||||
db.dirs.sort_unstable_by_key(|dir| OrderedFloat(dir.score(now)));
|
||||
let idxs = (0..db.dirs.len()).rev();
|
||||
|
||||
// If a directory is deleted and hasn't been used for 90 days, delete
|
||||
// it from the database.
|
||||
let expire_below = now.saturating_sub(90 * 24 * 60 * 60);
|
||||
|
||||
Stream {
|
||||
db,
|
||||
idxs,
|
||||
keywords: Vec::new(),
|
||||
check_exists: false,
|
||||
expire_below,
|
||||
resolve_symlinks: false,
|
||||
exclude_path: None,
|
||||
}
|
||||
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 with_exclude<S: Into<String>>(mut self, path: S) -> Self {
|
||||
self.exclude_path = Some(path.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_exists(mut self, resolve_symlinks: bool) -> Self {
|
||||
self.check_exists = true;
|
||||
self.resolve_symlinks = resolve_symlinks;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_keywords<S: AsRef<str>>(mut self, keywords: &[S]) -> Self {
|
||||
self.keywords = keywords.iter().map(util::to_lowercase).collect();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn next(&mut self) -> Option<&Dir<'file>> {
|
||||
pub fn next(&mut self) -> Option<&Dir<'_>> {
|
||||
while let Some(idx) = self.idxs.next() {
|
||||
let dir = &self.db.dirs[idx];
|
||||
let dir = &self.db.dirs()[idx];
|
||||
|
||||
if !self.matches_keywords(&dir.path) {
|
||||
if !self.filter_by_keywords(&dir.path) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if !self.matches_exists(&dir.path) {
|
||||
if dir.last_accessed < self.expire_below {
|
||||
self.db.dirs.swap_remove(idx);
|
||||
self.db.modified = true;
|
||||
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;
|
||||
}
|
||||
|
||||
if Some(dir.path.as_ref()) == self.exclude_path.as_deref() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let dir = &self.db.dirs[idx];
|
||||
let dir = &self.db.dirs()[idx];
|
||||
return Some(dir);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn matches_exists<S: AsRef<str>>(&self, path: S) -> bool {
|
||||
if !self.check_exists {
|
||||
return true;
|
||||
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,
|
||||
}
|
||||
let resolver = if self.resolve_symlinks { fs::symlink_metadata } else { fs::metadata };
|
||||
resolver(path.as_ref()).map(|m| m.is_dir()).unwrap_or_default()
|
||||
}
|
||||
|
||||
fn matches_keywords<S: AsRef<str>>(&self, path: S) -> bool {
|
||||
let (keywords_last, keywords) = match self.keywords.split_last() {
|
||||
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,
|
||||
};
|
||||
|
@ -122,13 +106,81 @@ impl<'db, 'file> Stream<'db, 'file> {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct StreamOptions {
|
||||
/// The current time.
|
||||
now: Epoch,
|
||||
|
||||
/// Only directories matching these keywords will be returned.
|
||||
keywords: Vec<String>,
|
||||
|
||||
/// Directories that match any of these globs will be lazily removed.
|
||||
exclude: Vec<Pattern>,
|
||||
|
||||
/// Directories will only be returned if they exist on the filesystem.
|
||||
exists: bool,
|
||||
|
||||
/// Whether to resolve symlinks when checking if a directory exists.
|
||||
resolve_symlinks: bool,
|
||||
|
||||
/// Directories that do not exist and haven't been accessed since TTL will
|
||||
/// be lazily removed.
|
||||
ttl: Epoch,
|
||||
|
||||
/// Only return directories within this parent directory
|
||||
/// Does not check if the path exists
|
||||
base_dir: Option<String>,
|
||||
}
|
||||
|
||||
impl StreamOptions {
|
||||
pub fn new(now: Epoch) -> Self {
|
||||
StreamOptions {
|
||||
now,
|
||||
keywords: Vec::new(),
|
||||
exclude: Vec::new(),
|
||||
exists: false,
|
||||
resolve_symlinks: false,
|
||||
ttl: now.saturating_sub(3 * MONTH),
|
||||
base_dir: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_keywords<I>(mut self, keywords: I) -> Self
|
||||
where
|
||||
I: IntoIterator,
|
||||
I::Item: AsRef<str>,
|
||||
{
|
||||
self.keywords = keywords.into_iter().map(util::to_lowercase).collect();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_exclude(mut self, exclude: Vec<Pattern>) -> Self {
|
||||
self.exclude = exclude;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_exists(mut self, exists: bool) -> Self {
|
||||
self.exists = exists;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_resolve_symlinks(mut self, resolve_symlinks: bool) -> Self {
|
||||
self.resolve_symlinks = resolve_symlinks;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_base_dir(mut self, base_dir: Option<String>) -> Self {
|
||||
self.base_dir = base_dir;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Database;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use rstest::rstest;
|
||||
|
||||
use std::path::PathBuf;
|
||||
use super::*;
|
||||
|
||||
#[rstest]
|
||||
// Case normalization
|
||||
|
@ -151,9 +203,9 @@ mod tests {
|
|||
#[case(&["/foo/", "/bar"], "/foo/bar", false)]
|
||||
#[case(&["/foo/", "/bar"], "/foo/baz/bar", true)]
|
||||
fn query(#[case] keywords: &[&str], #[case] path: &str, #[case] is_match: bool) {
|
||||
let mut db =
|
||||
Database { dirs: Vec::new().into(), modified: false, data_dir: &PathBuf::new() };
|
||||
let stream = db.stream(0).with_keywords(keywords);
|
||||
assert_eq!(is_match, stream.matches_keywords(path));
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
|
12
src/error.rs
12
src/error.rs
|
@ -1,16 +1,16 @@
|
|||
use anyhow::{bail, Context, Result};
|
||||
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::io;
|
||||
|
||||
// Custom error type for early exit.
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ impl BrokenPipeHandler for io::Result<()> {
|
|||
fn pipe_exit(self, device: &str) -> Result<()> {
|
||||
match self {
|
||||
Err(e) if e.kind() == io::ErrorKind::BrokenPipe => bail!(SilentExit { code: 0 }),
|
||||
result => result.with_context(|| format!("could not write to {}", device)),
|
||||
result => result.with_context(|| format!("could not write to {device}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
61
src/fzf.rs
61
src/fzf.rs
|
@ -1,61 +0,0 @@
|
|||
use crate::config;
|
||||
use crate::error::SilentExit;
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
|
||||
use std::io;
|
||||
use std::process::{Child, ChildStdin, Command, Stdio};
|
||||
|
||||
pub struct Fzf {
|
||||
child: Child,
|
||||
}
|
||||
|
||||
impl Fzf {
|
||||
pub fn new(multiple: bool) -> Result<Self> {
|
||||
let mut command = Command::new("fzf");
|
||||
if multiple {
|
||||
command.arg("-m");
|
||||
}
|
||||
command.arg("-n2..").stdin(Stdio::piped()).stdout(Stdio::piped());
|
||||
if let Some(fzf_opts) = config::fzf_opts() {
|
||||
command.env("FZF_DEFAULT_OPTS", fzf_opts);
|
||||
}
|
||||
|
||||
let child = match command.spawn() {
|
||||
Ok(child) => child,
|
||||
Err(e) if e.kind() == io::ErrorKind::NotFound => {
|
||||
bail!("could not find fzf, is it installed?")
|
||||
}
|
||||
Err(e) => Err(e).context("could not launch fzf")?,
|
||||
};
|
||||
|
||||
Ok(Fzf { child })
|
||||
}
|
||||
|
||||
pub fn stdin(&mut self) -> &mut ChildStdin {
|
||||
// unwrap is safe here because command.stdin() has been piped
|
||||
self.child.stdin.as_mut().unwrap()
|
||||
}
|
||||
|
||||
pub fn wait_select(self) -> Result<String> {
|
||||
let output = self.child.wait_with_output().context("wait failed on fzf")?;
|
||||
|
||||
match output.status.code() {
|
||||
// normal exit
|
||||
Some(0) => String::from_utf8(output.stdout).context("invalid unicode in fzf output"),
|
||||
|
||||
// no match
|
||||
Some(1) => bail!("no match found"),
|
||||
|
||||
// error
|
||||
Some(2) => bail!("fzf returned an error"),
|
||||
|
||||
// terminated by a signal
|
||||
Some(code @ 130) => bail!(SilentExit { code }),
|
||||
Some(128..=254) | None => bail!("fzf was terminated"),
|
||||
|
||||
// unknown
|
||||
_ => bail!("fzf returned an unknown error"),
|
||||
}
|
||||
}
|
||||
}
|
36
src/main.rs
36
src/main.rs
|
@ -1,32 +1,34 @@
|
|||
mod app;
|
||||
#![allow(clippy::single_component_path_imports)]
|
||||
|
||||
mod cmd;
|
||||
mod config;
|
||||
mod db;
|
||||
mod error;
|
||||
mod fzf;
|
||||
mod shell;
|
||||
mod util;
|
||||
|
||||
use crate::app::{App, Run};
|
||||
use crate::error::SilentExit;
|
||||
|
||||
use clap::Clap;
|
||||
|
||||
use std::env;
|
||||
use std::io::{self, Write};
|
||||
use std::process;
|
||||
use std::process::ExitCode;
|
||||
|
||||
pub fn main() {
|
||||
use clap::Parser;
|
||||
|
||||
use crate::cmd::{Cmd, Run};
|
||||
use crate::error::SilentExit;
|
||||
|
||||
pub fn main() -> ExitCode {
|
||||
// Forcibly disable backtraces.
|
||||
env::remove_var("RUST_LIB_BACKTRACE");
|
||||
env::remove_var("RUST_BACKTRACE");
|
||||
unsafe { env::remove_var("RUST_LIB_BACKTRACE") };
|
||||
unsafe { env::remove_var("RUST_BACKTRACE") };
|
||||
|
||||
if let Err(e) = App::parse().run() {
|
||||
match e.downcast::<SilentExit>() {
|
||||
Ok(SilentExit { code }) => process::exit(code),
|
||||
match Cmd::parse().run() {
|
||||
Ok(()) => ExitCode::SUCCESS,
|
||||
Err(e) => match e.downcast::<SilentExit>() {
|
||||
Ok(SilentExit { code }) => code.into(),
|
||||
Err(e) => {
|
||||
let _ = writeln!(io::stderr(), "zoxide: {:?}", e);
|
||||
process::exit(1);
|
||||
_ = writeln!(io::stderr(), "zoxide: {e:?}");
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
261
src/shell.rs
261
src/shell.rs
|
@ -1,4 +1,4 @@
|
|||
use crate::app::InitHook;
|
||||
use crate::cmd::InitHook;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct Opts<'a> {
|
||||
|
@ -29,48 +29,49 @@ make_template!(Fish, "fish.txt");
|
|||
make_template!(Nushell, "nushell.txt");
|
||||
make_template!(Posix, "posix.txt");
|
||||
make_template!(Powershell, "powershell.txt");
|
||||
make_template!(Tcsh, "tcsh.txt");
|
||||
make_template!(Xonsh, "xonsh.txt");
|
||||
make_template!(Zsh, "zsh.txt");
|
||||
|
||||
#[cfg(feature = "shell_tests")]
|
||||
#[cfg(feature = "nix-dev")]
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use askama::Template;
|
||||
use assert_cmd::Command;
|
||||
use rstest::rstest;
|
||||
use rstest_reuse::{apply, template};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[template]
|
||||
#[rstest]
|
||||
fn bash_bash(
|
||||
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,
|
||||
) {
|
||||
}
|
||||
|
||||
#[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", "-c", &source])
|
||||
.args(["--noprofile", "--norc", "-e", "-u", "-o", "pipefail", "-c", &source])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn bash_shellcheck(
|
||||
#[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,
|
||||
) {
|
||||
#[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", "--shell", "bash", "-"])
|
||||
.args(["--enable=all", "-"])
|
||||
.write_stdin(source)
|
||||
.assert()
|
||||
.success()
|
||||
|
@ -78,19 +79,14 @@ mod tests {
|
|||
.stderr("");
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn bash_shfmt(
|
||||
#[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,
|
||||
) {
|
||||
#[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(&["-d", "-s", "-ln", "bash", "-i", "4", "-ci", "-"])
|
||||
.args(["--diff", "--indent=4", "--language-dialect=bash", "--simplify", "-"])
|
||||
.write_stdin(source)
|
||||
.assert()
|
||||
.success()
|
||||
|
@ -98,40 +94,38 @@ mod tests {
|
|||
.stderr("");
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn elvish_elvish(
|
||||
#[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,
|
||||
) {
|
||||
#[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().split('\n').filter(|line| !line.contains("edit:"))
|
||||
{
|
||||
// Filter out lines using edit:*, since those functions are only available in
|
||||
// the interactive editor.
|
||||
for line in Elvish(&opts).render().unwrap().lines().filter(|line| !line.contains("edit:")) {
|
||||
source.push_str(line);
|
||||
source.push('\n');
|
||||
}
|
||||
|
||||
Command::new("elvish")
|
||||
.args(&["-c", &source, "-norc"])
|
||||
.args(["-c", &source, "-norc"])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn fish_fish(
|
||||
#[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,
|
||||
) {
|
||||
#[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();
|
||||
|
||||
|
@ -140,20 +134,15 @@ mod tests {
|
|||
|
||||
Command::new("fish")
|
||||
.env("HOME", tempdir)
|
||||
.args(&["--command", &source, "--private"])
|
||||
.args(["--command", &source, "--no-config", "--private"])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn fish_fishindent(
|
||||
#[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,
|
||||
) {
|
||||
#[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');
|
||||
|
@ -161,9 +150,8 @@ mod tests {
|
|||
let tempdir = tempfile::tempdir().unwrap();
|
||||
let tempdir = tempdir.path().to_str().unwrap();
|
||||
|
||||
Command::new("fish")
|
||||
Command::new("fish_indent")
|
||||
.env("HOME", tempdir)
|
||||
.args(&["--command", "fish_indent", "--private"])
|
||||
.write_stdin(source.to_string())
|
||||
.assert()
|
||||
.success()
|
||||
|
@ -171,22 +159,17 @@ mod tests {
|
|||
.stderr("");
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn nushell_nushell(
|
||||
#[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,
|
||||
) {
|
||||
#[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().to_str().unwrap();
|
||||
let tempdir = tempdir.path();
|
||||
|
||||
let assert = Command::new("nu")
|
||||
.env("HOME", tempdir)
|
||||
.args(&["--commands", &source])
|
||||
.args(["--commands", &source])
|
||||
.assert()
|
||||
.success()
|
||||
.stderr("");
|
||||
|
@ -196,55 +179,40 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn posix_bashposix(
|
||||
#[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,
|
||||
) {
|
||||
#[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();
|
||||
|
||||
let assert = Command::new("bash")
|
||||
.args(&["--posix", "--noprofile", "--norc", "-c", &source])
|
||||
.args(["--posix", "--noprofile", "--norc", "-e", "-u", "-o", "pipefail", "-c", &source])
|
||||
.assert()
|
||||
.success()
|
||||
.stderr("");
|
||||
|
||||
if opts.hook != InitHook::Pwd {
|
||||
assert.stdout("");
|
||||
}
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn posix_dash(
|
||||
#[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,
|
||||
) {
|
||||
#[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();
|
||||
|
||||
let assert = Command::new("dash").args(&["-c", &source]).assert().success().stderr("");
|
||||
let assert =
|
||||
Command::new("dash").args(["-e", "-u", "-c", &source]).assert().success().stderr("");
|
||||
if opts.hook != InitHook::Pwd {
|
||||
assert.stdout("");
|
||||
}
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn posix_shellcheck_(
|
||||
#[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,
|
||||
) {
|
||||
#[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", "--shell", "sh", "-"])
|
||||
.args(["--enable=all", "-"])
|
||||
.write_stdin(source)
|
||||
.assert()
|
||||
.success()
|
||||
|
@ -252,19 +220,14 @@ mod tests {
|
|||
.stderr("");
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn posix_shfmt(
|
||||
#[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,
|
||||
) {
|
||||
#[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(&["-d", "-s", "-ln", "posix", "-i", "4", "-ci", "-"])
|
||||
.args(["--diff", "--indent=4", "--language-dialect=posix", "--simplify", "-"])
|
||||
.write_stdin(source)
|
||||
.assert()
|
||||
.success()
|
||||
|
@ -272,114 +235,95 @@ mod tests {
|
|||
.stderr("");
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn powershell_pwsh(
|
||||
#[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,
|
||||
) {
|
||||
#[apply(opts)]
|
||||
fn powershell_pwsh(cmd: Option<&str>, hook: InitHook, echo: bool, resolve_symlinks: bool) {
|
||||
let opts = Opts { cmd, hook, echo, resolve_symlinks };
|
||||
let source = Powershell(&opts).render().unwrap();
|
||||
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])
|
||||
.args(["-NoLogo", "-NonInteractive", "-NoProfile", "-Command", &source])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn xonsh_black(
|
||||
#[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,
|
||||
) {
|
||||
#[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("");
|
||||
}
|
||||
|
||||
#[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", "-"])
|
||||
.args(["--check", "--diff", "-"])
|
||||
.write_stdin(source)
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("");
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn xonsh_mypy(
|
||||
#[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,
|
||||
) {
|
||||
#[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]).assert().success().stderr("");
|
||||
Command::new("mypy").args(["--command", &source, "--strict"]).assert().success().stderr("");
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn xonsh_pylint(
|
||||
#[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,
|
||||
) {
|
||||
#[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", "zoxide"])
|
||||
.args(["--from-stdin", "--persistent=n", "zoxide"])
|
||||
.write_stdin(source)
|
||||
.assert()
|
||||
.success()
|
||||
.stderr("");
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn xonsh_xonsh(
|
||||
#[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,
|
||||
) {
|
||||
#[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();
|
||||
|
||||
// We can't pass the source directly to `xonsh -c` due to
|
||||
// a bug: <https://github.com/xonsh/xonsh/issues/3959>
|
||||
let tempdir = tempfile::tempdir().unwrap();
|
||||
let tempdir = tempdir.path().to_str().unwrap();
|
||||
|
||||
Command::new("xonsh")
|
||||
.args(&[
|
||||
"-c",
|
||||
"import sys; execx(sys.stdin.read(), 'exec', __xonsh__.ctx, filename='zoxide')",
|
||||
"--no-rc",
|
||||
])
|
||||
.write_stdin(source.as_bytes())
|
||||
.args(["-c", &source, "--no-rc"])
|
||||
.env("HOME", tempdir)
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn zsh_shellcheck(
|
||||
#[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,
|
||||
) {
|
||||
#[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
|
||||
// ShellCheck doesn't support zsh yet: https://github.com/koalaman/shellcheck/issues/809
|
||||
Command::new("shellcheck")
|
||||
.args(&["--enable", "all", "--shell", "bash", "-"])
|
||||
.args(["--enable=all", "-"])
|
||||
.write_stdin(source)
|
||||
.assert()
|
||||
.success()
|
||||
|
@ -387,18 +331,13 @@ mod tests {
|
|||
.stderr("");
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn zsh_zsh(
|
||||
#[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,
|
||||
) {
|
||||
#[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(&["-c", &source, "--no-rcs"])
|
||||
.args(["-e", "-u", "-o", "pipefail", "--no-globalrcs", "--no-rcs", "-c", &source])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
|
|
310
src/util.rs
310
src/util.rs
|
@ -1,13 +1,252 @@
|
|||
use crate::db::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,17 +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()))
|
||||
}
|
||||
|
||||
/// 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;
|
||||
|
||||
|
@ -45,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();
|
||||
|
||||
|
@ -82,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) => {
|
||||
|
@ -120,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();
|
||||
|
@ -149,12 +377,8 @@ pub fn resolve_path<P: AsRef<Path>>(path: &P) -> Result<PathBuf> {
|
|||
Ok(stack.iter().collect())
|
||||
}
|
||||
|
||||
// Convert a string to lowercase, with a fast path for ASCII strings.
|
||||
pub fn to_lowercase<S: AsRef<str>>(s: S) -> String {
|
||||
/// 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()
|
||||
}
|
||||
if s.is_ascii() { s.to_ascii_lowercase() } else { s.to_lowercase() }
|
||||
}
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
{%- let section = "# =============================================================================\n#" -%}
|
||||
{%- let not_configured = "# -- not configured --" -%}
|
||||
|
||||
# shellcheck shell=bash
|
||||
|
||||
{{ section }}
|
||||
# Utility functions for zoxide.
|
||||
#
|
||||
|
||||
# pwd based on the value of _ZO_RESOLVE_SYMLINKS.
|
||||
function __zoxide_pwd() {
|
||||
{%- if resolve_symlinks %}
|
||||
{%- if cfg!(windows) %}
|
||||
\command cygpath -w "$(\builtin pwd -P)"
|
||||
{%- else if resolve_symlinks %}
|
||||
\builtin pwd -P
|
||||
{%- else %}
|
||||
\builtin pwd -L
|
||||
|
@ -17,114 +21,171 @@ function __zoxide_pwd() {
|
|||
# cd + custom logic based on the value of _ZO_ECHO.
|
||||
function __zoxide_cd() {
|
||||
# shellcheck disable=SC2164
|
||||
\builtin cd "$@" {%- if echo %} && __zoxide_pwd {%- endif %}
|
||||
\builtin cd -- "$@" {%- if echo %} && __zoxide_pwd {%- endif %}
|
||||
}
|
||||
|
||||
{{ section }}
|
||||
# Hook configuration for zoxide.
|
||||
#
|
||||
|
||||
{# Custom prompts often use "$?" to show the exit status of the previous
|
||||
# command. Adding __zoxide_hook to the front of $PROMPT_COMMAND would change
|
||||
# the exit status, so we must capture it and return it manually instead. -#}
|
||||
{%- if hook != InitHook::None %}
|
||||
|
||||
# Hook to add new entries to the database.
|
||||
{%- match hook %}
|
||||
{%- when InitHook::None %}
|
||||
{{ not_configured }}
|
||||
|
||||
{%- when InitHook::Prompt %}
|
||||
{%- if hook == InitHook::Prompt %}
|
||||
function __zoxide_hook() {
|
||||
\builtin local -r __zoxide_retval="$?"
|
||||
zoxide add -- "$(__zoxide_pwd)"
|
||||
return "${__zoxide_retval}"
|
||||
\builtin local -r retval="$?"
|
||||
# shellcheck disable=SC2312
|
||||
\command zoxide add -- "$(__zoxide_pwd)"
|
||||
return "${retval}"
|
||||
}
|
||||
|
||||
{%- when InitHook::Pwd %}
|
||||
{%- else if hook == InitHook::Pwd %}
|
||||
__zoxide_oldpwd="$(__zoxide_pwd)"
|
||||
|
||||
function __zoxide_hook() {
|
||||
\builtin local -r __zoxide_retval="$?"
|
||||
\builtin 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 "${__zoxide_retval}"
|
||||
return "${retval}"
|
||||
}
|
||||
|
||||
{%- endmatch %}
|
||||
|
||||
{# bash throws an error if $PROMPT_COMMAND contains two semicolons in sequence.
|
||||
# This is hard to avoid perfectly, but adding __zoxide_hook to the front of
|
||||
# $PROMPT_COMMAND rather than the back makes this scenario unlikely. -#}
|
||||
{%- endif %}
|
||||
|
||||
# Initialize hook.
|
||||
if [ "${__zoxide_hooked}" != '1' ]; then
|
||||
__zoxide_hooked='1'
|
||||
{%- if hook == InitHook::None %}
|
||||
{{ not_configured }}
|
||||
{%- else %}
|
||||
PROMPT_COMMAND="__zoxide_hook;${PROMPT_COMMAND:+${PROMPT_COMMAND}}"
|
||||
{%- endif %}
|
||||
if [[ ${PROMPT_COMMAND:=} != *'__zoxide_hook'* ]]; then
|
||||
PROMPT_COMMAND="__zoxide_hook;${PROMPT_COMMAND#;}"
|
||||
fi
|
||||
|
||||
{%- endif %}
|
||||
|
||||
# Report common issues.
|
||||
function __zoxide_doctor() {
|
||||
{%- if hook == InitHook::None %}
|
||||
return 0
|
||||
|
||||
{%- else %}
|
||||
[[ ${_ZO_DOCTOR:-1} -eq 0 ]] && return 0
|
||||
# shellcheck disable=SC2199
|
||||
[[ ${PROMPT_COMMAND[@]:-} == *'__zoxide_hook'* ]] && return 0
|
||||
# shellcheck disable=SC2199
|
||||
[[ ${__vsc_original_prompt_command[@]:-} == *'__zoxide_hook'* ]] && return 0
|
||||
|
||||
_ZO_DOCTOR=0
|
||||
\builtin printf '%s\n' \
|
||||
'zoxide: detected a possible configuration issue.' \
|
||||
'Please ensure that zoxide is initialized right at the end of your shell configuration file (usually ~/.bashrc).' \
|
||||
'' \
|
||||
'If the issue persists, consider filing an issue at:' \
|
||||
'https://github.com/ajeetdsouza/zoxide/issues' \
|
||||
'' \
|
||||
'Disable this message by setting _ZO_DOCTOR=0.' \
|
||||
'' >&2
|
||||
{%- endif %}
|
||||
}
|
||||
|
||||
{{ section }}
|
||||
# When using zoxide with --no-aliases, alias these internal functions as
|
||||
# desired.
|
||||
# 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
|
||||
# shellcheck disable=SC2016
|
||||
\builtin printf 'zoxide: $OLDPWD is not set\n'
|
||||
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
|
||||
\builtin local __zoxide_result
|
||||
__zoxide_result="$(zoxide query --exclude "$(__zoxide_pwd)" -- "$@")" && __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() {
|
||||
\builtin 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}"
|
||||
}
|
||||
|
||||
{{ section }}
|
||||
# Convenient aliases for zoxide. Disable these using --no-aliases.
|
||||
# Commands for zoxide. Disable these using --no-cmd.
|
||||
#
|
||||
|
||||
{%- match cmd %}
|
||||
{%- when Some with (cmd) %}
|
||||
|
||||
# Remove definitions.
|
||||
function __zoxide_unset() {
|
||||
# shellcheck disable=SC1001
|
||||
\builtin unset -f "$@" &>/dev/null
|
||||
# shellcheck disable=SC1001
|
||||
\builtin unset -v "$@" &>/dev/null
|
||||
}
|
||||
|
||||
__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 "$@"
|
||||
}
|
||||
|
||||
# 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
|
||||
|
||||
function __zoxide_z_complete_helper() {
|
||||
READLINE_LINE="{{ cmd }} ${__zoxide_result@Q}"
|
||||
READLINE_POINT={{ "${#READLINE_LINE}" }}
|
||||
bind '"\e[0n": accept-line'
|
||||
\builtin printf '\e[5n' >/dev/tty
|
||||
}
|
||||
|
||||
function __zoxide_z_complete() {
|
||||
# Only show completions when the cursor is at the end of the line.
|
||||
[[ {{ "${#COMP_WORDS[@]}" }} -eq $((COMP_CWORD + 1)) ]] || return
|
||||
|
||||
# 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}/")
|
||||
|
||||
# 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 }}
|
||||
|
@ -132,6 +193,6 @@ function {{cmd}}i() {
|
|||
{%- endmatch %}
|
||||
|
||||
{{ section }}
|
||||
# To initialize zoxide, add this to your configuration (usually ~/.bashrc):
|
||||
# To initialize zoxide, add this to your shell configuration file (usually ~/.bashrc):
|
||||
#
|
||||
# eval "$(zoxide init bash)"
|
||||
|
|
|
@ -9,7 +9,7 @@ use path
|
|||
#
|
||||
|
||||
# cd + custom logic based on the value of _ZO_ECHO.
|
||||
fn __zoxide_cd [path]{
|
||||
fn __zoxide_cd {|path|
|
||||
builtin:cd $path
|
||||
{%- if echo %}
|
||||
builtin:echo $pwd
|
||||
|
@ -20,58 +20,90 @@ fn __zoxide_cd [path]{
|
|||
# Hook configuration for zoxide.
|
||||
#
|
||||
|
||||
if (not (and (builtin:has-env __zoxide_hooked) (builtin:eq (builtin:get-env __zoxide_hooked) 1))) {
|
||||
builtin:set-env __zoxide_hooked 1
|
||||
# Initialize hook to track previous directory.
|
||||
var oldpwd = $builtin:pwd
|
||||
set builtin:before-chdir = [$@builtin:before-chdir {|_| set oldpwd = $builtin:pwd }]
|
||||
|
||||
# Initialize hook to track previous directory.
|
||||
builtin:set-env __zoxide_oldpwd $pwd
|
||||
before-chdir = [$@before-chdir [_]{ builtin:set-env __zoxide_oldpwd $pwd }]
|
||||
# Initialize hook to add directories to zoxide.
|
||||
{%- if hook == InitHook::None %}
|
||||
{{ not_configured }}
|
||||
|
||||
# Initialize hook to add directories to zoxide.
|
||||
{%- match hook %}
|
||||
{%- when InitHook::None %}
|
||||
{{ not_configured }}
|
||||
{%- when InitHook::Prompt %}
|
||||
edit:before-readline = [$@edit:before-readline []{ zoxide add -- $pwd }]
|
||||
{%- when InitHook::Pwd %}
|
||||
after-chdir = [$@after-chdir [_]{ zoxide add -- $pwd }]
|
||||
{%- endmatch %}
|
||||
{%- 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-aliases, alias these internal functions as
|
||||
# desired.
|
||||
# When using zoxide with --no-cmd, alias these internal functions as desired.
|
||||
#
|
||||
|
||||
# Jump to a directory using only keywords.
|
||||
fn __zoxide_z [@rest]{
|
||||
fn __zoxide_z {|@rest|
|
||||
if (builtin:eq [] $rest) {
|
||||
__zoxide_cd ~
|
||||
} elif (builtin:eq [-] $rest) {
|
||||
__zoxide_cd (builtin:get-env __zoxide_oldpwd)
|
||||
} elif (and (builtin:eq (builtin:count $rest) 1) (path:is-dir $rest[0])) {
|
||||
__zoxide_cd $oldpwd
|
||||
} elif (and ('builtin:==' (builtin:count $rest) 1) (path:is-dir &follow-symlink=$true $rest[0])) {
|
||||
__zoxide_cd $rest[0]
|
||||
} else {
|
||||
__zoxide_cd (zoxide query --exclude $pwd -- $@rest)
|
||||
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]{
|
||||
__zoxide_cd (zoxide query -i -- $@rest)
|
||||
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 }}
|
||||
# Convenient aliases for zoxide. Disable these using --no-aliases.
|
||||
# Commands for zoxide. Disable these using --no-cmd.
|
||||
#
|
||||
|
||||
{%- match cmd %}
|
||||
{%- when Some with (cmd) %}
|
||||
|
||||
edit:add-var z~ $__zoxide_z~
|
||||
edit:add-var zi~ $__zoxide_zi~
|
||||
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 %}
|
||||
|
||||
|
@ -83,4 +115,6 @@ edit:add-var zi~ $__zoxide_zi~
|
|||
# To initialize zoxide, add this to your configuration (usually
|
||||
# ~/.elvish/rc.elv):
|
||||
#
|
||||
# eval (zoxide init elvish | slurp)
|
||||
# eval (zoxide init elvish | slurp)
|
||||
#
|
||||
# Note: zoxide only supports elvish v0.18.0 and above.
|
||||
|
|
|
@ -7,7 +7,9 @@
|
|||
|
||||
# pwd based on the value of _ZO_RESOLVE_SYMLINKS.
|
||||
function __zoxide_pwd
|
||||
{%- if resolve_symlinks %}
|
||||
{%- if cfg!(windows) %}
|
||||
command cygpath -w (builtin pwd -P)
|
||||
{%- else if resolve_symlinks %}
|
||||
builtin pwd -P
|
||||
{%- else %}
|
||||
builtin pwd -L
|
||||
|
@ -16,90 +18,105 @@ end
|
|||
|
||||
# A copy of fish's internal cd function. This makes it possible to use
|
||||
# `alias cd=z` without causing an infinite loop.
|
||||
if ! builtin functions -q __zoxide_cd_internal
|
||||
if builtin functions -q cd
|
||||
builtin functions -c cd __zoxide_cd_internal
|
||||
else
|
||||
alias __zoxide_cd_internal="builtin cd"
|
||||
end
|
||||
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
|
||||
__zoxide_cd_internal $argv
|
||||
if set -q __zoxide_loop
|
||||
builtin echo "zoxide: infinite loop detected"
|
||||
builtin echo "Avoid aliasing `cd` to `z` directly, use `zoxide init --cmd=cd fish` instead"
|
||||
return 1
|
||||
end
|
||||
|
||||
{%- if cfg!(windows) %}
|
||||
__zoxide_loop=1 __zoxide_cd_internal (cygpath -u $argv)
|
||||
{%- else %}
|
||||
__zoxide_loop=1 __zoxide_cd_internal $argv
|
||||
{%- endif %}
|
||||
{%- if echo %}
|
||||
and __zoxide_pwd
|
||||
{%- endif %}
|
||||
and builtin commandline -f repaint
|
||||
end
|
||||
|
||||
{{ section }}
|
||||
# Hook configuration for zoxide.
|
||||
#
|
||||
|
||||
{% if hook == InitHook::None -%}
|
||||
{{ not_configured }}
|
||||
|
||||
{%- else -%}
|
||||
# Initialize hook to add new entries to the database.
|
||||
if test "$__zoxide_hooked" != 1
|
||||
set __zoxide_hooked 1
|
||||
{%- match hook %}
|
||||
{%- when InitHook::None %}
|
||||
function __zoxide_hook
|
||||
{%- when InitHook::Prompt %}
|
||||
function __zoxide_hook --on-event fish_prompt
|
||||
{%- when InitHook::Pwd %}
|
||||
function __zoxide_hook --on-variable PWD
|
||||
{%- endmatch %}
|
||||
test -z "$fish_private_mode"
|
||||
and command zoxide add -- (__zoxide_pwd)
|
||||
end
|
||||
{%- if hook == InitHook::Prompt %}
|
||||
function __zoxide_hook --on-event fish_prompt
|
||||
{%- else if hook == InitHook::Pwd %}
|
||||
function __zoxide_hook --on-variable PWD
|
||||
{%- endif %}
|
||||
test -z "$fish_private_mode"
|
||||
and command zoxide add -- (__zoxide_pwd)
|
||||
end
|
||||
|
||||
{%- endif %}
|
||||
|
||||
{{ section }}
|
||||
# When using zoxide with --no-aliases, alias these internal functions as
|
||||
# desired.
|
||||
# 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 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 (command zoxide query --exclude (__zoxide_pwd) -- $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 (command zoxide query -i -- $argv)
|
||||
and __zoxide_cd $__zoxide_result
|
||||
set -l result (command zoxide query --interactive -- $argv)
|
||||
and __zoxide_cd $result
|
||||
end
|
||||
|
||||
{{ section }}
|
||||
# Convenient aliases for zoxide. Disable these using --no-aliases.
|
||||
# Commands for zoxide. Disable these using --no-cmd.
|
||||
#
|
||||
|
||||
{%- match cmd %}
|
||||
{%- when Some with (cmd) %}
|
||||
|
||||
# Remove definitions.
|
||||
function __zoxide_unset
|
||||
set --erase $argv >/dev/null 2>&1
|
||||
abbr --erase $argv >/dev/null 2>&1
|
||||
builtin functions --erase $argv >/dev/null 2>&1
|
||||
end
|
||||
abbr --erase {{cmd}} &>/dev/null
|
||||
alias {{cmd}}=__zoxide_z
|
||||
|
||||
__zoxide_unset {{cmd}}
|
||||
alias {{cmd}}="__zoxide_z"
|
||||
|
||||
__zoxide_unset {{cmd}}i
|
||||
alias {{cmd}}i="__zoxide_zi"
|
||||
abbr --erase {{cmd}}i &>/dev/null
|
||||
alias {{cmd}}i=__zoxide_zi
|
||||
|
||||
{%- when None %}
|
||||
|
||||
|
@ -111,4 +128,4 @@ alias {{cmd}}i="__zoxide_zi"
|
|||
# To initialize zoxide, add this to your configuration (usually
|
||||
# ~/.config/fish/config.fish):
|
||||
#
|
||||
# zoxide init fish | source
|
||||
# zoxide init fish | source
|
||||
|
|
|
@ -1,83 +1,86 @@
|
|||
{%- let section = "# =============================================================================\n#" -%}
|
||||
{%- let not_configured = "# -- not configured --" -%}
|
||||
|
||||
{{ section }}
|
||||
# Utility functions for zoxide.
|
||||
#
|
||||
|
||||
# Default prompt for Nushell.
|
||||
def __zoxide_prompt [] {
|
||||
let git = $"(do -i {git rev-parse --abbrev-ref HEAD} | str trim)"
|
||||
let git = (if (echo $git | str length) == 0 {
|
||||
""
|
||||
} {
|
||||
build-string (char lparen) (ansi cb) $git (ansi reset) (char rparen)
|
||||
})
|
||||
build-string (ansi gb) (pwd) (ansi reset) $git "> "
|
||||
}
|
||||
# Code generated by zoxide. DO NOT EDIT.
|
||||
|
||||
{{ section }}
|
||||
# Hook configuration for zoxide.
|
||||
#
|
||||
|
||||
# Hook to add new entries to the database.
|
||||
{%- match hook %}
|
||||
{%- when InitHook::None %}
|
||||
def __zoxide_hook [] {}
|
||||
{% if hook == InitHook::None -%}
|
||||
{{ not_configured }}
|
||||
|
||||
{%- when InitHook::Prompt %}
|
||||
def __zoxide_hook [] {
|
||||
shells | where active == $true && name == filesystem | get path | each { zoxide add -- $it }
|
||||
{%- 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 %}
|
||||
}
|
||||
|
||||
{%- when InitHook::Pwd %}
|
||||
def __zoxide_hook [] {}
|
||||
$"zoxide: PWD hooks are not supported on Nushell.(char nl)Use 'zoxide init nushell --hook prompt' instead.(char nl)"
|
||||
{%- endmatch %}
|
||||
{%- endif %}
|
||||
|
||||
{{ section }}
|
||||
# When using zoxide with --no-aliases, alias these internal functions as
|
||||
# desired.
|
||||
# When using zoxide with --no-cmd, alias these internal functions as desired.
|
||||
#
|
||||
|
||||
# Jump to a directory using only keywords.
|
||||
def __zoxide_z [...rest:string] {
|
||||
if (shells | where active == $true | get name) != filesystem {
|
||||
if (echo $rest | length) > 1 {
|
||||
$"zoxide: can only jump directories on filesystem(char nl)"
|
||||
} {
|
||||
cd (echo $rest)
|
||||
{%- if echo %}
|
||||
pwd
|
||||
{%- endif %}
|
||||
}
|
||||
} {
|
||||
let arg0 = (echo $rest | append '~' | first 1);
|
||||
if (echo $rest | length) <= 1 && ($arg0 == '-' || (echo $arg0 | path expand | path exists)) {
|
||||
cd $arg0
|
||||
} {
|
||||
cd (zoxide query --exclude (pwd) -- $rest | str trim)
|
||||
}
|
||||
{%- if echo %}
|
||||
pwd
|
||||
{%- endif %}
|
||||
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 __zoxide_zi [...rest:string] {
|
||||
if (shells | where active == $true | get name) != filesystem {
|
||||
$"zoxide: can only jump directories on filesystem(char nl)"
|
||||
} {
|
||||
cd (zoxide query -i -- $rest | str trim)
|
||||
def --env --wrapped __zoxide_zi [...rest:string] {
|
||||
cd $'(^zoxide query --interactive -- ...$rest | str trim -r -c "\n")'
|
||||
{%- if echo %}
|
||||
pwd
|
||||
echo $env.PWD
|
||||
{%- endif %}
|
||||
}
|
||||
}
|
||||
|
||||
{{ section }}
|
||||
# Convenient aliases for zoxide. Disable these using --no-aliases.
|
||||
# Commands for zoxide. Disable these using --no-cmd.
|
||||
#
|
||||
|
||||
{%- match cmd %}
|
||||
|
@ -93,13 +96,13 @@ alias {{cmd}}i = __zoxide_zi
|
|||
{%- endmatch %}
|
||||
|
||||
{{ section }}
|
||||
# To initialize zoxide, first create a Nushell script:
|
||||
# Add this to your env file (find it by running `$nu.env-path` in Nushell):
|
||||
#
|
||||
# zoxide init nushell --hook prompt | save ~/.zoxide.nu
|
||||
# zoxide init nushell | save -f ~/.zoxide.nu
|
||||
#
|
||||
# Add this to your configuration (usually ~/.config/nu/config.toml):
|
||||
# Now, add this to the end of your config file (find it by running
|
||||
# `$nu.config-path` in Nushell):
|
||||
#
|
||||
# prompt = "__zoxide_hook;__zoxide_prompt"
|
||||
# startup = ["zoxide init nushell --hook prompt | save ~/.zoxide.nu", "source ~/.zoxide.nu"]
|
||||
# source ~/.zoxide.nu
|
||||
#
|
||||
# You can replace __zoxide_prompt with a custom prompt.
|
||||
# Note: zoxide only supports Nushell v0.89.0+.
|
||||
|
|
|
@ -1,66 +1,87 @@
|
|||
{%- let section = "# =============================================================================\n#" -%}
|
||||
{%- let not_configured = "# -- not configured --" -%}
|
||||
|
||||
# shellcheck shell=sh
|
||||
|
||||
{{ 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 }}
|
||||
# Hook configuration for zoxide.
|
||||
#
|
||||
|
||||
# Hook to add new entries to the database.
|
||||
{%- match hook %}
|
||||
{%- when InitHook::None %}
|
||||
{% match hook %}
|
||||
{%- when InitHook::None -%}
|
||||
{{ not_configured }}
|
||||
|
||||
{%- when InitHook::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 InitHook::Pwd %}
|
||||
{{ not_configured }}
|
||||
|
||||
{%- endmatch %}
|
||||
|
||||
# Initialize hook.
|
||||
if [ "${__zoxide_hooked}" != '1' ]; then
|
||||
__zoxide_hooked='1'
|
||||
{%- match hook %}
|
||||
{%- when InitHook::None %}
|
||||
{{ not_configured }}
|
||||
{%- when InitHook::Prompt %}
|
||||
if [ "${PS1:=}" = "${PS1#*\$(__zoxide_hook)}" ]; then
|
||||
PS1="${PS1}\$(__zoxide_hook)"
|
||||
{%- when InitHook::Pwd %}
|
||||
\printf "%s\n%s\n" \
|
||||
"zoxide: PWD hooks are not supported on POSIX shells." \
|
||||
" Use 'zoxide init posix --hook prompt' instead."
|
||||
{%- endmatch %}
|
||||
fi
|
||||
|
||||
# Report common issues.
|
||||
__zoxide_doctor() {
|
||||
{%- if hook != InitHook::Prompt %}
|
||||
return 0
|
||||
{%- else %}
|
||||
[ "${_ZO_DOCTOR:-1}" -eq 0 ] && return 0
|
||||
case "${PS1:-}" in
|
||||
*__zoxide_hook*) return 0 ;;
|
||||
*) ;;
|
||||
esac
|
||||
|
||||
_ZO_DOCTOR=0
|
||||
\command printf '%s\n' \
|
||||
'zoxide: detected a possible configuration issue.' \
|
||||
'Please ensure that zoxide is initialized right at the end of your shell configuration file.' \
|
||||
'' \
|
||||
'If the issue persists, consider filing an issue at:' \
|
||||
'https://github.com/ajeetdsouza/zoxide/issues' \
|
||||
'' \
|
||||
'Disable this message by setting _ZO_DOCTOR=0.' \
|
||||
'' >&2
|
||||
{%- endif %}
|
||||
}
|
||||
|
||||
{%- when InitHook::Pwd -%}
|
||||
\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.
|
||||
# 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
|
||||
|
@ -68,42 +89,36 @@ __zoxide_z() {
|
|||
__zoxide_cd "${OLDPWD}"
|
||||
else
|
||||
# shellcheck disable=SC2016
|
||||
\printf 'zoxide: $OLDPWD is not set'
|
||||
\command printf 'zoxide: $OLDPWD is not set'
|
||||
return 1
|
||||
fi
|
||||
elif [ "$#" -eq 1 ] && [ -d "$1" ]; then
|
||||
__zoxide_cd "$1"
|
||||
else
|
||||
__zoxide_result="$(zoxide query --exclude "$(__zoxide_pwd)" -- "$@")" && __zoxide_cd "${__zoxide_result}"
|
||||
__zoxide_result="$(\command zoxide query --exclude "$(__zoxide_pwd || \builtin true)" -- "$@")" &&
|
||||
__zoxide_cd "${__zoxide_result}"
|
||||
fi
|
||||
}
|
||||
|
||||
# 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}"
|
||||
}
|
||||
|
||||
{{ section }}
|
||||
# Convenient aliases for zoxide. Disable these using --no-aliases.
|
||||
# Commands for zoxide. Disable these using --no-cmd.
|
||||
#
|
||||
|
||||
{%- match cmd %}
|
||||
{%- when Some with (cmd) %}
|
||||
|
||||
# Remove definitions.
|
||||
__zoxide_unset() {
|
||||
# shellcheck disable=SC1001
|
||||
\unset -f "$@" >/dev/null 2>&1
|
||||
# shellcheck disable=SC1001
|
||||
\unset -v "$@" >/dev/null 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 "$@"
|
||||
}
|
||||
|
|
|
@ -5,19 +5,43 @@
|
|||
# Utility functions for zoxide.
|
||||
#
|
||||
|
||||
# Call zoxide binary, returning the output as UTF-8.
|
||||
function global:__zoxide_bin {
|
||||
$encoding = [Console]::OutputEncoding
|
||||
try {
|
||||
[Console]::OutputEncoding = [System.Text.Utf8Encoding]::new()
|
||||
$result = zoxide @args
|
||||
return $result
|
||||
} finally {
|
||||
[Console]::OutputEncoding = $encoding
|
||||
}
|
||||
}
|
||||
|
||||
# pwd based on zoxide's format.
|
||||
function __zoxide_pwd {
|
||||
$__zoxide_pwd = Get-Location
|
||||
if ($__zoxide_pwd.Provider.Name -eq "FileSystem") {
|
||||
$__zoxide_pwd.ProviderPath
|
||||
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
|
||||
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 %}
|
||||
$(Get-Location).Path
|
||||
Write-Output $dir.Path
|
||||
{%- endif %}
|
||||
}
|
||||
|
||||
|
@ -25,85 +49,100 @@ function __zoxide_cd($dir) {
|
|||
# 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_result = __zoxide_pwd
|
||||
if ($__zoxide_result -ne $null) {
|
||||
zoxide add -- $__zoxide_result
|
||||
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.
|
||||
if ($__zoxide_hooked -ne '1') {
|
||||
$__zoxide_hooked = '1'
|
||||
{%- match hook %}
|
||||
{%- when InitHook::None %}
|
||||
{{ not_configured }}
|
||||
{%- when InitHook::Prompt %}
|
||||
$__zoxide_prompt_old = $function:prompt
|
||||
function prompt {
|
||||
$null = __zoxide_hook
|
||||
& $__zoxide_prompt_old
|
||||
}
|
||||
{%- when InitHook::Pwd %}
|
||||
if ($PSVersionTable.PSVersion.Major -ge 6) {
|
||||
$ExecutionContext.InvokeCommand.LocationChangedAction = {
|
||||
$null = __zoxide_hook
|
||||
$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
|
||||
|
||||
function global:prompt {
|
||||
if ($null -ne $__zoxide_prompt_old) {
|
||||
& $__zoxide_prompt_old
|
||||
}
|
||||
} else {
|
||||
Write-Error ("`n" +
|
||||
"zoxide: PWD hooks are not supported below powershell 6.`n" +
|
||||
" Use 'zoxide init powershell --hook prompt' instead.")
|
||||
$null = __zoxide_hook
|
||||
}
|
||||
{%- endmatch %}
|
||||
}
|
||||
{%- endif %}
|
||||
|
||||
{{ section }}
|
||||
# When using zoxide with --no-aliases, alias these internal functions as
|
||||
# desired.
|
||||
# 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_pwd
|
||||
if ($__zoxide_result -ne $null) {
|
||||
$__zoxide_result = zoxide query --exclude $__zoxide_result -- @args
|
||||
} 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
|
||||
}
|
||||
}
|
||||
|
||||
{{ section }}
|
||||
# Convenient aliases for zoxide. Disable these using --no-aliases.
|
||||
# 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 -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 %}
|
||||
|
||||
|
@ -112,7 +151,7 @@ Set-Alias {{cmd}}i __zoxide_zi
|
|||
{%- endmatch %}
|
||||
|
||||
{{ section }}
|
||||
# To initialize zoxide, add this to your configuration (the location is stored
|
||||
# in $profile):
|
||||
# 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,20 +1,14 @@
|
|||
{%- let section = "# =============================================================================\n#" -%}
|
||||
{%- let not_configured = "# -- not configured --" -%}
|
||||
|
||||
"""Initialize zoxide on Xonsh."""
|
||||
# pylint: disable=missing-module-docstring
|
||||
|
||||
import builtins # pylint: disable=unused-import
|
||||
import os
|
||||
import os.path
|
||||
import subprocess
|
||||
import sys
|
||||
{%- if cmd.is_some() %}
|
||||
from builtins import aliases # type: ignore # pylint: disable=no-name-in-module
|
||||
{%- endif %}
|
||||
{%- if hook != InitHook::None %}
|
||||
from builtins import events # type: ignore # pylint: disable=no-name-in-module
|
||||
{%- endif %}
|
||||
from subprocess import CalledProcessError
|
||||
from typing import AnyStr, List, Optional
|
||||
import typing
|
||||
|
||||
import xonsh.dirstack # type: ignore # pylint: disable=import-error
|
||||
import xonsh.environ # type: ignore # pylint: disable=import-error
|
||||
|
@ -26,35 +20,40 @@ import xonsh.environ # type: ignore # pylint: disable=import-error
|
|||
|
||||
def __zoxide_bin() -> str:
|
||||
"""Finds and returns the location of the zoxide binary."""
|
||||
zoxide = xonsh.environ.locate_binary("zoxide")
|
||||
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 %}
|
||||
pwd = os.getcwd()
|
||||
{%- else %}
|
||||
pwd = os.getenv("PWD")
|
||||
pwd = __zoxide_env().get("PWD")
|
||||
if pwd is None:
|
||||
raise Exception("$PWD not found in env")
|
||||
raise RuntimeError("$PWD not found")
|
||||
{%- endif %}
|
||||
return pwd
|
||||
|
||||
|
||||
def __zoxide_cd(path: Optional[AnyStr] = None):
|
||||
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")]
|
||||
elif isinstance(path, str):
|
||||
else:
|
||||
args = [path]
|
||||
_, exc, _ = xonsh.dirstack.cd(args)
|
||||
if exc is not None:
|
||||
raise Exception(exc)
|
||||
raise RuntimeError(exc)
|
||||
{%- if echo %}
|
||||
print(__zoxide_pwd())
|
||||
{%- endif %}
|
||||
|
@ -64,10 +63,12 @@ class ZoxideSilentException(Exception):
|
|||
"""Exit without complaining."""
|
||||
|
||||
|
||||
def __zoxide_errhandler(func):
|
||||
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]):
|
||||
def wrapper(args: list[str]) -> int:
|
||||
try:
|
||||
func(args)
|
||||
return 0
|
||||
|
@ -84,32 +85,35 @@ def __zoxide_errhandler(func):
|
|||
# Hook configuration for zoxide.
|
||||
#
|
||||
|
||||
{% if hook == InitHook::None -%}
|
||||
{{ not_configured }}
|
||||
|
||||
{%- else -%}
|
||||
# Initialize hook to add new entries to the database.
|
||||
if globals().get("__zoxide_hooked") is not True:
|
||||
globals()["__zoxide_hooked"] = True
|
||||
{% match hook -%}
|
||||
{%- when InitHook::None %}
|
||||
{{ not_configured }}
|
||||
{%- when InitHook::Prompt %}
|
||||
@events.on_post_prompt # type: ignore # pylint:disable=undefined-variable
|
||||
{%- when InitHook::Pwd %}
|
||||
@events.on_chdir # type: ignore # pylint:disable=undefined-variable
|
||||
{%- endmatch %}
|
||||
def __zoxide_hook(**_kwargs):
|
||||
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)
|
||||
|
||||
subprocess.run(
|
||||
[zoxide, "add", "--", pwd],
|
||||
check=False,
|
||||
env=__zoxide_env(),
|
||||
)
|
||||
{% endif %}
|
||||
|
||||
{{ section }}
|
||||
# When using zoxide with --no-aliases, alias these internal functions as
|
||||
# desired.
|
||||
# When using zoxide with --no-cmd, alias these internal functions as desired.
|
||||
#
|
||||
|
||||
|
||||
@__zoxide_errhandler
|
||||
def __zoxide_z(args: List[str]):
|
||||
def __zoxide_z(args: list[str]) -> None:
|
||||
"""Jump to a directory using only keywords."""
|
||||
if args == []:
|
||||
__zoxide_cd()
|
||||
|
@ -120,41 +124,46 @@ def __zoxide_z(args: List[str]):
|
|||
else:
|
||||
try:
|
||||
zoxide = __zoxide_bin()
|
||||
__zoxide_cmd = subprocess.run(
|
||||
cmd = subprocess.run(
|
||||
[zoxide, "query", "--exclude", __zoxide_pwd(), "--"] + args,
|
||||
check=True,
|
||||
env=__zoxide_env(),
|
||||
stdout=subprocess.PIPE,
|
||||
)
|
||||
except CalledProcessError as exc:
|
||||
except subprocess.CalledProcessError as exc:
|
||||
raise ZoxideSilentException() from exc
|
||||
|
||||
__zoxide_result = __zoxide_cmd.stdout[:-1]
|
||||
__zoxide_cd(__zoxide_result)
|
||||
result = cmd.stdout[:-1]
|
||||
__zoxide_cd(result)
|
||||
|
||||
|
||||
def __zoxide_zi(args: List[str]):
|
||||
@__zoxide_errhandler
|
||||
def __zoxide_zi(args: list[str]) -> None:
|
||||
"""Jump to a directory using interactive search."""
|
||||
try:
|
||||
zoxide = __zoxide_bin()
|
||||
__zoxide_cmd = subprocess.run(
|
||||
[zoxide, "query", "-i", "--"] + args, check=True, stdout=subprocess.PIPE
|
||||
cmd = subprocess.run(
|
||||
[zoxide, "query", "-i", "--"] + args,
|
||||
check=True,
|
||||
env=__zoxide_env(),
|
||||
stdout=subprocess.PIPE,
|
||||
)
|
||||
except CalledProcessError as exc:
|
||||
except subprocess.CalledProcessError as exc:
|
||||
raise ZoxideSilentException() from exc
|
||||
|
||||
__zoxide_result = __zoxide_cmd.stdout[:-1]
|
||||
__zoxide_cd(__zoxide_result)
|
||||
result = cmd.stdout[:-1]
|
||||
__zoxide_cd(result)
|
||||
|
||||
|
||||
{{ section }}
|
||||
# Convenient aliases for zoxide. Disable these using --no-aliases.
|
||||
# Commands for zoxide. Disable these using --no-cmd.
|
||||
#
|
||||
|
||||
{%- match cmd %}
|
||||
{%- when Some with (cmd) %}
|
||||
|
||||
aliases["{{cmd}}"] = __zoxide_z
|
||||
aliases["{{cmd}}i"] = __zoxide_zi
|
||||
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 %}
|
||||
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
{%- let section = "# =============================================================================\n#" -%}
|
||||
{%- let not_configured = "# -- not configured --" -%}
|
||||
|
||||
# shellcheck shell=bash
|
||||
|
||||
{{ section }}
|
||||
# Utility functions for zoxide.
|
||||
#
|
||||
|
||||
# pwd based on the value of _ZO_RESOLVE_SYMLINKS.
|
||||
function __zoxide_pwd() {
|
||||
{%- if resolve_symlinks %}
|
||||
{%- if cfg!(windows) %}
|
||||
\command cygpath -w "$(\builtin pwd -P)"
|
||||
{%- else if resolve_symlinks %}
|
||||
\builtin pwd -P
|
||||
{%- else %}
|
||||
\builtin pwd -L
|
||||
|
@ -17,7 +21,7 @@ function __zoxide_pwd() {
|
|||
# cd + custom logic based on the value of _ZO_ECHO.
|
||||
function __zoxide_cd() {
|
||||
# shellcheck disable=SC2164
|
||||
\builtin cd "$@" {%- if echo %} && __zoxide_pwd {%- endif %}
|
||||
\builtin cd -- "$@" {%- if echo %} && __zoxide_pwd {%- endif %}
|
||||
}
|
||||
|
||||
{{ section }}
|
||||
|
@ -26,75 +30,90 @@ function __zoxide_cd() {
|
|||
|
||||
# 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.
|
||||
if [ "${__zoxide_hooked}" != '1' ]; then
|
||||
__zoxide_hooked='1'
|
||||
{%- match hook %}
|
||||
{%- when InitHook::None %}
|
||||
{{ not_configured }}
|
||||
{%- when InitHook::Prompt %}
|
||||
precmd_functions+=(__zoxide_hook)
|
||||
{%- when InitHook::Pwd %}
|
||||
chpwd_functions=("${chpwd_functions[@]}" "__zoxide_hook")
|
||||
{%- endmatch %}
|
||||
fi
|
||||
\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}")
|
||||
|
||||
{%- 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 %}
|
||||
}
|
||||
|
||||
{{ section }}
|
||||
# When using zoxide with --no-aliases, alias these internal functions as
|
||||
# desired.
|
||||
# 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
|
||||
# shellcheck disable=SC2016
|
||||
\builtin printf '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
|
||||
\builtin local __zoxide_result
|
||||
__zoxide_result="$(zoxide query --exclude "$(__zoxide_pwd)" -- "$@")" \
|
||||
&& __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() {
|
||||
\builtin 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}"
|
||||
}
|
||||
|
||||
{{ section }}
|
||||
# Convenient aliases for zoxide. Disable these using --no-aliases.
|
||||
# Commands for zoxide. Disable these using --no-cmd.
|
||||
#
|
||||
|
||||
{%- match cmd %}
|
||||
{%- when Some with (cmd) %}
|
||||
|
||||
# Remove definitions.
|
||||
function __zoxide_unset() {
|
||||
\builtin unalias "$@" &>/dev/null
|
||||
\builtin unfunction "$@" &>/dev/null
|
||||
\builtin unset "$@" &>/dev/null
|
||||
}
|
||||
|
||||
__zoxide_unset '{{cmd}}'
|
||||
function {{cmd}}() {
|
||||
function {{ cmd }}() {
|
||||
__zoxide_z "$@"
|
||||
}
|
||||
|
||||
__zoxide_unset '{{cmd}}i'
|
||||
function {{cmd}}i() {
|
||||
function {{ cmd }}i() {
|
||||
__zoxide_zi "$@"
|
||||
}
|
||||
|
||||
|
@ -104,7 +123,57 @@ function {{cmd}}i() {
|
|||
|
||||
{%- endmatch %}
|
||||
|
||||
# Completions.
|
||||
if [[ -o zle ]]; then
|
||||
__zoxide_result=''
|
||||
|
||||
function __zoxide_z_complete() {
|
||||
# Only show completions when the cursor is at the end of the line.
|
||||
# shellcheck disable=SC2154
|
||||
[[ "{{ "${#words[@]}" }}" -eq "${CURRENT}" ]] || return 0
|
||||
|
||||
if [[ "{{ "${#words[@]}" }}" -eq 2 ]]; then
|
||||
# Show completions for local directories.
|
||||
_cd -/
|
||||
|
||||
elif [[ "${words[-1]}" == '' ]]; then
|
||||
# Show completions for Space-Tab.
|
||||
# shellcheck disable=SC2086
|
||||
__zoxide_result="$(\command zoxide query --exclude "$(__zoxide_pwd || \builtin true)" --interactive -- ${words[2,-1]})" || __zoxide_result=''
|
||||
|
||||
# Set a result to ensure completion doesn't re-run
|
||||
compadd -Q ""
|
||||
|
||||
# Bind '\e[0n' to helper function.
|
||||
\builtin bindkey '\e[0n' '__zoxide_z_complete_helper'
|
||||
# Sends query device status code, which results in a '\e[0n' being sent to console input.
|
||||
\builtin printf '\e[5n'
|
||||
|
||||
# Report that the completion was successful, so that we don't fall back
|
||||
# to another completion function.
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
function __zoxide_z_complete_helper() {
|
||||
if [[ -n "${__zoxide_result}" ]]; then
|
||||
# shellcheck disable=SC2034,SC2296
|
||||
BUFFER="{{ cmd.unwrap_or("cd") }} ${(q-)__zoxide_result}"
|
||||
__zoxide_result=''
|
||||
\builtin zle reset-prompt
|
||||
\builtin zle accept-line
|
||||
else
|
||||
\builtin zle reset-prompt
|
||||
fi
|
||||
}
|
||||
\builtin zle -N __zoxide_z_complete_helper
|
||||
{%- if let Some(cmd) = cmd %}
|
||||
|
||||
[[ "${+functions[compdef]}" -ne 0 ]] && \compdef __zoxide_z_complete {{ cmd }}
|
||||
{%- endif %}
|
||||
fi
|
||||
|
||||
{{ section }}
|
||||
# To initialize zoxide, add this to your configuration (usually ~/.zshrc):
|
||||
# To initialize zoxide, add this to your shell configuration file (usually ~/.zshrc):
|
||||
#
|
||||
# eval "$(zoxide init zsh)"
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
//! Syntax checking for auto-generated shell completions.
|
||||
//! Test clap generated completions.
|
||||
#![cfg(feature = "nix-dev")]
|
||||
|
||||
#![cfg(feature = "shell_tests")]
|
||||
use assert_cmd::Command;
|
||||
|
||||
#[test]
|
||||
fn completions_bash() {
|
||||
let source = include_str!("../contrib/completions/zoxide.bash");
|
||||
Command::new("bash")
|
||||
.args(&["--noprofile", "--norc", "-c", source])
|
||||
.args(["--noprofile", "--norc", "-c", source])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
|
@ -16,8 +16,7 @@ fn completions_bash() {
|
|||
|
||||
// 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.
|
||||
// <https://github.com/elves/elvish/issues/1299>
|
||||
// script, so we can't create a test for this. See: https://github.com/elves/elvish/issues/1299
|
||||
|
||||
#[test]
|
||||
fn completions_fish() {
|
||||
|
@ -27,7 +26,7 @@ fn completions_fish() {
|
|||
|
||||
Command::new("fish")
|
||||
.env("HOME", tempdir)
|
||||
.args(&["--command", source, "--private"])
|
||||
.args(["--command", source, "--private"])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
|
@ -38,7 +37,7 @@ fn completions_fish() {
|
|||
fn completions_powershell() {
|
||||
let source = include_str!("../contrib/completions/_zoxide.ps1");
|
||||
Command::new("pwsh")
|
||||
.args(&["-NoLogo", "-NonInteractive", "-NoProfile", "-Command", source])
|
||||
.args(["-NoLogo", "-NonInteractive", "-NoProfile", "-Command", source])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
|
@ -56,5 +55,5 @@ fn completions_zsh() {
|
|||
compinit -u
|
||||
"#;
|
||||
|
||||
Command::new("zsh").args(&["-c", source, "--no-rcs"]).assert().success().stdout("").stderr("");
|
||||
Command::new("zsh").args(["-c", source, "--no-rcs"]).assert().success().stdout("").stderr("");
|
||||
}
|
|
@ -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