Merge branch 'main' into issue#458
This commit is contained in:
commit
b61f98b269
|
|
@ -1,2 +0,0 @@
|
|||
[advisories]
|
||||
ignore = ["RUSTSEC-2020-0095"]
|
||||
|
|
@ -1,24 +1,19 @@
|
|||
name: ci
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- next
|
||||
branches: [main]
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
name: ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
# FIXME: Enable macos-latest when this is merged: https://nixpk.gs/pr-tracker.html?pr=163924
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -1,15 +1,11 @@
|
|||
name: release
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- next
|
||||
branches: [main]
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: ${{ matrix.target }}
|
||||
|
|
@ -48,7 +44,7 @@ jobs:
|
|||
|
||||
- name: Get version
|
||||
id: get_version
|
||||
uses: SebRollen/toml-action@v1.0.0
|
||||
uses: SebRollen/toml-action@v1.0.2
|
||||
with:
|
||||
file: Cargo.toml
|
||||
field: package.version
|
||||
|
|
|
|||
20
CHANGELOG.md
20
CHANGELOG.md
|
|
@ -9,11 +9,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## 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
|
||||
|
||||
- Zsh: completions clashing with `zsh-autocomplete`.
|
||||
- Fzf: 'invalid option' on macOS.
|
||||
- PowerShell: handle UTF-8 encoding correctly.
|
||||
- Zsh: don't hide output from `chpwd` hooks.
|
||||
- Nushell: upgrade minimum supported version to v0.73.0.
|
||||
- Zsh: fix extra space in interactive completions when no match is found.
|
||||
- Fzf: various improvements.
|
||||
- Nushell: Accidental redefinition of hooks when initialized twice.
|
||||
|
||||
### Removed
|
||||
|
||||
- `remove -i` subcommand: use `edit` instead.
|
||||
|
||||
## [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.
|
||||
- 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.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
|
||||
|
|
|
|||
|
|
@ -3,19 +3,31 @@
|
|||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.18"
|
||||
name = "Inflector"
|
||||
version = "0.11.4"
|
||||
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 = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.61"
|
||||
name = "aliasable"
|
||||
version = "0.1.3"
|
||||
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]]
|
||||
name = "askama"
|
||||
|
|
@ -62,9 +74,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "assert_cmd"
|
||||
version = "2.0.4"
|
||||
version = "2.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93ae1ddd39efd67689deb1979d80bad3bf7f2b09c6e6117c8d1f2443b5e2f83e"
|
||||
checksum = "fa3d466004a8b4cb1bc34044240a2fd29d17607e2e3bd613eb44fd48e8100da3"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"doc-comment",
|
||||
|
|
@ -74,23 +86,6 @@ dependencies = [
|
|||
"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]]
|
||||
name = "bincode"
|
||||
version = "1.3.3"
|
||||
|
|
@ -108,15 +103,22 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
|||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "0.2.17"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
|
||||
checksum = "b45ea9b00a7b3f2988e9a65ad3917e62123c38dba709b666506207be96d1790b"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"memchr",
|
||||
"once_cell",
|
||||
"regex-automata",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.78"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
|
|
@ -125,35 +127,33 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
|||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "3.2.16"
|
||||
version = "4.0.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3dbbb6653e7c55cc8595ad3e1f7be8f32aba4eb7ff7f0fd1163d4f3d137c0a9"
|
||||
checksum = "a7db700bc935f9e43e88d00b0850dae18a63773cfbec6d8e070fccf7fef89a39"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"bitflags",
|
||||
"clap_derive",
|
||||
"clap_lex",
|
||||
"indexmap",
|
||||
"is-terminal",
|
||||
"once_cell",
|
||||
"strsim",
|
||||
"termcolor",
|
||||
"textwrap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_complete"
|
||||
version = "3.2.3"
|
||||
version = "4.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ead064480dfc4880a10764488415a97fdd36a4cf1bb022d372f02e8faf8386e1"
|
||||
checksum = "10861370d2ba66b0f5989f83ebf35db6421713fd92351790e7fdd6c36774c56b"
|
||||
dependencies = [
|
||||
"clap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_complete_fig"
|
||||
version = "3.2.4"
|
||||
version = "4.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed37b4c0c1214673eba6ad8ea31666626bf72be98ffb323067d973c48b4964b9"
|
||||
checksum = "46b30e010e669cd021e5004f3be26cff6b7c08d2a8a0d65b48d43a8cc0efd6c3"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"clap_complete",
|
||||
|
|
@ -161,9 +161,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "3.2.15"
|
||||
version = "4.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ba52acd3b0a5c33aeada5cdaa3267cdc7c594a98731d4268cdc1532f4264cb4"
|
||||
checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro-error",
|
||||
|
|
@ -174,23 +174,13 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.2.4"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
|
||||
checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8"
|
||||
dependencies = [
|
||||
"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]]
|
||||
name = "difflib"
|
||||
version = "0.4.0"
|
||||
|
|
@ -225,15 +215,36 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
|
|||
|
||||
[[package]]
|
||||
name = "dunce"
|
||||
version = "1.0.2"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "453440c271cf5577fd2a40e4942540cb7d0d2f85e27c8d07dd0023c925a67541"
|
||||
checksum = "0bd4b30a6560bbd9b4620f4de34c3f14f60848e58a9b7216801afcb4c7b31c3c"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.7.0"
|
||||
version = "1.8.0"
|
||||
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]]
|
||||
name = "fastrand"
|
||||
|
|
@ -252,9 +263,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
|||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.7"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
|
||||
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
|
|
@ -263,15 +274,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
|
||||
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
||||
|
||||
[[package]]
|
||||
name = "globset"
|
||||
version = "0.4.9"
|
||||
version = "0.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a"
|
||||
checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"bstr",
|
||||
|
|
@ -280,12 +291,6 @@ dependencies = [
|
|||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.0"
|
||||
|
|
@ -294,20 +299,19 @@ checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
|
|||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||
checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ignore"
|
||||
version = "0.4.18"
|
||||
version = "0.4.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d"
|
||||
checksum = "a05705bc64e0b66a806c3740bd6578ea66051b157ec42dc219c785cbf185aef3"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
"globset",
|
||||
"lazy_static",
|
||||
"log",
|
||||
|
|
@ -319,16 +323,6 @@ dependencies = [
|
|||
"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]]
|
||||
name = "instant"
|
||||
version = "0.1.12"
|
||||
|
|
@ -339,10 +333,32 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.3"
|
||||
name = "io-lifetimes"
|
||||
version = "1.0.3"
|
||||
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 = [
|
||||
"either",
|
||||
]
|
||||
|
|
@ -355,9 +371,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
|||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.131"
|
||||
version = "0.2.139"
|
||||
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]]
|
||||
name = "log"
|
||||
|
|
@ -398,20 +420,21 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
|||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.24.2"
|
||||
version = "0.26.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "195cdbc1741b8134346d515b3a56a1c94b0912758009cfd53f99ea0f57b065fc"
|
||||
checksum = "46a58d1d356c6597d08cde02c2f09d785b09e28711837b1ed667dc652c08a694"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.1"
|
||||
version = "7.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36"
|
||||
checksum = "e5507769c4919c998e69e49c839d9dc6e693ede4cc4290d6ad8b41d4f09c548c"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"minimal-lexical",
|
||||
|
|
@ -419,21 +442,44 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.13.0"
|
||||
version = "1.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"
|
||||
checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
|
||||
|
||||
[[package]]
|
||||
name = "os_str_bytes"
|
||||
version = "6.2.0"
|
||||
version = "6.4.1"
|
||||
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]]
|
||||
name = "predicates"
|
||||
version = "2.1.1"
|
||||
version = "2.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5aab5be6e4732b473071984b3164dbbfb7a3674d30ea5ff44410b6bcd960c3c"
|
||||
checksum = "59230a63c37f3e18569bdb90e4a89cbf5bf8b06fea0b84e65ea10cc4df47addd"
|
||||
dependencies = [
|
||||
"difflib",
|
||||
"itertools",
|
||||
|
|
@ -442,15 +488,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "predicates-core"
|
||||
version = "1.0.3"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da1c2388b1513e1b605fcec39a95e0a9e8ef088f71443ef37099fa9ae6673fcb"
|
||||
checksum = "72f883590242d3c6fc5bf50299011695fa6590c2c70eac95ee1bdb9a733ad1a2"
|
||||
|
||||
[[package]]
|
||||
name = "predicates-tree"
|
||||
version = "1.0.5"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d86de6de25020a36c6d3643a86d9a6a9f552107c0559c60ea03551b5e16c032"
|
||||
checksum = "54ff541861505aabf6ea722d2131ee980b8276e10a1297b94e896dd8b621850d"
|
||||
dependencies = [
|
||||
"predicates-core",
|
||||
"termtree",
|
||||
|
|
@ -482,18 +528,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.43"
|
||||
version = "1.0.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab"
|
||||
checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.21"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
|
||||
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
|
@ -520,9 +566,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.6.0"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
|
||||
checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
|
|
@ -537,9 +583,9 @@ checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
|
|||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.27"
|
||||
version = "0.6.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
|
||||
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
|
||||
|
||||
[[package]]
|
||||
name = "remove_dir_all"
|
||||
|
|
@ -552,9 +598,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rstest"
|
||||
version = "0.15.0"
|
||||
version = "0.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e9c9dc66cc29792b663ffb5269be669f1613664e69ad56441fdb895c2347b930"
|
||||
checksum = "b07f2d176c472198ec1e6551dc7da28f1c089652f66a7b722676c2238ebc0edf"
|
||||
dependencies = [
|
||||
"rstest_macros",
|
||||
"rustc_version",
|
||||
|
|
@ -562,15 +608,16 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rstest_macros"
|
||||
version = "0.14.0"
|
||||
version = "0.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5015e68a0685a95ade3eee617ff7101ab6a3fc689203101ca16ebc16f2b89c66"
|
||||
checksum = "7229b505ae0706e64f37ffc54a9c163e11022a6636d58fe1f3f52018257ff9f7"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustc_version",
|
||||
"syn",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -593,6 +640,20 @@ dependencies = [
|
|||
"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]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
|
|
@ -604,24 +665,24 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.13"
|
||||
version = "1.0.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93f6841e709003d68bb2deee8c343572bf446003ec20a583e76f7b15cebf3711"
|
||||
checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.143"
|
||||
version = "1.0.152"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53e8e5d5b70924f74ff5c6d64d9a5acd91422117c60f48c4e07855238a254553"
|
||||
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.143"
|
||||
version = "1.0.152"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3d8e8de557aee63c26b85b947f5e59b690d0454c753f3adeb5cd7835ab88391"
|
||||
checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
@ -634,6 +695,12 @@ version = "1.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
|
|
@ -642,9 +709,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
|||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.99"
|
||||
version = "1.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13"
|
||||
checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
@ -676,30 +743,24 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "termtree"
|
||||
version = "0.2.4"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "507e9898683b6c43a9aa55b64259b721b52ba226e0f3779137e50ad114a4c90b"
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
|
||||
checksum = "95059e91184749cb66be6dc994f67f182b6d897cb3df74a5bf66b5e709295fd8"
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.32"
|
||||
version = "1.0.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994"
|
||||
checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.32"
|
||||
version = "1.0.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21"
|
||||
checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
@ -726,9 +787,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.3"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf"
|
||||
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
|
|
@ -764,13 +825,13 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
|||
|
||||
[[package]]
|
||||
name = "which"
|
||||
version = "4.2.5"
|
||||
version = "4.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae"
|
||||
checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b"
|
||||
dependencies = [
|
||||
"either",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -804,6 +865,63 @@ version = "0.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "xtask"
|
||||
version = "0.1.0"
|
||||
|
|
@ -816,7 +934,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "zoxide"
|
||||
version = "0.8.3"
|
||||
version = "0.9.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"askama",
|
||||
|
|
@ -830,6 +948,7 @@ dependencies = [
|
|||
"fastrand",
|
||||
"glob",
|
||||
"nix",
|
||||
"ouroboros",
|
||||
"rstest",
|
||||
"rstest_reuse",
|
||||
"serde",
|
||||
|
|
|
|||
60
Cargo.toml
60
Cargo.toml
|
|
@ -9,8 +9,8 @@ license = "MIT"
|
|||
name = "zoxide"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/ajeetdsouza/zoxide"
|
||||
rust-version = "1.62"
|
||||
version = "0.8.3"
|
||||
rust-version = "1.65"
|
||||
version = "0.9.0"
|
||||
|
||||
[badges]
|
||||
maintenance = { status = "actively-developed" }
|
||||
|
|
@ -18,44 +18,64 @@ maintenance = { status = "actively-developed" }
|
|||
[workspace]
|
||||
members = ["xtask/"]
|
||||
|
||||
[dependencies]
|
||||
[workspace.dependencies]
|
||||
anyhow = "1.0.32"
|
||||
askama = { version = "0.11.0", default-features = false }
|
||||
assert_cmd = "2.0.0"
|
||||
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"
|
||||
dunce = "1.0.1"
|
||||
fastrand = "1.7.0"
|
||||
glob = "0.3.0"
|
||||
serde = { version = "1.0.116", features = ["derive"] }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
nix = { version = "0.24.1", default-features = false, features = [
|
||||
ignore = "0.4.18"
|
||||
nix = { version = "0.26.1", default-features = false, features = [
|
||||
"fs",
|
||||
"user",
|
||||
] }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
ouroboros = "0.15.5"
|
||||
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"
|
||||
|
||||
[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]
|
||||
clap = { version = "3.1.0", features = ["derive"] }
|
||||
clap_complete = "3.1.0"
|
||||
clap_complete_fig = "3.1.0"
|
||||
clap.workspace = true
|
||||
clap_complete.workspace = true
|
||||
clap_complete_fig.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
assert_cmd = "2.0.0"
|
||||
rstest = { version = "0.15.0", default-features = false }
|
||||
rstest_reuse = "0.4.0"
|
||||
tempfile = "3.1.0"
|
||||
assert_cmd.workspace = true
|
||||
rstest.workspace = true
|
||||
rstest_reuse.workspace = true
|
||||
tempfile.workspace = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
nix-dev = []
|
||||
|
||||
[profile.dev]
|
||||
debug = 0
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
debug = 0
|
||||
|
|
|
|||
19
README.md
19
README.md
|
|
@ -13,7 +13,6 @@
|
|||
|
||||
[![crates.io][crates.io-badge]][crates.io]
|
||||
[![Downloads][downloads-badge]][releases]
|
||||
[![License][license-badge]][license]
|
||||
[![Built with Nix][builtwithnix-badge]][builtwithnix]
|
||||
|
||||
zoxide is a **smarter cd command**, inspired by z and autojump.
|
||||
|
|
@ -81,10 +80,11 @@ Or, you can use a package manager:
|
|||
| Fedora 32+ | [Fedora Packages] | `dnf install zoxide` |
|
||||
| Gentoo | [GURU Overlay] | `eselect repository enable guru` <br /> `emerge --sync guru` <br /> `emerge app-shells/zoxide` |
|
||||
| Manjaro | | `pacman -S zoxide` |
|
||||
| NixOS | [nixpkgs] | `nix-env -iA nixpkgs.zoxide` |
|
||||
| NixOS 21.05+ | [nixpkgs] | `nix-env -iA nixpkgs.zoxide` |
|
||||
| openSUSE Tumbleweed | [openSUSE Factory] | `zypper install zoxide` |
|
||||
| Parrot OS | | `apt install zoxide` |
|
||||
| Raspbian 11+ | [Raspbian Packages] | `apt install zoxide` |
|
||||
| Slackware 15.0+ | [SlackBuilds] | [Instructions][slackbuilds-howto] |
|
||||
| Ubuntu 21.04+ | [Ubuntu Packages] | `apt install 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):
|
||||
|
||||
```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
|
||||
|
|
@ -215,7 +215,7 @@ source ~/.zoxide.nu
|
|||
```
|
||||
|
||||
> **Note**
|
||||
> zoxide only supports Nushell v0.63.0 and above.
|
||||
> zoxide only supports Nushell v0.73.0 and above.
|
||||
|
||||
</details>
|
||||
|
||||
|
|
@ -398,7 +398,7 @@ They must be set before `zoxide init` is called.
|
|||
| [nnn] | File manager | [nnn-autojump] |
|
||||
| [ranger] | File manager | [ranger-zoxide] |
|
||||
| [telescope.nvim] | Fuzzy finder for Neovim | [telescope-zoxide] |
|
||||
| [vim] | Text editor | [zoxide.vim] |
|
||||
| [vim] / [neovim] | Text editor | [zoxide.vim] |
|
||||
| [xplr] | File manager | [zoxide.xplr] |
|
||||
| [xxh] | Transports shell configuration over SSH | [xxh-plugin-prerun-zoxide] |
|
||||
| [zabb] | Finds the shortest possible query for a path | Natively supported |
|
||||
|
|
@ -408,18 +408,18 @@ They must be set before `zoxide init` is called.
|
|||
[algorithm-matching]: https://github.com/ajeetdsouza/zoxide/wiki/Algorithm#matching
|
||||
[alpine linux packages]: https://pkgs.alpinelinux.org/packages?name=zoxide
|
||||
[arch linux community]: https://archlinux.org/packages/community/x86_64/zoxide/
|
||||
[builtwithnix-badge]: https://img.shields.io/badge/builtwith-nix-7d81f7?style=flat-square
|
||||
[builtwithnix-badge]: https://img.shields.io/badge/builtwith-nix-7d81f7?logo=nixos&logoColor=white&style=flat-square
|
||||
[builtwithnix]: https://builtwithnix.org/
|
||||
[chocolatey]: https://community.chocolatey.org/packages/zoxide
|
||||
[clink-zoxide]: https://github.com/shunsambongi/clink-zoxide
|
||||
[clink]: https://github.com/mridgers/clink
|
||||
[conda-forge]: https://anaconda.org/conda-forge/zoxide
|
||||
[copr]: https://copr.fedorainfracloud.org/coprs/atim/zoxide/
|
||||
[crates.io-badge]: https://img.shields.io/crates/v/zoxide?style=flat-square
|
||||
[crates.io-badge]: https://img.shields.io/crates/v/zoxide?logo=rust&logoColor=white&style=flat-square
|
||||
[crates.io]: https://crates.io/crates/zoxide
|
||||
[debian packages]: https://packages.debian.org/stable/admin/zoxide
|
||||
[devuan packages]: https://pkginfo.devuan.org/cgi-bin/package-query.html?c=package&q=zoxide
|
||||
[downloads-badge]: https://img.shields.io/endpoint?color=bright-green&label=downloads&style=flat-square&cacheSeconds=3600&url=https%3A%2F%2Fzoxide-dl-rlvir7rbe5ac.runkit.sh%2F
|
||||
[downloads-badge]: https://img.shields.io/github/downloads/ajeetdsouza/zoxide/total?logo=github&logoColor=white&style=flat-square
|
||||
[dports]: https://github.com/DragonFlyBSD/DPorts/tree/master/sysutils/zoxide
|
||||
[emacs]: https://www.gnu.org/software/emacs/
|
||||
[fedora packages]: https://src.fedoraproject.org/rpms/rust-zoxide
|
||||
|
|
@ -435,8 +435,6 @@ They must be set before `zoxide init` is called.
|
|||
[joshuto]: https://github.com/kamiyaa/joshuto
|
||||
[lf]: https://github.com/gokcehan/lf
|
||||
[lf-wiki]: https://github.com/gokcehan/lf/wiki/Integrations#zoxide
|
||||
[license-badge]: https://img.shields.io/github/license/ajeetdsouza/zoxide?color=lightgray&style=flat-square
|
||||
[license]: https://github.com/ajeetdsouza/zoxide/blob/main/LICENSE
|
||||
[linuxbrew]: https://formulae.brew.sh/formula-linux/zoxide
|
||||
[macports]: https://ports.macports.org/port/zoxide/summary
|
||||
[neovim]: https://github.com/neovim/neovim
|
||||
|
|
@ -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/
|
||||
[releases]: https://github.com/ajeetdsouza/zoxide/releases
|
||||
[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.nvim]: https://github.com/nvim-telescope/telescope.nvim
|
||||
[termux]: https://github.com/termux/termux-packages/tree/master/packages/zoxide
|
||||
|
|
|
|||
6
build.rs
6
build.rs
|
|
@ -9,8 +9,8 @@ fn main() {
|
|||
};
|
||||
println!("cargo:rustc-env=ZOXIDE_VERSION={version}");
|
||||
|
||||
// Since we are generating completions in the package directory, we need to set this so that
|
||||
// Cargo doesn't rebuild every time.
|
||||
// Since we are generating completions in the package directory, we need to set
|
||||
// this so that Cargo doesn't rebuild every time.
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
println!("cargo:rerun-if-changed=src/");
|
||||
println!("cargo:rerun-if-changed=templates/");
|
||||
|
|
@ -22,7 +22,7 @@ fn main() {
|
|||
fn git_version() -> Option<String> {
|
||||
let dir = env!("CARGO_MANIFEST_DIR");
|
||||
let mut git = Command::new("git");
|
||||
git.args(&["-C", dir, "describe", "--tags", "--match=v*.*.*", "--always", "--broken"]);
|
||||
git.args(["-C", dir, "describe", "--tags", "--match=v*.*.*", "--always", "--broken"]);
|
||||
|
||||
let output = git.output().ok()?;
|
||||
if !output.status.success() || output.stdout.is_empty() || !output.stderr.is_empty() {
|
||||
|
|
|
|||
|
|
@ -37,6 +37,61 @@ _arguments "${_arguments_options[@]}" \
|
|||
'*::paths:_files -/' \
|
||||
&& 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)
|
||||
_arguments "${_arguments_options[@]}" \
|
||||
'--from=[Application to import from]:FROM:(autojump z)' \
|
||||
|
|
@ -62,7 +117,7 @@ _arguments "${_arguments_options[@]}" \
|
|||
;;
|
||||
(query)
|
||||
_arguments "${_arguments_options[@]}" \
|
||||
'--exclude=[Exclude a path from results]:path:_files -/' \
|
||||
'--exclude=[Exclude the current directory]:path:_files -/' \
|
||||
'--all[Show deleted directories]' \
|
||||
'(-l --list)-i[Use interactive selection]' \
|
||||
'(-l --list)--interactive[Use interactive selection]' \
|
||||
|
|
@ -79,8 +134,6 @@ _arguments "${_arguments_options[@]}" \
|
|||
;;
|
||||
(remove)
|
||||
_arguments "${_arguments_options[@]}" \
|
||||
'-i[Use interactive selection]' \
|
||||
'--interactive[Use interactive selection]' \
|
||||
'-h[Print help information]' \
|
||||
'--help[Print help information]' \
|
||||
'-V[Print version information]' \
|
||||
|
|
@ -97,6 +150,7 @@ esac
|
|||
_zoxide_commands() {
|
||||
local commands; commands=(
|
||||
'add:Add a new directory or increment its rank' \
|
||||
'edit:Edit the database' \
|
||||
'import:Import entries from another application' \
|
||||
'init:Generate shell configuration' \
|
||||
'query:Search for a directory in the database' \
|
||||
|
|
@ -109,11 +163,36 @@ _zoxide__add_commands() {
|
|||
local commands; commands=()
|
||||
_describe -t commands 'zoxide add commands' commands "$@"
|
||||
}
|
||||
(( $+functions[_zoxide__edit__decrement_commands] )) ||
|
||||
_zoxide__edit__decrement_commands() {
|
||||
local commands; commands=()
|
||||
_describe -t commands 'zoxide edit decrement commands' commands "$@"
|
||||
}
|
||||
(( $+functions[_zoxide__edit__delete_commands] )) ||
|
||||
_zoxide__edit__delete_commands() {
|
||||
local commands; commands=()
|
||||
_describe -t commands 'zoxide edit delete commands' commands "$@"
|
||||
}
|
||||
(( $+functions[_zoxide__edit_commands] )) ||
|
||||
_zoxide__edit_commands() {
|
||||
local commands; commands=(
|
||||
'decrement:' \
|
||||
'delete:' \
|
||||
'increment:' \
|
||||
'reload:' \
|
||||
)
|
||||
_describe -t commands 'zoxide edit commands' commands "$@"
|
||||
}
|
||||
(( $+functions[_zoxide__import_commands] )) ||
|
||||
_zoxide__import_commands() {
|
||||
local commands; commands=()
|
||||
_describe -t commands 'zoxide import commands' commands "$@"
|
||||
}
|
||||
(( $+functions[_zoxide__edit__increment_commands] )) ||
|
||||
_zoxide__edit__increment_commands() {
|
||||
local commands; commands=()
|
||||
_describe -t commands 'zoxide edit increment commands' commands "$@"
|
||||
}
|
||||
(( $+functions[_zoxide__init_commands] )) ||
|
||||
_zoxide__init_commands() {
|
||||
local commands; commands=()
|
||||
|
|
@ -124,6 +203,11 @@ _zoxide__query_commands() {
|
|||
local commands; commands=()
|
||||
_describe -t commands 'zoxide query commands' commands "$@"
|
||||
}
|
||||
(( $+functions[_zoxide__edit__reload_commands] )) ||
|
||||
_zoxide__edit__reload_commands() {
|
||||
local commands; commands=()
|
||||
_describe -t commands 'zoxide edit reload commands' commands "$@"
|
||||
}
|
||||
(( $+functions[_zoxide__remove_commands] )) ||
|
||||
_zoxide__remove_commands() {
|
||||
local commands; commands=()
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ Register-ArgumentCompleter -Native -CommandName 'zoxide' -ScriptBlock {
|
|||
[CompletionResult]::new('-V', 'V', [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('edit', 'edit', [CompletionResultType]::ParameterValue, 'Edit the database')
|
||||
[CompletionResult]::new('import', 'import', [CompletionResultType]::ParameterValue, 'Import entries from another application')
|
||||
[CompletionResult]::new('init', 'init', [CompletionResultType]::ParameterValue, 'Generate shell configuration')
|
||||
[CompletionResult]::new('query', 'query', [CompletionResultType]::ParameterValue, 'Search for a directory in the database')
|
||||
|
|
@ -39,6 +40,45 @@ Register-ArgumentCompleter -Native -CommandName 'zoxide' -ScriptBlock {
|
|||
[CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Print version information')
|
||||
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' {
|
||||
[CompletionResult]::new('--from', 'from', [CompletionResultType]::ParameterName, 'Application to import from')
|
||||
[CompletionResult]::new('--merge', 'merge', [CompletionResultType]::ParameterName, 'Merge into existing database')
|
||||
|
|
@ -59,7 +99,7 @@ Register-ArgumentCompleter -Native -CommandName 'zoxide' -ScriptBlock {
|
|||
break
|
||||
}
|
||||
'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('-i', 'i', [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
|
||||
}
|
||||
'zoxide;remove' {
|
||||
[CompletionResult]::new('-i', 'i', [CompletionResultType]::ParameterName, 'Use interactive selection')
|
||||
[CompletionResult]::new('--interactive', 'interactive', [CompletionResultType]::ParameterName, 'Use interactive selection')
|
||||
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help information')
|
||||
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help information')
|
||||
[CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Print version information')
|
||||
|
|
|
|||
|
|
@ -8,24 +8,39 @@ _zoxide() {
|
|||
|
||||
for i in ${COMP_WORDS[@]}
|
||||
do
|
||||
case "${i}" in
|
||||
"$1")
|
||||
case "${cmd},${i}" in
|
||||
",$1")
|
||||
cmd="zoxide"
|
||||
;;
|
||||
add)
|
||||
cmd+="__add"
|
||||
zoxide,add)
|
||||
cmd="zoxide__add"
|
||||
;;
|
||||
import)
|
||||
cmd+="__import"
|
||||
zoxide,edit)
|
||||
cmd="zoxide__edit"
|
||||
;;
|
||||
init)
|
||||
cmd+="__init"
|
||||
zoxide,import)
|
||||
cmd="zoxide__import"
|
||||
;;
|
||||
query)
|
||||
cmd+="__query"
|
||||
zoxide,init)
|
||||
cmd="zoxide__init"
|
||||
;;
|
||||
remove)
|
||||
cmd+="__remove"
|
||||
zoxide,query)
|
||||
cmd="zoxide__query"
|
||||
;;
|
||||
zoxide,remove)
|
||||
cmd="zoxide__remove"
|
||||
;;
|
||||
zoxide__edit,decrement)
|
||||
cmd="zoxide__edit__decrement"
|
||||
;;
|
||||
zoxide__edit,delete)
|
||||
cmd="zoxide__edit__delete"
|
||||
;;
|
||||
zoxide__edit,increment)
|
||||
cmd="zoxide__edit__increment"
|
||||
;;
|
||||
zoxide__edit,reload)
|
||||
cmd="zoxide__edit__reload"
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
|
|
@ -34,7 +49,7 @@ _zoxide() {
|
|||
|
||||
case "${cmd}" in
|
||||
zoxide)
|
||||
opts="-h -V --help --version add import init query remove"
|
||||
opts="-h -V --help --version add edit import init query remove"
|
||||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
|
|
@ -61,6 +76,76 @@ _zoxide() {
|
|||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
;;
|
||||
zoxide__edit)
|
||||
opts="-h -V --help --version decrement delete increment reload"
|
||||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
fi
|
||||
case "${prev}" in
|
||||
*)
|
||||
COMPREPLY=()
|
||||
;;
|
||||
esac
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
;;
|
||||
zoxide__edit__decrement)
|
||||
opts="-h -V --help --version <PATH>"
|
||||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
fi
|
||||
case "${prev}" in
|
||||
*)
|
||||
COMPREPLY=()
|
||||
;;
|
||||
esac
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
;;
|
||||
zoxide__edit__delete)
|
||||
opts="-h -V --help --version <PATH>"
|
||||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
fi
|
||||
case "${prev}" in
|
||||
*)
|
||||
COMPREPLY=()
|
||||
;;
|
||||
esac
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
;;
|
||||
zoxide__edit__increment)
|
||||
opts="-h -V --help --version <PATH>"
|
||||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
fi
|
||||
case "${prev}" in
|
||||
*)
|
||||
COMPREPLY=()
|
||||
;;
|
||||
esac
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
;;
|
||||
zoxide__edit__reload)
|
||||
opts="-h -V --help --version"
|
||||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
fi
|
||||
case "${prev}" in
|
||||
*)
|
||||
COMPREPLY=()
|
||||
;;
|
||||
esac
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
;;
|
||||
zoxide__import)
|
||||
opts="-h -V --from --merge --help --version <PATH>"
|
||||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
|
||||
|
|
@ -102,7 +187,7 @@ _zoxide() {
|
|||
return 0
|
||||
;;
|
||||
zoxide__query)
|
||||
opts="-i -l -s -h -V --all --interactive --list --score --exclude --help --version <KEYWORDS>..."
|
||||
opts="-i -l -s -h -V --all --interactive --list --score --exclude --help --version [KEYWORDS]..."
|
||||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
|
|
@ -120,7 +205,7 @@ _zoxide() {
|
|||
return 0
|
||||
;;
|
||||
zoxide__remove)
|
||||
opts="-i -h -V --interactive --help --version <PATHS>..."
|
||||
opts="-h -V --help --version [PATHS]..."
|
||||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ set edit:completion:arg-completer[zoxide] = {|@words|
|
|||
cand -V 'Print version information'
|
||||
cand --version 'Print version information'
|
||||
cand add 'Add a new directory or increment its rank'
|
||||
cand edit 'Edit the database'
|
||||
cand import 'Import entries from another application'
|
||||
cand init 'Generate shell configuration'
|
||||
cand query 'Search for a directory in the database'
|
||||
|
|
@ -34,6 +35,40 @@ set edit:completion:arg-completer[zoxide] = {|@words|
|
|||
cand -V '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'= {
|
||||
cand --from 'Application to import from'
|
||||
cand --merge 'Merge into existing database'
|
||||
|
|
@ -52,7 +87,7 @@ set edit:completion:arg-completer[zoxide] = {|@words|
|
|||
cand --version 'Print version information'
|
||||
}
|
||||
&'zoxide;query'= {
|
||||
cand --exclude 'Exclude a path from results'
|
||||
cand --exclude 'Exclude the current directory'
|
||||
cand --all 'Show deleted directories'
|
||||
cand -i 'Use interactive selection'
|
||||
cand --interactive 'Use interactive selection'
|
||||
|
|
@ -66,8 +101,6 @@ set edit:completion:arg-completer[zoxide] = {|@words|
|
|||
cand --version 'Print version information'
|
||||
}
|
||||
&'zoxide;remove'= {
|
||||
cand -i 'Use interactive selection'
|
||||
cand --interactive 'Use interactive selection'
|
||||
cand -h 'Print help information'
|
||||
cand --help 'Print help information'
|
||||
cand -V 'Print version information'
|
||||
|
|
|
|||
|
|
@ -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 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 "edit" -d 'Edit the database'
|
||||
complete -c zoxide -n "__fish_use_subcommand" -f -a "import" -d 'Import entries from another application'
|
||||
complete -c zoxide -n "__fish_use_subcommand" -f -a "init" -d 'Generate shell configuration'
|
||||
complete -c zoxide -n "__fish_use_subcommand" -f -a "query" -d 'Search for a directory in the database'
|
||||
complete -c zoxide -n "__fish_use_subcommand" -f -a "remove" -d 'Remove a directory from the database'
|
||||
complete -c zoxide -n "__fish_seen_subcommand_from add" -s h -l help -d 'Print help information'
|
||||
complete -c zoxide -n "__fish_seen_subcommand_from add" -s V -l version -d 'Print version information'
|
||||
complete -c zoxide -n "__fish_seen_subcommand_from 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 merge -d 'Merge into existing database'
|
||||
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" -s h -l help -d 'Print help information'
|
||||
complete -c zoxide -n "__fish_seen_subcommand_from init" -s V -l version -d 'Print version information'
|
||||
complete -c zoxide -n "__fish_seen_subcommand_from query" -l exclude -d 'Exclude a path from results' -r -f -a "(__fish_complete_directories)"
|
||||
complete -c zoxide -n "__fish_seen_subcommand_from query" -l 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" -s i -l interactive -d 'Use interactive selection'
|
||||
complete -c zoxide -n "__fish_seen_subcommand_from query" -s l -l list -d 'List all matching directories'
|
||||
complete -c zoxide -n "__fish_seen_subcommand_from query" -s s -l score -d 'Print score with results'
|
||||
complete -c zoxide -n "__fish_seen_subcommand_from query" -s h -l help -d 'Print help information'
|
||||
complete -c zoxide -n "__fish_seen_subcommand_from query" -s V -l version -d 'Print version information'
|
||||
complete -c zoxide -n "__fish_seen_subcommand_from remove" -s i -l interactive -d 'Use interactive selection'
|
||||
complete -c zoxide -n "__fish_seen_subcommand_from remove" -s h -l help -d 'Print help information'
|
||||
complete -c zoxide -n "__fish_seen_subcommand_from remove" -s V -l version -d 'Print version information'
|
||||
|
|
|
|||
|
|
@ -21,6 +21,87 @@ const completion: Fig.Spec = {
|
|||
template: "folders",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "edit",
|
||||
description: "Edit the database",
|
||||
subcommands: [
|
||||
{
|
||||
name: "decrement",
|
||||
hidden: true,
|
||||
options: [
|
||||
{
|
||||
name: ["-h", "--help"],
|
||||
description: "Print help 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",
|
||||
description: "Import entries from another application",
|
||||
|
|
@ -28,6 +109,7 @@ const completion: Fig.Spec = {
|
|||
{
|
||||
name: "--from",
|
||||
description: "Application to import from",
|
||||
isRepeatable: true,
|
||||
args: {
|
||||
name: "from",
|
||||
suggestions: [
|
||||
|
|
@ -61,6 +143,7 @@ const completion: Fig.Spec = {
|
|||
{
|
||||
name: "--cmd",
|
||||
description: "Changes the prefix of the `z` and `zi` commands",
|
||||
isRepeatable: true,
|
||||
args: {
|
||||
name: "cmd",
|
||||
isOptional: true,
|
||||
|
|
@ -69,6 +152,7 @@ const completion: Fig.Spec = {
|
|||
{
|
||||
name: "--hook",
|
||||
description: "Changes how often zoxide increments a directory's score",
|
||||
isRepeatable: true,
|
||||
args: {
|
||||
name: "hook",
|
||||
isOptional: true,
|
||||
|
|
@ -112,7 +196,8 @@ const completion: Fig.Spec = {
|
|||
options: [
|
||||
{
|
||||
name: "--exclude",
|
||||
description: "Exclude a path from results",
|
||||
description: "Exclude the current directory",
|
||||
isRepeatable: true,
|
||||
args: {
|
||||
name: "exclude",
|
||||
isOptional: true,
|
||||
|
|
@ -154,6 +239,7 @@ const completion: Fig.Spec = {
|
|||
],
|
||||
args: {
|
||||
name: "keywords",
|
||||
isVariadic: true,
|
||||
isOptional: true,
|
||||
},
|
||||
},
|
||||
|
|
@ -161,10 +247,6 @@ const completion: Fig.Spec = {
|
|||
name: "remove",
|
||||
description: "Remove a directory from the database",
|
||||
options: [
|
||||
{
|
||||
name: ["-i", "--interactive"],
|
||||
description: "Use interactive selection",
|
||||
},
|
||||
{
|
||||
name: ["-h", "--help"],
|
||||
description: "Print help information",
|
||||
|
|
@ -176,6 +258,7 @@ const completion: Fig.Spec = {
|
|||
],
|
||||
args: {
|
||||
name: "paths",
|
||||
isVariadic: true,
|
||||
isOptional: true,
|
||||
template: "folders",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -91,10 +91,12 @@ download_zoxide() {
|
|||
wget) _releases="$(wget -qO- "$_releases_url")" ||
|
||||
err "wget: failed to download $_releases_url" ;;
|
||||
esac
|
||||
(echo "$_releases" | grep -q 'API rate limit exceeded') &&
|
||||
err "you have exceeded GitHub's API rate limit. Please try again later, or use a different installation method: https://github.com/ajeetdsouza/zoxide/#installation"
|
||||
|
||||
local _package_url
|
||||
_package_url="$(echo "$_releases" | grep "browser_download_url" | cut -d '"' -f 4 | grep "$_arch")" ||
|
||||
err "zoxide has not yet been packaged for your architecture ($_arch), please file an issue at https://github.com/ajeetdsouza/zoxide/issues"
|
||||
err "zoxide has not yet been packaged for your architecture ($_arch), please file an issue: https://github.com/ajeetdsouza/zoxide/issues"
|
||||
|
||||
local _ext
|
||||
case "$_package_url" in
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
.sp
|
||||
.nf
|
||||
\fBzoxide init nushell --hook prompt | save ~/.zoxide.nu\fR
|
||||
\fBzoxide init nushell | save -f ~/.zoxide.nu\fR
|
||||
.fi
|
||||
.sp
|
||||
Now, add this to the end of your config file (find it by running
|
||||
|
|
@ -45,7 +45,7 @@ Now, add this to the end of your config file (find it by running
|
|||
\fBsource ~/.zoxide.nu\fR
|
||||
.fi
|
||||
.sp
|
||||
Note: zoxide only supports Nushell v0.63.0 and above.
|
||||
Note: zoxide only supports Nushell v0.73.0 and above.
|
||||
.TP
|
||||
.B powershell
|
||||
Add this to your configuration (find it by running \fBecho $profile\fR in
|
||||
|
|
|
|||
|
|
@ -10,9 +10,6 @@ If you'd like to permanently exclude a directory from the database, see the
|
|||
.TP
|
||||
.B -h, --help
|
||||
Print help information.
|
||||
.TP
|
||||
.B -i, --interactive [KEYWORDS]
|
||||
Use interactive selection. This option requires \fBfzf\fR(1).
|
||||
.SH REPORTING BUGS
|
||||
For any issues, feature requests, or questions, please visit:
|
||||
.sp
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
comment_width = 100
|
||||
group_imports = "StdExternalCrate"
|
||||
imports_granularity = "Module"
|
||||
max_width = 120
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
let
|
||||
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
|
||||
"https://github.com/NixOS/nixpkgs/archive/0323e1f8bac882f19905174639a89397db1930f1.tar.gz") {
|
||||
"https://github.com/NixOS/nixpkgs/archive/5f902ae769594aaeaf326e8623a48482eeacfe89.tar.gz") {
|
||||
overlays = [ rust ];
|
||||
};
|
||||
in pkgs.mkShell {
|
||||
|
|
@ -21,7 +21,6 @@ in pkgs.mkShell {
|
|||
pkgs.zsh
|
||||
|
||||
# Tools
|
||||
pkgs.cargo-audit
|
||||
pkgs.cargo-nextest
|
||||
pkgs.mandoc
|
||||
pkgs.nixfmt
|
||||
|
|
|
|||
|
|
@ -3,42 +3,39 @@ use std::path::Path;
|
|||
use anyhow::{bail, Result};
|
||||
|
||||
use crate::cmd::{Add, Run};
|
||||
use crate::db::DatabaseFile;
|
||||
use crate::db::Database;
|
||||
use crate::{config, util};
|
||||
|
||||
impl Run for Add {
|
||||
fn run(&self) -> Result<()> {
|
||||
// These characters can't be printed cleanly to a single line, so they can cause confusion
|
||||
// when writing to fzf / stdout.
|
||||
// These characters can't be printed cleanly to a single line, so they can cause
|
||||
// confusion when writing to stdout.
|
||||
const EXCLUDE_CHARS: &[char] = &['\n', '\r'];
|
||||
|
||||
let data_dir = config::data_dir()?;
|
||||
let exclude_dirs = config::exclude_dirs()?;
|
||||
let max_age = config::maxage()?;
|
||||
let now = util::current_time()?;
|
||||
|
||||
let mut db = DatabaseFile::new(data_dir);
|
||||
let mut db = db.open()?;
|
||||
let mut db = Database::open()?;
|
||||
|
||||
for path in &self.paths {
|
||||
let path = if config::resolve_symlinks() { util::canonicalize } else { util::resolve_path }(path)?;
|
||||
let path = util::path_to_str(&path)?;
|
||||
|
||||
// Ignore path if it contains unsupported characters, or if it's in the exclude list.
|
||||
// Ignore path if it contains unsupported characters, or if it's in the exclude
|
||||
// list.
|
||||
if path.contains(EXCLUDE_CHARS) || exclude_dirs.iter().any(|glob| glob.matches(path)) {
|
||||
continue;
|
||||
}
|
||||
if !Path::new(path).is_dir() {
|
||||
bail!("not a directory: {path}");
|
||||
}
|
||||
db.add(path, now);
|
||||
db.add_update(path, 1.0, now);
|
||||
}
|
||||
|
||||
if db.modified {
|
||||
if db.dirty() {
|
||||
db.age(max_age);
|
||||
db.save()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
db.save()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,15 +2,15 @@
|
|||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::{ArgEnum, Parser, ValueHint};
|
||||
use clap::{Parser, Subcommand, ValueEnum, ValueHint};
|
||||
|
||||
const ENV_HELP: &str = "ENVIRONMENT VARIABLES:
|
||||
_ZO_DATA_DIR Path for zoxide data files
|
||||
_ZO_ECHO Print the matched directory before navigating to it when set to 1
|
||||
_ZO_EXCLUDE_DIRS List of directory globs to be excluded
|
||||
_ZO_FZF_OPTS Custom flags to pass to fzf
|
||||
_ZO_MAXAGE Maximum total age after which entries start getting deleted
|
||||
_ZO_RESOLVE_SYMLINKS Resolve symlinks when storing paths";
|
||||
const ENV_HELP: &str = "Environment variables:
|
||||
_ZO_DATA_DIR Path for zoxide data files
|
||||
_ZO_ECHO Print the matched directory before navigating to it when set to 1
|
||||
_ZO_EXCLUDE_DIRS List of directory globs to be excluded
|
||||
_ZO_FZF_OPTS Custom flags to pass to fzf
|
||||
_ZO_MAXAGE Maximum total age after which entries start getting deleted
|
||||
_ZO_RESOLVE_SYMLINKS Resolve symlinks when storing paths";
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[clap(
|
||||
|
|
@ -24,6 +24,7 @@ const ENV_HELP: &str = "ENVIRONMENT VARIABLES:
|
|||
)]
|
||||
pub enum Cmd {
|
||||
Add(Add),
|
||||
Edit(Edit),
|
||||
Import(Import),
|
||||
Init(Init),
|
||||
Query(Query),
|
||||
|
|
@ -33,10 +34,29 @@ pub enum Cmd {
|
|||
/// Add a new directory or increment its rank
|
||||
#[derive(Debug, Parser)]
|
||||
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>,
|
||||
}
|
||||
|
||||
/// 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
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Import {
|
||||
|
|
@ -44,7 +64,7 @@ pub struct Import {
|
|||
pub path: PathBuf,
|
||||
|
||||
/// Application to import from
|
||||
#[clap(arg_enum, long)]
|
||||
#[clap(value_enum, long)]
|
||||
pub from: ImportFrom,
|
||||
|
||||
/// Merge into existing database
|
||||
|
|
@ -52,7 +72,7 @@ pub struct Import {
|
|||
pub merge: bool,
|
||||
}
|
||||
|
||||
#[derive(ArgEnum, Clone, Debug)]
|
||||
#[derive(ValueEnum, Clone, Debug)]
|
||||
pub enum ImportFrom {
|
||||
Autojump,
|
||||
Z,
|
||||
|
|
@ -61,7 +81,7 @@ pub enum ImportFrom {
|
|||
/// Generate shell configuration
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Init {
|
||||
#[clap(arg_enum)]
|
||||
#[clap(value_enum)]
|
||||
pub shell: InitShell,
|
||||
|
||||
/// Prevents zoxide from defining the `z` and `zi` commands
|
||||
|
|
@ -73,18 +93,18 @@ pub struct Init {
|
|||
pub cmd: String,
|
||||
|
||||
/// 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,
|
||||
}
|
||||
|
||||
#[derive(ArgEnum, Clone, Copy, Debug, Eq, PartialEq)]
|
||||
#[derive(ValueEnum, Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum InitHook {
|
||||
None,
|
||||
Prompt,
|
||||
Pwd,
|
||||
}
|
||||
|
||||
#[derive(ArgEnum, Clone, Debug)]
|
||||
#[derive(ValueEnum, Clone, Debug)]
|
||||
pub enum InitShell {
|
||||
Bash,
|
||||
Elvish,
|
||||
|
|
@ -117,7 +137,7 @@ pub struct Query {
|
|||
#[clap(long, short)]
|
||||
pub score: bool,
|
||||
|
||||
/// Exclude a path from results
|
||||
/// Exclude the current directory
|
||||
#[clap(long, value_hint = ValueHint::DirPath, value_name = "path")]
|
||||
pub exclude: Option<String>,
|
||||
}
|
||||
|
|
@ -125,9 +145,6 @@ pub struct Query {
|
|||
/// Remove a directory from the database
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Remove {
|
||||
/// Use interactive selection
|
||||
#[clap(long, short)]
|
||||
pub interactive: bool,
|
||||
#[clap(value_hint = ValueHint::DirPath)]
|
||||
pub paths: Vec<String>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -3,24 +3,21 @@ use std::fs;
|
|||
use anyhow::{bail, Context, Result};
|
||||
|
||||
use crate::cmd::{Import, ImportFrom, Run};
|
||||
use crate::config;
|
||||
use crate::db::{Database, DatabaseFile, Dir};
|
||||
use crate::db::Database;
|
||||
|
||||
impl Run for Import {
|
||||
fn run(&self) -> Result<()> {
|
||||
let buffer = fs::read_to_string(&self.path)
|
||||
.with_context(|| format!("could not open database for importing: {}", &self.path.display()))?;
|
||||
|
||||
let data_dir = config::data_dir()?;
|
||||
let mut db = DatabaseFile::new(data_dir);
|
||||
let db = &mut db.open()?;
|
||||
if !self.merge && !db.dirs.is_empty() {
|
||||
let mut db = Database::open()?;
|
||||
if !self.merge && !db.dirs().is_empty() {
|
||||
bail!("current database is not empty, specify --merge to continue anyway");
|
||||
}
|
||||
|
||||
match self.from {
|
||||
ImportFrom::Autojump => from_autojump(db, &buffer),
|
||||
ImportFrom::Z => from_z(db, &buffer),
|
||||
ImportFrom::Autojump => import_autojump(&mut db, &buffer),
|
||||
ImportFrom::Z => import_z(&mut db, &buffer),
|
||||
}
|
||||
.context("import error")?;
|
||||
|
||||
|
|
@ -28,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() {
|
||||
if line.is_empty() {
|
||||
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 mut rank = rank.parse::<f64>().with_context(|| format!("invalid rank: {rank}"))?;
|
||||
// Normalize the rank using a sigmoid function. Don't import actual ranks from autojump,
|
||||
// since its scoring algorithm is very different and might take a while to get normalized.
|
||||
// Normalize the rank using a sigmoid function. Don't import actual ranks from
|
||||
// autojump, since its scoring algorithm is very different and might
|
||||
// take a while to get normalized.
|
||||
rank = sigmoid(rank);
|
||||
|
||||
let path = split.next().with_context(|| format!("invalid entry: {line}"))?;
|
||||
|
||||
db.dirs.push(Dir { path: path.into(), rank, last_accessed: 0 });
|
||||
db.modified = true;
|
||||
db.add_unchecked(path, rank, 0);
|
||||
}
|
||||
|
||||
if db.modified {
|
||||
if db.dirty() {
|
||||
db.dedup();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn from_z<'a>(db: &mut Database<'a>, buffer: &'a str) -> Result<()> {
|
||||
fn import_z(db: &mut Database, buffer: &str) -> Result<()> {
|
||||
for line in buffer.lines() {
|
||||
if line.is_empty() {
|
||||
continue;
|
||||
|
|
@ -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}"))?;
|
||||
|
||||
db.dirs.push(Dir { path: path.into(), rank, last_accessed });
|
||||
db.modified = true;
|
||||
db.add_unchecked(path, rank, last_accessed);
|
||||
}
|
||||
|
||||
if db.modified {
|
||||
if db.dirty() {
|
||||
db.dedup();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -86,33 +80,33 @@ fn sigmoid(x: f64) -> f64 {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::sigmoid;
|
||||
use crate::db::{Database, Dir};
|
||||
use super::*;
|
||||
use crate::db::Dir;
|
||||
|
||||
#[test]
|
||||
fn from_autojump() {
|
||||
let buffer = r#"
|
||||
let data_dir = tempfile::tempdir().unwrap();
|
||||
let mut db = Database::open_dir(data_dir.path()).unwrap();
|
||||
for (path, rank, last_accessed) in [
|
||||
("/quux/quuz", 1.0, 100),
|
||||
("/corge/grault/garply", 6.0, 600),
|
||||
("/waldo/fred/plugh", 3.0, 300),
|
||||
("/xyzzy/thud", 8.0, 800),
|
||||
("/foo/bar", 9.0, 900),
|
||||
] {
|
||||
db.add_unchecked(path, rank, last_accessed);
|
||||
}
|
||||
|
||||
let buffer = "\
|
||||
7.0 /baz
|
||||
2.0 /foo/bar
|
||||
5.0 /quux/quuz
|
||||
"#;
|
||||
5.0 /quux/quuz";
|
||||
import_autojump(&mut db, buffer).unwrap();
|
||||
|
||||
let dirs = vec![
|
||||
Dir { path: "/quux/quuz".into(), rank: 1.0, last_accessed: 100 },
|
||||
Dir { path: "/corge/grault/garply".into(), rank: 6.0, last_accessed: 600 },
|
||||
Dir { path: "/waldo/fred/plugh".into(), rank: 3.0, last_accessed: 300 },
|
||||
Dir { path: "/xyzzy/thud".into(), rank: 8.0, last_accessed: 800 },
|
||||
Dir { path: "/foo/bar".into(), rank: 9.0, last_accessed: 900 },
|
||||
];
|
||||
let data_dir = tempfile::tempdir().unwrap();
|
||||
let data_dir = &data_dir.path().to_path_buf();
|
||||
let mut db = Database { dirs: dirs.into(), modified: false, data_dir };
|
||||
db.sort_by_path();
|
||||
println!("got: {:?}", &db.dirs());
|
||||
|
||||
super::from_autojump(&mut db, buffer).unwrap();
|
||||
db.dirs.sort_by(|dir1, dir2| dir1.path.cmp(&dir2.path));
|
||||
println!("got: {:?}", &db.dirs.as_slice());
|
||||
|
||||
let exp = &[
|
||||
let exp = [
|
||||
Dir { path: "/baz".into(), rank: sigmoid(7.0), last_accessed: 0 },
|
||||
Dir { path: "/corge/grault/garply".into(), rank: 6.0, last_accessed: 600 },
|
||||
Dir { path: "/foo/bar".into(), rank: 9.0 + sigmoid(2.0), last_accessed: 900 },
|
||||
|
|
@ -122,7 +116,7 @@ mod tests {
|
|||
];
|
||||
println!("exp: {exp:?}");
|
||||
|
||||
for (dir1, dir2) in db.dirs.iter().zip(exp) {
|
||||
for (dir1, dir2) in db.dirs().iter().zip(exp) {
|
||||
assert_eq!(dir1.path, dir2.path);
|
||||
assert!((dir1.rank - dir2.rank).abs() < 0.01);
|
||||
assert_eq!(dir1.last_accessed, dir2.last_accessed);
|
||||
|
|
@ -131,29 +125,29 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn from_z() {
|
||||
let buffer = r#"
|
||||
let data_dir = tempfile::tempdir().unwrap();
|
||||
let mut db = Database::open_dir(data_dir.path()).unwrap();
|
||||
for (path, rank, last_accessed) in [
|
||||
("/quux/quuz", 1.0, 100),
|
||||
("/corge/grault/garply", 6.0, 600),
|
||||
("/waldo/fred/plugh", 3.0, 300),
|
||||
("/xyzzy/thud", 8.0, 800),
|
||||
("/foo/bar", 9.0, 900),
|
||||
] {
|
||||
db.add_unchecked(path, rank, last_accessed);
|
||||
}
|
||||
|
||||
let buffer = "\
|
||||
/baz|7|700
|
||||
/quux/quuz|4|400
|
||||
/foo/bar|2|200
|
||||
/quux/quuz|5|500
|
||||
"#;
|
||||
/quux/quuz|5|500";
|
||||
import_z(&mut db, buffer).unwrap();
|
||||
|
||||
let dirs = vec![
|
||||
Dir { path: "/quux/quuz".into(), rank: 1.0, last_accessed: 100 },
|
||||
Dir { path: "/corge/grault/garply".into(), rank: 6.0, last_accessed: 600 },
|
||||
Dir { path: "/waldo/fred/plugh".into(), rank: 3.0, last_accessed: 300 },
|
||||
Dir { path: "/xyzzy/thud".into(), rank: 8.0, last_accessed: 800 },
|
||||
Dir { path: "/foo/bar".into(), rank: 9.0, last_accessed: 900 },
|
||||
];
|
||||
let data_dir = tempfile::tempdir().unwrap();
|
||||
let data_dir = &data_dir.path().to_path_buf();
|
||||
let mut db = Database { dirs: dirs.into(), modified: false, data_dir };
|
||||
db.sort_by_path();
|
||||
println!("got: {:?}", &db.dirs());
|
||||
|
||||
super::from_z(&mut db, buffer).unwrap();
|
||||
db.dirs.sort_by(|dir1, dir2| dir1.path.cmp(&dir2.path));
|
||||
println!("got: {:?}", &db.dirs.as_slice());
|
||||
|
||||
let exp = &[
|
||||
let exp = [
|
||||
Dir { path: "/baz".into(), rank: 7.0, last_accessed: 700 },
|
||||
Dir { path: "/corge/grault/garply".into(), rank: 6.0, last_accessed: 600 },
|
||||
Dir { path: "/foo/bar".into(), rank: 11.0, last_accessed: 900 },
|
||||
|
|
@ -163,7 +157,7 @@ mod tests {
|
|||
];
|
||||
println!("exp: {exp:?}");
|
||||
|
||||
for (dir1, dir2) in db.dirs.iter().zip(exp) {
|
||||
for (dir1, dir2) in db.dirs().iter().zip(exp) {
|
||||
assert_eq!(dir1.path, dir2.path);
|
||||
assert!((dir1.rank - dir2.rank).abs() < 0.01);
|
||||
assert_eq!(dir1.last_accessed, dir2.last_accessed);
|
||||
|
|
|
|||
|
|
@ -11,10 +11,8 @@ use crate::shell::{self, Opts};
|
|||
impl Run for Init {
|
||||
fn run(&self) -> Result<()> {
|
||||
let cmd = if self.no_cmd { None } else { Some(self.cmd.as_str()) };
|
||||
|
||||
let echo = config::echo();
|
||||
let resolve_symlinks = config::resolve_symlinks();
|
||||
|
||||
let opts = &Opts { cmd, hook: self.hook, echo, resolve_symlinks };
|
||||
|
||||
let source = match self.shell {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
mod add;
|
||||
mod cmd;
|
||||
mod edit;
|
||||
mod import;
|
||||
mod init;
|
||||
mod query;
|
||||
|
|
@ -17,6 +18,7 @@ impl Run for Cmd {
|
|||
fn run(&self) -> Result<()> {
|
||||
match self {
|
||||
Cmd::Add(cmd) => cmd.run(),
|
||||
Cmd::Edit(cmd) => cmd.run(),
|
||||
Cmd::Import(cmd) => cmd.run(),
|
||||
Cmd::Init(cmd) => cmd.run(),
|
||||
Cmd::Query(cmd) => cmd.run(),
|
||||
|
|
|
|||
140
src/cmd/query.rs
140
src/cmd/query.rs
|
|
@ -1,20 +1,16 @@
|
|||
use anyhow::{Context, Result};
|
||||
use std::env;
|
||||
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::config;
|
||||
use crate::db::{Database, DatabaseFile};
|
||||
use crate::db::{Database, Epoch, Stream};
|
||||
use crate::error::BrokenPipeHandler;
|
||||
use crate::util::{self, Fzf};
|
||||
use crate::util::{self, Fzf, FzfChild};
|
||||
|
||||
impl Run for Query {
|
||||
fn run(&self) -> Result<()> {
|
||||
let data_dir = config::data_dir()?;
|
||||
let mut db = DatabaseFile::new(data_dir);
|
||||
let mut db = db.open()?;
|
||||
let mut db = crate::db::Database::open()?;
|
||||
self.query(&mut db).and(db.save())
|
||||
}
|
||||
}
|
||||
|
|
@ -22,7 +18,49 @@ impl Run for Query {
|
|||
impl Query {
|
||||
fn query(&self, db: &mut Database) -> Result<()> {
|
||||
let now = util::current_time()?;
|
||||
let mut stream = self.get_stream(db, now);
|
||||
|
||||
if self.interactive {
|
||||
let mut fzf = Self::get_fzf()?;
|
||||
let selection = loop {
|
||||
match stream.next() {
|
||||
Some(dir) => {
|
||||
if let Some(selection) = fzf.write(dir, now)? {
|
||||
break selection;
|
||||
}
|
||||
}
|
||||
None => break fzf.wait()?,
|
||||
}
|
||||
};
|
||||
|
||||
if self.score {
|
||||
print!("{selection}");
|
||||
} else {
|
||||
let path = selection.get(7..).context("could not read selection from fzf")?;
|
||||
print!("{path}");
|
||||
}
|
||||
} else if self.list {
|
||||
let handle = &mut io::stdout().lock();
|
||||
while let Some(dir) = stream.next() {
|
||||
let dir = if self.score { dir.display().with_score(now) } else { dir.display() };
|
||||
writeln!(handle, "{dir}").pipe_exit("stdout")?;
|
||||
}
|
||||
} else {
|
||||
let handle = &mut io::stdout();
|
||||
let Some(dir) = stream.next() else {
|
||||
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);
|
||||
if !self.all {
|
||||
let resolve_symlinks = config::resolve_symlinks();
|
||||
|
|
@ -31,64 +69,36 @@ impl Query {
|
|||
if let Some(path) = &self.exclude {
|
||||
stream = stream.with_exclude(path);
|
||||
}
|
||||
stream
|
||||
}
|
||||
|
||||
if self.interactive {
|
||||
let mut fzf = Fzf::new(false)?;
|
||||
let stdin = fzf.stdin();
|
||||
|
||||
let selection = loop {
|
||||
let dir = match stream.next() {
|
||||
Some(dir) => dir,
|
||||
None => break fzf.select()?,
|
||||
};
|
||||
|
||||
match writeln!(stdin, "{}", dir.display_score(now)) {
|
||||
Err(e) if e.kind() == io::ErrorKind::BrokenPipe => break fzf.select()?,
|
||||
result => result.context("could not write to fzf")?,
|
||||
}
|
||||
};
|
||||
|
||||
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")?;
|
||||
fn get_fzf() -> Result<FzfChild> {
|
||||
let mut fzf = Fzf::new()?;
|
||||
if let Some(fzf_opts) = config::fzf_opts() {
|
||||
fzf.env("FZF_DEFAULT_OPTS", fzf_opts)
|
||||
} else {
|
||||
let excluded_dir = self.exclude.as_ref().map_or(PathBuf::new(), |path| PathBuf::from_str(path).unwrap());
|
||||
|
||||
let try_dir = stream.next();
|
||||
let dir = match try_dir {
|
||||
Some(dir) => dir,
|
||||
None => {
|
||||
// Current Directory is passed as excluded in __zoxide_z
|
||||
if excluded_dir == env::current_dir()? {
|
||||
try_dir.context("already in the matched directory")?
|
||||
} else {
|
||||
try_dir.context("no match found")?
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if self.score {
|
||||
writeln!(io::stdout(), "{}", dir.display_score(now))
|
||||
} else {
|
||||
writeln!(io::stdout(), "{}", dir.display())
|
||||
}
|
||||
.pipe_exit("stdout")?;
|
||||
fzf.args([
|
||||
// Search mode
|
||||
"--scheme=path",
|
||||
// Search result
|
||||
"--tiebreak=end,chunk,index",
|
||||
// Interface
|
||||
"--bind=ctrl-z:ignore,btab:up,tab:down",
|
||||
"--cycle",
|
||||
"--keep-right",
|
||||
// Layout
|
||||
"--border=sharp", // rounded edges don't display correctly on some terminals
|
||||
"--height=45%",
|
||||
"--info=inline",
|
||||
"--layout=reverse",
|
||||
// Display
|
||||
"--tabstop=1",
|
||||
// Scripting
|
||||
"--exit-0",
|
||||
"--select-1",
|
||||
])
|
||||
.enable_preview()
|
||||
}
|
||||
|
||||
Ok(())
|
||||
.spawn()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,54 +1,19 @@
|
|||
use std::io::{self, Write};
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
use crate::cmd::{Remove, Run};
|
||||
use crate::config;
|
||||
use crate::db::DatabaseFile;
|
||||
use crate::util::{self, Fzf};
|
||||
use crate::db::Database;
|
||||
use crate::util;
|
||||
|
||||
impl Run for Remove {
|
||||
fn run(&self) -> Result<()> {
|
||||
let data_dir = config::data_dir()?;
|
||||
let mut db = DatabaseFile::new(data_dir);
|
||||
let mut db = db.open()?;
|
||||
let mut db = Database::open()?;
|
||||
|
||||
if self.interactive {
|
||||
let keywords = &self.paths;
|
||||
let now = util::current_time()?;
|
||||
let mut stream = db.stream(now).with_keywords(keywords);
|
||||
|
||||
let mut fzf = Fzf::new(true)?;
|
||||
let stdin = fzf.stdin();
|
||||
|
||||
let selection = loop {
|
||||
let dir = match stream.next() {
|
||||
Some(dir) => dir,
|
||||
None => break fzf.select()?,
|
||||
};
|
||||
|
||||
match writeln!(stdin, "{}", dir.display_score(now)) {
|
||||
Err(e) if e.kind() == io::ErrorKind::BrokenPipe => break fzf.select()?,
|
||||
result => result.context("could not write to fzf")?,
|
||||
}
|
||||
};
|
||||
|
||||
let paths = selection.lines().filter_map(|line| line.get(5..));
|
||||
for path in paths {
|
||||
if !db.remove(path) {
|
||||
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}")
|
||||
}
|
||||
for path in &self.paths {
|
||||
if !db.remove(path) {
|
||||
let path_abs = util::resolve_path(path)?;
|
||||
let path_abs = util::path_to_str(&path_abs)?;
|
||||
if path_abs == path || !db.remove(path_abs) {
|
||||
bail!("path not found in database: {path}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
145
src/db/dir.rs
145
src/db/dir.rs
|
|
@ -1,83 +1,9 @@
|
|||
use std::borrow::Cow;
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
use bincode::Options as _;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct DirList<'a>(#[serde(borrow)] pub Vec<Dir<'a>>);
|
||||
|
||||
impl DirList<'_> {
|
||||
const VERSION: u32 = 3;
|
||||
|
||||
pub fn new() -> DirList<'static> {
|
||||
DirList(Vec::new())
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<DirList> {
|
||||
// Assume a maximum size for the database. This prevents bincode from throwing strange
|
||||
// errors when it encounters invalid data.
|
||||
const MAX_SIZE: u64 = 32 << 20; // 32 MiB
|
||||
let deserializer = &mut bincode::options().with_fixint_encoding().with_limit(MAX_SIZE);
|
||||
|
||||
// Split bytes into sections.
|
||||
let version_size = deserializer.serialized_size(&Self::VERSION).unwrap() as _;
|
||||
if bytes.len() < version_size {
|
||||
bail!("could not deserialize database: corrupted data");
|
||||
}
|
||||
let (bytes_version, bytes_dirs) = bytes.split_at(version_size);
|
||||
|
||||
// Deserialize sections.
|
||||
(|| {
|
||||
let version = deserializer.deserialize(bytes_version)?;
|
||||
match version {
|
||||
Self::VERSION => Ok(deserializer.deserialize(bytes_dirs)?),
|
||||
version => {
|
||||
bail!("unsupported version (got {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)
|
||||
}
|
||||
}
|
||||
use crate::util::{DAY, HOUR, WEEK};
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct Dir<'a> {
|
||||
|
|
@ -88,11 +14,11 @@ pub struct Dir<'a> {
|
|||
}
|
||||
|
||||
impl Dir<'_> {
|
||||
pub fn score(&self, now: Epoch) -> Rank {
|
||||
const HOUR: Epoch = 60 * 60;
|
||||
const DAY: Epoch = 24 * HOUR;
|
||||
const WEEK: Epoch = 7 * DAY;
|
||||
pub fn display(&self) -> DirDisplay<'_> {
|
||||
DirDisplay::new(self)
|
||||
}
|
||||
|
||||
pub fn score(&self, now: Epoch) -> Rank {
|
||||
// The older the entry, the lesser its importance.
|
||||
let duration = now.saturating_sub(self.last_accessed);
|
||||
if duration < HOUR {
|
||||
|
|
@ -105,56 +31,39 @@ impl Dir<'_> {
|
|||
self.rank * 0.25
|
||||
}
|
||||
}
|
||||
|
||||
pub fn display(&self) -> DirDisplay {
|
||||
DirDisplay { dir: self }
|
||||
}
|
||||
|
||||
pub fn display_score(&self, now: Epoch) -> DirDisplayScore {
|
||||
DirDisplayScore { dir: self, now }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DirDisplay<'a> {
|
||||
dir: &'a Dir<'a>,
|
||||
now: Option<Epoch>,
|
||||
separator: char,
|
||||
}
|
||||
|
||||
impl<'a> DirDisplay<'a> {
|
||||
fn new(dir: &'a Dir) -> Self {
|
||||
Self { dir, separator: ' ', now: None }
|
||||
}
|
||||
|
||||
pub fn with_score(mut self, now: Epoch) -> Self {
|
||||
self.now = Some(now);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_separator(mut self, separator: char) -> Self {
|
||||
self.separator = separator;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for DirDisplay<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
if let Some(now) = self.now {
|
||||
let score = self.dir.score(now).clamp(0.0, 9999.0);
|
||||
write!(f, "{score:>6.1}{}", self.separator)?;
|
||||
}
|
||||
write!(f, "{}", self.dir.path)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DirDisplayScore<'a> {
|
||||
dir: &'a Dir<'a>,
|
||||
now: Epoch,
|
||||
}
|
||||
|
||||
impl Display for DirDisplayScore<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
let score = self.dir.score(self.now).clamp(0.0, 9999.0) as u32;
|
||||
write!(f, "{:>4} {}", score, self.dir.path)
|
||||
}
|
||||
}
|
||||
|
||||
pub type Rank = f64;
|
||||
pub type Epoch = u64;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::borrow::Cow;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[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(_)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
360
src/db/mod.rs
360
src/db/mod.rs
|
|
@ -4,145 +4,220 @@ mod stream;
|
|||
use std::path::{Path, PathBuf};
|
||||
use std::{fs, io};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
pub use dir::{Dir, DirList, Epoch, Rank};
|
||||
pub use stream::Stream;
|
||||
use anyhow::{bail, Context, Result};
|
||||
use bincode::Options;
|
||||
use ouroboros::self_referencing;
|
||||
|
||||
use crate::util;
|
||||
pub use crate::db::dir::{Dir, Epoch, Rank};
|
||||
pub use crate::db::stream::Stream;
|
||||
use crate::{config, util};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Database<'file> {
|
||||
pub dirs: DirList<'file>,
|
||||
pub modified: bool,
|
||||
pub data_dir: &'file Path,
|
||||
#[self_referencing]
|
||||
pub struct Database {
|
||||
path: PathBuf,
|
||||
bytes: Vec<u8>,
|
||||
#[borrows(bytes)]
|
||||
#[covariant]
|
||||
pub dirs: Vec<Dir<'this>>,
|
||||
dirty: bool,
|
||||
}
|
||||
|
||||
impl<'file> Database<'file> {
|
||||
pub fn save(&mut self) -> Result<()> {
|
||||
if !self.modified {
|
||||
return Ok(());
|
||||
}
|
||||
impl Database {
|
||||
const VERSION: u32 = 3;
|
||||
|
||||
let buffer = self.dirs.to_bytes()?;
|
||||
let path = db_path(&self.data_dir);
|
||||
util::write(&path, &buffer).context("could not write to database")?;
|
||||
self.modified = false;
|
||||
Ok(())
|
||||
pub fn open() -> Result<Self> {
|
||||
let data_dir = config::data_dir()?;
|
||||
Self::open_dir(data_dir)
|
||||
}
|
||||
|
||||
/// Adds a new directory or increments its rank. Also updates its last accessed time.
|
||||
pub fn add<S: AsRef<str>>(&mut self, path: S, now: Epoch) {
|
||||
let path = path.as_ref();
|
||||
pub fn open_dir(data_dir: impl AsRef<Path>) -> Result<Self> {
|
||||
let data_dir = data_dir.as_ref();
|
||||
let path = data_dir.join("db.zo");
|
||||
|
||||
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) {
|
||||
Ok(buffer) => {
|
||||
self.buffer = buffer;
|
||||
let dirs = DirList::from_bytes(&self.buffer)
|
||||
.with_context(|| format!("could not deserialize database: {}", path.display()))?;
|
||||
Ok(Database { dirs, modified: false, data_dir: &self.data_dir })
|
||||
}
|
||||
Ok(bytes) => Self::try_new(path, bytes, |bytes| Self::deserialize(bytes), false),
|
||||
Err(e) if e.kind() == io::ErrorKind::NotFound => {
|
||||
// Create data directory, but don't create any file yet. The file will be created
|
||||
// later by [`Database::save`] if any data is modified.
|
||||
fs::create_dir_all(&self.data_dir)
|
||||
.with_context(|| format!("unable to create data directory: {}", self.data_dir.display()))?;
|
||||
Ok(Database { dirs: DirList::new(), modified: false, data_dir: &self.data_dir })
|
||||
// Create data directory, but don't create any file yet. The file will be
|
||||
// created later by [`Database::save`] if any data is modified.
|
||||
fs::create_dir_all(data_dir)
|
||||
.with_context(|| format!("unable to create data directory: {}", data_dir.display()))?;
|
||||
Ok(Self::new(path, Vec::new(), |_| Vec::new(), false))
|
||||
}
|
||||
Err(e) => Err(e).with_context(|| format!("could not read from database: {}", path.display())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn db_path<P: AsRef<Path>>(data_dir: P) -> PathBuf {
|
||||
const DB_FILENAME: &str = "db.zo";
|
||||
data_dir.as_ref().join(DB_FILENAME)
|
||||
pub fn save(&mut self) -> Result<()> {
|
||||
// Only write to disk if the database is modified.
|
||||
if !self.dirty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let bytes = Self::serialize(self.dirs())?;
|
||||
util::write(self.borrow_path(), bytes).context("could not write to database")?;
|
||||
self.with_dirty_mut(|dirty| *dirty = false);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Increments the rank of a directory, or creates it if it does not exist.
|
||||
pub fn add(&mut self, path: impl AsRef<str> + Into<String>, by: Rank, now: Epoch) {
|
||||
self.with_dirs_mut(|dirs| match dirs.iter_mut().find(|dir| dir.path == path.as_ref()) {
|
||||
Some(dir) => dir.rank = (dir.rank + by).max(0.0),
|
||||
None => dirs.push(Dir { path: path.into().into(), rank: by.max(0.0), last_accessed: now }),
|
||||
});
|
||||
self.with_dirty_mut(|dirty| *dirty = true);
|
||||
}
|
||||
|
||||
/// Creates a new directory. This will create a duplicate entry if this
|
||||
/// directory is always in the database, it is expected that the user either
|
||||
/// does a check before calling this, or calls `dedup()` afterward.
|
||||
pub fn add_unchecked(&mut self, path: impl AsRef<str> + Into<String>, rank: Rank, now: Epoch) {
|
||||
self.with_dirs_mut(|dirs| dirs.push(Dir { path: path.into().into(), rank, last_accessed: now }));
|
||||
self.with_dirty_mut(|dirty| *dirty = true);
|
||||
}
|
||||
|
||||
/// Increments the rank and updates the last_accessed of a directory, or
|
||||
/// creates it if it does not exist.
|
||||
pub fn add_update(&mut self, path: impl AsRef<str> + Into<String>, by: Rank, now: Epoch) {
|
||||
self.with_dirs_mut(|dirs| match dirs.iter_mut().find(|dir| dir.path == path.as_ref()) {
|
||||
Some(dir) => {
|
||||
dir.rank = (dir.rank + by).max(0.0);
|
||||
dir.last_accessed = now;
|
||||
}
|
||||
None => dirs.push(Dir { path: path.into().into(), rank: by.max(0.0), last_accessed: now }),
|
||||
});
|
||||
self.with_dirty_mut(|dirty| *dirty = true);
|
||||
}
|
||||
|
||||
/// Removes the directory with `path` from the store. This does not preserve
|
||||
/// ordering, but is O(1).
|
||||
pub fn remove(&mut self, path: impl AsRef<str>) -> bool {
|
||||
match self.dirs().iter().position(|dir| dir.path == path.as_ref()) {
|
||||
Some(idx) => {
|
||||
self.swap_remove(idx);
|
||||
true
|
||||
}
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn swap_remove(&mut self, idx: usize) {
|
||||
self.with_dirs_mut(|dirs| dirs.swap_remove(idx));
|
||||
self.with_dirty_mut(|dirty| *dirty = true);
|
||||
}
|
||||
|
||||
pub fn age(&mut self, max_age: Rank) {
|
||||
let mut dirty = false;
|
||||
self.with_dirs_mut(|dirs| {
|
||||
let total_age = dirs.iter().map(|dir| dir.rank).sum::<Rank>();
|
||||
if total_age > max_age {
|
||||
let factor = 0.9 * max_age / total_age;
|
||||
for idx in (0..dirs.len()).rev() {
|
||||
let dir = &mut dirs[idx];
|
||||
dir.rank *= factor;
|
||||
if dir.rank < 1.0 {
|
||||
dirs.swap_remove(idx);
|
||||
}
|
||||
}
|
||||
dirty = true;
|
||||
}
|
||||
});
|
||||
self.with_dirty_mut(|dirty_prev| *dirty_prev |= dirty);
|
||||
}
|
||||
|
||||
pub fn 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)]
|
||||
|
|
@ -151,50 +226,49 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn add() {
|
||||
let data_dir = tempfile::tempdir().unwrap();
|
||||
let path = if cfg!(windows) { r"C:\foo\bar" } else { "/foo/bar" };
|
||||
let now = 946684800;
|
||||
|
||||
let data_dir = tempfile::tempdir().unwrap();
|
||||
{
|
||||
let mut db = DatabaseFile::new(data_dir.path());
|
||||
let mut db = db.open().unwrap();
|
||||
db.add(path, now);
|
||||
db.add(path, now);
|
||||
let mut db = Database::open_dir(data_dir.path()).unwrap();
|
||||
db.add(path, 1.0, now);
|
||||
db.add(path, 1.0, now);
|
||||
db.save().unwrap();
|
||||
}
|
||||
{
|
||||
let mut db = DatabaseFile::new(data_dir.path());
|
||||
let db = db.open().unwrap();
|
||||
assert_eq!(db.dirs.len(), 1);
|
||||
|
||||
let dir = &db.dirs[0];
|
||||
{
|
||||
let db = Database::open_dir(data_dir.path()).unwrap();
|
||||
assert_eq!(db.dirs().len(), 1);
|
||||
|
||||
let dir = &db.dirs()[0];
|
||||
assert_eq!(dir.path, path);
|
||||
assert!((dir.rank - 2.0).abs() < 0.01);
|
||||
assert_eq!(dir.last_accessed, now);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove() {
|
||||
let data_dir = tempfile::tempdir().unwrap();
|
||||
let path = if cfg!(windows) { r"C:\foo\bar" } else { "/foo/bar" };
|
||||
let now = 946684800;
|
||||
|
||||
let data_dir = tempfile::tempdir().unwrap();
|
||||
{
|
||||
let mut db = DatabaseFile::new(data_dir.path());
|
||||
let mut db = db.open().unwrap();
|
||||
db.add(path, now);
|
||||
let mut db = Database::open_dir(data_dir.path()).unwrap();
|
||||
db.add(path, 1.0, now);
|
||||
db.save().unwrap();
|
||||
}
|
||||
|
||||
{
|
||||
let mut db = DatabaseFile::new(data_dir.path());
|
||||
let mut db = db.open().unwrap();
|
||||
let mut db = Database::open_dir(data_dir.path()).unwrap();
|
||||
assert!(db.remove(path));
|
||||
db.save().unwrap();
|
||||
}
|
||||
|
||||
{
|
||||
let mut db = DatabaseFile::new(data_dir.path());
|
||||
let mut db = db.open().unwrap();
|
||||
assert!(db.dirs.is_empty());
|
||||
let mut db = Database::open_dir(data_dir.path()).unwrap();
|
||||
assert!(db.dirs().is_empty());
|
||||
assert!(!db.remove(path));
|
||||
db.save().unwrap();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,33 +3,35 @@ use std::ops::Range;
|
|||
use std::{fs, path};
|
||||
|
||||
use crate::db::{Database, Dir, Epoch};
|
||||
use crate::util;
|
||||
use crate::util::{self, MONTH};
|
||||
|
||||
pub struct Stream<'db, 'file> {
|
||||
db: &'db mut Database<'file>,
|
||||
pub struct Stream<'a> {
|
||||
// State
|
||||
db: &'a mut Database,
|
||||
idxs: Rev<Range<usize>>,
|
||||
did_exclude: bool,
|
||||
|
||||
// Configuration
|
||||
keywords: Vec<String>,
|
||||
|
||||
check_exists: bool,
|
||||
expire_below: Epoch,
|
||||
resolve_symlinks: bool,
|
||||
|
||||
exclude_path: Option<String>,
|
||||
}
|
||||
|
||||
impl<'db, 'file> Stream<'db, 'file> {
|
||||
pub fn new(db: &'db mut Database<'file>, now: Epoch) -> Self {
|
||||
// Iterate in descending order of score.
|
||||
db.dirs.sort_unstable_by(|dir1, dir2| dir1.score(now).total_cmp(&dir2.score(now)));
|
||||
let idxs = (0..db.dirs.len()).rev();
|
||||
impl<'a> Stream<'a> {
|
||||
pub fn new(db: &'a mut Database, now: Epoch) -> Self {
|
||||
db.sort_by_score(now);
|
||||
let idxs = (0..db.dirs().len()).rev();
|
||||
|
||||
// If a directory is deleted and hasn't been used for 90 days, delete it from the database.
|
||||
let expire_below = now.saturating_sub(90 * 24 * 60 * 60);
|
||||
// If a directory is deleted and hasn't been used for 3 months, delete
|
||||
// it from the database.
|
||||
let expire_below = now.saturating_sub(3 * MONTH);
|
||||
|
||||
Stream {
|
||||
db,
|
||||
idxs,
|
||||
did_exclude: false,
|
||||
keywords: Vec::new(),
|
||||
check_exists: false,
|
||||
expire_below,
|
||||
|
|
@ -38,7 +40,7 @@ impl<'db, 'file> Stream<'db, 'file> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn with_exclude<S: Into<String>>(mut self, path: S) -> Self {
|
||||
pub fn with_exclude(mut self, path: impl Into<String>) -> Self {
|
||||
self.exclude_path = Some(path.into());
|
||||
self
|
||||
}
|
||||
|
|
@ -49,14 +51,14 @@ impl<'db, 'file> Stream<'db, 'file> {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn with_keywords<S: AsRef<str>>(mut self, keywords: &[S]) -> Self {
|
||||
pub fn with_keywords(mut self, keywords: &[impl AsRef<str>]) -> Self {
|
||||
self.keywords = keywords.iter().map(util::to_lowercase).collect();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn next(&mut self) -> Option<&Dir<'file>> {
|
||||
pub fn next(&mut self) -> Option<&Dir> {
|
||||
while let Some(idx) = self.idxs.next() {
|
||||
let dir = &self.db.dirs[idx];
|
||||
let dir = &self.db.dirs()[idx];
|
||||
|
||||
if !self.matches_keywords(&dir.path) {
|
||||
continue;
|
||||
|
|
@ -64,32 +66,36 @@ impl<'db, 'file> Stream<'db, 'file> {
|
|||
|
||||
if !self.matches_exists(&dir.path) {
|
||||
if dir.last_accessed < self.expire_below {
|
||||
self.db.dirs.swap_remove(idx);
|
||||
self.db.modified = true;
|
||||
self.db.swap_remove(idx);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if Some(dir.path.as_ref()) == self.exclude_path.as_deref() {
|
||||
self.did_exclude = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
let dir = &self.db.dirs[idx];
|
||||
let dir = &self.db.dirs()[idx];
|
||||
return Some(dir);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn matches_exists<S: AsRef<str>>(&self, path: S) -> bool {
|
||||
pub fn did_exclude(&self) -> bool {
|
||||
self.did_exclude
|
||||
}
|
||||
|
||||
fn matches_exists(&self, path: &str) -> bool {
|
||||
if !self.check_exists {
|
||||
return true;
|
||||
}
|
||||
let resolver = if self.resolve_symlinks { fs::symlink_metadata } else { fs::metadata };
|
||||
resolver(path.as_ref()).map(|m| m.is_dir()).unwrap_or_default()
|
||||
resolver(path).map(|m| m.is_dir()).unwrap_or_default()
|
||||
}
|
||||
|
||||
fn matches_keywords<S: AsRef<str>>(&self, path: S) -> bool {
|
||||
fn matches_keywords(&self, path: &str) -> bool {
|
||||
let (keywords_last, keywords) = match self.keywords.split_last() {
|
||||
Some(split) => split,
|
||||
None => return true,
|
||||
|
|
@ -147,8 +153,8 @@ mod tests {
|
|||
#[case(&["/foo/", "/bar"], "/foo/bar", false)]
|
||||
#[case(&["/foo/", "/bar"], "/foo/baz/bar", true)]
|
||||
fn query(#[case] keywords: &[&str], #[case] path: &str, #[case] is_match: bool) {
|
||||
let mut db = Database { dirs: Vec::new().into(), modified: false, data_dir: &PathBuf::new() };
|
||||
let stream = db.stream(0).with_keywords(keywords);
|
||||
let db = &mut Database::new(PathBuf::new(), Vec::new(), |_| Vec::new(), false);
|
||||
let stream = Stream::new(db, 0).with_keywords(keywords);
|
||||
assert_eq!(is_match, stream.matches_keywords(path));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
38
src/shell.rs
38
src/shell.rs
|
|
@ -57,7 +57,7 @@ mod tests {
|
|||
let opts = Opts { cmd, hook, echo, resolve_symlinks };
|
||||
let source = Bash(&opts).render().unwrap();
|
||||
Command::new("bash")
|
||||
.args(&["--noprofile", "--norc", "-e", "-u", "-o", "pipefail", "-c", &source])
|
||||
.args(["--noprofile", "--norc", "-e", "-u", "-o", "pipefail", "-c", &source])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
|
|
@ -70,7 +70,7 @@ mod tests {
|
|||
let source = Bash(&opts).render().unwrap();
|
||||
|
||||
Command::new("shellcheck")
|
||||
.args(&["--enable", "all", "--shell", "bash", "-"])
|
||||
.args(["--enable", "all", "--shell", "bash", "-"])
|
||||
.write_stdin(source)
|
||||
.assert()
|
||||
.success()
|
||||
|
|
@ -85,7 +85,7 @@ mod tests {
|
|||
source.push('\n');
|
||||
|
||||
Command::new("shfmt")
|
||||
.args(&["-d", "-s", "-ln", "bash", "-i", "4", "-ci", "-"])
|
||||
.args(["-d", "-s", "-ln", "bash", "-i", "4", "-ci", "-"])
|
||||
.write_stdin(source)
|
||||
.assert()
|
||||
.success()
|
||||
|
|
@ -98,14 +98,14 @@ mod tests {
|
|||
let opts = Opts { cmd, hook, echo, resolve_symlinks };
|
||||
let mut source = String::new();
|
||||
|
||||
// Filter out lines using edit:*, since those functions are only available in the
|
||||
// interactive editor.
|
||||
// Filter out lines using edit:*, since those functions are only available in
|
||||
// the interactive editor.
|
||||
for line in Elvish(&opts).render().unwrap().split('\n').filter(|line| !line.contains("edit:")) {
|
||||
source.push_str(line);
|
||||
source.push('\n');
|
||||
}
|
||||
|
||||
Command::new("elvish").args(&["-c", &source, "-norc"]).assert().success().stdout("").stderr("");
|
||||
Command::new("elvish").args(["-c", &source, "-norc"]).assert().success().stdout("").stderr("");
|
||||
}
|
||||
|
||||
#[apply(opts)]
|
||||
|
|
@ -118,7 +118,7 @@ mod tests {
|
|||
|
||||
Command::new("fish")
|
||||
.env("HOME", tempdir)
|
||||
.args(&["--command", &source, "--private"])
|
||||
.args(["--command", &source, "--no-config", "--private"])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
|
|
@ -152,7 +152,7 @@ mod tests {
|
|||
let tempdir = tempdir.path();
|
||||
|
||||
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 {
|
||||
assert.stdout("");
|
||||
|
|
@ -165,7 +165,7 @@ mod tests {
|
|||
let source = Posix(&opts).render().unwrap();
|
||||
|
||||
let assert = Command::new("bash")
|
||||
.args(&["--posix", "--noprofile", "--norc", "-e", "-u", "-o", "pipefail", "-c", &source])
|
||||
.args(["--posix", "--noprofile", "--norc", "-e", "-u", "-o", "pipefail", "-c", &source])
|
||||
.assert()
|
||||
.success()
|
||||
.stderr("");
|
||||
|
|
@ -179,7 +179,7 @@ mod tests {
|
|||
let opts = Opts { cmd, hook, echo, resolve_symlinks };
|
||||
let source = Posix(&opts).render().unwrap();
|
||||
|
||||
let assert = Command::new("dash").args(&["-e", "-u", "-c", &source]).assert().success().stderr("");
|
||||
let assert = Command::new("dash").args(["-e", "-u", "-c", &source]).assert().success().stderr("");
|
||||
if opts.hook != InitHook::Pwd {
|
||||
assert.stdout("");
|
||||
}
|
||||
|
|
@ -191,7 +191,7 @@ mod tests {
|
|||
let source = Posix(&opts).render().unwrap();
|
||||
|
||||
Command::new("shellcheck")
|
||||
.args(&["--enable", "all", "--shell", "sh", "-"])
|
||||
.args(["--enable", "all", "--shell", "sh", "-"])
|
||||
.write_stdin(source)
|
||||
.assert()
|
||||
.success()
|
||||
|
|
@ -206,7 +206,7 @@ mod tests {
|
|||
source.push('\n');
|
||||
|
||||
Command::new("shfmt")
|
||||
.args(&["-d", "-s", "-ln", "posix", "-i", "4", "-ci", "-"])
|
||||
.args(["-d", "-s", "-ln", "posix", "-i", "4", "-ci", "-"])
|
||||
.write_stdin(source)
|
||||
.assert()
|
||||
.success()
|
||||
|
|
@ -221,7 +221,7 @@ mod tests {
|
|||
Powershell(&opts).render_into(&mut source).unwrap();
|
||||
|
||||
Command::new("pwsh")
|
||||
.args(&["-NoLogo", "-NonInteractive", "-NoProfile", "-Command", &source])
|
||||
.args(["-NoLogo", "-NonInteractive", "-NoProfile", "-Command", &source])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
|
|
@ -234,7 +234,7 @@ mod tests {
|
|||
let mut source = Xonsh(&opts).render().unwrap();
|
||||
source.push('\n');
|
||||
|
||||
Command::new("black").args(&["--check", "--diff", "-"]).write_stdin(source).assert().success().stdout("");
|
||||
Command::new("black").args(["--check", "--diff", "-"]).write_stdin(source).assert().success().stdout("");
|
||||
}
|
||||
|
||||
#[apply(opts)]
|
||||
|
|
@ -242,7 +242,7 @@ mod tests {
|
|||
let opts = Opts { cmd, hook, echo, resolve_symlinks };
|
||||
let source = Xonsh(&opts).render().unwrap();
|
||||
|
||||
Command::new("mypy").args(&["--command", &source, "--strict"]).assert().success().stderr("");
|
||||
Command::new("mypy").args(["--command", &source, "--strict"]).assert().success().stderr("");
|
||||
}
|
||||
|
||||
#[apply(opts)]
|
||||
|
|
@ -252,7 +252,7 @@ mod tests {
|
|||
source.push('\n');
|
||||
|
||||
Command::new("pylint")
|
||||
.args(&["--from-stdin", "--persistent=n", "zoxide"])
|
||||
.args(["--from-stdin", "--persistent=n", "zoxide"])
|
||||
.write_stdin(source)
|
||||
.assert()
|
||||
.success()
|
||||
|
|
@ -268,7 +268,7 @@ mod tests {
|
|||
let tempdir = tempdir.path().to_str().unwrap();
|
||||
|
||||
Command::new("xonsh")
|
||||
.args(&["-c", &source, "--no-rc"])
|
||||
.args(["-c", &source, "--no-rc"])
|
||||
.env("HOME", tempdir)
|
||||
.assert()
|
||||
.success()
|
||||
|
|
@ -283,7 +283,7 @@ mod tests {
|
|||
|
||||
// ShellCheck doesn't support zsh yet: https://github.com/koalaman/shellcheck/issues/809
|
||||
Command::new("shellcheck")
|
||||
.args(&["--enable", "all", "--shell", "bash", "-"])
|
||||
.args(["--enable", "all", "--shell", "bash", "-"])
|
||||
.write_stdin(source)
|
||||
.assert()
|
||||
.success()
|
||||
|
|
@ -297,7 +297,7 @@ mod tests {
|
|||
let source = Zsh(&opts).render().unwrap();
|
||||
|
||||
Command::new("zsh")
|
||||
.args(&["-e", "-u", "-o", "pipefail", "--no-globalrcs", "--no-rcs", "-c", &source])
|
||||
.args(["-e", "-u", "-o", "pipefail", "--no-globalrcs", "--no-rcs", "-c", &source])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
|
|
|
|||
212
src/util.rs
212
src/util.rs
|
|
@ -1,7 +1,8 @@
|
|||
use std::ffi::OsStr;
|
||||
use std::fs::{self, File, OpenOptions};
|
||||
use std::io::{self, Read, Write};
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
use std::process::{Child, ChildStdin, Command, Stdio};
|
||||
use std::process::{Child, Command, Stdio};
|
||||
use std::time::SystemTime;
|
||||
use std::{env, mem};
|
||||
|
||||
|
|
@ -9,81 +10,133 @@ use std::{env, mem};
|
|||
use anyhow::anyhow;
|
||||
use anyhow::{bail, Context, Result};
|
||||
|
||||
use crate::config;
|
||||
use crate::db::Epoch;
|
||||
use crate::db::{Dir, Epoch};
|
||||
use crate::error::SilentExit;
|
||||
|
||||
pub struct Fzf {
|
||||
child: Child,
|
||||
}
|
||||
pub const SECOND: Epoch = 1;
|
||||
pub const MINUTE: Epoch = 60 * SECOND;
|
||||
pub const HOUR: Epoch = 60 * MINUTE;
|
||||
pub const DAY: Epoch = 24 * HOUR;
|
||||
pub const WEEK: Epoch = 7 * DAY;
|
||||
pub const MONTH: Epoch = 30 * DAY;
|
||||
|
||||
pub struct Fzf(Command);
|
||||
|
||||
impl Fzf {
|
||||
pub fn new(multiple: bool) -> Result<Self> {
|
||||
const ERR_FZF_NOT_FOUND: &str = "could not find fzf, is it installed?";
|
||||
const ERR_FZF_NOT_FOUND: &str = "could not find fzf, is it installed?";
|
||||
|
||||
pub fn new() -> Result<Self> {
|
||||
// On Windows, CreateProcess implicitly searches the current working
|
||||
// directory for the executable, which is a potential security issue.
|
||||
// Instead, we resolve the path to the executable and then pass it to
|
||||
// CreateProcess.
|
||||
#[cfg(windows)]
|
||||
let mut command = Command::new(which::which("fzf.exe").map_err(|_| anyhow!(ERR_FZF_NOT_FOUND))?);
|
||||
let program = which::which("fzf.exe").map_err(|_| anyhow!(Self::ERR_FZF_NOT_FOUND))?;
|
||||
#[cfg(not(windows))]
|
||||
let mut command = Command::new("fzf");
|
||||
if multiple {
|
||||
command.arg("-m");
|
||||
}
|
||||
command.arg("--nth=2..").stdin(Stdio::piped()).stdout(Stdio::piped());
|
||||
if let Some(fzf_opts) = config::fzf_opts() {
|
||||
command.env("FZF_DEFAULT_OPTS", fzf_opts);
|
||||
} else {
|
||||
command.args(&[
|
||||
// Search result
|
||||
"--no-sort",
|
||||
// Interface
|
||||
"--keep-right",
|
||||
// Layout
|
||||
"--height=50%",
|
||||
"--info=inline",
|
||||
"--layout=reverse",
|
||||
// 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 program = "fzf";
|
||||
|
||||
let child = match command.spawn() {
|
||||
Ok(child) => child,
|
||||
Err(e) if e.kind() == io::ErrorKind::NotFound => bail!(ERR_FZF_NOT_FOUND),
|
||||
Err(e) => Err(e).context("could not launch fzf")?,
|
||||
};
|
||||
let mut cmd = Command::new(program);
|
||||
cmd.args([
|
||||
// Search mode
|
||||
"--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 {
|
||||
self.child.stdin.as_mut().unwrap()
|
||||
pub fn enable_preview(&mut self) -> &mut Self {
|
||||
// Previews are only supported on UNIX.
|
||||
if !cfg!(unix) {
|
||||
return self;
|
||||
}
|
||||
|
||||
self.args([
|
||||
// Non-POSIX args are only available on certain operating systems.
|
||||
if cfg!(target_os = "linux") {
|
||||
r"--preview=\command -p ls -Cp --color=always --group-directories-first {2..}"
|
||||
} else {
|
||||
r"--preview=\command -p ls -Cp {2..}"
|
||||
},
|
||||
// Rounded edges don't display correctly on some terminals.
|
||||
"--preview-window=down,30%,sharp",
|
||||
])
|
||||
.envs([
|
||||
// Enables colorized `ls` output on macOS / FreeBSD.
|
||||
("CLICOLOR", "1"),
|
||||
// Forces colorized `ls` output when the output is not a
|
||||
// TTY (like in fzf's preview window) on macOS /
|
||||
// FreeBSD.
|
||||
("CLICOLOR_FORCE", "1"),
|
||||
// Ensures that the preview command is run in a
|
||||
// POSIX-compliant shell, regardless of what shell the
|
||||
// user has selected.
|
||||
("SHELL", "sh"),
|
||||
])
|
||||
}
|
||||
|
||||
pub fn 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.
|
||||
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();
|
||||
stdout.read_to_string(&mut output).context("failed to read from fzf")?;
|
||||
|
||||
let status = self.child.wait().context("wait failed on fzf")?;
|
||||
let status = self.0.wait().context("wait failed on fzf")?;
|
||||
match status.code() {
|
||||
Some(0) => Ok(output),
|
||||
Some(1) => bail!("no match found"),
|
||||
|
|
@ -96,7 +149,7 @@ impl Fzf {
|
|||
}
|
||||
|
||||
/// Similar to [`fs::write`], but atomic (best effort on Windows).
|
||||
pub fn write<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()> {
|
||||
pub fn write(path: impl AsRef<Path>, contents: impl AsRef<[u8]>) -> Result<()> {
|
||||
let path = path.as_ref();
|
||||
let contents = contents.as_ref();
|
||||
let dir = path.parent().unwrap();
|
||||
|
|
@ -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.
|
||||
fn tmpfile<P: AsRef<Path>>(dir: P) -> Result<(File, PathBuf)> {
|
||||
fn tmpfile(dir: impl AsRef<Path>) -> Result<(File, PathBuf)> {
|
||||
const MAX_ATTEMPTS: usize = 5;
|
||||
const TMP_NAME_LEN: usize = 16;
|
||||
let dir = dir.as_ref();
|
||||
|
|
@ -153,35 +206,34 @@ fn tmpfile<P: AsRef<Path>>(dir: P) -> Result<(File, PathBuf)> {
|
|||
// Atomically create the tmpfile.
|
||||
match OpenOptions::new().write(true).create_new(true).open(&path) {
|
||||
Ok(file) => break Ok((file, path)),
|
||||
Err(e) if e.kind() == io::ErrorKind::AlreadyExists && attempts < MAX_ATTEMPTS => (),
|
||||
Err(e) => break Err(e).with_context(|| format!("could not create file: {}", path.display())),
|
||||
Err(e) if e.kind() == io::ErrorKind::AlreadyExists && attempts < MAX_ATTEMPTS => {}
|
||||
Err(e) => {
|
||||
break Err(e).with_context(|| format!("could not create file: {}", path.display()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Similar to [`fs::rename`], but retries on Windows.
|
||||
fn rename<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> Result<()> {
|
||||
const MAX_ATTEMPTS: usize = 5;
|
||||
/// Similar to [`fs::rename`], but with retries on Windows.
|
||||
fn rename(from: impl AsRef<Path>, to: impl AsRef<Path>) -> Result<()> {
|
||||
let from = from.as_ref();
|
||||
let to = to.as_ref();
|
||||
|
||||
if cfg!(windows) {
|
||||
let mut attempts = 0;
|
||||
loop {
|
||||
attempts += 1;
|
||||
match fs::rename(from, to) {
|
||||
Err(e) if e.kind() == io::ErrorKind::PermissionDenied && attempts < MAX_ATTEMPTS => (),
|
||||
result => break result,
|
||||
const MAX_ATTEMPTS: usize = if cfg!(windows) { 5 } else { 1 };
|
||||
let mut attempts = 0;
|
||||
|
||||
loop {
|
||||
match fs::rename(from, to) {
|
||||
Err(e) if e.kind() == io::ErrorKind::PermissionDenied && attempts < MAX_ATTEMPTS => attempts += 1,
|
||||
result => {
|
||||
break result.with_context(|| format!("could not rename file: {} -> {}", from.display(), to.display()));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fs::rename(from, to)
|
||||
}
|
||||
.with_context(|| format!("could not rename file: {} -> {}", from.display(), to.display()))
|
||||
}
|
||||
|
||||
pub fn canonicalize<P: AsRef<Path>>(path: &P) -> Result<PathBuf> {
|
||||
dunce::canonicalize(path).with_context(|| format!("could not resolve path: {}", path.as_ref().display()))
|
||||
pub fn canonicalize(path: impl AsRef<Path>) -> Result<PathBuf> {
|
||||
dunce::canonicalize(&path).with_context(|| format!("could not resolve path: {}", path.as_ref().display()))
|
||||
}
|
||||
|
||||
pub fn current_dir() -> Result<PathBuf> {
|
||||
|
|
@ -195,14 +247,14 @@ pub fn current_time() -> Result<Epoch> {
|
|||
Ok(current_time)
|
||||
}
|
||||
|
||||
pub fn path_to_str<P: AsRef<Path>>(path: &P) -> Result<&str> {
|
||||
pub fn path_to_str(path: &impl AsRef<Path>) -> Result<&str> {
|
||||
let path = path.as_ref();
|
||||
path.to_str().with_context(|| format!("invalid unicode in path: {}", path.display()))
|
||||
}
|
||||
|
||||
/// Returns the absolute version of a path. Like [`std::path::Path::canonicalize`], but doesn't
|
||||
/// resolve symlinks.
|
||||
pub fn resolve_path<P: AsRef<Path>>(path: &P) -> Result<PathBuf> {
|
||||
/// Returns the absolute version of a path. Like
|
||||
/// [`std::path::Path::canonicalize`], but doesn't resolve symlinks.
|
||||
pub fn resolve_path(path: impl AsRef<Path>) -> Result<PathBuf> {
|
||||
let path = path.as_ref();
|
||||
let base_path;
|
||||
|
||||
|
|
@ -213,7 +265,7 @@ pub fn resolve_path<P: AsRef<Path>>(path: &P) -> Result<PathBuf> {
|
|||
if cfg!(windows) {
|
||||
use std::path::Prefix;
|
||||
|
||||
fn get_drive_letter<P: AsRef<Path>>(path: P) -> Option<u8> {
|
||||
fn get_drive_letter(path: impl AsRef<Path>) -> Option<u8> {
|
||||
let path = path.as_ref();
|
||||
let mut components = path.components();
|
||||
|
||||
|
|
@ -293,7 +345,7 @@ pub fn resolve_path<P: AsRef<Path>>(path: &P) -> Result<PathBuf> {
|
|||
for component in components {
|
||||
match component {
|
||||
Component::Normal(_) => stack.push(component),
|
||||
Component::CurDir => (),
|
||||
Component::CurDir => {}
|
||||
Component::ParentDir => {
|
||||
if stack.last() != Some(&Component::RootDir) {
|
||||
stack.pop();
|
||||
|
|
@ -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.
|
||||
pub fn to_lowercase<S: AsRef<str>>(s: S) -> String {
|
||||
pub fn to_lowercase(s: impl AsRef<str>) -> String {
|
||||
let s = s.as_ref();
|
||||
if s.is_ascii() { s.to_ascii_lowercase() } else { s.to_lowercase() }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ function __zoxide_z
|
|||
end
|
||||
end
|
||||
|
||||
# Completions for `z`.
|
||||
# Completions.
|
||||
function __zoxide_z_complete
|
||||
set -l tokens (commandline --current-process --tokenize)
|
||||
set -l curr_tokens (commandline --cut-at-cursor --current-process --tokenize)
|
||||
|
|
@ -99,6 +99,7 @@ function __zoxide_z_complete
|
|||
commandline --function repaint
|
||||
end
|
||||
end
|
||||
complete --command __zoxide_z --no-files --arguments '(__zoxide_z_complete)'
|
||||
|
||||
# Jump to a directory using interactive search.
|
||||
function __zoxide_zi
|
||||
|
|
@ -114,17 +115,10 @@ end
|
|||
{%- when Some with (cmd) %}
|
||||
|
||||
abbr --erase {{cmd}} &>/dev/null
|
||||
complete --command {{cmd}} --erase
|
||||
function {{cmd}}
|
||||
__zoxide_z $argv
|
||||
end
|
||||
complete --command {{cmd}} --no-files --arguments '(__zoxide_z_complete)'
|
||||
alias {{cmd}}=__zoxide_z
|
||||
|
||||
abbr --erase {{cmd}}i &>/dev/null
|
||||
complete --command {{cmd}}i --erase
|
||||
function {{cmd}}i
|
||||
__zoxide_zi $argv
|
||||
end
|
||||
alias {{cmd}}i=__zoxide_zi
|
||||
|
||||
{%- when None %}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,22 +12,25 @@
|
|||
|
||||
{%- else -%}
|
||||
# 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 %}
|
||||
let-env config = ($env | default {} config).config
|
||||
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.pre_prompt ($env.config.hooks.pre_prompt | append {
|
||||
zoxide add -- $env.PWD
|
||||
}))
|
||||
let-env config = ($env | default {} config).config
|
||||
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.pre_prompt ($env.config.hooks.pre_prompt | append {
|
||||
zoxide add -- $env.PWD
|
||||
}))
|
||||
{%- else if hook == InitHook::Pwd %}
|
||||
let-env config = ($env | default {} config).config
|
||||
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_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|
|
||||
zoxide add -- $dir
|
||||
}))
|
||||
let-env config = ($env | default {} config).config
|
||||
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_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|
|
||||
zoxide add -- $dir
|
||||
}))
|
||||
{%- 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] {
|
||||
# `z -` does not work yet, see https://github.com/nushell/nushell/issues/4769
|
||||
let arg0 = ($rest | append '~').0
|
||||
let path = if (($rest | length) <= 1) && ($arg0 == '-' || ($arg0 | path expand | path type) == dir) {
|
||||
let path = if (($rest | length) <= 1) and ($arg0 == '-' or ($arg0 | path expand | path type) == dir) {
|
||||
$arg0
|
||||
} else {
|
||||
(zoxide query --exclude $env.PWD -- $rest | str trim -r -c "\n")
|
||||
|
|
@ -77,11 +80,11 @@ alias {{cmd}}i = __zoxide_zi
|
|||
{{ section }}
|
||||
# Add this to your env file (find it by running `$nu.env-path` in Nushell):
|
||||
#
|
||||
# zoxide init nushell --hook prompt | save ~/.zoxide.nu
|
||||
# zoxide init nushell | save -f ~/.zoxide.nu
|
||||
#
|
||||
# Now, add this to the end of your config file (find it by running
|
||||
# `$nu.config-path` in Nushell):
|
||||
#
|
||||
# source ~/.zoxide.nu
|
||||
#
|
||||
# Note: zoxide only supports Nushell v0.63.0 and above.
|
||||
# Note: zoxide only supports Nushell v0.73.0 and above.
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ function __zoxide_pwd() {
|
|||
# cd + custom logic based on the value of _ZO_ECHO.
|
||||
function __zoxide_cd() {
|
||||
# shellcheck disable=SC2164
|
||||
\builtin cd -- "$@" >/dev/null {%- if echo %} && __zoxide_pwd {%- endif %}
|
||||
\builtin cd -- "$@" {%- if echo %} && __zoxide_pwd {%- endif %}
|
||||
}
|
||||
|
||||
{{ section }}
|
||||
|
|
@ -79,28 +79,12 @@ function __zoxide_zi() {
|
|||
result="$(\command zoxide query -i -- "$@")" && __zoxide_cd "${result}"
|
||||
}
|
||||
|
||||
{{ section }}
|
||||
# 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 "$@"
|
||||
}
|
||||
|
||||
# Completions.
|
||||
if [[ -o zle ]]; then
|
||||
function __zoxide_z_complete() {
|
||||
# Only show completions when the cursor is at the end of the line.
|
||||
# shellcheck disable=SC2154
|
||||
[[ "{{ "${#words[@]}" }}" -eq "${CURRENT}" ]] || return
|
||||
[[ "{{ "${#words[@]}" }}" -eq "${CURRENT}" ]] || return 0
|
||||
|
||||
if [[ "{{ "${#words[@]}" }}" -eq 2 ]]; then
|
||||
_files -/
|
||||
|
|
@ -111,25 +95,26 @@ if [[ -o zle ]]; then
|
|||
result="${__zoxide_z_prefix}${result}"
|
||||
# shellcheck disable=SC2296
|
||||
compadd -Q "${(q-)result}"
|
||||
else
|
||||
{#-
|
||||
zsh-autocomplete calls the completion function multiple times if no match is
|
||||
returned.
|
||||
#}
|
||||
compadd ""
|
||||
fi
|
||||
\builtin printf '\e[5n'
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
\builtin bindkey "\e[0n" 'reset-prompt'
|
||||
if [[ "${+functions[compdef]}" -ne 0 ]]; then
|
||||
\compdef -d {{cmd}}
|
||||
\compdef -d {{cmd}}i
|
||||
\compdef __zoxide_z_complete {{cmd}}
|
||||
fi
|
||||
\builtin bindkey '\e[0n' 'reset-prompt'
|
||||
[[ "${+functions[compdef]}" -ne 0 ]] && \compdef __zoxide_z_complete __zoxide_z
|
||||
fi
|
||||
|
||||
{{ section }}
|
||||
# Commands for zoxide. Disable these using --no-cmd.
|
||||
#
|
||||
|
||||
{%- match cmd %}
|
||||
{%- when Some with (cmd) %}
|
||||
|
||||
\builtin alias {{cmd}}=__zoxide_z
|
||||
\builtin alias {{cmd}}i=__zoxide_zi
|
||||
|
||||
{%- when None %}
|
||||
|
||||
{{ not_configured }}
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@ use assert_cmd::Command;
|
|||
#[test]
|
||||
fn completions_bash() {
|
||||
let source = include_str!("../contrib/completions/zoxide.bash");
|
||||
Command::new("bash").args(&["--noprofile", "--norc", "-c", source]).assert().success().stdout("").stderr("");
|
||||
Command::new("bash").args(["--noprofile", "--norc", "-c", source]).assert().success().stdout("").stderr("");
|
||||
}
|
||||
|
||||
// Elvish: the completions file uses editor commands to add completions to the shell. However,
|
||||
// Elvish does not support running editor commands from a script, so we can't create a test for
|
||||
// this. See: https://github.com/elves/elvish/issues/1299
|
||||
// Elvish: the completions file uses editor commands to add completions to the
|
||||
// shell. However, Elvish does not support running editor commands from a
|
||||
// script, so we can't create a test for this. See: https://github.com/elves/elvish/issues/1299
|
||||
|
||||
#[test]
|
||||
fn completions_fish() {
|
||||
|
|
@ -21,7 +21,7 @@ fn completions_fish() {
|
|||
|
||||
Command::new("fish")
|
||||
.env("HOME", tempdir)
|
||||
.args(&["--command", source, "--private"])
|
||||
.args(["--command", source, "--private"])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
|
|
@ -32,7 +32,7 @@ fn completions_fish() {
|
|||
fn completions_powershell() {
|
||||
let source = include_str!("../contrib/completions/_zoxide.ps1");
|
||||
Command::new("pwsh")
|
||||
.args(&["-NoLogo", "-NonInteractive", "-NoProfile", "-Command", source])
|
||||
.args(["-NoLogo", "-NonInteractive", "-NoProfile", "-Command", source])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
|
|
@ -50,5 +50,5 @@ fn completions_zsh() {
|
|||
compinit -u
|
||||
"#;
|
||||
|
||||
Command::new("zsh").args(&["-c", source, "--no-rcs"]).assert().success().stdout("").stderr("");
|
||||
Command::new("zsh").args(["-c", source, "--no-rcs"]).assert().success().stdout("").stderr("");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ edition = "2021"
|
|||
publish = false
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.32"
|
||||
clap = { version = "3.1.0", features = ["derive"] }
|
||||
ignore = "0.4.18"
|
||||
shell-words = "1.0.0"
|
||||
anyhow.workspace = true
|
||||
clap.workspace = true
|
||||
ignore.workspace = true
|
||||
shell-words.workspace = true
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ impl CommandExt for &mut Command {
|
|||
|
||||
fn run_ci(nix_enabled: bool) -> Result<()> {
|
||||
// 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_lint(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<()> {
|
||||
// Run cargo-fmt.
|
||||
// let check_args: &[&str] = if check { &["--check", "--files-with-diff"] } else { &[] };
|
||||
// Command::new("cargo").args(&["fmt", "--all", "--"]).args(check_args).run()?;
|
||||
// let check_args: &[&str] = if check {&["--check", "--files-with-diff"] } else
|
||||
// { &[] }; Command::new("cargo").args(&["fmt", "--all",
|
||||
// "--"]).args(check_args).run()?;
|
||||
|
||||
// Run nixfmt.
|
||||
if nix_enabled {
|
||||
|
|
@ -83,9 +84,6 @@ fn run_fmt(nix_enabled: bool, check: bool) -> Result<()> {
|
|||
|
||||
fn run_lint(nix_enabled: bool) -> Result<()> {
|
||||
if nix_enabled {
|
||||
// Run cargo-audit.
|
||||
Command::new("cargo").args(&["audit", "--deny=warnings"]).run()?;
|
||||
|
||||
// Run markdownlint.
|
||||
for result in Walk::new("./") {
|
||||
let entry = result.unwrap();
|
||||
|
|
@ -100,7 +98,7 @@ fn run_lint(nix_enabled: bool) -> Result<()> {
|
|||
let entry = result.unwrap();
|
||||
let path = entry.path();
|
||||
if path.is_file() && path.extension() == Some(OsStr::new("1")) {
|
||||
Command::new("mandoc").args(&["-man", "-Wall", "-Tlint", "--"]).arg(path).run()?;
|
||||
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<()> {
|
||||
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 {
|
||||
|
|
@ -131,6 +129,6 @@ fn enable_nix() -> bool {
|
|||
let args = env::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));
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue