Merge branch 'main' into issue#458

This commit is contained in:
Ajeet D'Souza 2023-01-09 11:05:47 +05:30
commit b61f98b269
39 changed files with 1481 additions and 891 deletions

View File

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

View File

@ -1,24 +1,19 @@
name: ci name: ci
on: on:
push: push:
branches: branches: [main]
- main
- next
pull_request: pull_request:
workflow_dispatch: workflow_dispatch:
env: env:
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }} CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
CARGO_INCREMENTAL: 0 CARGO_INCREMENTAL: 0
CARGO_TERM_COLOR: always CARGO_TERM_COLOR: always
jobs: jobs:
ci: ci:
name: ${{ matrix.os }} name: ${{ matrix.os }}
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:
# FIXME: Enable macos-latest when this is merged: https://nixpk.gs/pr-tracker.html?pr=163924
os: [ubuntu-latest, windows-latest] os: [ubuntu-latest, windows-latest]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2

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

@ -0,0 +1,22 @@
name: no-response
on:
issue_comment:
types: [created]
schedule:
- cron: "0 0 * * *" # daily at 00:00
permissions:
issues: write
jobs:
no-response:
if: github.repository == 'ajeetdsouza/zoxide'
runs-on: ubuntu-latest
steps:
- uses: lee-dohm/no-response@v0.5.0
with:
token: ${{ github.token }}
daysUntilClose: 30
responseRequiredLabel: waiting-for-response
closeComment: >
This issue has been automatically closed due to inactivity. If you
feel this is still relevant, please comment here or create a fresh
issue.

View File

@ -1,15 +1,11 @@
name: release name: release
on: on:
push: push:
branches: branches: [main]
- main
- next
pull_request: pull_request:
workflow_dispatch: workflow_dispatch:
env: env:
CARGO_INCREMENTAL: 0 CARGO_INCREMENTAL: 0
jobs: jobs:
release: release:
name: ${{ matrix.target }} name: ${{ matrix.target }}
@ -48,7 +44,7 @@ jobs:
- name: Get version - name: Get version
id: get_version id: get_version
uses: SebRollen/toml-action@v1.0.0 uses: SebRollen/toml-action@v1.0.2
with: with:
file: Cargo.toml file: Cargo.toml
field: package.version field: package.version

View File

@ -9,11 +9,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased ## Unreleased
### Added
- Fish/Zsh: Aliases on `__zoxide_z` will now use completions.
## [0.9.0] - 2023-01-08
### Added
- `edit` subcommand to adjust the scores of entries.
### Fixed ### Fixed
- Zsh: completions clashing with `zsh-autocomplete`. - Zsh: completions clashing with `zsh-autocomplete`.
- Fzf: 'invalid option' on macOS. - Fzf: 'invalid option' on macOS.
- PowerShell: handle UTF-8 encoding correctly. - 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.
- Nushell: Accidental redefinition of hooks when initialized twice.
### Removed
- `remove -i` subcommand: use `edit` instead.
## [0.8.3] - 2022-09-02 ## [0.8.3] - 2022-09-02
@ -411,6 +430,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- GitHub Actions pipeline to build and upload releases. - GitHub Actions pipeline to build and upload releases.
- Support for zsh. - Support for zsh.
[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.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.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.1]: https://github.com/ajeetdsouza/zoxide/compare/v0.8.0...v0.8.1

397
Cargo.lock generated
View File

@ -3,19 +3,31 @@
version = 3 version = 3
[[package]] [[package]]
name = "aho-corasick" name = "Inflector"
version = "0.7.18" version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
[[package]]
name = "aho-corasick"
version = "0.7.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
[[package]] [[package]]
name = "anyhow" name = "aliasable"
version = "1.0.61" version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "508b352bb5c066aac251f6daf6b36eccd03e8a88e8081cd44959ea277a3af9a8" checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd"
[[package]]
name = "anyhow"
version = "1.0.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61"
[[package]] [[package]]
name = "askama" name = "askama"
@ -62,9 +74,9 @@ dependencies = [
[[package]] [[package]]
name = "assert_cmd" name = "assert_cmd"
version = "2.0.4" version = "2.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93ae1ddd39efd67689deb1979d80bad3bf7f2b09c6e6117c8d1f2443b5e2f83e" checksum = "fa3d466004a8b4cb1bc34044240a2fd29d17607e2e3bd613eb44fd48e8100da3"
dependencies = [ dependencies = [
"bstr", "bstr",
"doc-comment", "doc-comment",
@ -74,23 +86,6 @@ dependencies = [
"wait-timeout", "wait-timeout",
] ]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]] [[package]]
name = "bincode" name = "bincode"
version = "1.3.3" version = "1.3.3"
@ -108,15 +103,22 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]] [[package]]
name = "bstr" name = "bstr"
version = "0.2.17" version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" checksum = "b45ea9b00a7b3f2988e9a65ad3917e62123c38dba709b666506207be96d1790b"
dependencies = [ dependencies = [
"lazy_static",
"memchr", "memchr",
"once_cell",
"regex-automata", "regex-automata",
"serde",
] ]
[[package]]
name = "cc"
version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d"
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "1.0.0" version = "1.0.0"
@ -125,35 +127,33 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "clap" name = "clap"
version = "3.2.16" version = "4.0.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3dbbb6653e7c55cc8595ad3e1f7be8f32aba4eb7ff7f0fd1163d4f3d137c0a9" checksum = "a7db700bc935f9e43e88d00b0850dae18a63773cfbec6d8e070fccf7fef89a39"
dependencies = [ dependencies = [
"atty",
"bitflags", "bitflags",
"clap_derive", "clap_derive",
"clap_lex", "clap_lex",
"indexmap", "is-terminal",
"once_cell", "once_cell",
"strsim", "strsim",
"termcolor", "termcolor",
"textwrap",
] ]
[[package]] [[package]]
name = "clap_complete" name = "clap_complete"
version = "3.2.3" version = "4.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ead064480dfc4880a10764488415a97fdd36a4cf1bb022d372f02e8faf8386e1" checksum = "10861370d2ba66b0f5989f83ebf35db6421713fd92351790e7fdd6c36774c56b"
dependencies = [ dependencies = [
"clap", "clap",
] ]
[[package]] [[package]]
name = "clap_complete_fig" name = "clap_complete_fig"
version = "3.2.4" version = "4.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed37b4c0c1214673eba6ad8ea31666626bf72be98ffb323067d973c48b4964b9" checksum = "46b30e010e669cd021e5004f3be26cff6b7c08d2a8a0d65b48d43a8cc0efd6c3"
dependencies = [ dependencies = [
"clap", "clap",
"clap_complete", "clap_complete",
@ -161,9 +161,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "3.2.15" version = "4.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ba52acd3b0a5c33aeada5cdaa3267cdc7c594a98731d4268cdc1532f4264cb4" checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro-error", "proc-macro-error",
@ -174,23 +174,13 @@ dependencies = [
[[package]] [[package]]
name = "clap_lex" name = "clap_lex"
version = "0.2.4" version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8"
dependencies = [ dependencies = [
"os_str_bytes", "os_str_bytes",
] ]
[[package]]
name = "crossbeam-utils"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc"
dependencies = [
"cfg-if",
"once_cell",
]
[[package]] [[package]]
name = "difflib" name = "difflib"
version = "0.4.0" version = "0.4.0"
@ -225,15 +215,36 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
[[package]] [[package]]
name = "dunce" name = "dunce"
version = "1.0.2" version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "453440c271cf5577fd2a40e4942540cb7d0d2f85e27c8d07dd0023c925a67541" checksum = "0bd4b30a6560bbd9b4620f4de34c3f14f60848e58a9b7216801afcb4c7b31c3c"
[[package]] [[package]]
name = "either" name = "either"
version = "1.7.0" version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
[[package]]
name = "errno"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1"
dependencies = [
"errno-dragonfly",
"libc",
"winapi",
]
[[package]]
name = "errno-dragonfly"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
dependencies = [
"cc",
"libc",
]
[[package]] [[package]]
name = "fastrand" name = "fastrand"
@ -252,9 +263,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.7" version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
@ -263,15 +274,15 @@ dependencies = [
[[package]] [[package]]
name = "glob" name = "glob"
version = "0.3.0" version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]] [[package]]
name = "globset" name = "globset"
version = "0.4.9" version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a" checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"bstr", "bstr",
@ -280,12 +291,6 @@ dependencies = [
"regex", "regex",
] ]
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]] [[package]]
name = "heck" name = "heck"
version = "0.4.0" version = "0.4.0"
@ -294,20 +299,19 @@ checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
version = "0.1.19" version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
dependencies = [ dependencies = [
"libc", "libc",
] ]
[[package]] [[package]]
name = "ignore" name = "ignore"
version = "0.4.18" version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d" checksum = "a05705bc64e0b66a806c3740bd6578ea66051b157ec42dc219c785cbf185aef3"
dependencies = [ dependencies = [
"crossbeam-utils",
"globset", "globset",
"lazy_static", "lazy_static",
"log", "log",
@ -319,16 +323,6 @@ dependencies = [
"winapi-util", "winapi-util",
] ]
[[package]]
name = "indexmap"
version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
dependencies = [
"autocfg",
"hashbrown",
]
[[package]] [[package]]
name = "instant" name = "instant"
version = "0.1.12" version = "0.1.12"
@ -339,10 +333,32 @@ dependencies = [
] ]
[[package]] [[package]]
name = "itertools" name = "io-lifetimes"
version = "0.10.3" version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "is-terminal"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189"
dependencies = [
"hermit-abi",
"io-lifetimes",
"rustix",
"windows-sys",
]
[[package]]
name = "itertools"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
dependencies = [ dependencies = [
"either", "either",
] ]
@ -355,9 +371,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.131" version = "0.2.139"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04c3b4822ccebfa39c02fc03d1534441b22ead323fa0f48bb7ddd8e6ba076a40" checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
[[package]]
name = "linux-raw-sys"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
[[package]] [[package]]
name = "log" name = "log"
@ -398,20 +420,21 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]] [[package]]
name = "nix" name = "nix"
version = "0.24.2" version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "195cdbc1741b8134346d515b3a56a1c94b0912758009cfd53f99ea0f57b065fc" checksum = "46a58d1d356c6597d08cde02c2f09d785b09e28711837b1ed667dc652c08a694"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"cfg-if", "cfg-if",
"libc", "libc",
"static_assertions",
] ]
[[package]] [[package]]
name = "nom" name = "nom"
version = "7.1.1" version = "7.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" checksum = "e5507769c4919c998e69e49c839d9dc6e693ede4cc4290d6ad8b41d4f09c548c"
dependencies = [ dependencies = [
"memchr", "memchr",
"minimal-lexical", "minimal-lexical",
@ -419,21 +442,44 @@ dependencies = [
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.13.0" version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
[[package]] [[package]]
name = "os_str_bytes" name = "os_str_bytes"
version = "6.2.0" version = "6.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "648001efe5d5c0102d8cea768e348da85d90af8ba91f0bea908f157951493cd4" checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee"
[[package]]
name = "ouroboros"
version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfbb50b356159620db6ac971c6d5c9ab788c9cc38a6f49619fca2a27acb062ca"
dependencies = [
"aliasable",
"ouroboros_macro",
]
[[package]]
name = "ouroboros_macro"
version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a0d9d1a6191c4f391f87219d1ea42b23f09ee84d64763cd05ee6ea88d9f384d"
dependencies = [
"Inflector",
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "predicates" name = "predicates"
version = "2.1.1" version = "2.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5aab5be6e4732b473071984b3164dbbfb7a3674d30ea5ff44410b6bcd960c3c" checksum = "59230a63c37f3e18569bdb90e4a89cbf5bf8b06fea0b84e65ea10cc4df47addd"
dependencies = [ dependencies = [
"difflib", "difflib",
"itertools", "itertools",
@ -442,15 +488,15 @@ dependencies = [
[[package]] [[package]]
name = "predicates-core" name = "predicates-core"
version = "1.0.3" version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da1c2388b1513e1b605fcec39a95e0a9e8ef088f71443ef37099fa9ae6673fcb" checksum = "72f883590242d3c6fc5bf50299011695fa6590c2c70eac95ee1bdb9a733ad1a2"
[[package]] [[package]]
name = "predicates-tree" name = "predicates-tree"
version = "1.0.5" version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d86de6de25020a36c6d3643a86d9a6a9f552107c0559c60ea03551b5e16c032" checksum = "54ff541861505aabf6ea722d2131ee980b8276e10a1297b94e896dd8b621850d"
dependencies = [ dependencies = [
"predicates-core", "predicates-core",
"termtree", "termtree",
@ -482,18 +528,18 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.43" version = "1.0.49"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.21" version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
@ -520,9 +566,9 @@ dependencies = [
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.6.0" version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
@ -537,9 +583,9 @@ checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
[[package]] [[package]]
name = "regex-syntax" name = "regex-syntax"
version = "0.6.27" version = "0.6.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
[[package]] [[package]]
name = "remove_dir_all" name = "remove_dir_all"
@ -552,9 +598,9 @@ dependencies = [
[[package]] [[package]]
name = "rstest" name = "rstest"
version = "0.15.0" version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9c9dc66cc29792b663ffb5269be669f1613664e69ad56441fdb895c2347b930" checksum = "b07f2d176c472198ec1e6551dc7da28f1c089652f66a7b722676c2238ebc0edf"
dependencies = [ dependencies = [
"rstest_macros", "rstest_macros",
"rustc_version", "rustc_version",
@ -562,15 +608,16 @@ dependencies = [
[[package]] [[package]]
name = "rstest_macros" name = "rstest_macros"
version = "0.14.0" version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5015e68a0685a95ade3eee617ff7101ab6a3fc689203101ca16ebc16f2b89c66" checksum = "7229b505ae0706e64f37ffc54a9c163e11022a6636d58fe1f3f52018257ff9f7"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"proc-macro2", "proc-macro2",
"quote", "quote",
"rustc_version", "rustc_version",
"syn", "syn",
"unicode-ident",
] ]
[[package]] [[package]]
@ -593,6 +640,20 @@ dependencies = [
"semver", "semver",
] ]
[[package]]
name = "rustix"
version = "0.36.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4feacf7db682c6c329c4ede12649cd36ecab0f3be5b7d74e6a20304725db4549"
dependencies = [
"bitflags",
"errno",
"io-lifetimes",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]] [[package]]
name = "same-file" name = "same-file"
version = "1.0.6" version = "1.0.6"
@ -604,24 +665,24 @@ dependencies = [
[[package]] [[package]]
name = "semver" name = "semver"
version = "1.0.13" version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93f6841e709003d68bb2deee8c343572bf446003ec20a583e76f7b15cebf3711" checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.143" version = "1.0.152"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53e8e5d5b70924f74ff5c6d64d9a5acd91422117c60f48c4e07855238a254553" checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.143" version = "1.0.152"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3d8e8de557aee63c26b85b947f5e59b690d0454c753f3adeb5cd7835ab88391" checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -634,6 +695,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]] [[package]]
name = "strsim" name = "strsim"
version = "0.10.0" version = "0.10.0"
@ -642,9 +709,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.99" version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -676,30 +743,24 @@ dependencies = [
[[package]] [[package]]
name = "termtree" name = "termtree"
version = "0.2.4" version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "507e9898683b6c43a9aa55b64259b721b52ba226e0f3779137e50ad114a4c90b" checksum = "95059e91184749cb66be6dc994f67f182b6d897cb3df74a5bf66b5e709295fd8"
[[package]]
name = "textwrap"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.32" version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994" checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "1.0.32" version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21" checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -726,9 +787,9 @@ dependencies = [
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.3" version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
[[package]] [[package]]
name = "version_check" name = "version_check"
@ -764,13 +825,13 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]] [[package]]
name = "which" name = "which"
version = "4.2.5" version = "4.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae" checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b"
dependencies = [ dependencies = [
"either", "either",
"lazy_static",
"libc", "libc",
"once_cell",
] ]
[[package]] [[package]]
@ -804,6 +865,63 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4"
[[package]]
name = "windows_i686_gnu"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7"
[[package]]
name = "windows_i686_msvc"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"
[[package]] [[package]]
name = "xtask" name = "xtask"
version = "0.1.0" version = "0.1.0"
@ -816,7 +934,7 @@ dependencies = [
[[package]] [[package]]
name = "zoxide" name = "zoxide"
version = "0.8.3" version = "0.9.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"askama", "askama",
@ -830,6 +948,7 @@ dependencies = [
"fastrand", "fastrand",
"glob", "glob",
"nix", "nix",
"ouroboros",
"rstest", "rstest",
"rstest_reuse", "rstest_reuse",
"serde", "serde",

View File

@ -9,8 +9,8 @@ license = "MIT"
name = "zoxide" name = "zoxide"
readme = "README.md" readme = "README.md"
repository = "https://github.com/ajeetdsouza/zoxide" repository = "https://github.com/ajeetdsouza/zoxide"
rust-version = "1.62" rust-version = "1.65"
version = "0.8.3" version = "0.9.0"
[badges] [badges]
maintenance = { status = "actively-developed" } maintenance = { status = "actively-developed" }
@ -18,44 +18,64 @@ maintenance = { status = "actively-developed" }
[workspace] [workspace]
members = ["xtask/"] members = ["xtask/"]
[dependencies] [workspace.dependencies]
anyhow = "1.0.32" anyhow = "1.0.32"
askama = { version = "0.11.0", default-features = false } askama = { version = "0.11.0", default-features = false }
assert_cmd = "2.0.0"
bincode = "1.3.1" bincode = "1.3.1"
clap = { version = "3.1.0", features = ["derive"] } clap = { version = "4.0.0", features = ["derive"] }
clap_complete = "4.0.0"
clap_complete_fig = "4.0.0"
dirs = "4.0.0" dirs = "4.0.0"
dunce = "1.0.1" dunce = "1.0.1"
fastrand = "1.7.0" fastrand = "1.7.0"
glob = "0.3.0" glob = "0.3.0"
serde = { version = "1.0.116", features = ["derive"] } ignore = "0.4.18"
nix = { version = "0.26.1", default-features = false, features = [
[target.'cfg(unix)'.dependencies]
nix = { version = "0.24.1", default-features = false, features = [
"fs", "fs",
"user", "user",
] } ] }
ouroboros = "0.15.5"
[target.'cfg(windows)'.dependencies] rstest = { version = "0.16.0", default-features = false }
rstest_reuse = "0.4.0"
serde = { version = "1.0.116", features = ["derive"] }
shell-words = "1.0.0"
tempfile = "3.1.0"
which = "4.2.5" which = "4.2.5"
[dependencies]
anyhow.workspace = true
askama.workspace = true
bincode.workspace = true
clap.workspace = true
dirs.workspace = true
dunce.workspace = true
fastrand.workspace = true
glob.workspace = true
ouroboros.workspace = true
serde.workspace = true
[target.'cfg(unix)'.dependencies]
nix.workspace = true
[target.'cfg(windows)'.dependencies]
which.workspace = true
[build-dependencies] [build-dependencies]
clap = { version = "3.1.0", features = ["derive"] } clap.workspace = true
clap_complete = "3.1.0" clap_complete.workspace = true
clap_complete_fig = "3.1.0" clap_complete_fig.workspace = true
[dev-dependencies] [dev-dependencies]
assert_cmd = "2.0.0" assert_cmd.workspace = true
rstest = { version = "0.15.0", default-features = false } rstest.workspace = true
rstest_reuse = "0.4.0" rstest_reuse.workspace = true
tempfile = "3.1.0" tempfile.workspace = true
[features] [features]
default = [] default = []
nix-dev = [] nix-dev = []
[profile.dev]
debug = 0
[profile.release] [profile.release]
codegen-units = 1 codegen-units = 1
debug = 0 debug = 0

View File

@ -13,7 +13,6 @@
[![crates.io][crates.io-badge]][crates.io] [![crates.io][crates.io-badge]][crates.io]
[![Downloads][downloads-badge]][releases] [![Downloads][downloads-badge]][releases]
[![License][license-badge]][license]
[![Built with Nix][builtwithnix-badge]][builtwithnix] [![Built with Nix][builtwithnix-badge]][builtwithnix]
zoxide is a **smarter cd command**, inspired by z and autojump. zoxide is a **smarter cd command**, inspired by z and autojump.
@ -81,10 +80,11 @@ Or, you can use a package manager:
| Fedora 32+ | [Fedora Packages] | `dnf install zoxide` | | Fedora 32+ | [Fedora Packages] | `dnf install zoxide` |
| Gentoo | [GURU Overlay] | `eselect repository enable guru` <br /> `emerge --sync guru` <br /> `emerge app-shells/zoxide` | | Gentoo | [GURU Overlay] | `eselect repository enable guru` <br /> `emerge --sync guru` <br /> `emerge app-shells/zoxide` |
| Manjaro | | `pacman -S zoxide` | | Manjaro | | `pacman -S zoxide` |
| NixOS | [nixpkgs] | `nix-env -iA nixpkgs.zoxide` | | NixOS 21.05+ | [nixpkgs] | `nix-env -iA nixpkgs.zoxide` |
| openSUSE Tumbleweed | [openSUSE Factory] | `zypper install zoxide` | | openSUSE Tumbleweed | [openSUSE Factory] | `zypper install zoxide` |
| Parrot OS | | `apt install zoxide` | | Parrot OS | | `apt install zoxide` |
| Raspbian 11+ | [Raspbian Packages] | `apt install zoxide` | | Raspbian 11+ | [Raspbian Packages] | `apt install zoxide` |
| Slackware 15.0+ | [SlackBuilds] | [Instructions][slackbuilds-howto] |
| Ubuntu 21.04+ | [Ubuntu Packages] | `apt install zoxide` | | Ubuntu 21.04+ | [Ubuntu Packages] | `apt install zoxide` |
| Void Linux | [Void Linux Packages] | `xbps-install -S zoxide` | | Void Linux | [Void Linux Packages] | `xbps-install -S zoxide` |
@ -204,7 +204,7 @@ zoxide init fish | source
Add this to your env file (find it by running `$nu.env-path` in Nushell): Add this to your env file (find it by running `$nu.env-path` in Nushell):
```sh ```sh
zoxide init nushell | save ~/.zoxide.nu zoxide init nushell | save -f ~/.zoxide.nu
``` ```
Now, add this to the end of your config file (find it by running Now, add this to the end of your config file (find it by running
@ -215,7 +215,7 @@ source ~/.zoxide.nu
``` ```
> **Note** > **Note**
> zoxide only supports Nushell v0.63.0 and above. > zoxide only supports Nushell v0.73.0 and above.
</details> </details>
@ -398,7 +398,7 @@ They must be set before `zoxide init` is called.
| [nnn] | File manager | [nnn-autojump] | | [nnn] | File manager | [nnn-autojump] |
| [ranger] | File manager | [ranger-zoxide] | | [ranger] | File manager | [ranger-zoxide] |
| [telescope.nvim] | Fuzzy finder for Neovim | [telescope-zoxide] | | [telescope.nvim] | Fuzzy finder for Neovim | [telescope-zoxide] |
| [vim] | Text editor | [zoxide.vim] | | [vim] / [neovim] | Text editor | [zoxide.vim] |
| [xplr] | File manager | [zoxide.xplr] | | [xplr] | File manager | [zoxide.xplr] |
| [xxh] | Transports shell configuration over SSH | [xxh-plugin-prerun-zoxide] | | [xxh] | Transports shell configuration over SSH | [xxh-plugin-prerun-zoxide] |
| [zabb] | Finds the shortest possible query for a path | Natively supported | | [zabb] | Finds the shortest possible query for a path | Natively supported |
@ -408,18 +408,18 @@ They must be set before `zoxide init` is called.
[algorithm-matching]: https://github.com/ajeetdsouza/zoxide/wiki/Algorithm#matching [algorithm-matching]: https://github.com/ajeetdsouza/zoxide/wiki/Algorithm#matching
[alpine linux packages]: https://pkgs.alpinelinux.org/packages?name=zoxide [alpine linux packages]: https://pkgs.alpinelinux.org/packages?name=zoxide
[arch linux community]: https://archlinux.org/packages/community/x86_64/zoxide/ [arch linux community]: https://archlinux.org/packages/community/x86_64/zoxide/
[builtwithnix-badge]: https://img.shields.io/badge/builtwith-nix-7d81f7?style=flat-square [builtwithnix-badge]: https://img.shields.io/badge/builtwith-nix-7d81f7?logo=nixos&logoColor=white&style=flat-square
[builtwithnix]: https://builtwithnix.org/ [builtwithnix]: https://builtwithnix.org/
[chocolatey]: https://community.chocolatey.org/packages/zoxide [chocolatey]: https://community.chocolatey.org/packages/zoxide
[clink-zoxide]: https://github.com/shunsambongi/clink-zoxide [clink-zoxide]: https://github.com/shunsambongi/clink-zoxide
[clink]: https://github.com/mridgers/clink [clink]: https://github.com/mridgers/clink
[conda-forge]: https://anaconda.org/conda-forge/zoxide [conda-forge]: https://anaconda.org/conda-forge/zoxide
[copr]: https://copr.fedorainfracloud.org/coprs/atim/zoxide/ [copr]: https://copr.fedorainfracloud.org/coprs/atim/zoxide/
[crates.io-badge]: https://img.shields.io/crates/v/zoxide?style=flat-square [crates.io-badge]: https://img.shields.io/crates/v/zoxide?logo=rust&logoColor=white&style=flat-square
[crates.io]: https://crates.io/crates/zoxide [crates.io]: https://crates.io/crates/zoxide
[debian packages]: https://packages.debian.org/stable/admin/zoxide [debian packages]: https://packages.debian.org/stable/admin/zoxide
[devuan packages]: https://pkginfo.devuan.org/cgi-bin/package-query.html?c=package&q=zoxide [devuan packages]: https://pkginfo.devuan.org/cgi-bin/package-query.html?c=package&q=zoxide
[downloads-badge]: https://img.shields.io/endpoint?color=bright-green&label=downloads&style=flat-square&cacheSeconds=3600&url=https%3A%2F%2Fzoxide-dl-rlvir7rbe5ac.runkit.sh%2F [downloads-badge]: https://img.shields.io/github/downloads/ajeetdsouza/zoxide/total?logo=github&logoColor=white&style=flat-square
[dports]: https://github.com/DragonFlyBSD/DPorts/tree/master/sysutils/zoxide [dports]: https://github.com/DragonFlyBSD/DPorts/tree/master/sysutils/zoxide
[emacs]: https://www.gnu.org/software/emacs/ [emacs]: https://www.gnu.org/software/emacs/
[fedora packages]: https://src.fedoraproject.org/rpms/rust-zoxide [fedora packages]: https://src.fedoraproject.org/rpms/rust-zoxide
@ -435,8 +435,6 @@ They must be set before `zoxide init` is called.
[joshuto]: https://github.com/kamiyaa/joshuto [joshuto]: https://github.com/kamiyaa/joshuto
[lf]: https://github.com/gokcehan/lf [lf]: https://github.com/gokcehan/lf
[lf-wiki]: https://github.com/gokcehan/lf/wiki/Integrations#zoxide [lf-wiki]: https://github.com/gokcehan/lf/wiki/Integrations#zoxide
[license-badge]: https://img.shields.io/github/license/ajeetdsouza/zoxide?color=lightgray&style=flat-square
[license]: https://github.com/ajeetdsouza/zoxide/blob/main/LICENSE
[linuxbrew]: https://formulae.brew.sh/formula-linux/zoxide [linuxbrew]: https://formulae.brew.sh/formula-linux/zoxide
[macports]: https://ports.macports.org/port/zoxide/summary [macports]: https://ports.macports.org/port/zoxide/summary
[neovim]: https://github.com/neovim/neovim [neovim]: https://github.com/neovim/neovim
@ -450,6 +448,7 @@ They must be set before `zoxide init` is called.
[raspbian packages]: https://archive.raspbian.org/raspbian/pool/main/r/rust-zoxide/ [raspbian packages]: https://archive.raspbian.org/raspbian/pool/main/r/rust-zoxide/
[releases]: https://github.com/ajeetdsouza/zoxide/releases [releases]: https://github.com/ajeetdsouza/zoxide/releases
[scoop]: https://github.com/ScoopInstaller/Main/tree/master/bucket/zoxide.json [scoop]: https://github.com/ScoopInstaller/Main/tree/master/bucket/zoxide.json
[slackbuilds-howto]: https://slackbuilds.org/howto/
[telescope-zoxide]: https://github.com/jvgrootveld/telescope-zoxide [telescope-zoxide]: https://github.com/jvgrootveld/telescope-zoxide
[telescope.nvim]: https://github.com/nvim-telescope/telescope.nvim [telescope.nvim]: https://github.com/nvim-telescope/telescope.nvim
[termux]: https://github.com/termux/termux-packages/tree/master/packages/zoxide [termux]: https://github.com/termux/termux-packages/tree/master/packages/zoxide

View File

@ -9,8 +9,8 @@ fn main() {
}; };
println!("cargo:rustc-env=ZOXIDE_VERSION={version}"); println!("cargo:rustc-env=ZOXIDE_VERSION={version}");
// Since we are generating completions in the package directory, we need to set this so that // Since we are generating completions in the package directory, we need to set
// Cargo doesn't rebuild every time. // this so that Cargo doesn't rebuild every time.
println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=src/"); println!("cargo:rerun-if-changed=src/");
println!("cargo:rerun-if-changed=templates/"); println!("cargo:rerun-if-changed=templates/");
@ -22,7 +22,7 @@ fn main() {
fn git_version() -> Option<String> { fn git_version() -> Option<String> {
let dir = env!("CARGO_MANIFEST_DIR"); let dir = env!("CARGO_MANIFEST_DIR");
let mut git = Command::new("git"); let mut git = Command::new("git");
git.args(&["-C", dir, "describe", "--tags", "--match=v*.*.*", "--always", "--broken"]); git.args(["-C", dir, "describe", "--tags", "--match=v*.*.*", "--always", "--broken"]);
let output = git.output().ok()?; let output = git.output().ok()?;
if !output.status.success() || output.stdout.is_empty() || !output.stderr.is_empty() { if !output.status.success() || output.stdout.is_empty() || !output.stderr.is_empty() {

View File

@ -37,6 +37,61 @@ _arguments "${_arguments_options[@]}" \
'*::paths:_files -/' \ '*::paths:_files -/' \
&& ret=0 && ret=0
;; ;;
(edit)
_arguments "${_arguments_options[@]}" \
'-h[Print help information]' \
'--help[Print help information]' \
'-V[Print version information]' \
'--version[Print version information]' \
":: :_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 information]' \
'--help[Print help information]' \
'-V[Print version information]' \
'--version[Print version information]' \
':path:' \
&& ret=0
;;
(delete)
_arguments "${_arguments_options[@]}" \
'-h[Print help information]' \
'--help[Print help information]' \
'-V[Print version information]' \
'--version[Print version information]' \
':path:' \
&& ret=0
;;
(increment)
_arguments "${_arguments_options[@]}" \
'-h[Print help information]' \
'--help[Print help information]' \
'-V[Print version information]' \
'--version[Print version information]' \
':path:' \
&& ret=0
;;
(reload)
_arguments "${_arguments_options[@]}" \
'-h[Print help information]' \
'--help[Print help information]' \
'-V[Print version information]' \
'--version[Print version information]' \
&& ret=0
;;
esac
;;
esac
;;
(import) (import)
_arguments "${_arguments_options[@]}" \ _arguments "${_arguments_options[@]}" \
'--from=[Application to import from]:FROM:(autojump z)' \ '--from=[Application to import from]:FROM:(autojump z)' \
@ -62,7 +117,7 @@ _arguments "${_arguments_options[@]}" \
;; ;;
(query) (query)
_arguments "${_arguments_options[@]}" \ _arguments "${_arguments_options[@]}" \
'--exclude=[Exclude a path from results]:path:_files -/' \ '--exclude=[Exclude the current directory]:path:_files -/' \
'--all[Show deleted directories]' \ '--all[Show deleted directories]' \
'(-l --list)-i[Use interactive selection]' \ '(-l --list)-i[Use interactive selection]' \
'(-l --list)--interactive[Use interactive selection]' \ '(-l --list)--interactive[Use interactive selection]' \
@ -79,8 +134,6 @@ _arguments "${_arguments_options[@]}" \
;; ;;
(remove) (remove)
_arguments "${_arguments_options[@]}" \ _arguments "${_arguments_options[@]}" \
'-i[Use interactive selection]' \
'--interactive[Use interactive selection]' \
'-h[Print help information]' \ '-h[Print help information]' \
'--help[Print help information]' \ '--help[Print help information]' \
'-V[Print version information]' \ '-V[Print version information]' \
@ -97,6 +150,7 @@ esac
_zoxide_commands() { _zoxide_commands() {
local commands; commands=( local commands; commands=(
'add:Add a new directory or increment its rank' \ 'add:Add a new directory or increment its rank' \
'edit:Edit the database' \
'import:Import entries from another application' \ 'import:Import entries from another application' \
'init:Generate shell configuration' \ 'init:Generate shell configuration' \
'query:Search for a directory in the database' \ 'query:Search for a directory in the database' \
@ -109,11 +163,36 @@ _zoxide__add_commands() {
local commands; commands=() local commands; commands=()
_describe -t commands 'zoxide add commands' commands "$@" _describe -t commands 'zoxide add commands' commands "$@"
} }
(( $+functions[_zoxide__edit__decrement_commands] )) ||
_zoxide__edit__decrement_commands() {
local commands; commands=()
_describe -t commands 'zoxide edit decrement commands' commands "$@"
}
(( $+functions[_zoxide__edit__delete_commands] )) ||
_zoxide__edit__delete_commands() {
local commands; commands=()
_describe -t commands 'zoxide edit delete commands' commands "$@"
}
(( $+functions[_zoxide__edit_commands] )) ||
_zoxide__edit_commands() {
local commands; commands=(
'decrement:' \
'delete:' \
'increment:' \
'reload:' \
)
_describe -t commands 'zoxide edit commands' commands "$@"
}
(( $+functions[_zoxide__import_commands] )) || (( $+functions[_zoxide__import_commands] )) ||
_zoxide__import_commands() { _zoxide__import_commands() {
local commands; commands=() local commands; commands=()
_describe -t commands 'zoxide import commands' commands "$@" _describe -t commands 'zoxide import commands' commands "$@"
} }
(( $+functions[_zoxide__edit__increment_commands] )) ||
_zoxide__edit__increment_commands() {
local commands; commands=()
_describe -t commands 'zoxide edit increment commands' commands "$@"
}
(( $+functions[_zoxide__init_commands] )) || (( $+functions[_zoxide__init_commands] )) ||
_zoxide__init_commands() { _zoxide__init_commands() {
local commands; commands=() local commands; commands=()
@ -124,6 +203,11 @@ _zoxide__query_commands() {
local commands; commands=() local commands; commands=()
_describe -t commands 'zoxide query commands' commands "$@" _describe -t commands 'zoxide query commands' commands "$@"
} }
(( $+functions[_zoxide__edit__reload_commands] )) ||
_zoxide__edit__reload_commands() {
local commands; commands=()
_describe -t commands 'zoxide edit reload commands' commands "$@"
}
(( $+functions[_zoxide__remove_commands] )) || (( $+functions[_zoxide__remove_commands] )) ||
_zoxide__remove_commands() { _zoxide__remove_commands() {
local commands; commands=() local commands; commands=()

View File

@ -26,6 +26,7 @@ Register-ArgumentCompleter -Native -CommandName 'zoxide' -ScriptBlock {
[CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Print version information') [CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Print version information')
[CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Print version information') [CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Print version information')
[CompletionResult]::new('add', 'add', [CompletionResultType]::ParameterValue, 'Add a new directory or increment its rank') [CompletionResult]::new('add', 'add', [CompletionResultType]::ParameterValue, 'Add a new directory or increment its rank')
[CompletionResult]::new('edit', 'edit', [CompletionResultType]::ParameterValue, 'Edit the database')
[CompletionResult]::new('import', 'import', [CompletionResultType]::ParameterValue, 'Import entries from another application') [CompletionResult]::new('import', 'import', [CompletionResultType]::ParameterValue, 'Import entries from another application')
[CompletionResult]::new('init', 'init', [CompletionResultType]::ParameterValue, 'Generate shell configuration') [CompletionResult]::new('init', 'init', [CompletionResultType]::ParameterValue, 'Generate shell configuration')
[CompletionResult]::new('query', 'query', [CompletionResultType]::ParameterValue, 'Search for a directory in the database') [CompletionResult]::new('query', 'query', [CompletionResultType]::ParameterValue, 'Search for a directory in the database')
@ -39,6 +40,45 @@ Register-ArgumentCompleter -Native -CommandName 'zoxide' -ScriptBlock {
[CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Print version information') [CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Print version information')
break break
} }
'zoxide;edit' {
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help information')
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help information')
[CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Print version information')
[CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Print version information')
[CompletionResult]::new('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 information')
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help information')
[CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Print version information')
[CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Print version information')
break
}
'zoxide;edit;delete' {
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help information')
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help information')
[CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Print version information')
[CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Print version information')
break
}
'zoxide;edit;increment' {
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help information')
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help information')
[CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Print version information')
[CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Print version information')
break
}
'zoxide;edit;reload' {
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help information')
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help information')
[CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Print version information')
[CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Print version information')
break
}
'zoxide;import' { 'zoxide;import' {
[CompletionResult]::new('--from', 'from', [CompletionResultType]::ParameterName, 'Application to import from') [CompletionResult]::new('--from', 'from', [CompletionResultType]::ParameterName, 'Application to import from')
[CompletionResult]::new('--merge', 'merge', [CompletionResultType]::ParameterName, 'Merge into existing database') [CompletionResult]::new('--merge', 'merge', [CompletionResultType]::ParameterName, 'Merge into existing database')
@ -59,7 +99,7 @@ Register-ArgumentCompleter -Native -CommandName 'zoxide' -ScriptBlock {
break break
} }
'zoxide;query' { 'zoxide;query' {
[CompletionResult]::new('--exclude', 'exclude', [CompletionResultType]::ParameterName, 'Exclude a path from results') [CompletionResult]::new('--exclude', 'exclude', [CompletionResultType]::ParameterName, 'Exclude the current directory')
[CompletionResult]::new('--all', 'all', [CompletionResultType]::ParameterName, 'Show deleted directories') [CompletionResult]::new('--all', 'all', [CompletionResultType]::ParameterName, 'Show deleted directories')
[CompletionResult]::new('-i', 'i', [CompletionResultType]::ParameterName, 'Use interactive selection') [CompletionResult]::new('-i', 'i', [CompletionResultType]::ParameterName, 'Use interactive selection')
[CompletionResult]::new('--interactive', 'interactive', [CompletionResultType]::ParameterName, 'Use interactive selection') [CompletionResult]::new('--interactive', 'interactive', [CompletionResultType]::ParameterName, 'Use interactive selection')
@ -74,8 +114,6 @@ Register-ArgumentCompleter -Native -CommandName 'zoxide' -ScriptBlock {
break break
} }
'zoxide;remove' { 'zoxide;remove' {
[CompletionResult]::new('-i', 'i', [CompletionResultType]::ParameterName, 'Use interactive selection')
[CompletionResult]::new('--interactive', 'interactive', [CompletionResultType]::ParameterName, 'Use interactive selection')
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help information') [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help information')
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help information') [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help information')
[CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Print version information') [CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Print version information')

View File

@ -8,24 +8,39 @@ _zoxide() {
for i in ${COMP_WORDS[@]} for i in ${COMP_WORDS[@]}
do do
case "${i}" in case "${cmd},${i}" in
"$1") ",$1")
cmd="zoxide" cmd="zoxide"
;; ;;
add) zoxide,add)
cmd+="__add" cmd="zoxide__add"
;; ;;
import) zoxide,edit)
cmd+="__import" cmd="zoxide__edit"
;; ;;
init) zoxide,import)
cmd+="__init" cmd="zoxide__import"
;; ;;
query) zoxide,init)
cmd+="__query" cmd="zoxide__init"
;; ;;
remove) zoxide,query)
cmd+="__remove" cmd="zoxide__query"
;;
zoxide,remove)
cmd="zoxide__remove"
;;
zoxide__edit,decrement)
cmd="zoxide__edit__decrement"
;;
zoxide__edit,delete)
cmd="zoxide__edit__delete"
;;
zoxide__edit,increment)
cmd="zoxide__edit__increment"
;;
zoxide__edit,reload)
cmd="zoxide__edit__reload"
;; ;;
*) *)
;; ;;
@ -34,7 +49,7 @@ _zoxide() {
case "${cmd}" in case "${cmd}" in
zoxide) zoxide)
opts="-h -V --help --version add import init query remove" opts="-h -V --help --version add edit import init query remove"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0 return 0
@ -61,6 +76,76 @@ _zoxide() {
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0 return 0
;; ;;
zoxide__edit)
opts="-h -V --help --version decrement delete increment reload"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
fi
case "${prev}" in
*)
COMPREPLY=()
;;
esac
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
zoxide__edit__decrement)
opts="-h -V --help --version <PATH>"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
fi
case "${prev}" in
*)
COMPREPLY=()
;;
esac
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
zoxide__edit__delete)
opts="-h -V --help --version <PATH>"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
fi
case "${prev}" in
*)
COMPREPLY=()
;;
esac
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
zoxide__edit__increment)
opts="-h -V --help --version <PATH>"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
fi
case "${prev}" in
*)
COMPREPLY=()
;;
esac
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
zoxide__edit__reload)
opts="-h -V --help --version"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
fi
case "${prev}" in
*)
COMPREPLY=()
;;
esac
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
zoxide__import) zoxide__import)
opts="-h -V --from --merge --help --version <PATH>" opts="-h -V --from --merge --help --version <PATH>"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
@ -102,7 +187,7 @@ _zoxide() {
return 0 return 0
;; ;;
zoxide__query) zoxide__query)
opts="-i -l -s -h -V --all --interactive --list --score --exclude --help --version <KEYWORDS>..." opts="-i -l -s -h -V --all --interactive --list --score --exclude --help --version [KEYWORDS]..."
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0 return 0
@ -120,7 +205,7 @@ _zoxide() {
return 0 return 0
;; ;;
zoxide__remove) zoxide__remove)
opts="-i -h -V --interactive --help --version <PATHS>..." opts="-h -V --help --version [PATHS]..."
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0 return 0

View File

@ -23,6 +23,7 @@ set edit:completion:arg-completer[zoxide] = {|@words|
cand -V 'Print version information' cand -V 'Print version information'
cand --version 'Print version information' cand --version 'Print version information'
cand add 'Add a new directory or increment its rank' cand add 'Add a new directory or increment its rank'
cand edit 'Edit the database'
cand import 'Import entries from another application' cand import 'Import entries from another application'
cand init 'Generate shell configuration' cand init 'Generate shell configuration'
cand query 'Search for a directory in the database' cand query 'Search for a directory in the database'
@ -34,6 +35,40 @@ set edit:completion:arg-completer[zoxide] = {|@words|
cand -V 'Print version information' cand -V 'Print version information'
cand --version 'Print version information' cand --version 'Print version information'
} }
&'zoxide;edit'= {
cand -h 'Print help information'
cand --help 'Print help information'
cand -V 'Print version information'
cand --version 'Print version information'
cand decrement 'decrement'
cand delete 'delete'
cand increment 'increment'
cand reload 'reload'
}
&'zoxide;edit;decrement'= {
cand -h 'Print help information'
cand --help 'Print help information'
cand -V 'Print version information'
cand --version 'Print version information'
}
&'zoxide;edit;delete'= {
cand -h 'Print help information'
cand --help 'Print help information'
cand -V 'Print version information'
cand --version 'Print version information'
}
&'zoxide;edit;increment'= {
cand -h 'Print help information'
cand --help 'Print help information'
cand -V 'Print version information'
cand --version 'Print version information'
}
&'zoxide;edit;reload'= {
cand -h 'Print help information'
cand --help 'Print help information'
cand -V 'Print version information'
cand --version 'Print version information'
}
&'zoxide;import'= { &'zoxide;import'= {
cand --from 'Application to import from' cand --from 'Application to import from'
cand --merge 'Merge into existing database' cand --merge 'Merge into existing database'
@ -52,7 +87,7 @@ set edit:completion:arg-completer[zoxide] = {|@words|
cand --version 'Print version information' cand --version 'Print version information'
} }
&'zoxide;query'= { &'zoxide;query'= {
cand --exclude 'Exclude a path from results' cand --exclude 'Exclude the current directory'
cand --all 'Show deleted directories' cand --all 'Show deleted directories'
cand -i 'Use interactive selection' cand -i 'Use interactive selection'
cand --interactive 'Use interactive selection' cand --interactive 'Use interactive selection'
@ -66,8 +101,6 @@ set edit:completion:arg-completer[zoxide] = {|@words|
cand --version 'Print version information' cand --version 'Print version information'
} }
&'zoxide;remove'= { &'zoxide;remove'= {
cand -i 'Use interactive selection'
cand --interactive 'Use interactive selection'
cand -h 'Print help information' cand -h 'Print help information'
cand --help 'Print help information' cand --help 'Print help information'
cand -V 'Print version information' cand -V 'Print version information'

View File

@ -1,12 +1,27 @@
complete -c zoxide -n "__fish_use_subcommand" -s h -l help -d 'Print help information' complete -c zoxide -n "__fish_use_subcommand" -s h -l help -d 'Print help information'
complete -c zoxide -n "__fish_use_subcommand" -s V -l version -d 'Print version information' complete -c zoxide -n "__fish_use_subcommand" -s V -l version -d 'Print 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 "add" -d 'Add a new directory or increment its rank'
complete -c zoxide -n "__fish_use_subcommand" -f -a "edit" -d 'Edit the database'
complete -c zoxide -n "__fish_use_subcommand" -f -a "import" -d 'Import entries from another application' complete -c zoxide -n "__fish_use_subcommand" -f -a "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 "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 "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_use_subcommand" -f -a "remove" -d 'Remove a directory from the database'
complete -c zoxide -n "__fish_seen_subcommand_from add" -s h -l help -d 'Print help information' complete -c zoxide -n "__fish_seen_subcommand_from add" -s h -l help -d 'Print help information'
complete -c zoxide -n "__fish_seen_subcommand_from add" -s V -l version -d 'Print version information' complete -c zoxide -n "__fish_seen_subcommand_from add" -s V -l version -d 'Print version information'
complete -c zoxide -n "__fish_seen_subcommand_from edit; and not __fish_seen_subcommand_from decrement; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from increment; and not __fish_seen_subcommand_from reload" -s h -l help -d 'Print help information'
complete -c zoxide -n "__fish_seen_subcommand_from edit; and not __fish_seen_subcommand_from decrement; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from increment; and not __fish_seen_subcommand_from reload" -s V -l version -d 'Print version information'
complete -c zoxide -n "__fish_seen_subcommand_from edit; and not __fish_seen_subcommand_from decrement; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from increment; and not __fish_seen_subcommand_from reload" -f -a "decrement"
complete -c zoxide -n "__fish_seen_subcommand_from edit; and not __fish_seen_subcommand_from decrement; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from increment; and not __fish_seen_subcommand_from reload" -f -a "delete"
complete -c zoxide -n "__fish_seen_subcommand_from edit; and not __fish_seen_subcommand_from decrement; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from increment; and not __fish_seen_subcommand_from reload" -f -a "increment"
complete -c zoxide -n "__fish_seen_subcommand_from edit; and not __fish_seen_subcommand_from decrement; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from increment; and not __fish_seen_subcommand_from reload" -f -a "reload"
complete -c zoxide -n "__fish_seen_subcommand_from edit; and __fish_seen_subcommand_from decrement" -s h -l help -d 'Print help information'
complete -c zoxide -n "__fish_seen_subcommand_from edit; and __fish_seen_subcommand_from decrement" -s V -l version -d 'Print version information'
complete -c zoxide -n "__fish_seen_subcommand_from edit; and __fish_seen_subcommand_from delete" -s h -l help -d 'Print help information'
complete -c zoxide -n "__fish_seen_subcommand_from edit; and __fish_seen_subcommand_from delete" -s V -l version -d 'Print version information'
complete -c zoxide -n "__fish_seen_subcommand_from edit; and __fish_seen_subcommand_from increment" -s h -l help -d 'Print help information'
complete -c zoxide -n "__fish_seen_subcommand_from edit; and __fish_seen_subcommand_from increment" -s V -l version -d 'Print version information'
complete -c zoxide -n "__fish_seen_subcommand_from edit; and __fish_seen_subcommand_from reload" -s h -l help -d 'Print help information'
complete -c zoxide -n "__fish_seen_subcommand_from edit; and __fish_seen_subcommand_from reload" -s V -l version -d 'Print version information'
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 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" -l merge -d 'Merge into existing database'
complete -c zoxide -n "__fish_seen_subcommand_from import" -s h -l help -d 'Print help information' complete -c zoxide -n "__fish_seen_subcommand_from import" -s h -l help -d 'Print help information'
@ -16,13 +31,12 @@ complete -c zoxide -n "__fish_seen_subcommand_from init" -l hook -d 'Changes how
complete -c zoxide -n "__fish_seen_subcommand_from init" -l no-cmd -d 'Prevents zoxide from defining the `z` and `zi` commands' complete -c zoxide -n "__fish_seen_subcommand_from init" -l no-cmd -d 'Prevents zoxide from defining the `z` and `zi` commands'
complete -c zoxide -n "__fish_seen_subcommand_from init" -s h -l help -d 'Print help information' complete -c zoxide -n "__fish_seen_subcommand_from init" -s h -l help -d 'Print help information'
complete -c zoxide -n "__fish_seen_subcommand_from init" -s V -l version -d 'Print version information' complete -c zoxide -n "__fish_seen_subcommand_from init" -s V -l version -d 'Print version information'
complete -c zoxide -n "__fish_seen_subcommand_from query" -l exclude -d 'Exclude a path from results' -r -f -a "(__fish_complete_directories)" complete -c zoxide -n "__fish_seen_subcommand_from query" -l exclude -d 'Exclude the current directory' -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" -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 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 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 s -l score -d 'Print score with results'
complete -c zoxide -n "__fish_seen_subcommand_from query" -s h -l help -d 'Print help information' complete -c zoxide -n "__fish_seen_subcommand_from query" -s h -l help -d 'Print help information'
complete -c zoxide -n "__fish_seen_subcommand_from query" -s V -l version -d 'Print version information' complete -c zoxide -n "__fish_seen_subcommand_from query" -s V -l version -d 'Print version information'
complete -c zoxide -n "__fish_seen_subcommand_from remove" -s i -l interactive -d 'Use interactive selection'
complete -c zoxide -n "__fish_seen_subcommand_from remove" -s h -l help -d 'Print help information' complete -c zoxide -n "__fish_seen_subcommand_from remove" -s h -l help -d 'Print help information'
complete -c zoxide -n "__fish_seen_subcommand_from remove" -s V -l version -d 'Print version information' complete -c zoxide -n "__fish_seen_subcommand_from remove" -s V -l version -d 'Print version information'

View File

@ -21,6 +21,87 @@ const completion: Fig.Spec = {
template: "folders", template: "folders",
}, },
}, },
{
name: "edit",
description: "Edit the database",
subcommands: [
{
name: "decrement",
hidden: true,
options: [
{
name: ["-h", "--help"],
description: "Print help information",
},
{
name: ["-V", "--version"],
description: "Print version information",
},
],
args: {
name: "path",
},
},
{
name: "delete",
hidden: true,
options: [
{
name: ["-h", "--help"],
description: "Print help information",
},
{
name: ["-V", "--version"],
description: "Print version information",
},
],
args: {
name: "path",
},
},
{
name: "increment",
hidden: true,
options: [
{
name: ["-h", "--help"],
description: "Print help information",
},
{
name: ["-V", "--version"],
description: "Print version information",
},
],
args: {
name: "path",
},
},
{
name: "reload",
hidden: true,
options: [
{
name: ["-h", "--help"],
description: "Print help information",
},
{
name: ["-V", "--version"],
description: "Print version information",
},
],
},
],
options: [
{
name: ["-h", "--help"],
description: "Print help information",
},
{
name: ["-V", "--version"],
description: "Print version information",
},
],
},
{ {
name: "import", name: "import",
description: "Import entries from another application", description: "Import entries from another application",
@ -28,6 +109,7 @@ const completion: Fig.Spec = {
{ {
name: "--from", name: "--from",
description: "Application to import from", description: "Application to import from",
isRepeatable: true,
args: { args: {
name: "from", name: "from",
suggestions: [ suggestions: [
@ -61,6 +143,7 @@ const completion: Fig.Spec = {
{ {
name: "--cmd", name: "--cmd",
description: "Changes the prefix of the `z` and `zi` commands", description: "Changes the prefix of the `z` and `zi` commands",
isRepeatable: true,
args: { args: {
name: "cmd", name: "cmd",
isOptional: true, isOptional: true,
@ -69,6 +152,7 @@ const completion: Fig.Spec = {
{ {
name: "--hook", name: "--hook",
description: "Changes how often zoxide increments a directory's score", description: "Changes how often zoxide increments a directory's score",
isRepeatable: true,
args: { args: {
name: "hook", name: "hook",
isOptional: true, isOptional: true,
@ -112,7 +196,8 @@ const completion: Fig.Spec = {
options: [ options: [
{ {
name: "--exclude", name: "--exclude",
description: "Exclude a path from results", description: "Exclude the current directory",
isRepeatable: true,
args: { args: {
name: "exclude", name: "exclude",
isOptional: true, isOptional: true,
@ -154,6 +239,7 @@ const completion: Fig.Spec = {
], ],
args: { args: {
name: "keywords", name: "keywords",
isVariadic: true,
isOptional: true, isOptional: true,
}, },
}, },
@ -161,10 +247,6 @@ const completion: Fig.Spec = {
name: "remove", name: "remove",
description: "Remove a directory from the database", description: "Remove a directory from the database",
options: [ options: [
{
name: ["-i", "--interactive"],
description: "Use interactive selection",
},
{ {
name: ["-h", "--help"], name: ["-h", "--help"],
description: "Print help information", description: "Print help information",
@ -176,6 +258,7 @@ const completion: Fig.Spec = {
], ],
args: { args: {
name: "paths", name: "paths",
isVariadic: true,
isOptional: true, isOptional: true,
template: "folders", template: "folders",
}, },

View File

@ -91,10 +91,12 @@ download_zoxide() {
wget) _releases="$(wget -qO- "$_releases_url")" || wget) _releases="$(wget -qO- "$_releases_url")" ||
err "wget: failed to download $_releases_url" ;; err "wget: failed to download $_releases_url" ;;
esac 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 local _package_url
_package_url="$(echo "$_releases" | grep "browser_download_url" | cut -d '"' -f 4 | grep "$_arch")" || _package_url="$(echo "$_releases" | grep "browser_download_url" | cut -d '"' -f 4 | grep "$_arch")" ||
err "zoxide has not yet been packaged for your architecture ($_arch), please file an issue at https://github.com/ajeetdsouza/zoxide/issues" err "zoxide has not yet been packaged for your architecture ($_arch), please file an issue: https://github.com/ajeetdsouza/zoxide/issues"
local _ext local _ext
case "$_package_url" in case "$_package_url" in

View File

@ -35,7 +35,7 @@ Note: zoxide only supports fish v3.4.0 and above.
Add this to your env file (find it by running \fB$nu.env-path\fR in Nushell): Add this to your env file (find it by running \fB$nu.env-path\fR in Nushell):
.sp .sp
.nf .nf
\fBzoxide init nushell --hook prompt | save ~/.zoxide.nu\fR \fBzoxide init nushell | save -f ~/.zoxide.nu\fR
.fi .fi
.sp .sp
Now, add this to the end of your config file (find it by running Now, add this to the end of your config file (find it by running
@ -45,7 +45,7 @@ Now, add this to the end of your config file (find it by running
\fBsource ~/.zoxide.nu\fR \fBsource ~/.zoxide.nu\fR
.fi .fi
.sp .sp
Note: zoxide only supports Nushell v0.63.0 and above. Note: zoxide only supports Nushell v0.73.0 and above.
.TP .TP
.B powershell .B powershell
Add this to your configuration (find it by running \fBecho $profile\fR in Add this to your configuration (find it by running \fBecho $profile\fR in

View File

@ -10,9 +10,6 @@ If you'd like to permanently exclude a directory from the database, see the
.TP .TP
.B -h, --help .B -h, --help
Print help information. Print help information.
.TP
.B -i, --interactive [KEYWORDS]
Use interactive selection. This option requires \fBfzf\fR(1).
.SH REPORTING BUGS .SH REPORTING BUGS
For any issues, feature requests, or questions, please visit: For any issues, feature requests, or questions, please visit:
.sp .sp

View File

@ -1,4 +1,3 @@
comment_width = 100
group_imports = "StdExternalCrate" group_imports = "StdExternalCrate"
imports_granularity = "Module" imports_granularity = "Module"
max_width = 120 max_width = 120

View File

@ -1,8 +1,8 @@
let let
rust = import (builtins.fetchTarball rust = import (builtins.fetchTarball
"https://github.com/oxalica/rust-overlay/archive/60c2cfaa8b90ed8cebd18b214fac8682dcf222dd.tar.gz"); "https://github.com/oxalica/rust-overlay/archive/9096306d4a1c3adcc8d20f2c9dcaee3dee30d1ad.tar.gz");
pkgs = import (builtins.fetchTarball pkgs = import (builtins.fetchTarball
"https://github.com/NixOS/nixpkgs/archive/0323e1f8bac882f19905174639a89397db1930f1.tar.gz") { "https://github.com/NixOS/nixpkgs/archive/5f902ae769594aaeaf326e8623a48482eeacfe89.tar.gz") {
overlays = [ rust ]; overlays = [ rust ];
}; };
in pkgs.mkShell { in pkgs.mkShell {
@ -21,7 +21,6 @@ in pkgs.mkShell {
pkgs.zsh pkgs.zsh
# Tools # Tools
pkgs.cargo-audit
pkgs.cargo-nextest pkgs.cargo-nextest
pkgs.mandoc pkgs.mandoc
pkgs.nixfmt pkgs.nixfmt

View File

@ -3,42 +3,39 @@ use std::path::Path;
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use crate::cmd::{Add, Run}; use crate::cmd::{Add, Run};
use crate::db::DatabaseFile; use crate::db::Database;
use crate::{config, util}; use crate::{config, util};
impl Run for Add { impl Run for Add {
fn run(&self) -> Result<()> { fn run(&self) -> Result<()> {
// These characters can't be printed cleanly to a single line, so they can cause confusion // These characters can't be printed cleanly to a single line, so they can cause
// when writing to fzf / stdout. // confusion when writing to stdout.
const EXCLUDE_CHARS: &[char] = &['\n', '\r']; const EXCLUDE_CHARS: &[char] = &['\n', '\r'];
let data_dir = config::data_dir()?;
let exclude_dirs = config::exclude_dirs()?; let exclude_dirs = config::exclude_dirs()?;
let max_age = config::maxage()?; let max_age = config::maxage()?;
let now = util::current_time()?; let now = util::current_time()?;
let mut db = DatabaseFile::new(data_dir); let mut db = Database::open()?;
let mut db = db.open()?;
for path in &self.paths { for path in &self.paths {
let path = if config::resolve_symlinks() { util::canonicalize } else { util::resolve_path }(path)?; let path = if config::resolve_symlinks() { util::canonicalize } else { util::resolve_path }(path)?;
let path = util::path_to_str(&path)?; let path = util::path_to_str(&path)?;
// Ignore path if it contains unsupported characters, or if it's in the exclude list. // Ignore path if it contains unsupported characters, or if it's in the exclude
// list.
if path.contains(EXCLUDE_CHARS) || exclude_dirs.iter().any(|glob| glob.matches(path)) { if path.contains(EXCLUDE_CHARS) || exclude_dirs.iter().any(|glob| glob.matches(path)) {
continue; continue;
} }
if !Path::new(path).is_dir() { if !Path::new(path).is_dir() {
bail!("not a directory: {path}"); bail!("not a directory: {path}");
} }
db.add(path, now); db.add_update(path, 1.0, now);
} }
if db.modified { if db.dirty() {
db.age(max_age); db.age(max_age);
db.save()?;
} }
db.save()
Ok(())
} }
} }

View File

@ -2,15 +2,15 @@
use std::path::PathBuf; use std::path::PathBuf;
use clap::{ArgEnum, Parser, ValueHint}; use clap::{Parser, Subcommand, ValueEnum, ValueHint};
const ENV_HELP: &str = "ENVIRONMENT VARIABLES: const ENV_HELP: &str = "Environment variables:
_ZO_DATA_DIR Path for zoxide data files _ZO_DATA_DIR Path for zoxide data files
_ZO_ECHO Print the matched directory before navigating to it when set to 1 _ZO_ECHO Print the matched directory before navigating to it when set to 1
_ZO_EXCLUDE_DIRS List of directory globs to be excluded _ZO_EXCLUDE_DIRS List of directory globs to be excluded
_ZO_FZF_OPTS Custom flags to pass to fzf _ZO_FZF_OPTS Custom flags to pass to fzf
_ZO_MAXAGE Maximum total age after which entries start getting deleted _ZO_MAXAGE Maximum total age after which entries start getting deleted
_ZO_RESOLVE_SYMLINKS Resolve symlinks when storing paths"; _ZO_RESOLVE_SYMLINKS Resolve symlinks when storing paths";
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
#[clap( #[clap(
@ -24,6 +24,7 @@ const ENV_HELP: &str = "ENVIRONMENT VARIABLES:
)] )]
pub enum Cmd { pub enum Cmd {
Add(Add), Add(Add),
Edit(Edit),
Import(Import), Import(Import),
Init(Init), Init(Init),
Query(Query), Query(Query),
@ -33,10 +34,29 @@ pub enum Cmd {
/// Add a new directory or increment its rank /// Add a new directory or increment its rank
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
pub struct Add { pub struct Add {
#[clap(min_values = 1, required = true, value_hint = ValueHint::DirPath)] #[clap(num_args = 1.., required = true, value_hint = ValueHint::DirPath)]
pub paths: Vec<PathBuf>, pub paths: Vec<PathBuf>,
} }
/// Edit the database
#[derive(Debug, Parser)]
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 /// Import entries from another application
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
pub struct Import { pub struct Import {
@ -44,7 +64,7 @@ pub struct Import {
pub path: PathBuf, pub path: PathBuf,
/// Application to import from /// Application to import from
#[clap(arg_enum, long)] #[clap(value_enum, long)]
pub from: ImportFrom, pub from: ImportFrom,
/// Merge into existing database /// Merge into existing database
@ -52,7 +72,7 @@ pub struct Import {
pub merge: bool, pub merge: bool,
} }
#[derive(ArgEnum, Clone, Debug)] #[derive(ValueEnum, Clone, Debug)]
pub enum ImportFrom { pub enum ImportFrom {
Autojump, Autojump,
Z, Z,
@ -61,7 +81,7 @@ pub enum ImportFrom {
/// Generate shell configuration /// Generate shell configuration
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
pub struct Init { pub struct Init {
#[clap(arg_enum)] #[clap(value_enum)]
pub shell: InitShell, pub shell: InitShell,
/// Prevents zoxide from defining the `z` and `zi` commands /// Prevents zoxide from defining the `z` and `zi` commands
@ -73,18 +93,18 @@ pub struct Init {
pub cmd: String, pub cmd: String,
/// Changes how often zoxide increments a directory's score /// Changes how often zoxide increments a directory's score
#[clap(arg_enum, long, default_value = "pwd")] #[clap(value_enum, long, default_value = "pwd")]
pub hook: InitHook, pub hook: InitHook,
} }
#[derive(ArgEnum, Clone, Copy, Debug, Eq, PartialEq)] #[derive(ValueEnum, Clone, Copy, Debug, Eq, PartialEq)]
pub enum InitHook { pub enum InitHook {
None, None,
Prompt, Prompt,
Pwd, Pwd,
} }
#[derive(ArgEnum, Clone, Debug)] #[derive(ValueEnum, Clone, Debug)]
pub enum InitShell { pub enum InitShell {
Bash, Bash,
Elvish, Elvish,
@ -117,7 +137,7 @@ pub struct Query {
#[clap(long, short)] #[clap(long, short)]
pub score: bool, pub score: bool,
/// Exclude a path from results /// Exclude the current directory
#[clap(long, value_hint = ValueHint::DirPath, value_name = "path")] #[clap(long, value_hint = ValueHint::DirPath, value_name = "path")]
pub exclude: Option<String>, pub exclude: Option<String>,
} }
@ -125,9 +145,6 @@ pub struct Query {
/// Remove a directory from the database /// Remove a directory from the database
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
pub struct Remove { pub struct Remove {
/// Use interactive selection
#[clap(long, short)]
pub interactive: bool,
#[clap(value_hint = ValueHint::DirPath)] #[clap(value_hint = ValueHint::DirPath)]
pub paths: Vec<String>, pub paths: Vec<String>,
} }

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

@ -0,0 +1,83 @@
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
"--scheme=path",
// Search result
"--tiebreak=end,chunk,index",
// Interface
"--bind=\
btab:up,\
ctrl-r:reload(zoxide edit reload),\
ctrl-d:reload(zoxide edit delete {2..}),\
ctrl-w:reload(zoxide edit increment {2..}),\
ctrl-s:reload(zoxide edit decrement {2..}),\
ctrl-z:ignore,\
double-click:ignore,\
enter:abort,\
start:reload(zoxide edit reload),\
tab:down",
"--cycle",
"--keep-right",
// Layout
"--border=sharp",
"--border-label= zoxide-edit ",
"--header=\
ctrl-r:reload \tctrl-d:delete
ctrl-w:increment\tctrl-s:decrement
SCORE\tPATH",
"--info=inline",
"--layout=reverse",
"--padding=1,0,0,0",
// Display
"--color=label:bold",
"--tabstop=1",
])
.enable_preview()
.spawn()
}
}

View File

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

View File

@ -11,10 +11,8 @@ use crate::shell::{self, Opts};
impl Run for Init { impl Run for Init {
fn run(&self) -> Result<()> { fn run(&self) -> Result<()> {
let cmd = if self.no_cmd { None } else { Some(self.cmd.as_str()) }; let cmd = if self.no_cmd { None } else { Some(self.cmd.as_str()) };
let echo = config::echo(); let echo = config::echo();
let resolve_symlinks = config::resolve_symlinks(); let resolve_symlinks = config::resolve_symlinks();
let opts = &Opts { cmd, hook: self.hook, echo, resolve_symlinks }; let opts = &Opts { cmd, hook: self.hook, echo, resolve_symlinks };
let source = match self.shell { let source = match self.shell {

View File

@ -1,5 +1,6 @@
mod add; mod add;
mod cmd; mod cmd;
mod edit;
mod import; mod import;
mod init; mod init;
mod query; mod query;
@ -17,6 +18,7 @@ impl Run for Cmd {
fn run(&self) -> Result<()> { fn run(&self) -> Result<()> {
match self { match self {
Cmd::Add(cmd) => cmd.run(), Cmd::Add(cmd) => cmd.run(),
Cmd::Edit(cmd) => cmd.run(),
Cmd::Import(cmd) => cmd.run(), Cmd::Import(cmd) => cmd.run(),
Cmd::Init(cmd) => cmd.run(), Cmd::Init(cmd) => cmd.run(),
Cmd::Query(cmd) => cmd.run(), Cmd::Query(cmd) => cmd.run(),

View File

@ -1,20 +1,16 @@
use anyhow::{Context, Result};
use std::env;
use std::io::{self, Write}; use std::io::{self, Write};
use std::path::PathBuf;
use std::str::FromStr; use anyhow::{bail, Context, Result};
use crate::cmd::{Query, Run}; use crate::cmd::{Query, Run};
use crate::config; use crate::config;
use crate::db::{Database, DatabaseFile}; use crate::db::{Database, Epoch, Stream};
use crate::error::BrokenPipeHandler; use crate::error::BrokenPipeHandler;
use crate::util::{self, Fzf}; use crate::util::{self, Fzf, FzfChild};
impl Run for Query { impl Run for Query {
fn run(&self) -> Result<()> { fn run(&self) -> Result<()> {
let data_dir = config::data_dir()?; let mut db = crate::db::Database::open()?;
let mut db = DatabaseFile::new(data_dir);
let mut db = db.open()?;
self.query(&mut db).and(db.save()) self.query(&mut db).and(db.save())
} }
} }
@ -22,7 +18,49 @@ impl Run for Query {
impl Query { impl Query {
fn query(&self, db: &mut Database) -> Result<()> { fn query(&self, db: &mut Database) -> Result<()> {
let now = util::current_time()?; let now = util::current_time()?;
let mut stream = self.get_stream(db, now);
if self.interactive {
let mut fzf = Self::get_fzf()?;
let selection = loop {
match stream.next() {
Some(dir) => {
if let Some(selection) = fzf.write(dir, now)? {
break selection;
}
}
None => break fzf.wait()?,
}
};
if self.score {
print!("{selection}");
} else {
let path = selection.get(7..).context("could not read selection from fzf")?;
print!("{path}");
}
} else if self.list {
let handle = &mut io::stdout().lock();
while let Some(dir) = stream.next() {
let dir = if self.score { dir.display().with_score(now) } else { dir.display() };
writeln!(handle, "{dir}").pipe_exit("stdout")?;
}
} else {
let handle = &mut io::stdout();
let Some(dir) = stream.next() else {
if stream.did_exclude() {
bail!("you are already in the only match");
}
bail!("no match found");
};
let dir = if self.score { dir.display().with_score(now) } else { dir.display() };
writeln!(handle, "{dir}").pipe_exit("stdout")?;
}
Ok(())
}
fn get_stream<'a>(&self, db: &'a mut Database, now: Epoch) -> Stream<'a> {
let mut stream = db.stream(now).with_keywords(&self.keywords); let mut stream = db.stream(now).with_keywords(&self.keywords);
if !self.all { if !self.all {
let resolve_symlinks = config::resolve_symlinks(); let resolve_symlinks = config::resolve_symlinks();
@ -31,64 +69,36 @@ impl Query {
if let Some(path) = &self.exclude { if let Some(path) = &self.exclude {
stream = stream.with_exclude(path); stream = stream.with_exclude(path);
} }
stream
}
if self.interactive { fn get_fzf() -> Result<FzfChild> {
let mut fzf = Fzf::new(false)?; let mut fzf = Fzf::new()?;
let stdin = fzf.stdin(); if let Some(fzf_opts) = config::fzf_opts() {
fzf.env("FZF_DEFAULT_OPTS", fzf_opts)
let selection = loop {
let dir = match stream.next() {
Some(dir) => dir,
None => break fzf.select()?,
};
match writeln!(stdin, "{}", dir.display_score(now)) {
Err(e) if e.kind() == io::ErrorKind::BrokenPipe => break fzf.select()?,
result => result.context("could not write to fzf")?,
}
};
if self.score {
print!("{selection}");
} else {
let path = selection.get(5..).context("could not read selection from fzf")?;
print!("{path}");
}
} else if self.list {
let handle = &mut io::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 { } else {
let excluded_dir = self.exclude.as_ref().map_or(PathBuf::new(), |path| PathBuf::from_str(path).unwrap()); fzf.args([
// Search mode
let try_dir = stream.next(); "--scheme=path",
let dir = match try_dir { // Search result
Some(dir) => dir, "--tiebreak=end,chunk,index",
None => { // Interface
// Current Directory is passed as excluded in __zoxide_z "--bind=ctrl-z:ignore,btab:up,tab:down",
if excluded_dir == env::current_dir()? { "--cycle",
try_dir.context("already in the matched directory")? "--keep-right",
} else { // Layout
try_dir.context("no match found")? "--border=sharp", // rounded edges don't display correctly on some terminals
} "--height=45%",
} "--info=inline",
}; "--layout=reverse",
// Display
if self.score { "--tabstop=1",
writeln!(io::stdout(), "{}", dir.display_score(now)) // Scripting
} else { "--exit-0",
writeln!(io::stdout(), "{}", dir.display()) "--select-1",
} ])
.pipe_exit("stdout")?; .enable_preview()
} }
.spawn()
Ok(())
} }
} }

View File

@ -1,54 +1,19 @@
use std::io::{self, Write}; use anyhow::{bail, Result};
use anyhow::{bail, Context, Result};
use crate::cmd::{Remove, Run}; use crate::cmd::{Remove, Run};
use crate::config; use crate::db::Database;
use crate::db::DatabaseFile; use crate::util;
use crate::util::{self, Fzf};
impl Run for Remove { impl Run for Remove {
fn run(&self) -> Result<()> { fn run(&self) -> Result<()> {
let data_dir = config::data_dir()?; let mut db = Database::open()?;
let mut db = DatabaseFile::new(data_dir);
let mut db = db.open()?;
if self.interactive { for path in &self.paths {
let keywords = &self.paths; if !db.remove(path) {
let now = util::current_time()?; let path_abs = util::resolve_path(path)?;
let mut stream = db.stream(now).with_keywords(keywords); let path_abs = util::path_to_str(&path_abs)?;
if path_abs == path || !db.remove(path_abs) {
let mut fzf = Fzf::new(true)?; bail!("path not found in database: {path}")
let stdin = fzf.stdin();
let selection = loop {
let dir = match stream.next() {
Some(dir) => dir,
None => break fzf.select()?,
};
match writeln!(stdin, "{}", dir.display_score(now)) {
Err(e) if e.kind() == io::ErrorKind::BrokenPipe => break fzf.select()?,
result => result.context("could not write to fzf")?,
}
};
let paths = selection.lines().filter_map(|line| line.get(5..));
for path in paths {
if !db.remove(path) {
db.modified = false;
bail!("path not found in database: {path}");
}
}
} else {
for path in &self.paths {
if !db.remove(path) {
let path_abs = util::resolve_path(path)?;
let path_abs = util::path_to_str(&path_abs)?;
if path_abs == path || !db.remove(path_abs) {
db.modified = false;
bail!("path not found in database: {path}")
}
} }
} }
} }

View File

@ -1,83 +1,9 @@
use std::borrow::Cow; use std::borrow::Cow;
use std::fmt::{self, Display, Formatter}; use std::fmt::{self, Display, Formatter};
use std::ops::{Deref, DerefMut};
use anyhow::{bail, Context, Result};
use bincode::Options as _;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize)] use crate::util::{DAY, HOUR, WEEK};
pub struct DirList<'a>(#[serde(borrow)] pub Vec<Dir<'a>>);
impl DirList<'_> {
const VERSION: u32 = 3;
pub fn new() -> DirList<'static> {
DirList(Vec::new())
}
pub fn from_bytes(bytes: &[u8]) -> Result<DirList> {
// Assume a maximum size for the database. This prevents bincode from throwing strange
// errors when it encounters invalid data.
const MAX_SIZE: u64 = 32 << 20; // 32 MiB
let deserializer = &mut bincode::options().with_fixint_encoding().with_limit(MAX_SIZE);
// Split bytes into sections.
let version_size = deserializer.serialized_size(&Self::VERSION).unwrap() as _;
if bytes.len() < version_size {
bail!("could not deserialize database: corrupted data");
}
let (bytes_version, bytes_dirs) = bytes.split_at(version_size);
// Deserialize sections.
(|| {
let version = deserializer.deserialize(bytes_version)?;
match version {
Self::VERSION => Ok(deserializer.deserialize(bytes_dirs)?),
version => {
bail!("unsupported version (got {version}, supports {})", Self::VERSION)
}
}
})()
.context("could not deserialize database")
}
pub fn to_bytes(&self) -> Result<Vec<u8>> {
(|| -> bincode::Result<_> {
// Preallocate buffer with combined size of sections.
let version_size = bincode::serialized_size(&Self::VERSION)?;
let dirs_size = bincode::serialized_size(&self)?;
let buffer_size = version_size + dirs_size;
let mut buffer = Vec::with_capacity(buffer_size as _);
// Serialize sections into buffer.
bincode::serialize_into(&mut buffer, &Self::VERSION)?;
bincode::serialize_into(&mut buffer, &self)?;
Ok(buffer)
})()
.context("could not serialize database")
}
}
impl<'a> Deref for DirList<'a> {
type Target = Vec<Dir<'a>>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<'a> DerefMut for DirList<'a> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<'a> From<Vec<Dir<'a>>> for DirList<'a> {
fn from(dirs: Vec<Dir<'a>>) -> Self {
DirList(dirs)
}
}
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Dir<'a> { pub struct Dir<'a> {
@ -88,11 +14,11 @@ pub struct Dir<'a> {
} }
impl Dir<'_> { impl Dir<'_> {
pub fn score(&self, now: Epoch) -> Rank { pub fn display(&self) -> DirDisplay<'_> {
const HOUR: Epoch = 60 * 60; DirDisplay::new(self)
const DAY: Epoch = 24 * HOUR; }
const WEEK: Epoch = 7 * DAY;
pub fn score(&self, now: Epoch) -> Rank {
// The older the entry, the lesser its importance. // The older the entry, the lesser its importance.
let duration = now.saturating_sub(self.last_accessed); let duration = now.saturating_sub(self.last_accessed);
if duration < HOUR { if duration < HOUR {
@ -105,56 +31,39 @@ impl Dir<'_> {
self.rank * 0.25 self.rank * 0.25
} }
} }
pub fn display(&self) -> DirDisplay {
DirDisplay { dir: self }
}
pub fn display_score(&self, now: Epoch) -> DirDisplayScore {
DirDisplayScore { dir: self, now }
}
} }
pub struct DirDisplay<'a> { pub struct DirDisplay<'a> {
dir: &'a Dir<'a>, dir: &'a Dir<'a>,
now: Option<Epoch>,
separator: char,
}
impl<'a> DirDisplay<'a> {
fn new(dir: &'a Dir) -> Self {
Self { dir, separator: ' ', now: None }
}
pub fn with_score(mut self, now: Epoch) -> Self {
self.now = Some(now);
self
}
pub fn with_separator(mut self, separator: char) -> Self {
self.separator = separator;
self
}
} }
impl Display for DirDisplay<'_> { impl Display for DirDisplay<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
if let Some(now) = self.now {
let score = self.dir.score(now).clamp(0.0, 9999.0);
write!(f, "{score:>6.1}{}", self.separator)?;
}
write!(f, "{}", self.dir.path) write!(f, "{}", self.dir.path)
} }
} }
pub struct DirDisplayScore<'a> {
dir: &'a Dir<'a>,
now: Epoch,
}
impl Display for DirDisplayScore<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let score = self.dir.score(self.now).clamp(0.0, 9999.0) as u32;
write!(f, "{:>4} {}", score, self.dir.path)
}
}
pub type Rank = f64; pub type Rank = f64;
pub type Epoch = u64; pub type Epoch = u64;
#[cfg(test)]
mod tests {
use std::borrow::Cow;
use super::*;
#[test]
fn zero_copy() {
let dirs = DirList(vec![Dir { path: "/".into(), rank: 0.0, last_accessed: 0 }]);
let bytes = dirs.to_bytes().unwrap();
let dirs = DirList::from_bytes(&bytes).unwrap();
for dir in dirs.iter() {
assert!(matches!(dir.path, Cow::Borrowed(_)))
}
}
}

View File

@ -4,145 +4,220 @@ mod stream;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::{fs, io}; use std::{fs, io};
use anyhow::{Context, Result}; use anyhow::{bail, Context, Result};
pub use dir::{Dir, DirList, Epoch, Rank}; use bincode::Options;
pub use stream::Stream; use ouroboros::self_referencing;
use crate::util; pub use crate::db::dir::{Dir, Epoch, Rank};
pub use crate::db::stream::Stream;
use crate::{config, util};
#[derive(Debug)] #[self_referencing]
pub struct Database<'file> { pub struct Database {
pub dirs: DirList<'file>, path: PathBuf,
pub modified: bool, bytes: Vec<u8>,
pub data_dir: &'file Path, #[borrows(bytes)]
#[covariant]
pub dirs: Vec<Dir<'this>>,
dirty: bool,
} }
impl<'file> Database<'file> { impl Database {
pub fn save(&mut self) -> Result<()> { const VERSION: u32 = 3;
if !self.modified {
return Ok(());
}
let buffer = self.dirs.to_bytes()?; pub fn open() -> Result<Self> {
let path = db_path(&self.data_dir); let data_dir = config::data_dir()?;
util::write(&path, &buffer).context("could not write to database")?; Self::open_dir(data_dir)
self.modified = false;
Ok(())
} }
/// Adds a new directory or increments its rank. Also updates its last accessed time. pub fn open_dir(data_dir: impl AsRef<Path>) -> Result<Self> {
pub fn add<S: AsRef<str>>(&mut self, path: S, now: Epoch) { let data_dir = data_dir.as_ref();
let path = path.as_ref(); let path = data_dir.join("db.zo");
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;
}
}
}
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) { match fs::read(&path) {
Ok(buffer) => { Ok(bytes) => Self::try_new(path, bytes, |bytes| Self::deserialize(bytes), false),
self.buffer = buffer;
let dirs = DirList::from_bytes(&self.buffer)
.with_context(|| format!("could not deserialize database: {}", path.display()))?;
Ok(Database { dirs, modified: false, data_dir: &self.data_dir })
}
Err(e) if e.kind() == io::ErrorKind::NotFound => { Err(e) if e.kind() == io::ErrorKind::NotFound => {
// Create data directory, but don't create any file yet. The file will be created // Create data directory, but don't create any file yet. The file will be
// later by [`Database::save`] if any data is modified. // created later by [`Database::save`] if any data is modified.
fs::create_dir_all(&self.data_dir) fs::create_dir_all(data_dir)
.with_context(|| format!("unable to create data directory: {}", self.data_dir.display()))?; .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())), Err(e) => Err(e).with_context(|| format!("could not read from database: {}", path.display())),
} }
} }
}
fn db_path<P: AsRef<Path>>(data_dir: P) -> PathBuf { pub fn save(&mut self) -> Result<()> {
const DB_FILENAME: &str = "db.zo"; // Only write to disk if the database is modified.
data_dir.as_ref().join(DB_FILENAME) 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 stream(&mut self, now: Epoch) -> Stream {
Stream::new(self, now)
}
pub fn dedup(&mut self) {
// Sort by path, so that equal paths are next to each other.
self.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)] #[cfg(test)]
@ -151,50 +226,49 @@ mod tests {
#[test] #[test]
fn add() { fn add() {
let data_dir = tempfile::tempdir().unwrap();
let path = if cfg!(windows) { r"C:\foo\bar" } else { "/foo/bar" }; let path = if cfg!(windows) { r"C:\foo\bar" } else { "/foo/bar" };
let now = 946684800; let now = 946684800;
let data_dir = tempfile::tempdir().unwrap();
{ {
let mut db = DatabaseFile::new(data_dir.path()); let mut db = Database::open_dir(data_dir.path()).unwrap();
let mut db = db.open().unwrap(); db.add(path, 1.0, now);
db.add(path, now); db.add(path, 1.0, now);
db.add(path, now);
db.save().unwrap(); db.save().unwrap();
} }
{
let mut db = DatabaseFile::new(data_dir.path());
let db = db.open().unwrap();
assert_eq!(db.dirs.len(), 1);
let dir = &db.dirs[0]; {
let db = Database::open_dir(data_dir.path()).unwrap();
assert_eq!(db.dirs().len(), 1);
let dir = &db.dirs()[0];
assert_eq!(dir.path, path); assert_eq!(dir.path, path);
assert!((dir.rank - 2.0).abs() < 0.01);
assert_eq!(dir.last_accessed, now); assert_eq!(dir.last_accessed, now);
} }
} }
#[test] #[test]
fn remove() { fn remove() {
let data_dir = tempfile::tempdir().unwrap();
let path = if cfg!(windows) { r"C:\foo\bar" } else { "/foo/bar" }; let path = if cfg!(windows) { r"C:\foo\bar" } else { "/foo/bar" };
let now = 946684800; let now = 946684800;
let data_dir = tempfile::tempdir().unwrap();
{ {
let mut db = DatabaseFile::new(data_dir.path()); let mut db = Database::open_dir(data_dir.path()).unwrap();
let mut db = db.open().unwrap(); db.add(path, 1.0, now);
db.add(path, now);
db.save().unwrap(); db.save().unwrap();
} }
{ {
let mut db = DatabaseFile::new(data_dir.path()); let mut db = Database::open_dir(data_dir.path()).unwrap();
let mut db = db.open().unwrap();
assert!(db.remove(path)); assert!(db.remove(path));
db.save().unwrap(); db.save().unwrap();
} }
{ {
let mut db = DatabaseFile::new(data_dir.path()); let mut db = Database::open_dir(data_dir.path()).unwrap();
let mut db = db.open().unwrap(); assert!(db.dirs().is_empty());
assert!(db.dirs.is_empty());
assert!(!db.remove(path)); assert!(!db.remove(path));
db.save().unwrap(); db.save().unwrap();
} }

View File

@ -3,33 +3,35 @@ use std::ops::Range;
use std::{fs, path}; use std::{fs, path};
use crate::db::{Database, Dir, Epoch}; use crate::db::{Database, Dir, Epoch};
use crate::util; use crate::util::{self, MONTH};
pub struct Stream<'db, 'file> { pub struct Stream<'a> {
db: &'db mut Database<'file>, // State
db: &'a mut Database,
idxs: Rev<Range<usize>>, idxs: Rev<Range<usize>>,
did_exclude: bool,
// Configuration
keywords: Vec<String>, keywords: Vec<String>,
check_exists: bool, check_exists: bool,
expire_below: Epoch, expire_below: Epoch,
resolve_symlinks: bool, resolve_symlinks: bool,
exclude_path: Option<String>, exclude_path: Option<String>,
} }
impl<'db, 'file> Stream<'db, 'file> { impl<'a> Stream<'a> {
pub fn new(db: &'db mut Database<'file>, now: Epoch) -> Self { pub fn new(db: &'a mut Database, now: Epoch) -> Self {
// Iterate in descending order of score. db.sort_by_score(now);
db.dirs.sort_unstable_by(|dir1, dir2| dir1.score(now).total_cmp(&dir2.score(now))); let idxs = (0..db.dirs().len()).rev();
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. // If a directory is deleted and hasn't been used for 3 months, delete
let expire_below = now.saturating_sub(90 * 24 * 60 * 60); // it from the database.
let expire_below = now.saturating_sub(3 * MONTH);
Stream { Stream {
db, db,
idxs, idxs,
did_exclude: false,
keywords: Vec::new(), keywords: Vec::new(),
check_exists: false, check_exists: false,
expire_below, expire_below,
@ -38,7 +40,7 @@ impl<'db, 'file> Stream<'db, 'file> {
} }
} }
pub fn with_exclude<S: Into<String>>(mut self, path: S) -> Self { pub fn with_exclude(mut self, path: impl Into<String>) -> Self {
self.exclude_path = Some(path.into()); self.exclude_path = Some(path.into());
self self
} }
@ -49,14 +51,14 @@ impl<'db, 'file> Stream<'db, 'file> {
self self
} }
pub fn with_keywords<S: AsRef<str>>(mut self, keywords: &[S]) -> Self { pub fn with_keywords(mut self, keywords: &[impl AsRef<str>]) -> Self {
self.keywords = keywords.iter().map(util::to_lowercase).collect(); self.keywords = keywords.iter().map(util::to_lowercase).collect();
self self
} }
pub fn next(&mut self) -> Option<&Dir<'file>> { pub fn next(&mut self) -> Option<&Dir> {
while let Some(idx) = self.idxs.next() { while let Some(idx) = self.idxs.next() {
let dir = &self.db.dirs[idx]; let dir = &self.db.dirs()[idx];
if !self.matches_keywords(&dir.path) { if !self.matches_keywords(&dir.path) {
continue; continue;
@ -64,32 +66,36 @@ impl<'db, 'file> Stream<'db, 'file> {
if !self.matches_exists(&dir.path) { if !self.matches_exists(&dir.path) {
if dir.last_accessed < self.expire_below { if dir.last_accessed < self.expire_below {
self.db.dirs.swap_remove(idx); self.db.swap_remove(idx);
self.db.modified = true;
} }
continue; continue;
} }
if Some(dir.path.as_ref()) == self.exclude_path.as_deref() { if Some(dir.path.as_ref()) == self.exclude_path.as_deref() {
self.did_exclude = true;
continue; continue;
} }
let dir = &self.db.dirs[idx]; let dir = &self.db.dirs()[idx];
return Some(dir); return Some(dir);
} }
None None
} }
fn matches_exists<S: AsRef<str>>(&self, path: S) -> bool { pub fn did_exclude(&self) -> bool {
self.did_exclude
}
fn matches_exists(&self, path: &str) -> bool {
if !self.check_exists { if !self.check_exists {
return true; return true;
} }
let resolver = if self.resolve_symlinks { fs::symlink_metadata } else { fs::metadata }; let resolver = if self.resolve_symlinks { fs::symlink_metadata } else { fs::metadata };
resolver(path.as_ref()).map(|m| m.is_dir()).unwrap_or_default() resolver(path).map(|m| m.is_dir()).unwrap_or_default()
} }
fn matches_keywords<S: AsRef<str>>(&self, path: S) -> bool { fn matches_keywords(&self, path: &str) -> bool {
let (keywords_last, keywords) = match self.keywords.split_last() { let (keywords_last, keywords) = match self.keywords.split_last() {
Some(split) => split, Some(split) => split,
None => return true, None => return true,
@ -147,8 +153,8 @@ mod tests {
#[case(&["/foo/", "/bar"], "/foo/bar", false)] #[case(&["/foo/", "/bar"], "/foo/bar", false)]
#[case(&["/foo/", "/bar"], "/foo/baz/bar", true)] #[case(&["/foo/", "/bar"], "/foo/baz/bar", true)]
fn query(#[case] keywords: &[&str], #[case] path: &str, #[case] is_match: bool) { fn query(#[case] keywords: &[&str], #[case] path: &str, #[case] is_match: bool) {
let mut db = Database { dirs: Vec::new().into(), modified: false, data_dir: &PathBuf::new() }; let db = &mut Database::new(PathBuf::new(), Vec::new(), |_| Vec::new(), false);
let stream = db.stream(0).with_keywords(keywords); let stream = Stream::new(db, 0).with_keywords(keywords);
assert_eq!(is_match, stream.matches_keywords(path)); assert_eq!(is_match, stream.matches_keywords(path));
} }
} }

View File

@ -57,7 +57,7 @@ mod tests {
let opts = Opts { cmd, hook, echo, resolve_symlinks }; let opts = Opts { cmd, hook, echo, resolve_symlinks };
let source = Bash(&opts).render().unwrap(); let source = Bash(&opts).render().unwrap();
Command::new("bash") Command::new("bash")
.args(&["--noprofile", "--norc", "-e", "-u", "-o", "pipefail", "-c", &source]) .args(["--noprofile", "--norc", "-e", "-u", "-o", "pipefail", "-c", &source])
.assert() .assert()
.success() .success()
.stdout("") .stdout("")
@ -70,7 +70,7 @@ mod tests {
let source = Bash(&opts).render().unwrap(); let source = Bash(&opts).render().unwrap();
Command::new("shellcheck") Command::new("shellcheck")
.args(&["--enable", "all", "--shell", "bash", "-"]) .args(["--enable", "all", "--shell", "bash", "-"])
.write_stdin(source) .write_stdin(source)
.assert() .assert()
.success() .success()
@ -85,7 +85,7 @@ mod tests {
source.push('\n'); source.push('\n');
Command::new("shfmt") Command::new("shfmt")
.args(&["-d", "-s", "-ln", "bash", "-i", "4", "-ci", "-"]) .args(["-d", "-s", "-ln", "bash", "-i", "4", "-ci", "-"])
.write_stdin(source) .write_stdin(source)
.assert() .assert()
.success() .success()
@ -98,14 +98,14 @@ mod tests {
let opts = Opts { cmd, hook, echo, resolve_symlinks }; let opts = Opts { cmd, hook, echo, resolve_symlinks };
let mut source = String::new(); let mut source = String::new();
// Filter out lines using edit:*, since those functions are only available in the // Filter out lines using edit:*, since those functions are only available in
// interactive editor. // the interactive editor.
for line in Elvish(&opts).render().unwrap().split('\n').filter(|line| !line.contains("edit:")) { for line in Elvish(&opts).render().unwrap().split('\n').filter(|line| !line.contains("edit:")) {
source.push_str(line); source.push_str(line);
source.push('\n'); source.push('\n');
} }
Command::new("elvish").args(&["-c", &source, "-norc"]).assert().success().stdout("").stderr(""); Command::new("elvish").args(["-c", &source, "-norc"]).assert().success().stdout("").stderr("");
} }
#[apply(opts)] #[apply(opts)]
@ -118,7 +118,7 @@ mod tests {
Command::new("fish") Command::new("fish")
.env("HOME", tempdir) .env("HOME", tempdir)
.args(&["--command", &source, "--private"]) .args(["--command", &source, "--no-config", "--private"])
.assert() .assert()
.success() .success()
.stdout("") .stdout("")
@ -152,7 +152,7 @@ mod tests {
let tempdir = tempdir.path(); let tempdir = tempdir.path();
let assert = let assert =
Command::new("nu").env("HOME", tempdir).args(&["--commands", &source]).assert().success().stderr(""); Command::new("nu").env("HOME", tempdir).args(["--commands", &source]).assert().success().stderr("");
if opts.hook != InitHook::Pwd { if opts.hook != InitHook::Pwd {
assert.stdout(""); assert.stdout("");
@ -165,7 +165,7 @@ mod tests {
let source = Posix(&opts).render().unwrap(); let source = Posix(&opts).render().unwrap();
let assert = Command::new("bash") let assert = Command::new("bash")
.args(&["--posix", "--noprofile", "--norc", "-e", "-u", "-o", "pipefail", "-c", &source]) .args(["--posix", "--noprofile", "--norc", "-e", "-u", "-o", "pipefail", "-c", &source])
.assert() .assert()
.success() .success()
.stderr(""); .stderr("");
@ -179,7 +179,7 @@ mod tests {
let opts = Opts { cmd, hook, echo, resolve_symlinks }; let opts = Opts { cmd, hook, echo, resolve_symlinks };
let source = Posix(&opts).render().unwrap(); let source = Posix(&opts).render().unwrap();
let assert = Command::new("dash").args(&["-e", "-u", "-c", &source]).assert().success().stderr(""); let assert = Command::new("dash").args(["-e", "-u", "-c", &source]).assert().success().stderr("");
if opts.hook != InitHook::Pwd { if opts.hook != InitHook::Pwd {
assert.stdout(""); assert.stdout("");
} }
@ -191,7 +191,7 @@ mod tests {
let source = Posix(&opts).render().unwrap(); let source = Posix(&opts).render().unwrap();
Command::new("shellcheck") Command::new("shellcheck")
.args(&["--enable", "all", "--shell", "sh", "-"]) .args(["--enable", "all", "--shell", "sh", "-"])
.write_stdin(source) .write_stdin(source)
.assert() .assert()
.success() .success()
@ -206,7 +206,7 @@ mod tests {
source.push('\n'); source.push('\n');
Command::new("shfmt") Command::new("shfmt")
.args(&["-d", "-s", "-ln", "posix", "-i", "4", "-ci", "-"]) .args(["-d", "-s", "-ln", "posix", "-i", "4", "-ci", "-"])
.write_stdin(source) .write_stdin(source)
.assert() .assert()
.success() .success()
@ -221,7 +221,7 @@ mod tests {
Powershell(&opts).render_into(&mut source).unwrap(); Powershell(&opts).render_into(&mut source).unwrap();
Command::new("pwsh") Command::new("pwsh")
.args(&["-NoLogo", "-NonInteractive", "-NoProfile", "-Command", &source]) .args(["-NoLogo", "-NonInteractive", "-NoProfile", "-Command", &source])
.assert() .assert()
.success() .success()
.stdout("") .stdout("")
@ -234,7 +234,7 @@ mod tests {
let mut source = Xonsh(&opts).render().unwrap(); let mut source = Xonsh(&opts).render().unwrap();
source.push('\n'); source.push('\n');
Command::new("black").args(&["--check", "--diff", "-"]).write_stdin(source).assert().success().stdout(""); Command::new("black").args(["--check", "--diff", "-"]).write_stdin(source).assert().success().stdout("");
} }
#[apply(opts)] #[apply(opts)]
@ -242,7 +242,7 @@ mod tests {
let opts = Opts { cmd, hook, echo, resolve_symlinks }; let opts = Opts { cmd, hook, echo, resolve_symlinks };
let source = Xonsh(&opts).render().unwrap(); let source = Xonsh(&opts).render().unwrap();
Command::new("mypy").args(&["--command", &source, "--strict"]).assert().success().stderr(""); Command::new("mypy").args(["--command", &source, "--strict"]).assert().success().stderr("");
} }
#[apply(opts)] #[apply(opts)]
@ -252,7 +252,7 @@ mod tests {
source.push('\n'); source.push('\n');
Command::new("pylint") Command::new("pylint")
.args(&["--from-stdin", "--persistent=n", "zoxide"]) .args(["--from-stdin", "--persistent=n", "zoxide"])
.write_stdin(source) .write_stdin(source)
.assert() .assert()
.success() .success()
@ -268,7 +268,7 @@ mod tests {
let tempdir = tempdir.path().to_str().unwrap(); let tempdir = tempdir.path().to_str().unwrap();
Command::new("xonsh") Command::new("xonsh")
.args(&["-c", &source, "--no-rc"]) .args(["-c", &source, "--no-rc"])
.env("HOME", tempdir) .env("HOME", tempdir)
.assert() .assert()
.success() .success()
@ -283,7 +283,7 @@ mod tests {
// ShellCheck doesn't support zsh yet: https://github.com/koalaman/shellcheck/issues/809 // ShellCheck doesn't support zsh yet: https://github.com/koalaman/shellcheck/issues/809
Command::new("shellcheck") Command::new("shellcheck")
.args(&["--enable", "all", "--shell", "bash", "-"]) .args(["--enable", "all", "--shell", "bash", "-"])
.write_stdin(source) .write_stdin(source)
.assert() .assert()
.success() .success()
@ -297,7 +297,7 @@ mod tests {
let source = Zsh(&opts).render().unwrap(); let source = Zsh(&opts).render().unwrap();
Command::new("zsh") Command::new("zsh")
.args(&["-e", "-u", "-o", "pipefail", "--no-globalrcs", "--no-rcs", "-c", &source]) .args(["-e", "-u", "-o", "pipefail", "--no-globalrcs", "--no-rcs", "-c", &source])
.assert() .assert()
.success() .success()
.stdout("") .stdout("")

View File

@ -1,7 +1,8 @@
use std::ffi::OsStr;
use std::fs::{self, File, OpenOptions}; use std::fs::{self, File, OpenOptions};
use std::io::{self, Read, Write}; use std::io::{self, Read, Write};
use std::path::{Component, Path, PathBuf}; use std::path::{Component, Path, PathBuf};
use std::process::{Child, ChildStdin, Command, Stdio}; use std::process::{Child, Command, Stdio};
use std::time::SystemTime; use std::time::SystemTime;
use std::{env, mem}; use std::{env, mem};
@ -9,81 +10,133 @@ use std::{env, mem};
use anyhow::anyhow; use anyhow::anyhow;
use anyhow::{bail, Context, Result}; use anyhow::{bail, Context, Result};
use crate::config; use crate::db::{Dir, Epoch};
use crate::db::Epoch;
use crate::error::SilentExit; use crate::error::SilentExit;
pub struct Fzf { pub const SECOND: Epoch = 1;
child: Child, 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 { impl Fzf {
pub fn new(multiple: bool) -> Result<Self> { const ERR_FZF_NOT_FOUND: &str = "could not find fzf, is it installed?";
const ERR_FZF_NOT_FOUND: &str = "could not find fzf, is it installed?";
pub fn new() -> Result<Self> {
// On Windows, CreateProcess implicitly searches the current working // On Windows, CreateProcess implicitly searches the current working
// directory for the executable, which is a potential security issue. // directory for the executable, which is a potential security issue.
// Instead, we resolve the path to the executable and then pass it to // Instead, we resolve the path to the executable and then pass it to
// CreateProcess. // CreateProcess.
#[cfg(windows)] #[cfg(windows)]
let mut command = Command::new(which::which("fzf.exe").map_err(|_| anyhow!(ERR_FZF_NOT_FOUND))?); let program = which::which("fzf.exe").map_err(|_| anyhow!(Self::ERR_FZF_NOT_FOUND))?;
#[cfg(not(windows))] #[cfg(not(windows))]
let mut command = Command::new("fzf"); let program = "fzf";
if multiple {
command.arg("-m");
}
command.arg("--nth=2..").stdin(Stdio::piped()).stdout(Stdio::piped());
if let Some(fzf_opts) = config::fzf_opts() {
command.env("FZF_DEFAULT_OPTS", fzf_opts);
} else {
command.args(&[
// Search result
"--no-sort",
// Interface
"--keep-right",
// Layout
"--height=50%",
"--info=inline",
"--layout=reverse",
// Scripting
"--exit-0",
"--select-1",
// Key/Event bindings
"--bind=ctrl-z:ignore",
]);
if cfg!(unix) {
// Non-POSIX args are only available on certain operating systems.
const PREVIEW_CMD: &str = if cfg!(target_os = "linux") {
r"\command -p ls -Cp --color=always --group-directories-first {2..}"
} else {
r"\command -p ls -Cp {2..}"
};
command.args(&["--preview", PREVIEW_CMD, "--preview-window=down,30%"]).env("SHELL", "sh");
}
}
let child = match command.spawn() { let mut cmd = Command::new(program);
Ok(child) => child, cmd.args([
Err(e) if e.kind() == io::ErrorKind::NotFound => bail!(ERR_FZF_NOT_FOUND), // Search mode
Err(e) => Err(e).context("could not launch fzf")?, "--delimiter=\t",
}; "--nth=2",
// Scripting
"--read0",
])
.stdin(Stdio::piped())
.stdout(Stdio::piped());
Ok(Fzf { child }) Ok(Fzf(cmd))
} }
pub fn stdin(&mut self) -> &mut ChildStdin { pub fn enable_preview(&mut self) -> &mut Self {
self.child.stdin.as_mut().unwrap() // Previews are only supported on UNIX.
if !cfg!(unix) {
return self;
}
self.args([
// Non-POSIX args are only available on certain operating systems.
if cfg!(target_os = "linux") {
r"--preview=\command -p ls -Cp --color=always --group-directories-first {2..}"
} else {
r"--preview=\command -p ls -Cp {2..}"
},
// Rounded edges don't display correctly on some terminals.
"--preview-window=down,30%,sharp",
])
.envs([
// Enables colorized `ls` output on macOS / FreeBSD.
("CLICOLOR", "1"),
// Forces colorized `ls` output when the output is not a
// TTY (like in fzf's preview window) on macOS /
// FreeBSD.
("CLICOLOR_FORCE", "1"),
// Ensures that the preview command is run in a
// POSIX-compliant shell, regardless of what shell the
// user has selected.
("SHELL", "sh"),
])
} }
pub fn select(mut self) -> Result<String> { 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. // Drop stdin to prevent deadlock.
mem::drop(self.child.stdin.take()); mem::drop(self.0.stdin.take());
let mut stdout = self.child.stdout.take().unwrap(); let mut stdout = self.0.stdout.take().unwrap();
let mut output = String::new(); let mut output = String::new();
stdout.read_to_string(&mut output).context("failed to read from fzf")?; stdout.read_to_string(&mut output).context("failed to read from fzf")?;
let status = self.child.wait().context("wait failed on fzf")?; let status = self.0.wait().context("wait failed on fzf")?;
match status.code() { match status.code() {
Some(0) => Ok(output), Some(0) => Ok(output),
Some(1) => bail!("no match found"), Some(1) => bail!("no match found"),
@ -96,7 +149,7 @@ impl Fzf {
} }
/// Similar to [`fs::write`], but atomic (best effort on Windows). /// Similar to [`fs::write`], but atomic (best effort on Windows).
pub fn write<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()> { pub fn write(path: impl AsRef<Path>, contents: impl AsRef<[u8]>) -> Result<()> {
let path = path.as_ref(); let path = path.as_ref();
let contents = contents.as_ref(); let contents = contents.as_ref();
let dir = path.parent().unwrap(); let dir = path.parent().unwrap();
@ -133,7 +186,7 @@ pub fn write<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()>
} }
/// Atomically create a tmpfile in the given directory. /// Atomically create a tmpfile in the given directory.
fn tmpfile<P: AsRef<Path>>(dir: P) -> Result<(File, PathBuf)> { fn tmpfile(dir: impl AsRef<Path>) -> Result<(File, PathBuf)> {
const MAX_ATTEMPTS: usize = 5; const MAX_ATTEMPTS: usize = 5;
const TMP_NAME_LEN: usize = 16; const TMP_NAME_LEN: usize = 16;
let dir = dir.as_ref(); let dir = dir.as_ref();
@ -153,35 +206,34 @@ fn tmpfile<P: AsRef<Path>>(dir: P) -> Result<(File, PathBuf)> {
// Atomically create the tmpfile. // Atomically create the tmpfile.
match OpenOptions::new().write(true).create_new(true).open(&path) { match OpenOptions::new().write(true).create_new(true).open(&path) {
Ok(file) => break Ok((file, path)), Ok(file) => break Ok((file, path)),
Err(e) if e.kind() == io::ErrorKind::AlreadyExists && attempts < MAX_ATTEMPTS => (), Err(e) if e.kind() == io::ErrorKind::AlreadyExists && attempts < MAX_ATTEMPTS => {}
Err(e) => break Err(e).with_context(|| format!("could not create file: {}", path.display())), Err(e) => {
break Err(e).with_context(|| format!("could not create file: {}", path.display()));
}
} }
} }
} }
/// Similar to [`fs::rename`], but retries on Windows. /// Similar to [`fs::rename`], but with retries on Windows.
fn rename<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> Result<()> { fn rename(from: impl AsRef<Path>, to: impl AsRef<Path>) -> Result<()> {
const MAX_ATTEMPTS: usize = 5;
let from = from.as_ref(); let from = from.as_ref();
let to = to.as_ref(); let to = to.as_ref();
if cfg!(windows) { const MAX_ATTEMPTS: usize = if cfg!(windows) { 5 } else { 1 };
let mut attempts = 0; let mut attempts = 0;
loop {
attempts += 1; loop {
match fs::rename(from, to) { match fs::rename(from, to) {
Err(e) if e.kind() == io::ErrorKind::PermissionDenied && attempts < MAX_ATTEMPTS => (), Err(e) if e.kind() == io::ErrorKind::PermissionDenied && attempts < MAX_ATTEMPTS => attempts += 1,
result => break result, result => {
break result.with_context(|| format!("could not rename file: {} -> {}", from.display(), to.display()));
} }
} }
} else {
fs::rename(from, to)
} }
.with_context(|| format!("could not rename file: {} -> {}", from.display(), to.display()))
} }
pub fn canonicalize<P: AsRef<Path>>(path: &P) -> Result<PathBuf> { pub fn canonicalize(path: impl AsRef<Path>) -> Result<PathBuf> {
dunce::canonicalize(path).with_context(|| format!("could not resolve path: {}", path.as_ref().display())) dunce::canonicalize(&path).with_context(|| format!("could not resolve path: {}", path.as_ref().display()))
} }
pub fn current_dir() -> Result<PathBuf> { pub fn current_dir() -> Result<PathBuf> {
@ -195,14 +247,14 @@ pub fn current_time() -> Result<Epoch> {
Ok(current_time) Ok(current_time)
} }
pub fn path_to_str<P: AsRef<Path>>(path: &P) -> Result<&str> { pub fn path_to_str(path: &impl AsRef<Path>) -> Result<&str> {
let path = path.as_ref(); let path = path.as_ref();
path.to_str().with_context(|| format!("invalid unicode in path: {}", path.display())) path.to_str().with_context(|| format!("invalid unicode in path: {}", path.display()))
} }
/// Returns the absolute version of a path. Like [`std::path::Path::canonicalize`], but doesn't /// Returns the absolute version of a path. Like
/// resolve symlinks. /// [`std::path::Path::canonicalize`], but doesn't resolve symlinks.
pub fn resolve_path<P: AsRef<Path>>(path: &P) -> Result<PathBuf> { pub fn resolve_path(path: impl AsRef<Path>) -> Result<PathBuf> {
let path = path.as_ref(); let path = path.as_ref();
let base_path; let base_path;
@ -213,7 +265,7 @@ pub fn resolve_path<P: AsRef<Path>>(path: &P) -> Result<PathBuf> {
if cfg!(windows) { if cfg!(windows) {
use std::path::Prefix; use std::path::Prefix;
fn get_drive_letter<P: AsRef<Path>>(path: P) -> Option<u8> { fn get_drive_letter(path: impl AsRef<Path>) -> Option<u8> {
let path = path.as_ref(); let path = path.as_ref();
let mut components = path.components(); let mut components = path.components();
@ -293,7 +345,7 @@ pub fn resolve_path<P: AsRef<Path>>(path: &P) -> Result<PathBuf> {
for component in components { for component in components {
match component { match component {
Component::Normal(_) => stack.push(component), Component::Normal(_) => stack.push(component),
Component::CurDir => (), Component::CurDir => {}
Component::ParentDir => { Component::ParentDir => {
if stack.last() != Some(&Component::RootDir) { if stack.last() != Some(&Component::RootDir) {
stack.pop(); stack.pop();
@ -307,7 +359,7 @@ pub fn resolve_path<P: AsRef<Path>>(path: &P) -> Result<PathBuf> {
} }
/// Convert a string to lowercase, with a fast path for ASCII strings. /// Convert a string to lowercase, with a fast path for ASCII strings.
pub fn to_lowercase<S: AsRef<str>>(s: S) -> String { pub fn to_lowercase(s: impl AsRef<str>) -> String {
let s = s.as_ref(); let s = s.as_ref();
if s.is_ascii() { s.to_ascii_lowercase() } else { s.to_lowercase() } if s.is_ascii() { s.to_ascii_lowercase() } else { s.to_lowercase() }
} }

View File

@ -83,7 +83,7 @@ function __zoxide_z
end end
end end
# Completions for `z`. # Completions.
function __zoxide_z_complete function __zoxide_z_complete
set -l tokens (commandline --current-process --tokenize) set -l tokens (commandline --current-process --tokenize)
set -l curr_tokens (commandline --cut-at-cursor --current-process --tokenize) set -l curr_tokens (commandline --cut-at-cursor --current-process --tokenize)
@ -99,6 +99,7 @@ function __zoxide_z_complete
commandline --function repaint commandline --function repaint
end end
end end
complete --command __zoxide_z --no-files --arguments '(__zoxide_z_complete)'
# Jump to a directory using interactive search. # Jump to a directory using interactive search.
function __zoxide_zi function __zoxide_zi
@ -114,17 +115,10 @@ end
{%- when Some with (cmd) %} {%- when Some with (cmd) %}
abbr --erase {{cmd}} &>/dev/null abbr --erase {{cmd}} &>/dev/null
complete --command {{cmd}} --erase alias {{cmd}}=__zoxide_z
function {{cmd}}
__zoxide_z $argv
end
complete --command {{cmd}} --no-files --arguments '(__zoxide_z_complete)'
abbr --erase {{cmd}}i &>/dev/null abbr --erase {{cmd}}i &>/dev/null
complete --command {{cmd}}i --erase alias {{cmd}}i=__zoxide_zi
function {{cmd}}i
__zoxide_zi $argv
end
{%- when None %} {%- when None %}

View File

@ -12,22 +12,25 @@
{%- else -%} {%- else -%}
# Initialize hook to add new entries to the database. # Initialize hook to add new entries to the database.
if (not ($env | default false __zoxide_hooked | get __zoxide_hooked)) {
let-env __zoxide_hooked = true
{%- if hook == InitHook::Prompt %} {%- if hook == InitHook::Prompt %}
let-env config = ($env | default {} config).config let-env config = ($env | default {} config).config
let-env config = ($env.config | default {} hooks) let-env config = ($env.config | default {} hooks)
let-env config = ($env.config | update hooks ($env.config.hooks | default [] pre_prompt)) let-env config = ($env.config | update hooks ($env.config.hooks | default [] pre_prompt))
let-env config = ($env.config | update hooks.pre_prompt ($env.config.hooks.pre_prompt | append { let-env config = ($env.config | update hooks.pre_prompt ($env.config.hooks.pre_prompt | append {
zoxide add -- $env.PWD zoxide add -- $env.PWD
})) }))
{%- else if hook == InitHook::Pwd %} {%- else if hook == InitHook::Pwd %}
let-env config = ($env | default {} config).config let-env config = ($env | default {} config).config
let-env config = ($env.config | default {} hooks) let-env config = ($env.config | default {} hooks)
let-env config = ($env.config | update hooks ($env.config.hooks | default {} env_change)) let-env config = ($env.config | update hooks ($env.config.hooks | default {} env_change))
let-env config = ($env.config | update hooks.env_change ($env.config.hooks.env_change | default [] PWD)) let-env config = ($env.config | update hooks.env_change ($env.config.hooks.env_change | default [] PWD))
let-env config = ($env.config | update hooks.env_change.PWD ($env.config.hooks.env_change.PWD | append {|_, dir| let-env config = ($env.config | update hooks.env_change.PWD ($env.config.hooks.env_change.PWD | append {|_, dir|
zoxide add -- $dir zoxide add -- $dir
})) }))
{%- endif %} {%- endif %}
}
{%- endif %} {%- endif %}
@ -39,7 +42,7 @@ let-env config = ($env.config | update hooks.env_change.PWD ($env.config.hooks.e
def-env __zoxide_z [...rest:string] { def-env __zoxide_z [...rest:string] {
# `z -` does not work yet, see https://github.com/nushell/nushell/issues/4769 # `z -` does not work yet, see https://github.com/nushell/nushell/issues/4769
let arg0 = ($rest | append '~').0 let arg0 = ($rest | append '~').0
let path = if (($rest | length) <= 1) && ($arg0 == '-' || ($arg0 | path expand | path type) == dir) { let path = if (($rest | length) <= 1) and ($arg0 == '-' or ($arg0 | path expand | path type) == dir) {
$arg0 $arg0
} else { } else {
(zoxide query --exclude $env.PWD -- $rest | str trim -r -c "\n") (zoxide query --exclude $env.PWD -- $rest | str trim -r -c "\n")
@ -77,11 +80,11 @@ alias {{cmd}}i = __zoxide_zi
{{ section }} {{ section }}
# Add this to your env file (find it by running `$nu.env-path` in Nushell): # Add this to your env file (find it by running `$nu.env-path` in Nushell):
# #
# zoxide init nushell --hook prompt | save ~/.zoxide.nu # zoxide init nushell | save -f ~/.zoxide.nu
# #
# Now, add this to the end of your config file (find it by running # Now, add this to the end of your config file (find it by running
# `$nu.config-path` in Nushell): # `$nu.config-path` in Nushell):
# #
# source ~/.zoxide.nu # source ~/.zoxide.nu
# #
# Note: zoxide only supports Nushell v0.63.0 and above. # Note: zoxide only supports Nushell v0.73.0 and above.

View File

@ -19,7 +19,7 @@ function __zoxide_pwd() {
# cd + custom logic based on the value of _ZO_ECHO. # cd + custom logic based on the value of _ZO_ECHO.
function __zoxide_cd() { function __zoxide_cd() {
# shellcheck disable=SC2164 # shellcheck disable=SC2164
\builtin cd -- "$@" >/dev/null {%- if echo %} && __zoxide_pwd {%- endif %} \builtin cd -- "$@" {%- if echo %} && __zoxide_pwd {%- endif %}
} }
{{ section }} {{ section }}
@ -79,28 +79,12 @@ function __zoxide_zi() {
result="$(\command zoxide query -i -- "$@")" && __zoxide_cd "${result}" result="$(\command zoxide query -i -- "$@")" && __zoxide_cd "${result}"
} }
{{ section }} # Completions.
# Commands for zoxide. Disable these using --no-cmd.
#
{%- match cmd %}
{%- when Some with (cmd) %}
\builtin unalias {{cmd}} &>/dev/null || \builtin true
function {{cmd}}() {
__zoxide_z "$@"
}
\builtin unalias {{cmd}}i &>/dev/null || \builtin true
function {{cmd}}i() {
__zoxide_zi "$@"
}
if [[ -o zle ]]; then if [[ -o zle ]]; then
function __zoxide_z_complete() { function __zoxide_z_complete() {
# Only show completions when the cursor is at the end of the line. # Only show completions when the cursor is at the end of the line.
# shellcheck disable=SC2154 # shellcheck disable=SC2154
[[ "{{ "${#words[@]}" }}" -eq "${CURRENT}" ]] || return [[ "{{ "${#words[@]}" }}" -eq "${CURRENT}" ]] || return 0
if [[ "{{ "${#words[@]}" }}" -eq 2 ]]; then if [[ "{{ "${#words[@]}" }}" -eq 2 ]]; then
_files -/ _files -/
@ -111,25 +95,26 @@ if [[ -o zle ]]; then
result="${__zoxide_z_prefix}${result}" result="${__zoxide_z_prefix}${result}"
# shellcheck disable=SC2296 # shellcheck disable=SC2296
compadd -Q "${(q-)result}" compadd -Q "${(q-)result}"
else
{#-
zsh-autocomplete calls the completion function multiple times if no match is
returned.
#}
compadd ""
fi fi
\builtin printf '\e[5n' \builtin printf '\e[5n'
fi fi
return 0
} }
\builtin bindkey "\e[0n" 'reset-prompt' \builtin bindkey '\e[0n' 'reset-prompt'
if [[ "${+functions[compdef]}" -ne 0 ]]; then [[ "${+functions[compdef]}" -ne 0 ]] && \compdef __zoxide_z_complete __zoxide_z
\compdef -d {{cmd}}
\compdef -d {{cmd}}i
\compdef __zoxide_z_complete {{cmd}}
fi
fi fi
{{ section }}
# Commands for zoxide. Disable these using --no-cmd.
#
{%- match cmd %}
{%- when Some with (cmd) %}
\builtin alias {{cmd}}=__zoxide_z
\builtin alias {{cmd}}i=__zoxide_zi
{%- when None %} {%- when None %}
{{ not_configured }} {{ not_configured }}

View File

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

View File

@ -5,7 +5,7 @@ edition = "2021"
publish = false publish = false
[dependencies] [dependencies]
anyhow = "1.0.32" anyhow.workspace = true
clap = { version = "3.1.0", features = ["derive"] } clap.workspace = true
ignore = "0.4.18" ignore.workspace = true
shell-words = "1.0.0" shell-words.workspace = true

View File

@ -55,7 +55,7 @@ impl CommandExt for &mut Command {
fn run_ci(nix_enabled: bool) -> Result<()> { fn run_ci(nix_enabled: bool) -> Result<()> {
// Run cargo-clippy. // Run cargo-clippy.
Command::new("cargo").args(&["clippy", "--all-features", "--all-targets"]).args(&["--", "-Dwarnings"]).run()?; Command::new("cargo").args(["clippy", "--all-features", "--all-targets"]).args(["--", "-Dwarnings"]).run()?;
run_fmt(nix_enabled, true)?; run_fmt(nix_enabled, true)?;
run_lint(nix_enabled)?; run_lint(nix_enabled)?;
run_tests(nix_enabled, "") run_tests(nix_enabled, "")
@ -63,8 +63,9 @@ fn run_ci(nix_enabled: bool) -> Result<()> {
fn run_fmt(nix_enabled: bool, check: bool) -> Result<()> { fn run_fmt(nix_enabled: bool, check: bool) -> Result<()> {
// Run cargo-fmt. // Run cargo-fmt.
// let check_args: &[&str] = if check { &["--check", "--files-with-diff"] } else { &[] }; // let check_args: &[&str] = if check {&["--check", "--files-with-diff"] } else
// Command::new("cargo").args(&["fmt", "--all", "--"]).args(check_args).run()?; // { &[] }; Command::new("cargo").args(&["fmt", "--all",
// "--"]).args(check_args).run()?;
// Run nixfmt. // Run nixfmt.
if nix_enabled { if nix_enabled {
@ -83,9 +84,6 @@ fn run_fmt(nix_enabled: bool, check: bool) -> Result<()> {
fn run_lint(nix_enabled: bool) -> Result<()> { fn run_lint(nix_enabled: bool) -> Result<()> {
if nix_enabled { if nix_enabled {
// Run cargo-audit.
Command::new("cargo").args(&["audit", "--deny=warnings"]).run()?;
// Run markdownlint. // Run markdownlint.
for result in Walk::new("./") { for result in Walk::new("./") {
let entry = result.unwrap(); let entry = result.unwrap();
@ -100,7 +98,7 @@ fn run_lint(nix_enabled: bool) -> Result<()> {
let entry = result.unwrap(); let entry = result.unwrap();
let path = entry.path(); let path = entry.path();
if path.is_file() && path.extension() == Some(OsStr::new("1")) { if path.is_file() && path.extension() == Some(OsStr::new("1")) {
Command::new("mandoc").args(&["-man", "-Wall", "-Tlint", "--"]).arg(path).run()?; Command::new("mandoc").args(["-man", "-Wall", "-Tlint", "--"]).arg(path).run()?;
} }
} }
} }
@ -110,7 +108,7 @@ fn run_lint(nix_enabled: bool) -> Result<()> {
fn run_tests(nix_enabled: bool, name: &str) -> Result<()> { fn run_tests(nix_enabled: bool, name: &str) -> Result<()> {
let args: &[&str] = if nix_enabled { &["nextest", "run", "--all-features"] } else { &["test"] }; let args: &[&str] = if nix_enabled { &["nextest", "run", "--all-features"] } else { &["test"] };
Command::new("cargo").args(args).args(&["--no-fail-fast", "--workspace", "--", name]).run() Command::new("cargo").args(args).args(["--no-fail-fast", "--workspace", "--", name]).run()
} }
fn enable_nix() -> bool { fn enable_nix() -> bool {
@ -131,6 +129,6 @@ fn enable_nix() -> bool {
let args = env::args(); let args = env::args();
let cmd = shell_words::join(args); let cmd = shell_words::join(args);
let status = Command::new("nix-shell").args(&["--pure", "--run", &cmd, "--", "shell.nix"]).status().unwrap(); let status = Command::new("nix-shell").args(["--pure", "--run", &cmd, "--", "shell.nix"]).status().unwrap();
process::exit(status.code().unwrap_or(1)); process::exit(status.code().unwrap_or(1));
} }