├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ ├── mean_bean_ci.yml │ └── mean_bean_deploy.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── ci ├── build.bash ├── common.bash ├── set_rust_version.bash └── test.bash ├── integrations ├── _j ├── autojump.bash ├── autojump.bat ├── autojump.fish ├── autojump.lua ├── autojump.sh ├── autojump.tcsh ├── autojump.zsh ├── icon.png ├── j.bat ├── jc.bat ├── jco.bat └── jo.bat └── src ├── bin └── autojump │ ├── main.rs │ ├── manip.rs │ ├── purge.rs │ ├── query.rs │ ├── stat.rs │ └── utils │ ├── input.rs │ ├── mod.rs │ ├── shells.rs │ └── tabentry.rs ├── config └── mod.rs ├── data ├── datafile.rs ├── entry.rs └── mod.rs ├── lib.rs └── matcher ├── fuzzy.rs ├── mod.rs ├── re_based.rs ├── tests.rs └── tests_windows.rs /.gitattributes: -------------------------------------------------------------------------------- 1 | *.bat eol=crlf 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: '/' 5 | schedule: 6 | interval: daily 7 | -------------------------------------------------------------------------------- /.github/workflows/mean_bean_ci.yml: -------------------------------------------------------------------------------- 1 | name: Mean Bean CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | # This job downloads and stores `cross` as an artifact, so that it can be 7 | # redownloaded across all of the jobs. Currently this copied pasted between 8 | # `ci.yml` and `deploy.yml`. Make sure to update both places when making 9 | # changes. 10 | install-cross: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v1 14 | with: 15 | depth: 50 16 | - uses: XAMPPRocky/get-github-release@v1 17 | id: cross 18 | with: 19 | owner: cross-rs 20 | repo: cross 21 | matches: ${{ matrix.platform }} 22 | token: ${{ secrets.GITHUB_TOKEN }} 23 | - uses: actions/upload-artifact@v1 24 | with: 25 | name: cross-${{ matrix.platform }} 26 | path: ${{ steps.cross.outputs.install_path }} 27 | strategy: 28 | matrix: 29 | platform: [linux-musl, apple-darwin] 30 | 31 | windows: 32 | runs-on: windows-latest 33 | # Windows technically doesn't need this, but if we don't block windows on it 34 | # some of the windows jobs could fill up the concurrent job queue before 35 | # one of the install-cross jobs has started, so this makes sure all 36 | # artifacts are downloaded first. 37 | needs: install-cross 38 | steps: 39 | - uses: actions/checkout@v2 40 | with: 41 | depth: 50 42 | - run: ci/set_rust_version.bash ${{ matrix.channel }} ${{ matrix.target }} 43 | shell: bash 44 | - run: ci/build.bash cargo ${{ matrix.target }} 45 | shell: bash 46 | - run: ci/test.bash cargo ${{ matrix.target }} 47 | shell: bash 48 | 49 | strategy: 50 | fail-fast: true 51 | matrix: 52 | channel: [stable] 53 | target: 54 | # MSVC 55 | - i686-pc-windows-msvc 56 | - x86_64-pc-windows-msvc 57 | # GNU: You typically only need to test Windows GNU if you're 58 | # specifically targetting it, and it can cause issues with some 59 | # dependencies if you're not so it's disabled by self. 60 | # - i686-pc-windows-gnu 61 | # - x86_64-pc-windows-gnu 62 | 63 | macos: 64 | runs-on: macos-latest 65 | needs: install-cross 66 | steps: 67 | - uses: actions/checkout@v2 68 | with: 69 | depth: 50 70 | 71 | - uses: actions/download-artifact@v1 72 | with: 73 | name: cross-apple-darwin 74 | path: /usr/local/bin/ 75 | 76 | - run: chmod +x /usr/local/bin/cross 77 | 78 | - run: ci/set_rust_version.bash ${{ matrix.channel }} ${{ matrix.target }} 79 | - run: ci/build.bash cross ${{ matrix.target }} 80 | # Only test on macOS platforms since we can't simulate the others. 81 | - run: ci/test.bash cross ${{ matrix.target }} 82 | if: matrix.target == 'x86_64-apple-darwin' 83 | 84 | strategy: 85 | fail-fast: true 86 | matrix: 87 | channel: [stable] 88 | target: 89 | # macOS 90 | - aarch64-apple-darwin 91 | - x86_64-apple-darwin 92 | 93 | linux: 94 | runs-on: ubuntu-latest 95 | needs: install-cross 96 | steps: 97 | - uses: actions/checkout@v2 98 | with: 99 | depth: 50 100 | 101 | - name: Download Cross 102 | uses: actions/download-artifact@v1 103 | with: 104 | name: cross-linux-musl 105 | path: /tmp/ 106 | - run: chmod +x /tmp/cross 107 | - run: ci/set_rust_version.bash ${{ matrix.channel }} ${{ matrix.target }} 108 | - run: ci/build.bash /tmp/cross ${{ matrix.target }} 109 | # These targets have issues with being tested so they are disabled 110 | # by default. You can try disabling to see if they work for 111 | # your project. 112 | - run: ci/test.bash /tmp/cross ${{ matrix.target }} 113 | if: | 114 | !contains(matrix.target, 'android') && 115 | !contains(matrix.target, 'bsd') && 116 | !contains(matrix.target, 'solaris') && 117 | matrix.target != 'armv5te-unknown-linux-musleabi' && 118 | matrix.target != 'sparc64-unknown-linux-gnu' 119 | 120 | strategy: 121 | fail-fast: true 122 | matrix: 123 | channel: [stable] 124 | target: 125 | # Linux 126 | - aarch64-unknown-linux-musl 127 | - arm-unknown-linux-musleabi 128 | - arm-unknown-linux-musleabihf 129 | - armv7-unknown-linux-musleabihf 130 | - i686-unknown-linux-musl 131 | - mips-unknown-linux-musl 132 | - mips64-unknown-linux-muslabi64 133 | - mips64el-unknown-linux-muslabi64 134 | - mipsel-unknown-linux-musl 135 | - powerpc-unknown-linux-gnu 136 | - powerpc64-unknown-linux-gnu 137 | - powerpc64le-unknown-linux-gnu 138 | - s390x-unknown-linux-gnu 139 | - sparc64-unknown-linux-gnu 140 | - x86_64-unknown-linux-musl 141 | 142 | # Android 143 | - aarch64-linux-android 144 | - arm-linux-androideabi 145 | - armv7-linux-androideabi 146 | - i686-linux-android 147 | - x86_64-linux-android 148 | # *BSD 149 | # The FreeBSD targets can have issues linking so they are disabled 150 | # by default. 151 | # - i686-unknown-freebsd 152 | # - x86_64-unknown-freebsd 153 | - x86_64-unknown-netbsd 154 | # DragonFly (Doesn't currently work) 155 | # - x86_64-unknown-dragonfly 156 | # Solaris 157 | - sparcv9-sun-solaris 158 | - x86_64-sun-solaris 159 | -------------------------------------------------------------------------------- /.github/workflows/mean_bean_deploy.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | # Sequence of patterns matched against refs/tags 4 | tags: 5 | - "*" # our tags have no prefixes like "v" 6 | 7 | name: Mean Bean Deploy 8 | env: 9 | BIN: autojump 10 | 11 | jobs: 12 | # This job downloads and stores `cross` as an artifact, so that it can be 13 | # redownloaded across all of the jobs. Currently this copied pasted between 14 | # `mean_bean_ci.yml` and `mean_bean_deploy.yml`. Make sure to update both places when making 15 | # changes. 16 | install-cross: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v1 20 | with: 21 | depth: 50 22 | - uses: XAMPPRocky/get-github-release@v1 23 | id: cross 24 | with: 25 | owner: cross-rs 26 | repo: cross 27 | matches: ${{ matrix.platform }} 28 | token: ${{ secrets.GITHUB_TOKEN }} 29 | - uses: actions/upload-artifact@v1 30 | with: 31 | name: cross-${{ matrix.platform }} 32 | path: ${{ steps.cross.outputs.install_path }} 33 | strategy: 34 | matrix: 35 | platform: [linux-musl, apple-darwin] 36 | 37 | windows: 38 | runs-on: windows-latest 39 | needs: install-cross 40 | strategy: 41 | matrix: 42 | target: 43 | # MSVC 44 | - i686-pc-windows-msvc 45 | - x86_64-pc-windows-msvc 46 | # GNU 47 | # - i686-pc-windows-gnu 48 | # - x86_64-pc-windows-gnu 49 | steps: 50 | - uses: actions/checkout@v2 51 | - run: bash ci/set_rust_version.bash stable ${{ matrix.target }} 52 | - run: bash ci/build.bash cargo ${{ matrix.target }} RELEASE 53 | - run: | 54 | cd ./target/${{ matrix.target }}/release/ 55 | 7z a "${{ env.BIN }}.zip" "${{ env.BIN }}.exe" 56 | mv "${{ env.BIN }}.zip" $GITHUB_WORKSPACE 57 | shell: bash 58 | # We're using using a fork of `actions/create-release` that detects 59 | # whether a release is already available or not first. 60 | - uses: XAMPPRocky/create-release@v1.0.2 61 | id: create_release 62 | env: 63 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 64 | with: 65 | tag_name: ${{ github.ref }} 66 | release_name: ${{ github.ref }} 67 | # Draft should **always** be false. GitHub doesn't provide a way to 68 | # get draft releases from its API, so there's no point using it. 69 | draft: false 70 | prerelease: false 71 | - uses: actions/upload-release-asset@v1 72 | id: upload-release-asset 73 | env: 74 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 75 | with: 76 | upload_url: ${{ steps.create_release.outputs.upload_url }} 77 | asset_path: ${{ env.BIN }}.zip 78 | asset_name: ${{ env.BIN }}-${{ matrix.target }}.zip 79 | asset_content_type: application/zip 80 | 81 | macos: 82 | runs-on: macos-latest 83 | needs: install-cross 84 | strategy: 85 | matrix: 86 | target: 87 | # macOS 88 | - aarch64-apple-darwin 89 | - x86_64-apple-darwin 90 | steps: 91 | - uses: actions/checkout@v2 92 | - uses: actions/download-artifact@v1 93 | with: 94 | name: cross-apple-darwin 95 | path: /usr/local/bin/ 96 | - run: chmod +x /usr/local/bin/cross 97 | 98 | - run: ci/set_rust_version.bash stable ${{ matrix.target }} 99 | - run: ci/build.bash cross ${{ matrix.target }} RELEASE 100 | - run: tar -czvf ${{ env.BIN }}.tar.gz --directory=target/${{ matrix.target }}/release ${{ env.BIN }} 101 | - uses: XAMPPRocky/create-release@v1.0.2 102 | id: create_release 103 | env: 104 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 105 | with: 106 | tag_name: ${{ github.ref }} 107 | release_name: ${{ github.ref }} 108 | draft: false 109 | prerelease: false 110 | - uses: actions/upload-release-asset@v1 111 | id: upload-release-asset 112 | env: 113 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 114 | with: 115 | upload_url: ${{ steps.create_release.outputs.upload_url }} 116 | asset_path: ${{ env.BIN }}.tar.gz 117 | asset_name: ${{ env.BIN }}-${{ matrix.target }}.tar.gz 118 | asset_content_type: application/gzip 119 | 120 | linux: 121 | runs-on: ubuntu-latest 122 | needs: install-cross 123 | strategy: 124 | matrix: 125 | target: 126 | # Linux 127 | - aarch64-unknown-linux-musl 128 | - arm-unknown-linux-musleabi 129 | - armv7-unknown-linux-musleabihf 130 | - i686-unknown-linux-musl 131 | - mips-unknown-linux-musl 132 | - mips64-unknown-linux-muslabi64 133 | - mips64el-unknown-linux-muslabi64 134 | - mipsel-unknown-linux-musl 135 | - powerpc-unknown-linux-gnu 136 | - powerpc64-unknown-linux-gnu 137 | - powerpc64le-unknown-linux-gnu 138 | - s390x-unknown-linux-gnu 139 | - x86_64-unknown-linux-musl 140 | # Android 141 | - aarch64-linux-android 142 | - arm-linux-androideabi 143 | - armv7-linux-androideabi 144 | - i686-linux-android 145 | - x86_64-linux-android 146 | # *BSD 147 | # The FreeBSD targets can have issues linking so they are disabled 148 | # by default. 149 | # - i686-unknown-freebsd 150 | # - x86_64-unknown-freebsd 151 | - x86_64-unknown-netbsd 152 | # Solaris 153 | - sparcv9-sun-solaris 154 | - x86_64-sun-solaris 155 | # Bare Metal 156 | # These are no-std embedded targets, so they will only build if your 157 | # crate is `no_std` compatible. 158 | # - thumbv6m-none-eabi 159 | # - thumbv7em-none-eabi 160 | # - thumbv7em-none-eabihf 161 | # - thumbv7m-none-eabi 162 | steps: 163 | - uses: actions/checkout@v2 164 | - uses: actions/download-artifact@v1 165 | with: 166 | name: cross-linux-musl 167 | path: /tmp/ 168 | - run: chmod +x /tmp/cross 169 | 170 | - run: ci/set_rust_version.bash stable ${{ matrix.target }} 171 | - run: ci/build.bash /tmp/cross ${{ matrix.target }} RELEASE 172 | - run: tar -czvf ${{ env.BIN }}.tar.gz --directory=target/${{ matrix.target }}/release ${{ env.BIN }} 173 | - uses: XAMPPRocky/create-release@v1.0.2 174 | id: create_release 175 | env: 176 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 177 | with: 178 | tag_name: ${{ github.ref }} 179 | release_name: ${{ github.ref }} 180 | draft: false 181 | prerelease: false 182 | - name: Upload Release Asset 183 | id: upload-release-asset 184 | uses: actions/upload-release-asset@v1 185 | env: 186 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 187 | with: 188 | upload_url: ${{ steps.create_release.outputs.upload_url }} 189 | asset_path: ${{ env.BIN }}.tar.gz 190 | asset_name: ${{ env.BIN }}-${{ matrix.target }}.tar.gz 191 | asset_content_type: application/gzip 192 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # These are backup files generated by rustfmt 6 | **/*.rs.bk 7 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "0.7.20" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "atomicwrites" 16 | version = "0.3.1" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "eb8f2cd6962fa53c0e2a9d3f97eaa7dbd1e3cbbeeb4745403515b42ae07b3ff6" 19 | dependencies = [ 20 | "tempfile", 21 | "winapi", 22 | ] 23 | 24 | [[package]] 25 | name = "autojump" 26 | version = "0.5.1" 27 | dependencies = [ 28 | "atomicwrites", 29 | "clap", 30 | "dirs", 31 | "regex", 32 | "serde", 33 | "serde_derive", 34 | "strsim", 35 | ] 36 | 37 | [[package]] 38 | name = "bitflags" 39 | version = "1.3.2" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 42 | 43 | [[package]] 44 | name = "bitflags" 45 | version = "2.0.2" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "487f1e0fcbe47deb8b0574e646def1c903389d95241dd1bbcc6ce4a715dfc0c1" 48 | 49 | [[package]] 50 | name = "cc" 51 | version = "1.0.79" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" 54 | 55 | [[package]] 56 | name = "cfg-if" 57 | version = "1.0.0" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 60 | 61 | [[package]] 62 | name = "clap" 63 | version = "4.1.11" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "42dfd32784433290c51d92c438bb72ea5063797fc3cc9a21a8c4346bebbb2098" 66 | dependencies = [ 67 | "bitflags 2.0.2", 68 | "clap_lex", 69 | "is-terminal", 70 | "once_cell", 71 | "strsim", 72 | "termcolor", 73 | "terminal_size", 74 | ] 75 | 76 | [[package]] 77 | name = "clap_lex" 78 | version = "0.3.3" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "033f6b7a4acb1f358c742aaca805c939ee73b4c6209ae4318ec7aca81c42e646" 81 | dependencies = [ 82 | "os_str_bytes", 83 | ] 84 | 85 | [[package]] 86 | name = "dirs" 87 | version = "4.0.0" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" 90 | dependencies = [ 91 | "dirs-sys", 92 | ] 93 | 94 | [[package]] 95 | name = "dirs-sys" 96 | version = "0.3.7" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" 99 | dependencies = [ 100 | "libc", 101 | "redox_users", 102 | "winapi", 103 | ] 104 | 105 | [[package]] 106 | name = "errno" 107 | version = "0.2.8" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" 110 | dependencies = [ 111 | "errno-dragonfly", 112 | "libc", 113 | "winapi", 114 | ] 115 | 116 | [[package]] 117 | name = "errno-dragonfly" 118 | version = "0.1.2" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" 121 | dependencies = [ 122 | "cc", 123 | "libc", 124 | ] 125 | 126 | [[package]] 127 | name = "fastrand" 128 | version = "1.9.0" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" 131 | dependencies = [ 132 | "instant", 133 | ] 134 | 135 | [[package]] 136 | name = "getrandom" 137 | version = "0.2.8" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" 140 | dependencies = [ 141 | "cfg-if", 142 | "libc", 143 | "wasi", 144 | ] 145 | 146 | [[package]] 147 | name = "hermit-abi" 148 | version = "0.3.1" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" 151 | 152 | [[package]] 153 | name = "instant" 154 | version = "0.1.12" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 157 | dependencies = [ 158 | "cfg-if", 159 | ] 160 | 161 | [[package]] 162 | name = "io-lifetimes" 163 | version = "1.0.8" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "0dd6da19f25979c7270e70fa95ab371ec3b701cd0eefc47667a09785b3c59155" 166 | dependencies = [ 167 | "hermit-abi", 168 | "libc", 169 | "windows-sys 0.45.0", 170 | ] 171 | 172 | [[package]] 173 | name = "is-terminal" 174 | version = "0.4.5" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "8687c819457e979cc940d09cb16e42a1bf70aa6b60a549de6d3a62a0ee90c69e" 177 | dependencies = [ 178 | "hermit-abi", 179 | "io-lifetimes", 180 | "rustix", 181 | "windows-sys 0.45.0", 182 | ] 183 | 184 | [[package]] 185 | name = "libc" 186 | version = "0.2.140" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" 189 | 190 | [[package]] 191 | name = "linux-raw-sys" 192 | version = "0.1.4" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" 195 | 196 | [[package]] 197 | name = "memchr" 198 | version = "2.5.0" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 201 | 202 | [[package]] 203 | name = "once_cell" 204 | version = "1.17.1" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" 207 | 208 | [[package]] 209 | name = "os_str_bytes" 210 | version = "6.5.0" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267" 213 | 214 | [[package]] 215 | name = "proc-macro2" 216 | version = "1.0.52" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "1d0e1ae9e836cc3beddd63db0df682593d7e2d3d891ae8c9083d2113e1744224" 219 | dependencies = [ 220 | "unicode-ident", 221 | ] 222 | 223 | [[package]] 224 | name = "quote" 225 | version = "1.0.26" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" 228 | dependencies = [ 229 | "proc-macro2", 230 | ] 231 | 232 | [[package]] 233 | name = "redox_syscall" 234 | version = "0.2.16" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 237 | dependencies = [ 238 | "bitflags 1.3.2", 239 | ] 240 | 241 | [[package]] 242 | name = "redox_users" 243 | version = "0.4.3" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" 246 | dependencies = [ 247 | "getrandom", 248 | "redox_syscall", 249 | "thiserror", 250 | ] 251 | 252 | [[package]] 253 | name = "regex" 254 | version = "1.7.1" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" 257 | dependencies = [ 258 | "aho-corasick", 259 | "memchr", 260 | "regex-syntax", 261 | ] 262 | 263 | [[package]] 264 | name = "regex-syntax" 265 | version = "0.6.28" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" 268 | 269 | [[package]] 270 | name = "rustix" 271 | version = "0.36.10" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | checksum = "2fe885c3a125aa45213b68cc1472a49880cb5923dc23f522ad2791b882228778" 274 | dependencies = [ 275 | "bitflags 1.3.2", 276 | "errno", 277 | "io-lifetimes", 278 | "libc", 279 | "linux-raw-sys", 280 | "windows-sys 0.45.0", 281 | ] 282 | 283 | [[package]] 284 | name = "serde" 285 | version = "1.0.158" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "771d4d9c4163ee138805e12c710dd365e4f44be8be0503cb1bb9eb989425d9c9" 288 | 289 | [[package]] 290 | name = "serde_derive" 291 | version = "1.0.158" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "e801c1712f48475582b7696ac71e0ca34ebb30e09338425384269d9717c62cad" 294 | dependencies = [ 295 | "proc-macro2", 296 | "quote", 297 | "syn", 298 | ] 299 | 300 | [[package]] 301 | name = "strsim" 302 | version = "0.10.0" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 305 | 306 | [[package]] 307 | name = "syn" 308 | version = "2.0.3" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | checksum = "e8234ae35e70582bfa0f1fedffa6daa248e41dd045310b19800c4a36382c8f60" 311 | dependencies = [ 312 | "proc-macro2", 313 | "quote", 314 | "unicode-ident", 315 | ] 316 | 317 | [[package]] 318 | name = "tempfile" 319 | version = "3.4.0" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95" 322 | dependencies = [ 323 | "cfg-if", 324 | "fastrand", 325 | "redox_syscall", 326 | "rustix", 327 | "windows-sys 0.42.0", 328 | ] 329 | 330 | [[package]] 331 | name = "termcolor" 332 | version = "1.2.0" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" 335 | dependencies = [ 336 | "winapi-util", 337 | ] 338 | 339 | [[package]] 340 | name = "terminal_size" 341 | version = "0.2.5" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "4c9afddd2cec1c0909f06b00ef33f94ab2cc0578c4a610aa208ddfec8aa2b43a" 344 | dependencies = [ 345 | "rustix", 346 | "windows-sys 0.45.0", 347 | ] 348 | 349 | [[package]] 350 | name = "thiserror" 351 | version = "1.0.40" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" 354 | dependencies = [ 355 | "thiserror-impl", 356 | ] 357 | 358 | [[package]] 359 | name = "thiserror-impl" 360 | version = "1.0.40" 361 | source = "registry+https://github.com/rust-lang/crates.io-index" 362 | checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" 363 | dependencies = [ 364 | "proc-macro2", 365 | "quote", 366 | "syn", 367 | ] 368 | 369 | [[package]] 370 | name = "unicode-ident" 371 | version = "1.0.8" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" 374 | 375 | [[package]] 376 | name = "wasi" 377 | version = "0.11.0+wasi-snapshot-preview1" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 380 | 381 | [[package]] 382 | name = "winapi" 383 | version = "0.3.9" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 386 | dependencies = [ 387 | "winapi-i686-pc-windows-gnu", 388 | "winapi-x86_64-pc-windows-gnu", 389 | ] 390 | 391 | [[package]] 392 | name = "winapi-i686-pc-windows-gnu" 393 | version = "0.4.0" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 396 | 397 | [[package]] 398 | name = "winapi-util" 399 | version = "0.1.5" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 402 | dependencies = [ 403 | "winapi", 404 | ] 405 | 406 | [[package]] 407 | name = "winapi-x86_64-pc-windows-gnu" 408 | version = "0.4.0" 409 | source = "registry+https://github.com/rust-lang/crates.io-index" 410 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 411 | 412 | [[package]] 413 | name = "windows-sys" 414 | version = "0.42.0" 415 | source = "registry+https://github.com/rust-lang/crates.io-index" 416 | checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" 417 | dependencies = [ 418 | "windows_aarch64_gnullvm", 419 | "windows_aarch64_msvc", 420 | "windows_i686_gnu", 421 | "windows_i686_msvc", 422 | "windows_x86_64_gnu", 423 | "windows_x86_64_gnullvm", 424 | "windows_x86_64_msvc", 425 | ] 426 | 427 | [[package]] 428 | name = "windows-sys" 429 | version = "0.45.0" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" 432 | dependencies = [ 433 | "windows-targets", 434 | ] 435 | 436 | [[package]] 437 | name = "windows-targets" 438 | version = "0.42.2" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" 441 | dependencies = [ 442 | "windows_aarch64_gnullvm", 443 | "windows_aarch64_msvc", 444 | "windows_i686_gnu", 445 | "windows_i686_msvc", 446 | "windows_x86_64_gnu", 447 | "windows_x86_64_gnullvm", 448 | "windows_x86_64_msvc", 449 | ] 450 | 451 | [[package]] 452 | name = "windows_aarch64_gnullvm" 453 | version = "0.42.2" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" 456 | 457 | [[package]] 458 | name = "windows_aarch64_msvc" 459 | version = "0.42.2" 460 | source = "registry+https://github.com/rust-lang/crates.io-index" 461 | checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" 462 | 463 | [[package]] 464 | name = "windows_i686_gnu" 465 | version = "0.42.2" 466 | source = "registry+https://github.com/rust-lang/crates.io-index" 467 | checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" 468 | 469 | [[package]] 470 | name = "windows_i686_msvc" 471 | version = "0.42.2" 472 | source = "registry+https://github.com/rust-lang/crates.io-index" 473 | checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" 474 | 475 | [[package]] 476 | name = "windows_x86_64_gnu" 477 | version = "0.42.2" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" 480 | 481 | [[package]] 482 | name = "windows_x86_64_gnullvm" 483 | version = "0.42.2" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" 486 | 487 | [[package]] 488 | name = "windows_x86_64_msvc" 489 | version = "0.42.2" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" 492 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "autojump" 3 | version = "0.5.1" 4 | authors = ["Wang Xuerui "] 5 | description = "A Rust port and drop-in replacement of autojump" 6 | repository = "https://github.com/xen0n/autojump-rs" 7 | readme = "README.md" 8 | license = "GPL-3.0+" 9 | include = ["src/**/*.rs", "Cargo.toml"] 10 | edition = "2018" 11 | 12 | 13 | [profile.release] 14 | lto = true 15 | 16 | 17 | [features] 18 | default = [] 19 | nightly = [] 20 | 21 | 22 | [dependencies] 23 | atomicwrites = "0.3" 24 | clap = { version = "4", features = ["cargo", "wrap_help"] } 25 | regex = "1.7" 26 | serde = "1.0" 27 | serde_derive = "1.0" 28 | strsim = "0.10" 29 | dirs = "4.0" 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # autojump-rs [![Crates.io version](https://img.shields.io/crates/v/autojump.svg)][cratesio] [![Crates.io downloads](https://img.shields.io/crates/dv/autojump.svg)][cratesio] [![Crates.io license](https://img.shields.io/crates/l/autojump.svg)](LICENSE) ![GitHub branch checks state](https://img.shields.io/github/checks-status/xen0n/autojump-rs/develop) 2 | 3 | A port of the wildly popular helper application [`autojump`][aj] to Rust. 4 | 5 | [aj]: https://github.com/wting/autojump 6 | [cratesio]: https://crates.io/crates/autojump 7 | 8 | 9 | ## License 10 | 11 | As this project is technically a fork, the license is the same as `autojump`, 12 | which is GPL, either version 3 or any later version. See [LICENSE](LICENSE) 13 | for details. 14 | 15 | 16 | ## Install 17 | 18 | We have [prebuilt binaries available][releases] for a while now, thanks to 19 | the [trust] project! 20 | 21 | The package is a drop-in replacement of `autojump`. Assuming `autojump` is 22 | already installed, or at least the shell script part of it has been properly 23 | set up, and you have in `$PATH` `~/.cargo/bin` before the system binary 24 | locations, all you have to do is to put [a binary of your choice architecture][releases] 25 | in your PATH, overriding the original `autojump` script. 26 | 27 | You may have to issue `hash -r` for the shell to forget previous 28 | location of `autojump`, if you don't want to re-exec your shell. 29 | 30 | (Manually cloning the repository and building is okay, of course.) 31 | 32 | [releases]: https://github.com/xen0n/autojump-rs/releases 33 | [trust]: https://github.com/japaric/trust 34 | 35 | 36 | ## Features 37 | 38 | Why do a port when the original version works? Primarily for two reasons: 39 | 40 | * The author is *really* tired of `autojump` breakage inside Python virtualenvs, and 41 | * Rust is simply *awesome* for CLI applications, with its performance and (code) slickness! 42 | 43 | Indeed, being written in a compiled language, **`autojump-rs` is very light on 44 | modern hardware**. As the program itself is very short-running, the overhead of 45 | setting up and tearing down a whole Python VM could be overwhelming, 46 | especially on less capable hardware. With `autojump-rs` this latency is 47 | greatly reduced. Typical running time is like this on the author's Core 48 | i7-2670QM laptop, with a directory database of 1014 entries: 49 | 50 | ``` 51 | $ time ./autojump/bin/autojump au 52 | /home/xenon/src/autojump-rs 53 | ./autojump/bin/autojump au 0.09s user 0.01s system 99% cpu 0.103 total 54 | 55 | $ time ./autojump-rs/target/release/autojump au 56 | /home/xenon/src/autojump-rs 57 | ./autojump-rs/target/release/autojump au 0.00s user 0.00s system 87% cpu 0.007 total 58 | ``` 59 | 60 | The time savings are more pronounced on less powerful hardware, where every 61 | cycle shaved off counts. The running time on a 1.4GHz Loongson 3A3000 is 62 | about 10ms, for example, which is very close to the x86 figures despite the 63 | clock frequency difference. 64 | 65 | And, of course, the program no longer interacts with Python in any way, so the 66 | virtualenv-related crashes are no more. Say goodbye to the dreaded 67 | `ImportError`'s *showing every `$PS1` in a virtualenv with the system-default 68 | Python*! 69 | 70 | ``` 71 | # bye and you won't be missed! 72 | Traceback (most recent call last): 73 | File "/usr/lib/python-exec/python2.7/autojump", line 43, in 74 | from autojump_data import dictify 75 | ImportError: No module named autojump_data 76 | ``` 77 | 78 | 79 | ## Compatibility 80 | 81 | All of the command line flags and arguments are now implemented, and behave 82 | exactly like the original. Being a drop-in replacement, all other shell 83 | features like tab completion should work too. (Except `jc` and `jco`; see 84 | below.) 85 | 86 | As for the text database, the on-disk format should be identical. (Actually 87 | there is a little difference in the representation of floats, but it doesn't 88 | matter.) However, as the author is developing and using this on Linux, other 89 | platforms may need a little more love, although all the libraries used are 90 | lovingly cross-platform. (Patches are welcome, of course!) 91 | 92 | The Windows batch files shipped with the original `autojump` has Python 93 | hard-coded into them, and obviously that won't work with `autojump-rs`. 94 | Use the batch files in the `windows` directory instead; just replacing the 95 | original files and putting `autojump.exe` along with them should work. 96 | (Thanks @tomv564 for the Windows testing!) 97 | 98 | That said, there're some IMO very minor deviations from the original Python 99 | implementation. These are: 100 | 101 | * Argument handling and help messages. 102 | 103 | Original `autojump` uses Python's `argparse` to parse its arguments. There 104 | is [a Rust port of it][rust-argparse], but it's nowhere as popular as the 105 | [`docopt.rs`][docopt.rs] library, as is shown in `crates.io` statistics 106 | and GitHub activities. So it's necessary to re-arrange the help messages 107 | at least, as the `docopt` family of argument parsers mandate a specific 108 | style for them. However this shouldn't be any problem, just that it's 109 | different. Again, who looks at the usage screen all the day? XD 110 | 111 | * Different algorithm chosen for fuzzy matching. 112 | 113 | The Python version uses the [`difflib.SequenceMatcher`][difflib] algorithm 114 | for its fuzzy matches. Since it's quite a bit of complexity, I chose to 115 | leverage the [`strsim`][strsim-rs] library instead. The [Jaro-Winkler 116 | distance][jaro] is computed between every filename and the last part of 117 | query needles respectively, and results are filtered based on that. 118 | 119 | * `jc` may jump outside current directory. 120 | 121 | Exact reason may be different filtering logic involved, but I'm not very 122 | sure about this one. The behavior is also observed on original `autojump`, 123 | but the frequency seems to be lower, and both implementations actually 124 | don't check if the target is below current directory. However I only use 125 | plain `j` mostly, so if you're heavily reliant on `jc` and its friends 126 | please open an issue! 127 | 128 | 129 | [rust-argparse]: https://github.com/tailhook/rust-argparse 130 | [docopt.rs]: https://github.com/docopt/docopt.rs 131 | [difflib]: https://docs.python.org/3.5/library/difflib.html 132 | [strsim-rs]: https://github.com/dguo/strsim-rs 133 | [jaro]: https://en.wikipedia.org/wiki/Jaro%E2%80%93Winkler_distance 134 | 135 | 136 | ## Future plans 137 | 138 | Now that platform support is mostly considered okay, next steps would be 139 | more refactoring and bug fixing. The `jc` behavior differences are observed 140 | on original `autojump` too, in that you could jump outside `$(pwd)`, but the 141 | actual directory jumped to is different; this needs further investigation. 142 | Hell I even want to write a `fasd` backend too, but I don't presently have 143 | *that* much free time. Anyway, contributions and bug reports are welcome! 144 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /ci/build.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Script for building your rust projects. 3 | set -e 4 | 5 | source ci/common.bash 6 | 7 | # $1 {path} = Path to cross/cargo executable 8 | CROSS=$1 9 | # $1 {string} = e.g. x86_64-pc-windows-msvc 10 | TARGET_TRIPLE=$2 11 | # $3 {boolean} = Are we building for deployment? 12 | RELEASE_BUILD=$3 13 | 14 | required_arg $CROSS 'CROSS' 15 | required_arg $TARGET_TRIPLE '' 16 | 17 | if [ -z "$RELEASE_BUILD" ]; then 18 | $CROSS build --target $TARGET_TRIPLE 19 | $CROSS build --target $TARGET_TRIPLE --all-features 20 | else 21 | $CROSS build --target $TARGET_TRIPLE --all-features --release 22 | fi 23 | 24 | -------------------------------------------------------------------------------- /ci/common.bash: -------------------------------------------------------------------------------- 1 | required_arg() { 2 | if [ -z "$1" ]; then 3 | echo "Required argument $2 missing" 4 | exit 1 5 | fi 6 | } 7 | -------------------------------------------------------------------------------- /ci/set_rust_version.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | rustup default $1 4 | rustup target add $2 5 | -------------------------------------------------------------------------------- /ci/test.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Script for building your rust projects. 3 | set -e 4 | 5 | source ci/common.bash 6 | 7 | # $1 {path} = Path to cross/cargo executable 8 | CROSS=$1 9 | # $1 {string} = 10 | TARGET_TRIPLE=$2 11 | 12 | required_arg $CROSS 'CROSS' 13 | required_arg $TARGET_TRIPLE '' 14 | 15 | $CROSS test --target $TARGET_TRIPLE 16 | $CROSS test --target $TARGET_TRIPLE --all-features 17 | -------------------------------------------------------------------------------- /integrations/_j: -------------------------------------------------------------------------------- 1 | #compdef j 2 | cur=${words[2, -1]} 3 | 4 | autojump --complete ${=cur[*]} | while read i; do 5 | compadd -U "$i"; 6 | done 7 | -------------------------------------------------------------------------------- /integrations/autojump.bash: -------------------------------------------------------------------------------- 1 | export AUTOJUMP_SOURCED=1 2 | 3 | # set user installation paths 4 | if [[ -d ~/.autojump/ ]]; then 5 | export PATH=~/.autojump/bin:"${PATH}" 6 | fi 7 | 8 | 9 | # set error file location 10 | if [[ "$(uname)" == "Darwin" ]]; then 11 | export AUTOJUMP_ERROR_PATH=~/Library/autojump/errors.log 12 | elif [[ -n "${XDG_DATA_HOME}" ]]; then 13 | export AUTOJUMP_ERROR_PATH="${XDG_DATA_HOME}/autojump/errors.log" 14 | else 15 | export AUTOJUMP_ERROR_PATH=~/.local/share/autojump/errors.log 16 | fi 17 | 18 | if [[ ! -d "$(dirname ${AUTOJUMP_ERROR_PATH})" ]]; then 19 | mkdir -p "$(dirname ${AUTOJUMP_ERROR_PATH})" 20 | fi 21 | 22 | 23 | # enable tab completion 24 | _autojump() { 25 | local cur 26 | cur=${COMP_WORDS[*]:1} 27 | comps=$(autojump --complete $cur) 28 | while read i; do 29 | COMPREPLY=("${COMPREPLY[@]}" "${i}") 30 | done </dev/null 2>>${AUTOJUMP_ERROR_PATH} &) &>/dev/null 41 | else 42 | (autojump --add "$(pwd)" >/dev/null &) &>/dev/null 43 | fi 44 | } 45 | 46 | case $PROMPT_COMMAND in 47 | *autojump*) 48 | ;; 49 | *) 50 | PROMPT_COMMAND="${PROMPT_COMMAND:+$(echo "${PROMPT_COMMAND}" | awk '{gsub(/; *$/,"")}1') ; }autojump_add_to_database" 51 | ;; 52 | esac 53 | 54 | 55 | # default autojump command 56 | j() { 57 | if [[ ${1} == -* ]] && [[ ${1} != "--" ]]; then 58 | autojump ${@} 59 | return 60 | fi 61 | 62 | output="$(autojump ${@})" 63 | if [[ -d "${output}" ]]; then 64 | if [ -t 1 ]; then # if stdout is a terminal, use colors 65 | echo -e "\\033[31m${output}\\033[0m" 66 | else 67 | echo -e "${output}" 68 | fi 69 | cd "${output}" 70 | else 71 | echo "autojump: directory '${@}' not found" 72 | echo "\n${output}\n" 73 | echo "Try \`autojump --help\` for more information." 74 | false 75 | fi 76 | } 77 | 78 | 79 | # jump to child directory (subdirectory of current path) 80 | jc() { 81 | if [[ ${1} == -* ]] && [[ ${1} != "--" ]]; then 82 | autojump ${@} 83 | return 84 | else 85 | j $(pwd) ${@} 86 | fi 87 | } 88 | 89 | 90 | # open autojump results in file browser 91 | jo() { 92 | if [[ ${1} == -* ]] && [[ ${1} != "--" ]]; then 93 | autojump ${@} 94 | return 95 | fi 96 | 97 | output="$(autojump ${@})" 98 | if [[ -d "${output}" ]]; then 99 | case ${OSTYPE} in 100 | linux*) 101 | xdg-open "${output}" 102 | ;; 103 | darwin*) 104 | open "${output}" 105 | ;; 106 | cygwin) 107 | cygstart "" $(cygpath -w -a ${output}) 108 | ;; 109 | *) 110 | echo "Unknown operating system: ${OSTYPE}." 1>&2 111 | ;; 112 | esac 113 | else 114 | echo "autojump: directory '${@}' not found" 115 | echo "\n${output}\n" 116 | echo "Try \`autojump --help\` for more information." 117 | false 118 | fi 119 | } 120 | 121 | 122 | # open autojump results (child directory) in file browser 123 | jco() { 124 | if [[ ${1} == -* ]] && [[ ${1} != "--" ]]; then 125 | autojump ${@} 126 | return 127 | else 128 | jo $(pwd) ${@} 129 | fi 130 | } 131 | -------------------------------------------------------------------------------- /integrations/autojump.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | "%~dp0\autojump" %* 3 | -------------------------------------------------------------------------------- /integrations/autojump.fish: -------------------------------------------------------------------------------- 1 | set -gx AUTOJUMP_SOURCED 1 2 | 3 | # set user installation path 4 | if test -d ~/.autojump 5 | set -x PATH ~/.autojump/bin $PATH 6 | end 7 | 8 | # Set ostype, if not set 9 | if not set -q OSTYPE 10 | set -gx OSTYPE (bash -c 'echo ${OSTYPE}') 11 | end 12 | 13 | 14 | # enable tab completion 15 | complete -x -c j -a '(autojump --complete (commandline -t))' 16 | 17 | 18 | # set error file location 19 | if test (uname) = "Darwin" 20 | set -gx AUTOJUMP_ERROR_PATH ~/Library/autojump/errors.log 21 | else if test -d "$XDG_DATA_HOME" 22 | set -gx AUTOJUMP_ERROR_PATH $XDG_DATA_HOME/autojump/errors.log 23 | else 24 | set -gx AUTOJUMP_ERROR_PATH ~/.local/share/autojump/errors.log 25 | end 26 | 27 | if test ! -d (dirname $AUTOJUMP_ERROR_PATH) 28 | mkdir -p (dirname $AUTOJUMP_ERROR_PATH) 29 | end 30 | 31 | 32 | # change pwd hook 33 | function __aj_add --on-variable PWD 34 | status --is-command-substitution; and return 35 | autojump --add (pwd) >/dev/null 2>>$AUTOJUMP_ERROR_PATH & 36 | end 37 | 38 | 39 | # misc helper functions 40 | function __aj_err 41 | # TODO(ting|#247): set error file location 42 | echo -e $argv 1>&2; false 43 | end 44 | 45 | # default autojump command 46 | function j 47 | switch "$argv" 48 | case '-*' '--*' 49 | autojump $argv 50 | case '*' 51 | set -l output (autojump $argv) 52 | # Check for . and attempt a regular cd 53 | if [ $output = "." ] 54 | cd $argv 55 | else 56 | if test -d "$output" 57 | set_color red 58 | echo $output 59 | set_color normal 60 | cd $output 61 | else 62 | __aj_err "autojump: directory '"$argv"' not found" 63 | __aj_err "\n$output\n" 64 | __aj_err "Try `autojump --help` for more information." 65 | end 66 | end 67 | end 68 | end 69 | 70 | 71 | # jump to child directory (subdirectory of current path) 72 | function jc 73 | switch "$argv" 74 | case '-*' 75 | j $argv 76 | case '*' 77 | j (pwd) $argv 78 | end 79 | end 80 | 81 | 82 | # open autojump results in file browser 83 | function jo 84 | set -l output (autojump $argv) 85 | if test -d "$output" 86 | switch $OSTYPE 87 | case 'linux*' 88 | xdg-open (autojump $argv) 89 | case 'darwin*' 90 | open (autojump $argv) 91 | case cygwin 92 | cygstart "" (cygpath -w -a (pwd)) 93 | case '*' 94 | __aj_err "Unknown operating system: \"$OSTYPE\"" 95 | end 96 | else 97 | __aj_err "autojump: directory '"$argv"' not found" 98 | __aj_err "\n$output\n" 99 | __aj_err "Try `autojump --help` for more information." 100 | end 101 | end 102 | 103 | 104 | # open autojump results (child directory) in file browser 105 | function jco 106 | switch "$argv" 107 | case '-*' 108 | j $argv 109 | case '*' 110 | jo (pwd) $argv 111 | end 112 | end 113 | -------------------------------------------------------------------------------- /integrations/autojump.lua: -------------------------------------------------------------------------------- 1 | local AUTOJUMP_DIR = debug.getinfo(1, "S").source:match[[^@?(.*[\/])[^\/]-$]] .. "..\\AutoJump" 2 | local AUTOJUMP_BIN_DIR = AUTOJUMP_DIR .. "\\bin" 3 | local AUTOJUMP_BIN = (AUTOJUMP_BIN_DIR or clink.get_env("LOCALAPPDATA") .. "\\autojump\\bin") .. "\\autojump" 4 | 5 | function autojump_add_to_database() 6 | os.execute("\"" .. AUTOJUMP_BIN .. "\"" .. " --add " .. "\"" .. clink.get_cwd() .. "\"" .. " 2> " .. clink.get_env("TEMP") .. "\\autojump_error.txt") 7 | end 8 | 9 | clink.prompt.register_filter(autojump_add_to_database, 99) 10 | 11 | function autojump_completion(word) 12 | for line in io.popen("\"" .. AUTOJUMP_BIN .. "\"" .. " --complete " .. word):lines() do 13 | clink.add_match(line) 14 | end 15 | return {} 16 | end 17 | 18 | local autojump_parser = clink.arg.new_parser() 19 | autojump_parser:set_arguments({ autojump_completion }) 20 | 21 | clink.arg.register_parser("j", autojump_parser) 22 | -------------------------------------------------------------------------------- /integrations/autojump.sh: -------------------------------------------------------------------------------- 1 | # the login $SHELL isn't always the one used 2 | # NOTE: problems might occur if /bin/sh is symlinked to /bin/bash 3 | if [ -n "${BASH}" ]; then 4 | shell="bash" 5 | elif [ -n "${ZSH_NAME}" ]; then 6 | shell="zsh" 7 | elif [ -n "${__fish_datadir}" ]; then 8 | shell="fish" 9 | elif [ -n "${version}" ]; then 10 | shell="tcsh" 11 | else 12 | shell=$(echo ${SHELL} | awk -F/ '{ print $NF }') 13 | fi 14 | 15 | # prevent circular loop for sh shells 16 | if [ "${shell}" = "sh" ]; then 17 | return 0 18 | 19 | # check local install 20 | elif [ -s ~/.autojump/share/autojump/autojump.${shell} ]; then 21 | source ~/.autojump/share/autojump/autojump.${shell} 22 | 23 | # check global install 24 | elif [ -s /usr/local/share/autojump/autojump.${shell} ]; then 25 | source /usr/local/share/autojump/autojump.${shell} 26 | fi 27 | -------------------------------------------------------------------------------- /integrations/autojump.tcsh: -------------------------------------------------------------------------------- 1 | # set user installation paths 2 | if (-d ~/.autojump/bin) then 3 | set path = (~/.autojump/bin path) 4 | endif 5 | 6 | # prepend autojump to cwdcmd (run after every change of working directory) 7 | if (`alias cwdcmd` !~ *autojump*) then 8 | alias cwdcmd 'autojump --add $cwd >/dev/null;' `alias cwdcmd` 9 | endif 10 | 11 | #default autojump command 12 | alias j 'cd `autojump -- \!:1`' 13 | -------------------------------------------------------------------------------- /integrations/autojump.zsh: -------------------------------------------------------------------------------- 1 | export AUTOJUMP_SOURCED=1 2 | 3 | # set user installation paths 4 | if [[ -d ~/.autojump/bin ]]; then 5 | path=(~/.autojump/bin ${path}) 6 | fi 7 | if [[ -d ~/.autojump/functions ]]; then 8 | fpath=(~/.autojump/functions ${fpath}) 9 | fi 10 | 11 | 12 | # set homebrew installation paths 13 | if command -v brew &>/dev/null; then 14 | local brew_prefix=${BREW_PREFIX:-$(brew --prefix)} 15 | if [[ -d "${brew_prefix}/share/zsh/site-functions" ]]; then 16 | fpath=("${brew_prefix}/share/zsh/site-functions" ${fpath}) 17 | fi 18 | fi 19 | 20 | 21 | # set error file location 22 | if [[ "$(uname)" == "Darwin" ]]; then 23 | export AUTOJUMP_ERROR_PATH=~/Library/autojump/errors.log 24 | elif [[ -n "${XDG_DATA_HOME}" ]]; then 25 | export AUTOJUMP_ERROR_PATH="${XDG_DATA_HOME}/autojump/errors.log" 26 | else 27 | export AUTOJUMP_ERROR_PATH=~/.local/share/autojump/errors.log 28 | fi 29 | 30 | if [[ ! -d ${AUTOJUMP_ERROR_PATH:h} ]]; then 31 | mkdir -p ${AUTOJUMP_ERROR_PATH:h} 32 | fi 33 | 34 | 35 | # change pwd hook 36 | autojump_chpwd() { 37 | if [[ -f "${AUTOJUMP_ERROR_PATH}" ]]; then 38 | autojump --add "$(pwd)" >/dev/null 2>>${AUTOJUMP_ERROR_PATH} &! 39 | else 40 | autojump --add "$(pwd)" >/dev/null &! 41 | fi 42 | } 43 | 44 | typeset -gaU chpwd_functions 45 | chpwd_functions+=autojump_chpwd 46 | 47 | 48 | # default autojump command 49 | j() { 50 | if [[ ${1} == -* ]] && [[ ${1} != "--" ]]; then 51 | autojump ${@} 52 | return 53 | fi 54 | 55 | setopt localoptions noautonamedirs 56 | local output="$(autojump ${@})" 57 | if [[ -d "${output}" ]]; then 58 | if [ -t 1 ]; then # if stdout is a terminal, use colors 59 | echo -e "\\033[31m${output}\\033[0m" 60 | else 61 | echo -e "${output}" 62 | fi 63 | cd "${output}" 64 | else 65 | echo "autojump: directory '${@}' not found" 66 | echo "\n${output}\n" 67 | echo "Try \`autojump --help\` for more information." 68 | false 69 | fi 70 | } 71 | 72 | 73 | # jump to child directory (subdirectory of current path) 74 | jc() { 75 | if [[ ${1} == -* ]] && [[ ${1} != "--" ]]; then 76 | autojump ${@} 77 | return 78 | else 79 | j $(pwd) ${@} 80 | fi 81 | } 82 | 83 | 84 | # open autojump results in file browser 85 | jo() { 86 | if [[ ${1} == -* ]] && [[ ${1} != "--" ]]; then 87 | autojump ${@} 88 | return 89 | fi 90 | 91 | setopt localoptions noautonamedirs 92 | local output="$(autojump ${@})" 93 | if [[ -d "${output}" ]]; then 94 | case ${OSTYPE} in 95 | linux*) 96 | xdg-open "${output}" 97 | ;; 98 | darwin*) 99 | open "${output}" 100 | ;; 101 | cygwin) 102 | cygstart "" $(cygpath -w -a ${output}) 103 | ;; 104 | *) 105 | echo "Unknown operating system: ${OSTYPE}" 1>&2 106 | ;; 107 | esac 108 | else 109 | echo "autojump: directory '${@}' not found" 110 | echo "\n${output}\n" 111 | echo "Try \`autojump --help\` for more information." 112 | false 113 | fi 114 | } 115 | 116 | 117 | # open autojump results (child directory) in file browser 118 | jco() { 119 | if [[ ${1} == -* ]] && [[ ${1} != "--" ]]; then 120 | autojump ${@} 121 | return 122 | else 123 | jo $(pwd) ${@} 124 | fi 125 | } 126 | -------------------------------------------------------------------------------- /integrations/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xen0n/autojump-rs/65abf1150d366a1e182f12b680902137f98cb455/integrations/icon.png -------------------------------------------------------------------------------- /integrations/j.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal EnableDelayedExpansion 3 | 4 | echo %*|>nul findstr /rx \-.* 5 | if ERRORLEVEL 1 ( 6 | for /f "delims=" %%i in ('"%~dp0\autojump" %*') do set new_path=%%i 7 | if exist !new_path!\nul ( 8 | echo !new_path! 9 | pushd !new_path! 10 | REM endlocal is necessary so that we can change directory for outside of this script 11 | REM but will automatically popd. We mush pushd twice to work around this. 12 | pushd !new_path! 13 | endlocal 14 | popd 15 | ) else ( 16 | echo autojump: directory %* not found 17 | echo try `autojump --help` for more information 18 | ) 19 | ) else ( 20 | "%~dp0\autojump" %* 21 | ) 22 | -------------------------------------------------------------------------------- /integrations/jc.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | echo %*|>nul findstr /rx \-.* 4 | if ERRORLEVEL 1 ( 5 | "%~dp0\j.bat" "%cd%" %* 6 | ) else ( 7 | "%~dp0\autojump" %* 8 | ) 9 | -------------------------------------------------------------------------------- /integrations/jco.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | echo %*|>nul findstr /rx \-.* 4 | if ERRORLEVEL 1 ( 5 | "%~dp0\jc.bat" "%cd%" %* 6 | ) else ( 7 | "%~dp0\autojump" %* 8 | ) 9 | -------------------------------------------------------------------------------- /integrations/jo.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal EnableDelayedExpansion 3 | 4 | echo %*|>nul findstr /rx \-.* 5 | if ERRORLEVEL 1 ( 6 | for /f "delims=" %%i in ('"%~dp0\autojump" %*') do set new_path=%%i 7 | if exist !new_path!\nul ( 8 | start "" "explorer" !new_path! 9 | ) else ( 10 | echo autojump: directory %* not found 11 | echo try `autojump --help` for more information 12 | ) 13 | ) else ( 14 | "%~dp0\autojump" %* 15 | ) 16 | -------------------------------------------------------------------------------- /src/bin/autojump/main.rs: -------------------------------------------------------------------------------- 1 | use clap::{crate_version, value_parser, Arg, ArgAction, Command}; 2 | 3 | use autojump::Config; 4 | 5 | mod manip; 6 | mod purge; 7 | mod query; 8 | mod stat; 9 | mod utils; 10 | 11 | struct Args { 12 | arg_dir: Vec, 13 | flag_complete: bool, 14 | flag_purge: bool, 15 | flag_add: Option, 16 | flag_increase: Option>, 17 | flag_decrease: Option>, 18 | flag_stat: bool, 19 | } 20 | 21 | #[cfg(not(windows))] 22 | fn check_if_sourced() { 23 | if !utils::is_autojump_sourced() { 24 | println!("Please source the correct autojump file in your shell's"); 25 | println!("startup file. For more information, please reinstall autojump"); 26 | println!("and read the post installation instructions."); 27 | std::process::exit(1); 28 | } 29 | } 30 | 31 | #[cfg(windows)] 32 | fn check_if_sourced() { 33 | // no-op on Windows 34 | } 35 | 36 | pub fn main() { 37 | check_if_sourced(); 38 | 39 | let args: Args = { 40 | let app = Command::new("autojump-rs") 41 | .version(crate_version!()) 42 | .about("Automatically jump to directory passed as an argument.") 43 | .arg(Arg::new("dir").action(ArgAction::Append)) 44 | .arg( 45 | Arg::new("add") 46 | .short('a') 47 | .long("add") 48 | .value_name("DIR") 49 | .action(ArgAction::Set) 50 | .help("add path"), 51 | ) 52 | .arg( 53 | Arg::new("complete") 54 | .long("complete") 55 | .help("used for tab completion"), 56 | ) 57 | .arg( 58 | Arg::new("purge") 59 | .long("purge") 60 | .help("remove non-existent paths from database"), 61 | ) 62 | .arg( 63 | Arg::new("stat") 64 | .short('s') 65 | .long("stat") 66 | .help("show database entries and their key weights"), 67 | ) 68 | .arg( 69 | Arg::new("increase") 70 | .short('i') 71 | .long("increase") 72 | .value_parser(value_parser!(isize)) 73 | .action(ArgAction::Set) 74 | .value_name("WEIGHT") 75 | .num_args(0..=1) 76 | .help("increase current directory weight, default 10"), 77 | ) 78 | .arg( 79 | Arg::new("decrease") 80 | .short('d') 81 | .long("decrease") 82 | .value_parser(value_parser!(isize)) 83 | .action(ArgAction::Set) 84 | .value_name("WEIGHT") 85 | .num_args(0..=1) 86 | .help("decrease current directory weight, default 15"), 87 | ) 88 | .get_matches(); 89 | 90 | let flag_increase = if app.contains_id("increase") { 91 | Some(app.get_one::("increase").copied()) 92 | } else { 93 | None 94 | }; 95 | 96 | let flag_decrease = if app.contains_id("decrease") { 97 | Some(app.get_one::("decrease").copied()) 98 | } else { 99 | None 100 | }; 101 | 102 | Args { 103 | arg_dir: app 104 | .get_many::("dir") 105 | .map_or(vec![], |x| x.cloned().collect()), 106 | flag_complete: app.contains_id("complete"), 107 | flag_purge: app.contains_id("purge"), 108 | flag_add: app.get_one::("add").cloned(), 109 | flag_increase, 110 | flag_decrease, 111 | flag_stat: app.contains_id("stat"), 112 | } 113 | }; 114 | let config = Config::defaults(); 115 | 116 | // Process arguments. 117 | // All arguments are mutually exclusive, so we just check for presence 118 | // one-by-one. 119 | if args.flag_complete { 120 | query::complete(&config, args.arg_dir); 121 | return; 122 | } 123 | if args.flag_add.is_some() { 124 | manip::add(&config, args.flag_add.unwrap()); 125 | return; 126 | } 127 | if let Some(weight) = args.flag_increase { 128 | manip::increase(&config, weight); 129 | return; 130 | } 131 | if let Some(weight) = args.flag_decrease { 132 | manip::decrease(&config, weight); 133 | return; 134 | } 135 | if args.flag_purge { 136 | purge::purge(&config); 137 | return; 138 | } 139 | if args.flag_stat { 140 | stat::print_stat(&config); 141 | return; 142 | } 143 | 144 | query::query(&config, args.arg_dir); 145 | } 146 | -------------------------------------------------------------------------------- /src/bin/autojump/manip.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::path; 3 | 4 | use autojump::data; 5 | use autojump::data::Entry; 6 | use autojump::Config; 7 | 8 | const DEFAULT_INCREASE_WEIGHT: isize = 10; 9 | const DEFAULT_DECREASE_WEIGHT: isize = 15; 10 | 11 | fn increase_weight(old_w: f64, inc_w: f64) -> f64 { 12 | (old_w.powi(2) + inc_w.powi(2)).sqrt() 13 | } 14 | 15 | fn decrease_weight(old_w: f64, dec_w: f64) -> f64 { 16 | let result = old_w - dec_w; 17 | 18 | if result < 0.0 { 19 | 0.0 20 | } else { 21 | result 22 | } 23 | } 24 | 25 | fn do_increase

(entries: &mut Vec, p: P, w: f64) -> Entry 26 | where 27 | P: AsRef, 28 | { 29 | let p = p.as_ref(); 30 | 31 | // don't process $HOME 32 | if let Some(home) = dirs::home_dir() { 33 | if p == home { 34 | // synthesize a fake entry with weight zeroed 35 | return Entry::new(p, 0.0); 36 | } 37 | } 38 | 39 | for ent in entries.iter_mut() { 40 | if ent.path == p { 41 | let new_weight = increase_weight(ent.weight, w); 42 | ent.weight = new_weight; 43 | return ent.clone(); 44 | } 45 | } 46 | 47 | // add the path 48 | let entry = Entry::new(p, w); 49 | entries.push(entry.clone()); 50 | entry 51 | } 52 | 53 | fn do_increase_and_save

(config: &Config, p: P, w: f64) -> Entry 54 | where 55 | P: AsRef, 56 | { 57 | let mut entries = data::load(config); 58 | let entry = do_increase(&mut entries, p, w); 59 | data::save(config, &entries).unwrap(); 60 | entry 61 | } 62 | 63 | fn do_decrease

(entries: &mut Vec, p: P, w: f64) -> Entry 64 | where 65 | P: AsRef, 66 | { 67 | let p = p.as_ref(); 68 | for ent in entries.iter_mut() { 69 | if ent.path == p { 70 | let new_weight = decrease_weight(ent.weight, w); 71 | ent.weight = new_weight; 72 | return ent.clone(); 73 | } 74 | } 75 | 76 | // TODO: original impl also adds an entry in case the requested path is 77 | // absent, but is it desirable? 78 | // For now let's mimic its behavior... 79 | let entry = Entry::new(p, 0.0); // no need to compare 80 | entries.push(entry.clone()); 81 | entry 82 | } 83 | 84 | fn do_decrease_and_save

(config: &Config, p: P, w: f64) -> Entry 85 | where 86 | P: AsRef, 87 | { 88 | let mut entries = data::load(config); 89 | let entry = do_decrease(&mut entries, p, w); 90 | data::save(config, &entries).unwrap(); 91 | entry 92 | } 93 | 94 | pub fn add

(config: &Config, p: P) 95 | where 96 | P: AsRef, 97 | { 98 | do_increase_and_save(config, p, DEFAULT_INCREASE_WEIGHT as f64); 99 | } 100 | 101 | pub fn increase(config: &Config, w: Option) { 102 | let w = w.unwrap_or(DEFAULT_INCREASE_WEIGHT) as f64; 103 | let p = env::current_dir().unwrap(); 104 | let entry = do_increase_and_save(config, p, w); 105 | println!("{}", entry); 106 | } 107 | 108 | pub fn decrease(config: &Config, w: Option) { 109 | let w = w.unwrap_or(DEFAULT_DECREASE_WEIGHT) as f64; 110 | let p = env::current_dir().unwrap(); 111 | let entry = do_decrease_and_save(config, p, w); 112 | println!("{}", entry); 113 | } 114 | -------------------------------------------------------------------------------- /src/bin/autojump/purge.rs: -------------------------------------------------------------------------------- 1 | use autojump::data; 2 | use autojump::Config; 3 | 4 | pub fn purge(config: &Config) { 5 | let entries = data::load(config); 6 | let old_len = entries.len(); 7 | let entries: Vec<_> = entries 8 | .into_iter() 9 | .filter(|ent| ent.path.exists()) 10 | .collect(); 11 | 12 | data::save(config, &entries).unwrap(); 13 | 14 | let new_len = entries.len(); 15 | println!("Purged {} entries.", old_len - new_len); 16 | } 17 | -------------------------------------------------------------------------------- /src/bin/autojump/query.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::path; 3 | 4 | use crate::utils; 5 | use crate::utils::TabEntryInfo; 6 | use autojump::data; 7 | use autojump::matcher::Matcher; 8 | use autojump::Config; 9 | 10 | struct QueryConfig<'a> { 11 | needles: Vec<&'a str>, 12 | check_existence: bool, 13 | index: usize, 14 | count: usize, 15 | use_fallback: bool, 16 | } 17 | 18 | enum Query<'a> { 19 | Execute(QueryConfig<'a>), 20 | EarlyResult(path::PathBuf), 21 | } 22 | 23 | pub fn complete(config: &Config, needles: Vec) { 24 | // Override needles to only consider the first entry (if present). 25 | let needle = if needles.is_empty() { 26 | "" 27 | } else { 28 | needles[0].as_str() 29 | }; 30 | let needles = vec![needle]; 31 | 32 | match prepare_query(&needles, false, 9, false) { 33 | Query::Execute(query) => { 34 | let real_needle = query.needles[0].clone(); 35 | let result = do_query(config, query); 36 | // Convert to `&str` for tab entry info creation. 37 | let result: Vec<_> = result 38 | .into_iter() 39 | .map(|p| p.to_string_lossy().into_owned()) 40 | .collect(); 41 | let strs: Vec<_> = result.iter().map(|p| p.as_str()).collect(); 42 | // Directly print out the directory if it's the only entry. 43 | if strs.len() == 1 { 44 | println!("{}", strs[0]); 45 | return; 46 | } 47 | // Output the tab completion menu 48 | let tab_entries = TabEntryInfo::from_matches(real_needle, &strs); 49 | for tab_entry in tab_entries.into_iter() { 50 | println!("{}", tab_entry); 51 | } 52 | } 53 | Query::EarlyResult(path) => { 54 | println!("{}", path.to_string_lossy()); 55 | } 56 | } 57 | } 58 | 59 | pub fn query(config: &Config, needles: Vec) { 60 | let needles: Vec<_> = needles.iter().map(|s| s.as_str()).collect(); 61 | let result = match prepare_query(&needles, true, 1, true) { 62 | Query::Execute(query) => do_query(config, query).iter().next().unwrap().clone(), 63 | Query::EarlyResult(path) => path, 64 | }; 65 | println!("{}", result.to_string_lossy()); 66 | } 67 | 68 | fn prepare_query<'a>( 69 | needles: &'a [&'a str], 70 | check_existence: bool, 71 | count: usize, 72 | use_fallback: bool, 73 | ) -> Query<'a> { 74 | let mut count = count; 75 | let needles = if needles.is_empty() { 76 | vec![""] 77 | } else { 78 | utils::sanitize(needles) 79 | }; 80 | 81 | // Try to parse the first needle (command-line argument) as tab entry 82 | // spec. 83 | let tab = utils::get_tab_entry_info(needles[0]); 84 | if tab.path.is_some() { 85 | // Just trust the auto-completion, like the original impl does. 86 | let result = path::Path::new(tab.path.unwrap()).to_path_buf(); 87 | return Query::EarlyResult(result); 88 | } 89 | 90 | // Override query needles if tab entry is found, also set the index 91 | // requested. 92 | let index; 93 | let needles = if tab.index.is_some() { 94 | // process "foo__" and "foo__1" differently 95 | if tab.index_explicit { 96 | // explicit match requested, override count 97 | count = 1; 98 | } 99 | 100 | // index is 1-based on the command line! 101 | index = tab.index.unwrap() - 1; 102 | vec![tab.needle.unwrap()] 103 | } else { 104 | index = 0; 105 | needles 106 | }; 107 | 108 | Query::Execute(QueryConfig { 109 | needles, 110 | check_existence, 111 | index, 112 | count, 113 | use_fallback, 114 | }) 115 | } 116 | 117 | fn do_query<'a>(config: &Config, query: QueryConfig<'a>) -> Vec { 118 | let needles = query.needles; 119 | let check_existence = query.check_existence; 120 | let index = query.index; 121 | let count = query.count; 122 | 123 | let mut entries = data::load(config); 124 | // Default order is ascending, but apparently we want to match the 125 | // other way around. 126 | entries.sort_by(|a, b| b.cmp(a)); 127 | 128 | let matcher = Matcher::new_smartcase(needles); 129 | let result = matcher.execute(&entries); 130 | 131 | // Filter out cwd and (when requested) non-existent directories. 132 | let cwd: Option<_> = match env::current_dir() { 133 | Ok(cwd) => Some(cwd), 134 | Err(_) => None, 135 | }; 136 | let mut result: Vec<_> = result 137 | .filter(|p| { 138 | if let Some(cwd) = &cwd { 139 | &p.path != cwd 140 | } else { 141 | true 142 | } 143 | }) 144 | .filter(|p| { 145 | if check_existence { 146 | p.path.exists() 147 | } else { 148 | true 149 | } 150 | }) 151 | .skip(index) 152 | .take(count) 153 | .map(|p| p.path.clone()) 154 | .collect(); 155 | if query.use_fallback { 156 | result.push(".".into()) 157 | } 158 | 159 | result 160 | } 161 | -------------------------------------------------------------------------------- /src/bin/autojump/stat.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | use autojump::data; 4 | use autojump::Config; 5 | 6 | pub fn print_stat(config: &Config) { 7 | let cwd: Option<_> = match env::current_dir() { 8 | Ok(dir) => Some(dir), 9 | // The cwd is gone or inaccessible, disable weight reporting later. 10 | Err(_) => None, 11 | }; 12 | let mut cwd_weight: Option = None; 13 | 14 | let entries = { 15 | let mut tmp = data::load(config); 16 | tmp.sort(); 17 | tmp 18 | }; 19 | let mut weight_sum = 0.0f64; 20 | for ref entry in &entries { 21 | println!("{}", entry); 22 | // NOTE: This isn't exactly accurate due to floating-point nature, 23 | // but since this is only an estimate let's get over it! 24 | weight_sum += entry.weight; 25 | 26 | // Simultaneously check for current directory's weight, if current 27 | // directory is accessible. 28 | if cwd.is_some() && cwd_weight.is_none() { 29 | if &entry.path == cwd.as_ref().unwrap() { 30 | cwd_weight = Some(entry.weight); 31 | } 32 | } 33 | } 34 | 35 | println!("⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯\n"); 36 | println!("{:.0}:\t total weight", weight_sum.floor()); 37 | println!("{}:\t number of entries", entries.len()); 38 | 39 | if cwd.is_some() { 40 | let cwd_weight = cwd_weight.unwrap_or(0.0f64); 41 | println!("{:.2}:\t current directory weight", cwd_weight); 42 | } 43 | 44 | println!("\ndata:\t {}", config.data_path.to_string_lossy()); 45 | } 46 | -------------------------------------------------------------------------------- /src/bin/autojump/utils/input.rs: -------------------------------------------------------------------------------- 1 | use std::path; 2 | 3 | fn sanitize_one_needle(needle: &str) -> &str { 4 | if needle == path::MAIN_SEPARATOR.to_string() { 5 | needle 6 | } else { 7 | needle.trim_end_matches(path::MAIN_SEPARATOR) 8 | } 9 | } 10 | 11 | pub fn sanitize<'a, S>(needles: &'a [S]) -> Vec<&'a str> 12 | where 13 | S: AsRef, 14 | { 15 | needles 16 | .iter() 17 | .map(|s| s.as_ref()) 18 | .map(sanitize_one_needle) 19 | .collect() 20 | } 21 | 22 | #[cfg(test)] 23 | mod tests { 24 | use super::*; 25 | 26 | #[cfg(not(windows))] 27 | #[test] 28 | fn test_sanitize_one_needle() { 29 | assert_eq!(sanitize_one_needle(""), ""); 30 | assert_eq!(sanitize_one_needle("/"), "/"); 31 | assert_eq!(sanitize_one_needle("/a"), "/a"); 32 | assert_eq!(sanitize_one_needle("a"), "a"); 33 | assert_eq!(sanitize_one_needle("a/"), "a"); 34 | assert_eq!(sanitize_one_needle("a//"), "a"); 35 | } 36 | 37 | #[cfg(windows)] 38 | #[test] 39 | fn test_sanitize_one_needle() { 40 | assert_eq!(sanitize_one_needle(""), ""); 41 | assert_eq!(sanitize_one_needle("\\"), "\\"); 42 | assert_eq!(sanitize_one_needle("\\a"), "\\a"); 43 | assert_eq!(sanitize_one_needle("a"), "a"); 44 | assert_eq!(sanitize_one_needle("a\\"), "a"); 45 | assert_eq!(sanitize_one_needle("a\\\\"), "a"); 46 | } 47 | 48 | #[cfg(not(windows))] 49 | #[test] 50 | fn test_sanitize() { 51 | let a: Vec<&str> = vec![]; 52 | let b: Vec<&str> = vec![]; 53 | assert_eq!(sanitize(&a), b); 54 | 55 | assert_eq!(sanitize(&[""]), [""]); 56 | assert_eq!(sanitize(&["foo"]), ["foo"]); 57 | assert_eq!(sanitize(&["foo", "/bar"]), ["foo", "/bar"]); 58 | assert_eq!(sanitize(&["foo", "/"]), ["foo", "/"]); 59 | assert_eq!(sanitize(&["foo", "bar/"]), ["foo", "bar"]); 60 | } 61 | 62 | #[cfg(windows)] 63 | #[test] 64 | fn test_sanitize() { 65 | let a: Vec<&str> = vec![]; 66 | let b: Vec<&str> = vec![]; 67 | assert_eq!(sanitize(&a), b); 68 | 69 | assert_eq!(sanitize(&[""]), [""]); 70 | assert_eq!(sanitize(&["foo"]), ["foo"]); 71 | assert_eq!(sanitize(&["foo", "\\bar"]), ["foo", "\\bar"]); 72 | assert_eq!(sanitize(&["foo", "\\"]), ["foo", "\\"]); 73 | assert_eq!(sanitize(&["foo", "bar\\"]), ["foo", "bar"]); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/bin/autojump/utils/mod.rs: -------------------------------------------------------------------------------- 1 | mod input; 2 | mod shells; 3 | mod tabentry; 4 | 5 | pub use self::input::*; 6 | pub use self::shells::*; 7 | pub use self::tabentry::*; 8 | -------------------------------------------------------------------------------- /src/bin/autojump/utils/shells.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(windows))] 2 | pub fn is_autojump_sourced() -> bool { 3 | match ::std::env::var("AUTOJUMP_SOURCED") { 4 | Ok(s) => s == "1", 5 | // The only accepted value is "1", which is definitely valid UTF-8, 6 | // so if the value failed UTF-8 conversion it must be invalid. 7 | Err(_) => false, 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/bin/autojump/utils/tabentry.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | #[derive(PartialEq, Eq, Debug)] 4 | pub struct TabEntryInfo<'a> { 5 | pub needle: Option<&'a str>, 6 | pub index: Option, 7 | pub index_explicit: bool, 8 | pub path: Option<&'a str>, 9 | } 10 | 11 | impl<'a> fmt::Display for TabEntryInfo<'a> { 12 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 13 | if self.needle.is_some() { 14 | write!(f, "{}", self.needle.unwrap())?; 15 | } 16 | if self.index.is_some() { 17 | write!(f, "__{}", self.index.unwrap())?; 18 | } 19 | if self.path.is_some() { 20 | write!(f, "__{}", self.path.unwrap())?; 21 | } 22 | Ok(()) 23 | } 24 | } 25 | 26 | impl<'a> TabEntryInfo<'a> { 27 | fn new(needle: &'a str, index: usize, path: &'a str) -> TabEntryInfo<'a> { 28 | TabEntryInfo { 29 | needle: Some(needle), 30 | index: Some(index), 31 | index_explicit: true, 32 | path: Some(path), 33 | } 34 | } 35 | 36 | pub fn from_matches(needle: &'a str, matches: &'a [&'a str]) -> Vec> { 37 | matches 38 | .iter() 39 | .enumerate() 40 | .map(|(i, p)| TabEntryInfo::new(needle, i + 1, p)) 41 | .collect() 42 | } 43 | } 44 | 45 | /// Given a tab entry in the following format return needle, index, and path: 46 | /// 47 | /// ```ignore 48 | /// [needle]__[index]__[path] 49 | /// ``` 50 | pub fn get_tab_entry_info<'a>(entry: &'a str) -> TabEntryInfo<'a> { 51 | get_tab_entry_info_internal(entry, "__") 52 | } 53 | 54 | fn get_tab_entry_info_internal<'a>(entry: &'a str, separator: &'a str) -> TabEntryInfo<'a> { 55 | let mut needle = None; 56 | let mut index = None; 57 | let mut index_explicit = false; 58 | let mut path = None; 59 | 60 | // FIXME: remove this scope when nll is ready. 61 | { 62 | // "0" => Some(0) 63 | // "x" => None 64 | // "01" => Some(0) (first digit) 65 | let mut parse_index = |index_s: &str, explicit: bool| { 66 | if let Some(ch) = index_s.chars().next() { 67 | if let Some(index_u32) = ch.to_digit(10) { 68 | index = Some(index_u32 as usize); 69 | index_explicit = explicit; 70 | } 71 | } 72 | }; 73 | 74 | if let Some(i) = entry.find(separator) { 75 | let (needle_s, remaining) = entry.split_at(i); 76 | let (_, remaining) = remaining.split_at(separator.len()); 77 | 78 | needle = Some(needle_s); 79 | 80 | // It seems the index part is a single digit according to the 81 | // original implementation. 82 | if let Some(i) = remaining.find(separator) { 83 | // Path part is present. 84 | let (index_s, path_s) = remaining.split_at(i); 85 | let (_, path_s) = path_s.split_at(separator.len()); 86 | 87 | // Parse the index part. 88 | parse_index(index_s, true); 89 | 90 | // Pass-through the path part. 91 | path = Some(path_s); 92 | } else { 93 | // Handle "foo__" as if the missing index is 1. 94 | // Put the logic here for better locality (the original impl has 95 | // it in the driver script). 96 | if remaining.len() == 0 { 97 | // fxxk the borrow checker 98 | // index = Some(1); 99 | parse_index("1", false); 100 | } else { 101 | // Only the index part is present. 102 | parse_index(remaining, true); 103 | } 104 | } 105 | } else { 106 | // No separators at all, the original implementation returned all 107 | // None's. 108 | } 109 | } 110 | 111 | TabEntryInfo { 112 | needle, 113 | index, 114 | index_explicit, 115 | path, 116 | } 117 | } 118 | 119 | #[cfg(test)] 120 | mod tests { 121 | use super::*; 122 | 123 | macro_rules! assert_tab_entry_info { 124 | ($input: expr, $needle: expr, $index: expr, $explicit: expr, $path: expr) => { 125 | let expected = TabEntryInfo { 126 | needle: $needle, 127 | index: $index, 128 | index_explicit: $explicit, 129 | path: $path, 130 | }; 131 | assert_eq!(get_tab_entry_info($input), expected); 132 | }; 133 | } 134 | 135 | #[test] 136 | fn test_tab_entry_info_parse_wellformed() { 137 | assert_tab_entry_info!("", None, None, false, None); 138 | assert_tab_entry_info!("a__0", Some("a"), Some(0), true, None); 139 | assert_tab_entry_info!("a__0__b", Some("a"), Some(0), true, Some("b")); 140 | } 141 | 142 | #[test] 143 | fn test_tab_entry_info_parse_malformed() { 144 | assert_tab_entry_info!("a", None, None, false, None); 145 | assert_tab_entry_info!("a__", Some("a"), Some(1), false, None); 146 | assert_tab_entry_info!("a__x", Some("a"), None, false, None); 147 | assert_tab_entry_info!("a____b", Some("a"), None, false, Some("b")); 148 | } 149 | 150 | #[test] 151 | fn test_tab_entry_info_parse_malformed_deviations() { 152 | // Original impl: Some("a"), Some(0), None 153 | assert_tab_entry_info!("a__01__b", Some("a"), Some(0), true, Some("b")); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/config/mod.rs: -------------------------------------------------------------------------------- 1 | use std::path; 2 | 3 | pub struct Config { 4 | pub prefix: path::PathBuf, 5 | pub data_path: path::PathBuf, 6 | pub backup_path: path::PathBuf, 7 | } 8 | 9 | #[cfg(unix)] 10 | fn home_dir() -> path::PathBuf { 11 | match dirs::home_dir() { 12 | Some(p) => p, 13 | // be consistent with Python's `os.path.expand_user('~')` 14 | None => path::PathBuf::from("/"), 15 | } 16 | } 17 | 18 | #[cfg(unix)] 19 | pub fn xdg_home_hardcoded() -> path::PathBuf { 20 | // ~/.local/share/autojump 21 | let mut tmp = home_dir(); 22 | tmp.push(".local"); 23 | tmp.push("share"); 24 | tmp.push("autojump"); 25 | tmp 26 | } 27 | 28 | // TODO: is this cfg appropriate for *all* Unix platforms, especially BSD? 29 | #[cfg(all(unix, not(target_os = "macos")))] 30 | fn data_home() -> path::PathBuf { 31 | use std::env; 32 | // Use $XDG_DATA_HOME if defined, ~/.local/share/autojump otherwise. 33 | if let Some(home_s) = env::var_os("XDG_DATA_HOME") { 34 | let mut tmp = path::PathBuf::from(home_s); 35 | tmp.push("autojump"); 36 | tmp 37 | } else { 38 | xdg_home_hardcoded() 39 | } 40 | } 41 | 42 | #[cfg(target_os = "macos")] 43 | fn data_home() -> path::PathBuf { 44 | let mut tmp = home_dir(); 45 | tmp.push("Library"); 46 | tmp.push("autojump"); 47 | tmp 48 | } 49 | 50 | #[cfg(windows)] 51 | fn data_home() -> path::PathBuf { 52 | use std::env; 53 | // `%APPDATA%` is always present on Windows, unless someone actually 54 | // decided to remove it in Control Panel. We wouldn't want to support 55 | // those people indeed... 56 | let mut tmp = path::PathBuf::from(env::var_os("APPDATA").unwrap()); 57 | tmp.push("autojump"); 58 | tmp 59 | } 60 | 61 | impl Config { 62 | pub fn defaults() -> Config { 63 | let data_home = data_home(); 64 | Config::from_prefix(&data_home) 65 | } 66 | 67 | pub fn from_prefix(data_home: &path::Path) -> Config { 68 | let data_home = data_home.to_path_buf(); 69 | let data_path = data_home.join("autojump.txt"); 70 | let backup_path = data_home.join("autojump.txt.bak"); 71 | 72 | Config { 73 | prefix: data_home, 74 | data_path, 75 | backup_path, 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/data/datafile.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use std::io; 3 | use std::io::{BufRead, Write}; 4 | use std::path; 5 | use std::time; 6 | 7 | use atomicwrites; 8 | 9 | use super::entry::Entry; 10 | use crate::Config; 11 | 12 | const BACKUP_THRESHOLD: u64 = 24 * 60 * 60; // 1 d 13 | 14 | #[cfg(target_os = "macos")] 15 | fn migrate_osx_xdg_data(config: &Config) -> io::Result<()> { 16 | let xdg_aj_home = crate::xdg_home_hardcoded(); 17 | if !xdg_aj_home.exists() { 18 | return Ok(()); 19 | } 20 | 21 | let old_config = Config::from_prefix(&xdg_aj_home); 22 | 23 | fs::copy(&old_config.data_path, &config.data_path)?; 24 | fs::copy(&old_config.backup_path, &config.backup_path)?; 25 | 26 | fs::remove_file(&old_config.data_path)?; 27 | fs::remove_file(&old_config.backup_path)?; 28 | Ok(()) 29 | } 30 | 31 | #[cfg(not(target_os = "macos"))] 32 | fn migrate_osx_xdg_data(_: &Config) -> io::Result<()> { 33 | Ok(()) 34 | } 35 | 36 | fn load_line(line: &str) -> Option { 37 | let parts: Vec<_> = line.splitn(2, '\t').collect(); 38 | if parts.len() != 2 { 39 | return None; 40 | } 41 | 42 | let weight = parts[0].parse::().ok()?; 43 | let path = path::PathBuf::from(parts[1]); 44 | Some(Entry::new(path, weight)) 45 | } 46 | 47 | fn load_from_file(f: fs::File) -> io::Result> { 48 | let reader = io::BufReader::new(f); 49 | let mut result = vec![]; 50 | 51 | for line in reader.lines() { 52 | let line = line?; 53 | if let Some(entry) = load_line(&line) { 54 | result.push(entry) 55 | } 56 | } 57 | 58 | Ok(result) 59 | } 60 | 61 | pub fn load(config: &Config) -> Vec { 62 | // Only necessary when running on macOS, no-op on others 63 | migrate_osx_xdg_data(config).unwrap(); 64 | 65 | if !config.data_path.exists() { 66 | return vec![]; 67 | } 68 | 69 | let result = load_from_file(fs::File::open(&config.data_path).unwrap()); 70 | if let Ok(result) = result { 71 | result 72 | } else { 73 | load_backup(config) 74 | } 75 | } 76 | 77 | fn load_backup(config: &Config) -> Vec { 78 | if config.backup_path.exists() { 79 | fs::rename(&config.backup_path, &config.data_path).unwrap(); 80 | load_from_file(fs::File::open(&config.data_path).unwrap()).unwrap() 81 | } else { 82 | vec![] 83 | } 84 | } 85 | 86 | fn save_to(file: &fs::File, data: &[Entry]) -> io::Result<()> { 87 | let mut writer = io::BufWriter::new(file); 88 | for entry in data.iter() { 89 | writeln!( 90 | &mut writer, 91 | "{}\t{}", 92 | entry.weight, 93 | entry.path.to_string_lossy() 94 | )?; 95 | } 96 | 97 | Ok(()) 98 | } 99 | 100 | fn maybe_create_data_dir(config: &Config) -> io::Result<()> { 101 | if !config.prefix.exists() { 102 | fs::create_dir_all(&config.prefix) 103 | } else { 104 | Ok(()) 105 | } 106 | } 107 | 108 | fn need_backup(config: &Config) -> io::Result { 109 | if config.backup_path.exists() { 110 | let now = time::SystemTime::now(); 111 | 112 | let metadata = config.backup_path.metadata()?; 113 | let mtime = metadata.modified()?; 114 | 115 | match now.duration_since(mtime) { 116 | Ok(duration) => Ok(duration.as_secs() > BACKUP_THRESHOLD), 117 | Err(_) => { 118 | // Clock skew: mtime is in the future! 119 | // TODO: print warning 120 | // In the original impl a backup is not forced, so we mirror 121 | // that decision for now. 122 | Ok(false) 123 | } 124 | } 125 | } else { 126 | Ok(true) 127 | } 128 | } 129 | 130 | fn maybe_backup(config: &Config) -> io::Result<()> { 131 | if need_backup(config)? { 132 | fs::copy(&config.data_path, &config.backup_path)?; 133 | } 134 | 135 | Ok(()) 136 | } 137 | 138 | pub fn save(config: &Config, data: &[Entry]) -> io::Result<()> { 139 | maybe_create_data_dir(config)?; 140 | 141 | let af = atomicwrites::AtomicFile::new(&config.data_path, atomicwrites::AllowOverwrite); 142 | af.write(|f| save_to(f, data))?; 143 | 144 | maybe_backup(config)?; 145 | 146 | Ok(()) 147 | } 148 | -------------------------------------------------------------------------------- /src/data/entry.rs: -------------------------------------------------------------------------------- 1 | use std::cmp; 2 | use std::fmt; 3 | use std::path; 4 | 5 | #[derive(Clone, Debug)] 6 | pub struct Entry { 7 | pub path: path::PathBuf, 8 | pub weight: f64, 9 | } 10 | 11 | impl Entry { 12 | pub fn new

(path: P, weight: f64) -> Entry 13 | where 14 | P: Into, 15 | { 16 | Entry { 17 | path: path.into(), 18 | weight, 19 | } 20 | } 21 | } 22 | 23 | impl fmt::Display for Entry { 24 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 25 | write!(f, "{:.1}:\t{}", self.weight, self.path.to_string_lossy()) 26 | } 27 | } 28 | 29 | impl AsRef for Entry { 30 | fn as_ref(&self) -> &path::Path { 31 | self.path.as_path() 32 | } 33 | } 34 | 35 | impl PartialOrd for Entry { 36 | fn partial_cmp(&self, other: &Entry) -> Option { 37 | self.weight.partial_cmp(&other.weight) 38 | } 39 | } 40 | 41 | impl PartialEq for Entry { 42 | fn eq(&self, other: &Entry) -> bool { 43 | self.weight == other.weight 44 | } 45 | } 46 | 47 | impl Eq for Entry {} 48 | 49 | impl Ord for Entry { 50 | fn cmp(&self, other: &Entry) -> cmp::Ordering { 51 | // We know that NaN's don't exist in our use case, so just unwrap it. 52 | self.partial_cmp(other).unwrap() 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/data/mod.rs: -------------------------------------------------------------------------------- 1 | mod datafile; 2 | mod entry; 3 | 4 | pub use self::datafile::*; 5 | pub use self::entry::*; 6 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | pub mod config; 4 | pub mod data; 5 | pub mod matcher; 6 | 7 | pub use self::config::*; 8 | -------------------------------------------------------------------------------- /src/matcher/fuzzy.rs: -------------------------------------------------------------------------------- 1 | use std::iter; 2 | use std::path; 3 | 4 | use strsim; 5 | 6 | /// A fuzzy matcher based on Jaro-Winkler distances. 7 | /// 8 | /// The similarity is calculated between the last component of the path and 9 | /// the last part of the needle. 10 | pub struct FuzzyMatcher<'a> { 11 | needle: &'a str, 12 | threshold: f64, 13 | } 14 | 15 | const DEFAULT_FUZZY_THRESHOLD: f64 = 0.6; 16 | 17 | impl<'a> FuzzyMatcher<'a> { 18 | pub fn defaults(needle: &'a str) -> FuzzyMatcher<'a> { 19 | FuzzyMatcher::new(needle, DEFAULT_FUZZY_THRESHOLD) 20 | } 21 | 22 | pub fn new(needle: &'a str, threshold: f64) -> FuzzyMatcher<'a> { 23 | FuzzyMatcher { needle, threshold } 24 | } 25 | 26 | pub fn filter_path<'p, P>(&'a self, paths: &'p [P]) -> impl iter::Iterator + 'a 27 | where 28 | P: AsRef, 29 | 'p: 'a, 30 | { 31 | paths 32 | .iter() 33 | .map(|p| (p.as_ref().file_name(), p)) 34 | .filter(|&(s, _)| s.is_some()) 35 | .map(|(s, p)| (s.unwrap().to_string_lossy().into_owned(), p)) 36 | .map(move |(s, p)| (strsim::jaro_winkler(self.needle, &s), p)) 37 | .filter(move |&(sim, _)| sim >= self.threshold) 38 | .map(|(_, p)| p) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/matcher/mod.rs: -------------------------------------------------------------------------------- 1 | mod fuzzy; 2 | mod re_based; 3 | 4 | #[cfg(all(test, not(windows)))] 5 | mod tests; 6 | #[cfg(all(test, windows))] 7 | mod tests_windows; 8 | 9 | use std::iter; 10 | use std::path; 11 | 12 | use regex; 13 | 14 | pub struct Matcher<'a> { 15 | fuzzy_matcher: fuzzy::FuzzyMatcher<'a>, 16 | re_anywhere: regex::Regex, 17 | re_consecutive: regex::Regex, 18 | } 19 | 20 | /// Returns whether matches should ignore case based on uppercase letter's 21 | /// presence in the needles. 22 | fn detect_smartcase(needles: &[&str]) -> bool { 23 | for s in needles { 24 | for ch in s.chars() { 25 | if ch.is_uppercase() { 26 | return false; 27 | } 28 | } 29 | } 30 | 31 | true 32 | } 33 | 34 | fn filter_path_with_re<'a, P>( 35 | input: &'a [P], 36 | re: &'a regex::Regex, 37 | ) -> impl iter::Iterator 38 | where 39 | P: AsRef, 40 | { 41 | input 42 | .iter() 43 | .filter(move |&p| re.is_match(p.as_ref().to_string_lossy().to_mut())) 44 | } 45 | 46 | impl<'a> Matcher<'a> { 47 | pub fn new_smartcase(needles: Vec<&'a str>) -> Matcher<'a> { 48 | let ignore_case = detect_smartcase(&needles); 49 | Matcher::new(needles, ignore_case) 50 | } 51 | 52 | pub fn new(needles: Vec<&'a str>, ignore_case: bool) -> Matcher<'a> { 53 | let fuzzy_matcher = fuzzy::FuzzyMatcher::defaults(needles[needles.len() - 1]); 54 | let re_anywhere = 55 | re_based::prepare_regex(&needles, re_based::re_match_anywhere, ignore_case); 56 | let re_consecutive = 57 | re_based::prepare_regex(&needles, re_based::re_match_consecutive, ignore_case); 58 | 59 | Matcher { 60 | fuzzy_matcher, 61 | re_anywhere, 62 | re_consecutive, 63 | } 64 | } 65 | 66 | pub fn execute<'b, P>(&'b self, haystack: &'b [P]) -> impl iter::Iterator 67 | where 68 | P: AsRef, 69 | { 70 | filter_path_with_re(haystack, &self.re_consecutive) 71 | .chain(self.fuzzy_matcher.filter_path(haystack)) 72 | .chain(filter_path_with_re(haystack, &self.re_anywhere)) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/matcher/re_based.rs: -------------------------------------------------------------------------------- 1 | use std::path; 2 | 3 | use regex; 4 | 5 | pub fn prepare_regex(needles: &[&str], f: F, ignore_case: bool) -> regex::Regex 6 | where 7 | F: Fn(&[&str]) -> String, 8 | { 9 | let re = { 10 | let mut tmp = String::new(); 11 | tmp.push_str(if ignore_case { "(?iu)" } else { "(?u)" }); 12 | tmp.push_str(&f(needles)); 13 | tmp 14 | }; 15 | regex::Regex::new(&re).unwrap() 16 | } 17 | 18 | /// Port of Python's `re.escape()`, except that '/' is passed as-is. 19 | fn re_escape(s: &str) -> String { 20 | let mut result = String::with_capacity(s.len()); 21 | for ch in s.chars() { 22 | match ch { 23 | '0'..='9' | 'A'..='Z' | 'a'..='z' | '_' | '/' => result.push(ch), 24 | '\\' => result.push_str(r"\\"), 25 | _ => { 26 | result.push_str(r"\x"); 27 | // skip the r"\u" prefix and take the remaining "{xxxx}" part 28 | for escape_ch in ch.escape_unicode().skip(2) { 29 | result.push(escape_ch); 30 | } 31 | } 32 | } 33 | } 34 | result 35 | } 36 | 37 | pub fn re_match_anywhere(needles: &[&str]) -> String { 38 | let mut result = String::new(); 39 | result.push_str(r".*"); 40 | for s in needles { 41 | result.push_str(&re_escape(s)); 42 | result.push_str(r".*"); 43 | } 44 | result 45 | } 46 | 47 | pub fn re_match_consecutive(needles: &[&str]) -> String { 48 | let sep = { 49 | let mut tmp = String::with_capacity(1); 50 | tmp.push(path::MAIN_SEPARATOR); 51 | re_escape(&tmp) 52 | }; 53 | let no_sep = format!(r"[^{}]*", sep); 54 | 55 | let mut result = String::new(); 56 | for (i, s) in needles.iter().enumerate() { 57 | if i > 0 { 58 | result.push_str(&no_sep); 59 | result.push_str(&sep); 60 | result.push_str(&no_sep); 61 | } 62 | result.push_str(&re_escape(s)); 63 | } 64 | result.push_str(&no_sep); 65 | result.push('$'); 66 | result 67 | } 68 | 69 | #[cfg(test)] 70 | mod tests { 71 | use super::*; 72 | 73 | #[test] 74 | fn test_re_escape() { 75 | macro_rules! assert_re_escape { 76 | ($x: expr, $y: expr) => { 77 | assert_eq!(re_escape($x), $y); 78 | }; 79 | } 80 | 81 | assert_re_escape!("", ""); 82 | assert_re_escape!("test", "test"); 83 | assert_re_escape!("a/b/c", "a/b/c"); 84 | assert_re_escape!("test\0test", r"test\x{0}test"); 85 | assert_re_escape!("测试", r"\x{6d4b}\x{8bd5}"); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/matcher/tests.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(rustfmt, rustfmt_skip)] 2 | 3 | use std::path; 4 | 5 | use super::fuzzy::*; 6 | use super::re_based::*; 7 | use super::*; 8 | 9 | #[test] 10 | fn test_smartcase() { 11 | macro_rules! a { 12 | ($needles: tt, $y: expr) => { 13 | assert_eq!(detect_smartcase(&vec! $needles), $y); 14 | }; 15 | } 16 | 17 | a!([], true); 18 | a!([""], true); 19 | a!(["foo"], true); 20 | a!(["foo", "bar"], true); 21 | a!(["测试", "bar"], true); 22 | a!(["foo", "bar", "测试", "baZ"], false); 23 | } 24 | 25 | macro_rules! assert_re { 26 | ($fn_name: ident, $x: tt, $y: expr) => { 27 | assert_eq!($fn_name(&vec! $x), $y); 28 | }; 29 | } 30 | 31 | #[test] 32 | fn test_re_match_anywhere() { 33 | macro_rules! a { 34 | ($x: tt, $y: expr) => { 35 | assert_re!(re_match_anywhere, $x, $y); 36 | }; 37 | } 38 | 39 | a!(["foo"], r".*foo.*"); 40 | a!(["foo", "baz"], r".*foo.*baz.*"); 41 | a!(["测试", "baz"], r".*\x{6d4b}\x{8bd5}.*baz.*"); 42 | } 43 | 44 | #[test] 45 | fn test_re_match_consecutive() { 46 | macro_rules! a { 47 | ($x: tt, $y: expr) => { 48 | assert_re!(re_match_consecutive, $x, $y); 49 | }; 50 | } 51 | 52 | a!(["foo"], r"foo[^/]*$"); 53 | a!(["foo", "baz"], r"foo[^/]*/[^/]*baz[^/]*$"); 54 | a!(["测试", "baz"], r"\x{6d4b}\x{8bd5}[^/]*/[^/]*baz[^/]*$"); 55 | } 56 | 57 | #[test] 58 | fn test_fuzzy() { 59 | macro_rules! a { 60 | ($needle: expr, [$($x: expr, )*], [$($y: expr, )*]) => { 61 | let matcher = FuzzyMatcher::defaults($needle); 62 | let haystack: Vec<&path::Path> = vec![$(path::Path::new($x), )*]; 63 | let expected: Vec<&path::Path> = vec![$(path::Path::new($y), )*]; 64 | let actual: Vec<_> = matcher.filter_path(&haystack).collect(); 65 | assert_eq!(expected.len(), actual.len()); 66 | for (i, j) in expected.into_iter().zip(actual.into_iter()) { 67 | assert_eq!(&i, j); 68 | } 69 | }; 70 | } 71 | 72 | a!("foo", [], []); 73 | a!( 74 | "foo", 75 | [ 76 | "/fow/bar", 77 | "/bar/foo", 78 | "/bar/fooow", 79 | "/fuzzy", 80 | "/moo/foo/baz", 81 | "/foo/ooofoo", 82 | ], 83 | ["/bar/foo", "/bar/fooow", "/foo/ooofoo",] 84 | ); 85 | } 86 | 87 | #[test] 88 | fn test_matcher() { 89 | let needles = vec!["foo", "baz"]; 90 | let matcher = Matcher::new(needles, false); 91 | 92 | let haystack = vec![ 93 | path::Path::new("/foo/bar/baz"), 94 | path::Path::new("/moo/foo/baz"), 95 | path::Path::new("/baz/foo/bar"), 96 | path::Path::new("/moo/baz/foo"), 97 | path::Path::new("/foo/baz"), 98 | ]; 99 | 100 | let actual: Vec<_> = matcher.execute(&haystack).collect(); 101 | let expected = vec![ 102 | // consecutive matcher 103 | path::Path::new("/moo/foo/baz"), 104 | path::Path::new("/foo/baz"), 105 | // fuzzy matcher 106 | path::Path::new("/foo/bar/baz"), 107 | path::Path::new("/moo/foo/baz"), 108 | path::Path::new("/baz/foo/bar"), 109 | path::Path::new("/foo/baz"), 110 | // anywhere matcher 111 | path::Path::new("/foo/bar/baz"), 112 | path::Path::new("/moo/foo/baz"), 113 | path::Path::new("/foo/baz"), 114 | ]; 115 | assert_eq!(actual.len(), expected.len()); 116 | for (i, j) in expected.into_iter().zip(actual.into_iter()) { 117 | assert_eq!(&i, j); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/matcher/tests_windows.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(rustfmt, rustfmt_skip)] 2 | 3 | use std::path; 4 | 5 | use super::fuzzy::*; 6 | use super::re_based::*; 7 | use super::*; 8 | 9 | #[test] 10 | fn test_smartcase() { 11 | macro_rules! a { 12 | ($needles: tt, $y: expr) => { 13 | assert_eq!(detect_smartcase(&vec! $needles), $y); 14 | }; 15 | } 16 | 17 | a!([], true); 18 | a!([""], true); 19 | a!(["foo"], true); 20 | a!(["foo", "bar"], true); 21 | a!(["测试", "bar"], true); 22 | a!(["foo", "bar", "测试", "baZ"], false); 23 | } 24 | 25 | macro_rules! assert_re { 26 | ($fn_name: ident, $x: tt, $y: expr) => { 27 | assert_eq!($fn_name(&vec! $x), $y); 28 | }; 29 | } 30 | 31 | #[test] 32 | fn test_re_match_anywhere() { 33 | macro_rules! a { 34 | ($x: tt, $y: expr) => { 35 | assert_re!(re_match_anywhere, $x, $y); 36 | }; 37 | } 38 | 39 | a!(["foo"], r".*foo.*"); 40 | a!(["foo", "baz"], r".*foo.*baz.*"); 41 | a!(["测试", "baz"], r".*\x{6d4b}\x{8bd5}.*baz.*"); 42 | } 43 | 44 | #[test] 45 | fn test_re_match_consecutive() { 46 | macro_rules! a { 47 | ($x: tt, $y: expr) => { 48 | assert_re!(re_match_consecutive, $x, $y); 49 | }; 50 | } 51 | 52 | a!(["foo"], r"foo[^\\]*$"); 53 | a!(["foo", "baz"], r"foo[^\\]*\\[^\\]*baz[^\\]*$"); 54 | a!(["测试", "baz"], r"\x{6d4b}\x{8bd5}[^\\]*\\[^\\]*baz[^\\]*$"); 55 | } 56 | 57 | #[test] 58 | fn test_fuzzy() { 59 | macro_rules! a { 60 | ($needle: expr, [$($x: expr, )*], [$($y: expr, )*]) => { 61 | let matcher = FuzzyMatcher::defaults($needle); 62 | let haystack: Vec<&path::Path> = vec![$(path::Path::new($x), )*]; 63 | let expected: Vec<&path::Path> = vec![$(path::Path::new($y), )*]; 64 | let actual: Vec<_> = matcher.filter_path(&haystack).collect(); 65 | assert_eq!(expected.len(), actual.len()); 66 | for (i, j) in expected.into_iter().zip(actual.into_iter()) { 67 | assert_eq!(&i, j); 68 | } 69 | }; 70 | } 71 | 72 | a!("foo", [], []); 73 | a!( 74 | "foo", 75 | [ 76 | "\\fow\\bar", 77 | "\\bar\\foo", 78 | "\\bar\\fooow", 79 | "\\fuzzy", 80 | "\\moo\\foo\\baz", 81 | "\\foo\\ooofoo", 82 | ], 83 | ["\\bar\\foo", "\\bar\\fooow", "\\foo\\ooofoo",] 84 | ); 85 | } 86 | 87 | #[test] 88 | fn test_matcher() { 89 | let needles = vec!["foo", "baz"]; 90 | let matcher = Matcher::new(needles, false); 91 | 92 | let haystack = vec![ 93 | path::Path::new("\\foo\\bar\\baz"), 94 | path::Path::new("\\moo\\foo\\baz"), 95 | path::Path::new("\\baz\\foo\\bar"), 96 | path::Path::new("\\moo\\baz\\foo"), 97 | path::Path::new("\\foo\\baz"), 98 | ]; 99 | 100 | let actual: Vec<_> = matcher.execute(&haystack).collect(); 101 | let expected = vec![ 102 | // consecutive matcher 103 | path::Path::new("\\moo\\foo\\baz"), 104 | path::Path::new("\\foo\\baz"), 105 | // fuzzy matcher 106 | path::Path::new("\\foo\\bar\\baz"), 107 | path::Path::new("\\moo\\foo\\baz"), 108 | path::Path::new("\\baz\\foo\\bar"), 109 | path::Path::new("\\foo\\baz"), 110 | // anywhere matcher 111 | path::Path::new("\\foo\\bar\\baz"), 112 | path::Path::new("\\moo\\foo\\baz"), 113 | path::Path::new("\\foo\\baz"), 114 | ]; 115 | assert_eq!(actual.len(), expected.len()); 116 | for (i, j) in expected.into_iter().zip(actual.into_iter()) { 117 | assert_eq!(&i, j); 118 | } 119 | } 120 | --------------------------------------------------------------------------------