├── .envrc ├── .github ├── Designer.jpeg ├── FUNDING.yaml └── workflows │ └── build.yaml ├── .gitignore ├── .guix-channel ├── Cargo.lock ├── Cargo.toml ├── flake.lock ├── flake.nix ├── license ├── package.nix ├── readme.md └── src ├── main.rs └── utils.rs /.envrc: -------------------------------------------------------------------------------- 1 | use flake 2 | 3 | export RUST_LOG="hover=debug" 4 | export RUST_BACKTRACE="1" 5 | -------------------------------------------------------------------------------- /.github/Designer.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viperML/hover-rs/7a699b1e8a52c416e6d113a000b500752a6c3371/.github/Designer.jpeg -------------------------------------------------------------------------------- /.github/FUNDING.yaml: -------------------------------------------------------------------------------- 1 | github: [viperML] 2 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://raw.githubusercontent.com/SchemaStore/schemastore/master/src/schemas/json/github-workflow.json 2 | name: Build and release 3 | 4 | on: 5 | workflow_dispatch: 6 | push: 7 | branches: 8 | - "master" 9 | 10 | permissions: 11 | contents: write 12 | 13 | 14 | jobs: 15 | tag: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v3 20 | - name: Update release tag 21 | uses: EndBug/latest-tag@latest 22 | 23 | static: 24 | runs-on: ubuntu-latest 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@v3 28 | - name: Install Nix 29 | uses: DeterminateSystems/nix-installer-action@main 30 | - name: Install Magic Nix Cache 31 | uses: DeterminateSystems/magic-nix-cache-action@main 32 | - name: Build 33 | run: | 34 | mkdir release 35 | nix build .#pkgsCross.musl64.hover-rs -L 36 | cp -vL ./result/bin/hover release/hover-static-x86_64-linux 37 | 38 | gzip -c ./result/bin/hover > release/hover-static-x86_64-linux.gz 39 | 40 | - uses: ncipollo/release-action@v1 41 | name: Release 42 | with: 43 | tag: latest 44 | makeLatest: true 45 | omitBody: true 46 | omitName: true 47 | allowUpdates: true 48 | replacesArtifacts: true 49 | artifacts: "release/*" 50 | 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /.direnv 3 | *result* 4 | -------------------------------------------------------------------------------- /.guix-channel: -------------------------------------------------------------------------------- 1 | ;; -*- mode: scheme -*- 2 | (channel 3 | (version 0)) 4 | 5 | -------------------------------------------------------------------------------- /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 = "addr2line" 7 | version = "0.21.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "aho-corasick" 22 | version = "1.1.3" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "anstream" 31 | version = "0.6.15" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" 34 | dependencies = [ 35 | "anstyle", 36 | "anstyle-parse", 37 | "anstyle-query", 38 | "anstyle-wincon", 39 | "colorchoice", 40 | "is_terminal_polyfill", 41 | "utf8parse", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle" 46 | version = "1.0.8" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" 49 | 50 | [[package]] 51 | name = "anstyle-parse" 52 | version = "0.2.5" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" 55 | dependencies = [ 56 | "utf8parse", 57 | ] 58 | 59 | [[package]] 60 | name = "anstyle-query" 61 | version = "1.1.1" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" 64 | dependencies = [ 65 | "windows-sys", 66 | ] 67 | 68 | [[package]] 69 | name = "anstyle-wincon" 70 | version = "3.0.4" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" 73 | dependencies = [ 74 | "anstyle", 75 | "windows-sys", 76 | ] 77 | 78 | [[package]] 79 | name = "backtrace" 80 | version = "0.3.71" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" 83 | dependencies = [ 84 | "addr2line", 85 | "cc", 86 | "cfg-if", 87 | "libc", 88 | "miniz_oxide", 89 | "object", 90 | "rustc-demangle", 91 | ] 92 | 93 | [[package]] 94 | name = "bitflags" 95 | version = "2.6.0" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 98 | 99 | [[package]] 100 | name = "byteorder" 101 | version = "1.5.0" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 104 | 105 | [[package]] 106 | name = "caps" 107 | version = "0.5.5" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "190baaad529bcfbde9e1a19022c42781bdb6ff9de25721abdb8fd98c0807730b" 110 | dependencies = [ 111 | "libc", 112 | "thiserror", 113 | ] 114 | 115 | [[package]] 116 | name = "cc" 117 | version = "1.1.14" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "50d2eb3cd3d1bf4529e31c215ee6f93ec5a3d536d9f578f93d9d33ee19562932" 120 | dependencies = [ 121 | "shlex", 122 | ] 123 | 124 | [[package]] 125 | name = "cfg-if" 126 | version = "1.0.0" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 129 | 130 | [[package]] 131 | name = "clap" 132 | version = "4.4.18" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c" 135 | dependencies = [ 136 | "clap_builder", 137 | "clap_derive", 138 | ] 139 | 140 | [[package]] 141 | name = "clap_builder" 142 | version = "4.4.18" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7" 145 | dependencies = [ 146 | "anstream", 147 | "anstyle", 148 | "clap_lex", 149 | "strsim", 150 | ] 151 | 152 | [[package]] 153 | name = "clap_complete" 154 | version = "4.4.10" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "abb745187d7f4d76267b37485a65e0149edd0e91a4cfcdd3f27524ad86cee9f3" 157 | dependencies = [ 158 | "clap", 159 | ] 160 | 161 | [[package]] 162 | name = "clap_derive" 163 | version = "4.4.7" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" 166 | dependencies = [ 167 | "heck", 168 | "proc-macro2", 169 | "quote", 170 | "syn", 171 | ] 172 | 173 | [[package]] 174 | name = "clap_lex" 175 | version = "0.6.0" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" 178 | 179 | [[package]] 180 | name = "color-eyre" 181 | version = "0.6.3" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5" 184 | dependencies = [ 185 | "backtrace", 186 | "color-spantrace", 187 | "eyre", 188 | "indenter", 189 | "once_cell", 190 | "owo-colors", 191 | "tracing-error", 192 | ] 193 | 194 | [[package]] 195 | name = "color-spantrace" 196 | version = "0.2.1" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" 199 | dependencies = [ 200 | "once_cell", 201 | "owo-colors", 202 | "tracing-core", 203 | "tracing-error", 204 | ] 205 | 206 | [[package]] 207 | name = "colorchoice" 208 | version = "1.0.2" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" 211 | 212 | [[package]] 213 | name = "deranged" 214 | version = "0.3.11" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" 217 | dependencies = [ 218 | "powerfmt", 219 | ] 220 | 221 | [[package]] 222 | name = "eyre" 223 | version = "0.6.12" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" 226 | dependencies = [ 227 | "indenter", 228 | "once_cell", 229 | ] 230 | 231 | [[package]] 232 | name = "getrandom" 233 | version = "0.2.15" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 236 | dependencies = [ 237 | "cfg-if", 238 | "libc", 239 | "wasi", 240 | ] 241 | 242 | [[package]] 243 | name = "gimli" 244 | version = "0.28.1" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" 247 | 248 | [[package]] 249 | name = "heck" 250 | version = "0.4.1" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 253 | 254 | [[package]] 255 | name = "hover-rs" 256 | version = "0.1.1" 257 | dependencies = [ 258 | "caps", 259 | "clap", 260 | "clap_complete", 261 | "color-eyre", 262 | "eyre", 263 | "libc", 264 | "nix", 265 | "once_cell", 266 | "owo-colors", 267 | "rand", 268 | "time", 269 | "tracing", 270 | "tracing-subscriber", 271 | ] 272 | 273 | [[package]] 274 | name = "indenter" 275 | version = "0.3.3" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" 278 | 279 | [[package]] 280 | name = "is_terminal_polyfill" 281 | version = "1.70.1" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 284 | 285 | [[package]] 286 | name = "itoa" 287 | version = "1.0.11" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 290 | 291 | [[package]] 292 | name = "lazy_static" 293 | version = "1.5.0" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 296 | 297 | [[package]] 298 | name = "libc" 299 | version = "0.2.158" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" 302 | 303 | [[package]] 304 | name = "log" 305 | version = "0.4.22" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 308 | 309 | [[package]] 310 | name = "matchers" 311 | version = "0.1.0" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" 314 | dependencies = [ 315 | "regex-automata 0.1.10", 316 | ] 317 | 318 | [[package]] 319 | name = "memchr" 320 | version = "2.7.4" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 323 | 324 | [[package]] 325 | name = "miniz_oxide" 326 | version = "0.7.4" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" 329 | dependencies = [ 330 | "adler", 331 | ] 332 | 333 | [[package]] 334 | name = "nix" 335 | version = "0.27.1" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" 338 | dependencies = [ 339 | "bitflags", 340 | "cfg-if", 341 | "libc", 342 | ] 343 | 344 | [[package]] 345 | name = "nu-ansi-term" 346 | version = "0.46.0" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 349 | dependencies = [ 350 | "overload", 351 | "winapi", 352 | ] 353 | 354 | [[package]] 355 | name = "num-conv" 356 | version = "0.1.0" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 359 | 360 | [[package]] 361 | name = "object" 362 | version = "0.32.2" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" 365 | dependencies = [ 366 | "memchr", 367 | ] 368 | 369 | [[package]] 370 | name = "once_cell" 371 | version = "1.19.0" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 374 | 375 | [[package]] 376 | name = "overload" 377 | version = "0.1.1" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 380 | 381 | [[package]] 382 | name = "owo-colors" 383 | version = "3.5.0" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" 386 | 387 | [[package]] 388 | name = "pin-project-lite" 389 | version = "0.2.14" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" 392 | 393 | [[package]] 394 | name = "powerfmt" 395 | version = "0.2.0" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 398 | 399 | [[package]] 400 | name = "ppv-lite86" 401 | version = "0.2.20" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" 404 | dependencies = [ 405 | "zerocopy", 406 | ] 407 | 408 | [[package]] 409 | name = "proc-macro2" 410 | version = "1.0.86" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" 413 | dependencies = [ 414 | "unicode-ident", 415 | ] 416 | 417 | [[package]] 418 | name = "quote" 419 | version = "1.0.37" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 422 | dependencies = [ 423 | "proc-macro2", 424 | ] 425 | 426 | [[package]] 427 | name = "rand" 428 | version = "0.8.5" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 431 | dependencies = [ 432 | "libc", 433 | "rand_chacha", 434 | "rand_core", 435 | ] 436 | 437 | [[package]] 438 | name = "rand_chacha" 439 | version = "0.3.1" 440 | source = "registry+https://github.com/rust-lang/crates.io-index" 441 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 442 | dependencies = [ 443 | "ppv-lite86", 444 | "rand_core", 445 | ] 446 | 447 | [[package]] 448 | name = "rand_core" 449 | version = "0.6.4" 450 | source = "registry+https://github.com/rust-lang/crates.io-index" 451 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 452 | dependencies = [ 453 | "getrandom", 454 | ] 455 | 456 | [[package]] 457 | name = "regex" 458 | version = "1.10.6" 459 | source = "registry+https://github.com/rust-lang/crates.io-index" 460 | checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" 461 | dependencies = [ 462 | "aho-corasick", 463 | "memchr", 464 | "regex-automata 0.4.7", 465 | "regex-syntax 0.8.4", 466 | ] 467 | 468 | [[package]] 469 | name = "regex-automata" 470 | version = "0.1.10" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" 473 | dependencies = [ 474 | "regex-syntax 0.6.29", 475 | ] 476 | 477 | [[package]] 478 | name = "regex-automata" 479 | version = "0.4.7" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" 482 | dependencies = [ 483 | "aho-corasick", 484 | "memchr", 485 | "regex-syntax 0.8.4", 486 | ] 487 | 488 | [[package]] 489 | name = "regex-syntax" 490 | version = "0.6.29" 491 | source = "registry+https://github.com/rust-lang/crates.io-index" 492 | checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" 493 | 494 | [[package]] 495 | name = "regex-syntax" 496 | version = "0.8.4" 497 | source = "registry+https://github.com/rust-lang/crates.io-index" 498 | checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" 499 | 500 | [[package]] 501 | name = "rustc-demangle" 502 | version = "0.1.24" 503 | source = "registry+https://github.com/rust-lang/crates.io-index" 504 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 505 | 506 | [[package]] 507 | name = "serde" 508 | version = "1.0.209" 509 | source = "registry+https://github.com/rust-lang/crates.io-index" 510 | checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" 511 | dependencies = [ 512 | "serde_derive", 513 | ] 514 | 515 | [[package]] 516 | name = "serde_derive" 517 | version = "1.0.209" 518 | source = "registry+https://github.com/rust-lang/crates.io-index" 519 | checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" 520 | dependencies = [ 521 | "proc-macro2", 522 | "quote", 523 | "syn", 524 | ] 525 | 526 | [[package]] 527 | name = "sharded-slab" 528 | version = "0.1.7" 529 | source = "registry+https://github.com/rust-lang/crates.io-index" 530 | checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 531 | dependencies = [ 532 | "lazy_static", 533 | ] 534 | 535 | [[package]] 536 | name = "shlex" 537 | version = "1.3.0" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 540 | 541 | [[package]] 542 | name = "smallvec" 543 | version = "1.13.2" 544 | source = "registry+https://github.com/rust-lang/crates.io-index" 545 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 546 | 547 | [[package]] 548 | name = "strsim" 549 | version = "0.10.0" 550 | source = "registry+https://github.com/rust-lang/crates.io-index" 551 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 552 | 553 | [[package]] 554 | name = "syn" 555 | version = "2.0.76" 556 | source = "registry+https://github.com/rust-lang/crates.io-index" 557 | checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525" 558 | dependencies = [ 559 | "proc-macro2", 560 | "quote", 561 | "unicode-ident", 562 | ] 563 | 564 | [[package]] 565 | name = "thiserror" 566 | version = "1.0.63" 567 | source = "registry+https://github.com/rust-lang/crates.io-index" 568 | checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" 569 | dependencies = [ 570 | "thiserror-impl", 571 | ] 572 | 573 | [[package]] 574 | name = "thiserror-impl" 575 | version = "1.0.63" 576 | source = "registry+https://github.com/rust-lang/crates.io-index" 577 | checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" 578 | dependencies = [ 579 | "proc-macro2", 580 | "quote", 581 | "syn", 582 | ] 583 | 584 | [[package]] 585 | name = "thread_local" 586 | version = "1.1.8" 587 | source = "registry+https://github.com/rust-lang/crates.io-index" 588 | checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" 589 | dependencies = [ 590 | "cfg-if", 591 | "once_cell", 592 | ] 593 | 594 | [[package]] 595 | name = "time" 596 | version = "0.3.36" 597 | source = "registry+https://github.com/rust-lang/crates.io-index" 598 | checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" 599 | dependencies = [ 600 | "deranged", 601 | "itoa", 602 | "num-conv", 603 | "powerfmt", 604 | "serde", 605 | "time-core", 606 | "time-macros", 607 | ] 608 | 609 | [[package]] 610 | name = "time-core" 611 | version = "0.1.2" 612 | source = "registry+https://github.com/rust-lang/crates.io-index" 613 | checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" 614 | 615 | [[package]] 616 | name = "time-macros" 617 | version = "0.2.18" 618 | source = "registry+https://github.com/rust-lang/crates.io-index" 619 | checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" 620 | dependencies = [ 621 | "num-conv", 622 | "time-core", 623 | ] 624 | 625 | [[package]] 626 | name = "tracing" 627 | version = "0.1.40" 628 | source = "registry+https://github.com/rust-lang/crates.io-index" 629 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 630 | dependencies = [ 631 | "pin-project-lite", 632 | "tracing-attributes", 633 | "tracing-core", 634 | ] 635 | 636 | [[package]] 637 | name = "tracing-attributes" 638 | version = "0.1.27" 639 | source = "registry+https://github.com/rust-lang/crates.io-index" 640 | checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" 641 | dependencies = [ 642 | "proc-macro2", 643 | "quote", 644 | "syn", 645 | ] 646 | 647 | [[package]] 648 | name = "tracing-core" 649 | version = "0.1.32" 650 | source = "registry+https://github.com/rust-lang/crates.io-index" 651 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 652 | dependencies = [ 653 | "once_cell", 654 | "valuable", 655 | ] 656 | 657 | [[package]] 658 | name = "tracing-error" 659 | version = "0.2.0" 660 | source = "registry+https://github.com/rust-lang/crates.io-index" 661 | checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" 662 | dependencies = [ 663 | "tracing", 664 | "tracing-subscriber", 665 | ] 666 | 667 | [[package]] 668 | name = "tracing-log" 669 | version = "0.2.0" 670 | source = "registry+https://github.com/rust-lang/crates.io-index" 671 | checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" 672 | dependencies = [ 673 | "log", 674 | "once_cell", 675 | "tracing-core", 676 | ] 677 | 678 | [[package]] 679 | name = "tracing-subscriber" 680 | version = "0.3.18" 681 | source = "registry+https://github.com/rust-lang/crates.io-index" 682 | checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" 683 | dependencies = [ 684 | "matchers", 685 | "nu-ansi-term", 686 | "once_cell", 687 | "regex", 688 | "sharded-slab", 689 | "smallvec", 690 | "thread_local", 691 | "tracing", 692 | "tracing-core", 693 | "tracing-log", 694 | ] 695 | 696 | [[package]] 697 | name = "unicode-ident" 698 | version = "1.0.12" 699 | source = "registry+https://github.com/rust-lang/crates.io-index" 700 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 701 | 702 | [[package]] 703 | name = "utf8parse" 704 | version = "0.2.2" 705 | source = "registry+https://github.com/rust-lang/crates.io-index" 706 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 707 | 708 | [[package]] 709 | name = "valuable" 710 | version = "0.1.0" 711 | source = "registry+https://github.com/rust-lang/crates.io-index" 712 | checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" 713 | 714 | [[package]] 715 | name = "wasi" 716 | version = "0.11.0+wasi-snapshot-preview1" 717 | source = "registry+https://github.com/rust-lang/crates.io-index" 718 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 719 | 720 | [[package]] 721 | name = "winapi" 722 | version = "0.3.9" 723 | source = "registry+https://github.com/rust-lang/crates.io-index" 724 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 725 | dependencies = [ 726 | "winapi-i686-pc-windows-gnu", 727 | "winapi-x86_64-pc-windows-gnu", 728 | ] 729 | 730 | [[package]] 731 | name = "winapi-i686-pc-windows-gnu" 732 | version = "0.4.0" 733 | source = "registry+https://github.com/rust-lang/crates.io-index" 734 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 735 | 736 | [[package]] 737 | name = "winapi-x86_64-pc-windows-gnu" 738 | version = "0.4.0" 739 | source = "registry+https://github.com/rust-lang/crates.io-index" 740 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 741 | 742 | [[package]] 743 | name = "windows-sys" 744 | version = "0.52.0" 745 | source = "registry+https://github.com/rust-lang/crates.io-index" 746 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 747 | dependencies = [ 748 | "windows-targets", 749 | ] 750 | 751 | [[package]] 752 | name = "windows-targets" 753 | version = "0.52.6" 754 | source = "registry+https://github.com/rust-lang/crates.io-index" 755 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 756 | dependencies = [ 757 | "windows_aarch64_gnullvm", 758 | "windows_aarch64_msvc", 759 | "windows_i686_gnu", 760 | "windows_i686_gnullvm", 761 | "windows_i686_msvc", 762 | "windows_x86_64_gnu", 763 | "windows_x86_64_gnullvm", 764 | "windows_x86_64_msvc", 765 | ] 766 | 767 | [[package]] 768 | name = "windows_aarch64_gnullvm" 769 | version = "0.52.6" 770 | source = "registry+https://github.com/rust-lang/crates.io-index" 771 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 772 | 773 | [[package]] 774 | name = "windows_aarch64_msvc" 775 | version = "0.52.6" 776 | source = "registry+https://github.com/rust-lang/crates.io-index" 777 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 778 | 779 | [[package]] 780 | name = "windows_i686_gnu" 781 | version = "0.52.6" 782 | source = "registry+https://github.com/rust-lang/crates.io-index" 783 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 784 | 785 | [[package]] 786 | name = "windows_i686_gnullvm" 787 | version = "0.52.6" 788 | source = "registry+https://github.com/rust-lang/crates.io-index" 789 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 790 | 791 | [[package]] 792 | name = "windows_i686_msvc" 793 | version = "0.52.6" 794 | source = "registry+https://github.com/rust-lang/crates.io-index" 795 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 796 | 797 | [[package]] 798 | name = "windows_x86_64_gnu" 799 | version = "0.52.6" 800 | source = "registry+https://github.com/rust-lang/crates.io-index" 801 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 802 | 803 | [[package]] 804 | name = "windows_x86_64_gnullvm" 805 | version = "0.52.6" 806 | source = "registry+https://github.com/rust-lang/crates.io-index" 807 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 808 | 809 | [[package]] 810 | name = "windows_x86_64_msvc" 811 | version = "0.52.6" 812 | source = "registry+https://github.com/rust-lang/crates.io-index" 813 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 814 | 815 | [[package]] 816 | name = "zerocopy" 817 | version = "0.7.35" 818 | source = "registry+https://github.com/rust-lang/crates.io-index" 819 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 820 | dependencies = [ 821 | "byteorder", 822 | "zerocopy-derive", 823 | ] 824 | 825 | [[package]] 826 | name = "zerocopy-derive" 827 | version = "0.7.35" 828 | source = "registry+https://github.com/rust-lang/crates.io-index" 829 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 830 | dependencies = [ 831 | "proc-macro2", 832 | "quote", 833 | "syn", 834 | ] 835 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hover-rs" 3 | version = "0.1.1" 4 | edition = "2021" 5 | license = "EUPL-1.2" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [[bin]] 10 | name = "hover" 11 | path = "src/main.rs" 12 | 13 | [profile.release] 14 | lto = true 15 | strip = "symbols" 16 | opt-level = "z" 17 | 18 | [dependencies] 19 | caps = "0.5.5" 20 | clap = { version = "~4.4.7", features = ["derive"] } 21 | clap_complete = "~4.4.7" 22 | color-eyre = "0.6.2" 23 | eyre = "0.6.8" 24 | libc = "0.2.148" 25 | nix = { version = "0.27.0", features = ["fs", "feature", "sched", "user", "mount", "mman"] } 26 | once_cell = "1.19.0" 27 | owo-colors = "3.0.0" 28 | rand = "0.8.5" 29 | time = { version = "0.3.28", features = ["formatting"] } 30 | tracing = "0.1.38" 31 | tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } 32 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "nixpkgs": { 4 | "locked": { 5 | "lastModified": 1724224976, 6 | "narHash": "sha256-Z/ELQhrSd7bMzTO8r7NZgi9g5emh+aRKoCdaAv5fiO0=", 7 | "owner": "NixOS", 8 | "repo": "nixpkgs", 9 | "rev": "c374d94f1536013ca8e92341b540eba4c22f9c62", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "NixOS", 14 | "ref": "nixos-unstable", 15 | "repo": "nixpkgs", 16 | "type": "github" 17 | } 18 | }, 19 | "root": { 20 | "inputs": { 21 | "nixpkgs": "nixpkgs" 22 | } 23 | } 24 | }, 25 | "root": "root", 26 | "version": 7 27 | } 28 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 3 | outputs = { 4 | self, 5 | nixpkgs, 6 | }: { 7 | packages.x86_64-linux.default = nixpkgs.legacyPackages.x86_64-linux.callPackage ./package.nix {}; 8 | 9 | legacyPackages.x86_64-linux = import nixpkgs { 10 | system = "x86_64-linux"; 11 | overlays = [(final: prev: {hover-rs = final.callPackage ./package.nix {};})]; 12 | }; 13 | 14 | devShells.x86_64-linux.default = with nixpkgs.legacyPackages.x86_64-linux; 15 | mkShell { 16 | packages = [ 17 | cargo 18 | rustc 19 | rust-analyzer 20 | rustfmt 21 | man-pages 22 | man-pages-posix 23 | clippy 24 | bubblewrap 25 | strace 26 | ]; 27 | env.RUST_SRC_PATH = "${rustPlatform.rustLibSrc}"; 28 | }; 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | EUROPEAN UNION PUBLIC LICENCE v. 1.2 2 | EUPL © the European Union 2007, 2016 3 | 4 | This European Union Public Licence (the ‘EUPL’) applies to the Work (as defined 5 | below) which is provided under the terms of this Licence. Any use of the Work, 6 | other than as authorised under this Licence is prohibited (to the extent such 7 | use is covered by a right of the copyright holder of the Work). 8 | 9 | The Work is provided under the terms of this Licence when the Licensor (as 10 | defined below) has placed the following notice immediately following the 11 | copyright notice for the Work: 12 | 13 | Licensed under the EUPL 14 | 15 | or has expressed by any other means his willingness to license under the EUPL. 16 | 17 | 1. Definitions 18 | 19 | In this Licence, the following terms have the following meaning: 20 | 21 | - ‘The Licence’: this Licence. 22 | 23 | - ‘The Original Work’: the work or software distributed or communicated by the 24 | Licensor under this Licence, available as Source Code and also as Executable 25 | Code as the case may be. 26 | 27 | - ‘Derivative Works’: the works or software that could be created by the 28 | Licensee, based upon the Original Work or modifications thereof. This Licence 29 | does not define the extent of modification or dependence on the Original Work 30 | required in order to classify a work as a Derivative Work; this extent is 31 | determined by copyright law applicable in the country mentioned in Article 15. 32 | 33 | - ‘The Work’: the Original Work or its Derivative Works. 34 | 35 | - ‘The Source Code’: the human-readable form of the Work which is the most 36 | convenient for people to study and modify. 37 | 38 | - ‘The Executable Code’: any code which has generally been compiled and which is 39 | meant to be interpreted by a computer as a program. 40 | 41 | - ‘The Licensor’: the natural or legal person that distributes or communicates 42 | the Work under the Licence. 43 | 44 | - ‘Contributor(s)’: any natural or legal person who modifies the Work under the 45 | Licence, or otherwise contributes to the creation of a Derivative Work. 46 | 47 | - ‘The Licensee’ or ‘You’: any natural or legal person who makes any usage of 48 | the Work under the terms of the Licence. 49 | 50 | - ‘Distribution’ or ‘Communication’: any act of selling, giving, lending, 51 | renting, distributing, communicating, transmitting, or otherwise making 52 | available, online or offline, copies of the Work or providing access to its 53 | essential functionalities at the disposal of any other natural or legal 54 | person. 55 | 56 | 2. Scope of the rights granted by the Licence 57 | 58 | The Licensor hereby grants You a worldwide, royalty-free, non-exclusive, 59 | sublicensable licence to do the following, for the duration of copyright vested 60 | in the Original Work: 61 | 62 | - use the Work in any circumstance and for all usage, 63 | - reproduce the Work, 64 | - modify the Work, and make Derivative Works based upon the Work, 65 | - communicate to the public, including the right to make available or display 66 | the Work or copies thereof to the public and perform publicly, as the case may 67 | be, the Work, 68 | - distribute the Work or copies thereof, 69 | - lend and rent the Work or copies thereof, 70 | - sublicense rights in the Work or copies thereof. 71 | 72 | Those rights can be exercised on any media, supports and formats, whether now 73 | known or later invented, as far as the applicable law permits so. 74 | 75 | In the countries where moral rights apply, the Licensor waives his right to 76 | exercise his moral right to the extent allowed by law in order to make effective 77 | the licence of the economic rights here above listed. 78 | 79 | The Licensor grants to the Licensee royalty-free, non-exclusive usage rights to 80 | any patents held by the Licensor, to the extent necessary to make use of the 81 | rights granted on the Work under this Licence. 82 | 83 | 3. Communication of the Source Code 84 | 85 | The Licensor may provide the Work either in its Source Code form, or as 86 | Executable Code. If the Work is provided as Executable Code, the Licensor 87 | provides in addition a machine-readable copy of the Source Code of the Work 88 | along with each copy of the Work that the Licensor distributes or indicates, in 89 | a notice following the copyright notice attached to the Work, a repository where 90 | the Source Code is easily and freely accessible for as long as the Licensor 91 | continues to distribute or communicate the Work. 92 | 93 | 4. Limitations on copyright 94 | 95 | Nothing in this Licence is intended to deprive the Licensee of the benefits from 96 | any exception or limitation to the exclusive rights of the rights owners in the 97 | Work, of the exhaustion of those rights or of other applicable limitations 98 | thereto. 99 | 100 | 5. Obligations of the Licensee 101 | 102 | The grant of the rights mentioned above is subject to some restrictions and 103 | obligations imposed on the Licensee. Those obligations are the following: 104 | 105 | Attribution right: The Licensee shall keep intact all copyright, patent or 106 | trademarks notices and all notices that refer to the Licence and to the 107 | disclaimer of warranties. The Licensee must include a copy of such notices and a 108 | copy of the Licence with every copy of the Work he/she distributes or 109 | communicates. The Licensee must cause any Derivative Work to carry prominent 110 | notices stating that the Work has been modified and the date of modification. 111 | 112 | Copyleft clause: If the Licensee distributes or communicates copies of the 113 | Original Works or Derivative Works, this Distribution or Communication will be 114 | done under the terms of this Licence or of a later version of this Licence 115 | unless the Original Work is expressly distributed only under this version of the 116 | Licence — for example by communicating ‘EUPL v. 1.2 only’. The Licensee 117 | (becoming Licensor) cannot offer or impose any additional terms or conditions on 118 | the Work or Derivative Work that alter or restrict the terms of the Licence. 119 | 120 | Compatibility clause: If the Licensee Distributes or Communicates Derivative 121 | Works or copies thereof based upon both the Work and another work licensed under 122 | a Compatible Licence, this Distribution or Communication can be done under the 123 | terms of this Compatible Licence. For the sake of this clause, ‘Compatible 124 | Licence’ refers to the licences listed in the appendix attached to this Licence. 125 | Should the Licensee's obligations under the Compatible Licence conflict with 126 | his/her obligations under this Licence, the obligations of the Compatible 127 | Licence shall prevail. 128 | 129 | Provision of Source Code: When distributing or communicating copies of the Work, 130 | the Licensee will provide a machine-readable copy of the Source Code or indicate 131 | a repository where this Source will be easily and freely available for as long 132 | as the Licensee continues to distribute or communicate the Work. 133 | 134 | Legal Protection: This Licence does not grant permission to use the trade names, 135 | trademarks, service marks, or names of the Licensor, except as required for 136 | reasonable and customary use in describing the origin of the Work and 137 | reproducing the content of the copyright notice. 138 | 139 | 6. Chain of Authorship 140 | 141 | The original Licensor warrants that the copyright in the Original Work granted 142 | hereunder is owned by him/her or licensed to him/her and that he/she has the 143 | power and authority to grant the Licence. 144 | 145 | Each Contributor warrants that the copyright in the modifications he/she brings 146 | to the Work are owned by him/her or licensed to him/her and that he/she has the 147 | power and authority to grant the Licence. 148 | 149 | Each time You accept the Licence, the original Licensor and subsequent 150 | Contributors grant You a licence to their contributions to the Work, under the 151 | terms of this Licence. 152 | 153 | 7. Disclaimer of Warranty 154 | 155 | The Work is a work in progress, which is continuously improved by numerous 156 | Contributors. It is not a finished work and may therefore contain defects or 157 | ‘bugs’ inherent to this type of development. 158 | 159 | For the above reason, the Work is provided under the Licence on an ‘as is’ basis 160 | and without warranties of any kind concerning the Work, including without 161 | limitation merchantability, fitness for a particular purpose, absence of defects 162 | or errors, accuracy, non-infringement of intellectual property rights other than 163 | copyright as stated in Article 6 of this Licence. 164 | 165 | This disclaimer of warranty is an essential part of the Licence and a condition 166 | for the grant of any rights to the Work. 167 | 168 | 8. Disclaimer of Liability 169 | 170 | Except in the cases of wilful misconduct or damages directly caused to natural 171 | persons, the Licensor will in no event be liable for any direct or indirect, 172 | material or moral, damages of any kind, arising out of the Licence or of the use 173 | of the Work, including without limitation, damages for loss of goodwill, work 174 | stoppage, computer failure or malfunction, loss of data or any commercial 175 | damage, even if the Licensor has been advised of the possibility of such damage. 176 | However, the Licensor will be liable under statutory product liability laws as 177 | far such laws apply to the Work. 178 | 179 | 9. Additional agreements 180 | 181 | While distributing the Work, You may choose to conclude an additional agreement, 182 | defining obligations or services consistent with this Licence. However, if 183 | accepting obligations, You may act only on your own behalf and on your sole 184 | responsibility, not on behalf of the original Licensor or any other Contributor, 185 | and only if You agree to indemnify, defend, and hold each Contributor harmless 186 | for any liability incurred by, or claims asserted against such Contributor by 187 | the fact You have accepted any warranty or additional liability. 188 | 189 | 10. Acceptance of the Licence 190 | 191 | The provisions of this Licence can be accepted by clicking on an icon ‘I agree’ 192 | placed under the bottom of a window displaying the text of this Licence or by 193 | affirming consent in any other similar way, in accordance with the rules of 194 | applicable law. Clicking on that icon indicates your clear and irrevocable 195 | acceptance of this Licence and all of its terms and conditions. 196 | 197 | Similarly, you irrevocably accept this Licence and all of its terms and 198 | conditions by exercising any rights granted to You by Article 2 of this Licence, 199 | such as the use of the Work, the creation by You of a Derivative Work or the 200 | Distribution or Communication by You of the Work or copies thereof. 201 | 202 | 11. Information to the public 203 | 204 | In case of any Distribution or Communication of the Work by means of electronic 205 | communication by You (for example, by offering to download the Work from a 206 | remote location) the distribution channel or media (for example, a website) must 207 | at least provide to the public the information requested by the applicable law 208 | regarding the Licensor, the Licence and the way it may be accessible, concluded, 209 | stored and reproduced by the Licensee. 210 | 211 | 12. Termination of the Licence 212 | 213 | The Licence and the rights granted hereunder will terminate automatically upon 214 | any breach by the Licensee of the terms of the Licence. 215 | 216 | Such a termination will not terminate the licences of any person who has 217 | received the Work from the Licensee under the Licence, provided such persons 218 | remain in full compliance with the Licence. 219 | 220 | 13. Miscellaneous 221 | 222 | Without prejudice of Article 9 above, the Licence represents the complete 223 | agreement between the Parties as to the Work. 224 | 225 | If any provision of the Licence is invalid or unenforceable under applicable 226 | law, this will not affect the validity or enforceability of the Licence as a 227 | whole. Such provision will be construed or reformed so as necessary to make it 228 | valid and enforceable. 229 | 230 | The European Commission may publish other linguistic versions or new versions of 231 | this Licence or updated versions of the Appendix, so far this is required and 232 | reasonable, without reducing the scope of the rights granted by the Licence. New 233 | versions of the Licence will be published with a unique version number. 234 | 235 | All linguistic versions of this Licence, approved by the European Commission, 236 | have identical value. Parties can take advantage of the linguistic version of 237 | their choice. 238 | 239 | 14. Jurisdiction 240 | 241 | Without prejudice to specific agreement between parties, 242 | 243 | - any litigation resulting from the interpretation of this License, arising 244 | between the European Union institutions, bodies, offices or agencies, as a 245 | Licensor, and any Licensee, will be subject to the jurisdiction of the Court 246 | of Justice of the European Union, as laid down in article 272 of the Treaty on 247 | the Functioning of the European Union, 248 | 249 | - any litigation arising between other parties and resulting from the 250 | interpretation of this License, will be subject to the exclusive jurisdiction 251 | of the competent court where the Licensor resides or conducts its primary 252 | business. 253 | 254 | 15. Applicable Law 255 | 256 | Without prejudice to specific agreement between parties, 257 | 258 | - this Licence shall be governed by the law of the European Union Member State 259 | where the Licensor has his seat, resides or has his registered office, 260 | 261 | - this licence shall be governed by Belgian law if the Licensor has no seat, 262 | residence or registered office inside a European Union Member State. 263 | 264 | Appendix 265 | 266 | ‘Compatible Licences’ according to Article 5 EUPL are: 267 | 268 | - GNU General Public License (GPL) v. 2, v. 3 269 | - GNU Affero General Public License (AGPL) v. 3 270 | - Open Software License (OSL) v. 2.1, v. 3.0 271 | - Eclipse Public License (EPL) v. 1.0 272 | - CeCILL v. 2.0, v. 2.1 273 | - Mozilla Public Licence (MPL) v. 2 274 | - GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3 275 | - Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) for 276 | works other than software 277 | - European Union Public Licence (EUPL) v. 1.1, v. 1.2 278 | - Québec Free and Open-Source Licence — Reciprocity (LiLiQ-R) or Strong 279 | Reciprocity (LiLiQ-R+). 280 | 281 | The European Commission may update this Appendix to later versions of the above 282 | licences without producing a new version of the EUPL, as long as they provide 283 | the rights granted in Article 2 of this Licence and protect the covered Source 284 | Code from exclusive appropriation. 285 | 286 | All other changes or additions to this Appendix require the production of a new 287 | EUPL version. 288 | -------------------------------------------------------------------------------- /package.nix: -------------------------------------------------------------------------------- 1 | { 2 | rustPlatform, 3 | lib, 4 | targetPlatform, 5 | installShellFiles, 6 | }: 7 | rustPlatform.buildRustPackage { 8 | name = "hover-rs"; 9 | 10 | src = lib.fileset.toSource { 11 | root = ./.; 12 | fileset = lib.fileset.intersection (lib.fileset.fromSource (lib.sources.cleanSource ./.)) ( 13 | lib.fileset.unions [ 14 | ./src 15 | ./Cargo.toml 16 | ./Cargo.lock 17 | ] 18 | ); 19 | }; 20 | 21 | strictDeps = true; 22 | cargoLock.lockFile = ./Cargo.lock; 23 | 24 | env.RUSTFLAGS = lib.optionalString (targetPlatform.libc == "musl") "-C target-feature=+crt-static"; 25 | 26 | nativeBuildInputs = [ 27 | installShellFiles 28 | ]; 29 | 30 | preFixup = '' 31 | mkdir completions 32 | 33 | for shell in bash zsh fish; do 34 | $out/bin/hover --completions $shell > completions/hover.$shell 35 | done 36 | 37 | installShellCompletion completions/* 38 | ''; 39 | 40 | doCheck = false; 41 | 42 | meta = { 43 | mainProgram = "hover"; 44 | }; 45 | } 46 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | 2 |

3 |

4 | hover-rs - Protective home overlay 5 |

6 |

7 | 8 |

9 |

10 | Tired of programs messing your precious $HOME ? hover-rs is for you! 11 |

12 |

13 | 14 |

15 | 16 |

17 | 18 | 19 | --- 20 | 21 | hover-rs uses Linux's user namespaces to mount a volatile overlayfs over your 22 | $HOME. Any write or delete operation is redirected to the upper layer, while 23 | your $HOME is left intact. Read more in my blogpost: [https://ayats.org/blog/hover](https://ayats.org/blog/hover). 24 | 25 | ## Usage 26 | 27 | ``` 28 | $ hover # Enter hover by just running the command 29 | You are now hovering~ 30 | A layer is covering your /home/ayats 31 | You can find your top layer in: /home/ayats/.cache/hover-rs/layer-2024-06-11-0635-evCxznv 32 | 33 | $ touch foo # File "foo" is created under the hover 34 | 35 | $ exit # Hover is just a subprocess 36 | 37 | Leaving hover 38 | You can find your top layer in: /home/ayats/.cache/hover-rs/layer-2024-06-11-0635-evCxznv 39 | 40 | $ file foo # File "foo" is gone 41 | foo: cannot open `foo' (No such file or directory) 42 | ``` 43 | 44 | ## Requirements 45 | 46 | Your kernel must have user namespaces and overlayfs enabled: 47 | 48 | ``` 49 | $ zcat /proc/config.gz | grep -e NAMESPACES= -e USER_NS= -e OVERLAY_FS= 50 | CONFIG_NAMESPACES=y 51 | CONFIG_USER_NS=y 52 | CONFIG_OVERLAY_FS=m 53 | ``` 54 | 55 | ## Static binaries 56 | 57 | CI builds statically-linked binaries (with musl). You can use download and use 58 | them directly: 59 | 60 | ``` 61 | $ curl -OL https://github.com/viperML/hover-rs/releases/download/latest/hover-static-x86_64-linux 62 | $ chmod +x hover-static-x86_64-linux 63 | $ ./hover-static-x86_64-linux 64 | ``` 65 | 66 | ## Building 67 | 68 | With nix: 69 | 70 | ``` 71 | nix build github:viperML/hover-rs 72 | ``` 73 | 74 | With guix: 75 | 76 | ``` 77 | guix build -L . 78 | ``` 79 | 80 | If you bring your own cargo+rustc, just `cargo build`. 81 | 82 | ## Attribution 83 | 84 | Inspired by 85 | [https://github.com/max-privatevoid/hover](https://github.com/max-privatevoid/hover), 86 | which uses fuse-overlayfs. 87 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod utils; 2 | 3 | use crate::utils::{callback_wrapper, NNONE}; 4 | 5 | use caps::{CapSet, Capability}; 6 | use clap::ValueHint::CommandWithArguments; 7 | use eyre::{bail, ensure, Context}; 8 | use nix::errno::Errno; 9 | use nix::libc::SIGCHLD; 10 | use nix::mount::{mount, MsFlags}; 11 | use nix::sched::{clone, unshare, CloneFlags}; 12 | use nix::sys::prctl::set_pdeathsig; 13 | use nix::sys::signal::Signal; 14 | use nix::sys::wait::{waitpid, WaitStatus}; 15 | use nix::unistd::{close, isatty, Gid, Pid, Uid}; 16 | use owo_colors::OwoColorize; 17 | use std::ffi::OsString; 18 | use std::fs::OpenOptions; 19 | use std::io::{stderr, Write}; 20 | use std::os::unix::ffi::OsStringExt; 21 | use std::process::Command; 22 | use std::time::SystemTime; 23 | use std::{env, fs}; 24 | use std::{os::unix::process::CommandExt, path::PathBuf}; 25 | use tracing::{debug, error}; 26 | 27 | #[derive(Debug, clap::Parser)] 28 | /// hover-rs: protective home overlay 29 | struct Args { 30 | /// Generate completions 31 | #[clap(long)] 32 | completions: Option, 33 | 34 | /// Command and arguments to execute 35 | #[arg(trailing_var_arg = true, num_args=1.., value_hint=CommandWithArguments)] 36 | command: Vec, 37 | 38 | /// Don't show info messages 39 | #[arg(short, long)] 40 | quiet: bool, 41 | } 42 | 43 | #[derive(Debug, Clone)] 44 | struct Config { 45 | target: PathBuf, 46 | runtime: PathBuf, 47 | cache: PathBuf, 48 | allocation: String, 49 | layer: PathBuf, 50 | _work: PathBuf, // Implementation of overlayfs 51 | uid: Uid, 52 | gid: Gid, 53 | } 54 | 55 | fn main() -> eyre::Result<()> { 56 | { 57 | color_eyre::install()?; 58 | use tracing_subscriber::{fmt, prelude::*, EnvFilter}; 59 | tracing_subscriber::registry() 60 | .with( 61 | fmt::layer() 62 | .without_time() 63 | .with_line_number(true) 64 | .with_writer(stderr), 65 | ) 66 | .with(EnvFilter::from_default_env()) 67 | .init(); 68 | 69 | if env::var("HOVER").is_ok() { 70 | bail!("hover can't be stacked!"); 71 | }; 72 | env::set_var("HOVER", "1"); 73 | } 74 | 75 | let args = ::parse(); 76 | debug!(?args); 77 | 78 | if let Some(shell) = args.completions { 79 | let mut cmd = ::command(); 80 | clap_complete::generate(shell, &mut cmd, "hover", &mut std::io::stdout()); 81 | return Ok(()); 82 | } 83 | 84 | let config = Config::build()?; 85 | 86 | ensure!( 87 | !config.uid.is_root(), 88 | "hover-rs is not made to be run as root!" 89 | ); 90 | 91 | let (argv0, argv): (OsString, Vec) = if args.command.is_empty() { 92 | if !isatty(libc::STDIN_FILENO)? { 93 | bail!("Not running as a tty, and no program provided, aborting"); 94 | } 95 | 96 | let parent = Pid::parent(); 97 | let parent_exe = fs::read_link(format!("/proc/{}/exe", parent))?; 98 | let mut cmdline = fs::read(format!("/proc/{}/cmdline", parent))?; 99 | cmdline.pop(); // removes trailing \0 100 | let parent_argv = cmdline 101 | .into_iter() 102 | .fold(Vec::new(), |mut acc, x| { 103 | if x == 0 { 104 | acc.push(Vec::new()); 105 | } else { 106 | if acc.is_empty() { 107 | acc.push(Vec::new()); 108 | } 109 | acc.last_mut().unwrap().push(x); 110 | } 111 | acc 112 | }) 113 | .into_iter() 114 | .map(OsString::from_vec); 115 | 116 | let mut parent_argv = parent_argv.collect::>(); 117 | parent_argv.remove(0); // remove argv0, we use exe 118 | debug!(?parent_exe, ?parent_argv); 119 | 120 | (parent_exe.as_os_str().to_owned(), parent_argv) 121 | } else { 122 | let mut _args = args.command.into_iter(); 123 | (_args.next().unwrap(), _args.collect()) 124 | }; 125 | let mut cmd = Command::new(argv0); 126 | cmd.args(argv); 127 | 128 | let pipe = unsafe { 129 | let mut fds = [-1; 2]; 130 | let ret = libc::pipe(fds.as_mut_ptr()); 131 | if ret == -1 { 132 | return Err(Errno::last()).wrap_err("Failed to create pipe"); 133 | } 134 | (fds[0], fds[1]) 135 | }; 136 | debug!(?pipe); 137 | 138 | let mut stack = [0; 4000]; 139 | let child = unsafe { 140 | let config = config.clone(); 141 | clone( 142 | Box::new(move || { 143 | callback_wrapper(|| -> eyre::Result<()> { 144 | // Close writing pipe 145 | close(pipe.1)?; 146 | 147 | debug!("Waiting for parent..."); 148 | let mut dummy = [0]; 149 | libc::read(pipe.0, dummy.as_mut_ptr() as _, 1); 150 | debug!("Parent done"); 151 | 152 | set_pdeathsig(Some(Signal::SIGTERM))?; 153 | setup(&config)?; 154 | let error = cmd.exec(); 155 | Err(error).wrap_err("Failed to execute the command") 156 | }) 157 | }), 158 | &mut stack, 159 | CloneFlags::CLONE_NEWUSER | CloneFlags::CLONE_NEWNS, 160 | Some(SIGCHLD), 161 | ) 162 | }?; 163 | 164 | // Close reading pipe 165 | close(pipe.0)?; 166 | 167 | let privileged = { 168 | let mycaps = caps::read(None, CapSet::Effective)?; 169 | mycaps.contains(&Capability::CAP_SETUID) && mycaps.contains(&Capability::CAP_SETGID) 170 | }; 171 | 172 | debug!(?privileged); 173 | 174 | { 175 | let mut f = OpenOptions::new() 176 | .read(true) 177 | .write(true) 178 | .open(format!("/proc/{child}/uid_map"))?; 179 | let msg = format!("0 {} 1", config.uid); 180 | f.write(msg.as_bytes()) 181 | .wrap_err("Setting uid_map for child process")?; 182 | } 183 | { 184 | let mut f = OpenOptions::new() 185 | .read(true) 186 | .write(true) 187 | .open(format!("/proc/{child}/setgroups"))?; 188 | f.write("deny".as_bytes()) 189 | .wrap_err("Setting setgroups for child process")?; 190 | } 191 | { 192 | let mut f = OpenOptions::new() 193 | .read(true) 194 | .write(true) 195 | .open(format!("/proc/{child}/gid_map"))?; 196 | let msg = format!("0 {} 1", config.gid); 197 | f.write(msg.as_bytes()) 198 | .wrap_err("Setting gid_map for child process")?; 199 | } 200 | 201 | if !args.quiet { 202 | println!("You are now {}", "hovering~".bold()); 203 | println!( 204 | " A layer is covering your {}", 205 | config.target.to_string_lossy().bold().red() 206 | ); 207 | println!( 208 | " You can find your top layer in: {}", 209 | config.layer.to_string_lossy().bold().red() 210 | ); 211 | } 212 | 213 | // Close writing pipe. Setup is done 214 | close(pipe.1)?; 215 | 216 | let r#return = waitpid(child, None)?; 217 | if let WaitStatus::Exited(_, 0) = r#return { 218 | debug!(?r#return); 219 | } else { 220 | error!(?r#return); 221 | } 222 | 223 | if !args.quiet { 224 | println!("Leaving {}", "hover".bold()); 225 | println!( 226 | " You can find your top layer in: {}", 227 | config.layer.to_string_lossy().bold().red() 228 | ); 229 | } 230 | 231 | Ok(()) 232 | } 233 | 234 | fn setup(config: &Config) -> eyre::Result<()> { 235 | mount( 236 | Some("tmpfs"), 237 | &config.runtime, 238 | Some("tmpfs"), 239 | MsFlags::empty(), 240 | NNONE, 241 | )?; 242 | 243 | let ro_mount = config.runtime.join("oldroot"); 244 | fs::create_dir_all(&ro_mount)?; 245 | 246 | // Mount root dir as RO 247 | mount( 248 | Some(&config.target), 249 | &ro_mount, 250 | NNONE, 251 | MsFlags::MS_BIND, 252 | NNONE, 253 | )?; 254 | mount( 255 | NNONE, 256 | &ro_mount, 257 | NNONE, 258 | MsFlags::MS_BIND | MsFlags::MS_REMOUNT | MsFlags::MS_RDONLY, 259 | NNONE, 260 | )?; 261 | 262 | { 263 | // Don't use format! because the paths might not be valid str, keep OsStr's 264 | let mut opts = OsString::from("lowerdir="); 265 | opts.push(ro_mount.as_os_str()); 266 | opts.push(",upperdir="); 267 | opts.push(&config.layer); 268 | opts.push(",workdir="); 269 | opts.push(&config._work); 270 | 271 | mount( 272 | Some("overlay"), 273 | &config.target, 274 | Some("overlay"), 275 | MsFlags::empty(), 276 | Some(opts.as_os_str()), 277 | )?; 278 | } 279 | 280 | // Workdir is under the overlay 281 | env::set_current_dir(env::current_dir()?)?; 282 | 283 | // Seal the working dirs from the user 284 | mount( 285 | Some("/var/empty"), 286 | &config.runtime, 287 | NNONE, 288 | MsFlags::MS_BIND, 289 | NNONE, 290 | )?; 291 | 292 | mount( 293 | Some("/var/empty"), 294 | &config.cache, 295 | NNONE, 296 | MsFlags::MS_BIND, 297 | NNONE, 298 | )?; 299 | 300 | // Map back to original user 301 | unshare(CloneFlags::CLONE_NEWUSER)?; 302 | 303 | { 304 | let mut f = OpenOptions::new() 305 | .read(true) 306 | .write(true) 307 | .open("/proc/self/uid_map")?; 308 | let msg = format!("{} 0 1", config.uid); 309 | f.write(msg.as_bytes())?; 310 | } 311 | { 312 | let mut f = OpenOptions::new() 313 | .read(true) 314 | .write(true) 315 | .open("/proc/self/gid_map")?; 316 | let msg = format!("{} 0 1", config.gid); 317 | f.write(msg.as_bytes())?; 318 | } 319 | 320 | Ok(()) 321 | } 322 | 323 | impl Config { 324 | fn build() -> eyre::Result { 325 | let cache = env::var("XDG_CACHE_HOME") 326 | .map(PathBuf::from) 327 | .unwrap_or_else(|_| env::var("HOME").map(PathBuf::from).unwrap().join(".cache")) 328 | .join("hover-rs"); 329 | std::fs::create_dir_all(&cache)?; 330 | 331 | let allocation = { 332 | let format = time::format_description::parse("[year]-[month]-[day]-[hour][minute]")?; 333 | let now: time::OffsetDateTime = SystemTime::now().into(); 334 | let mytime = now.format(&format)?; 335 | use rand::Rng; 336 | let seed: String = rand::thread_rng() 337 | .sample_iter(&rand::distributions::Alphanumeric) 338 | .take(7) 339 | .map(char::from) 340 | .collect(); 341 | format!("{mytime}-{seed}") 342 | }; 343 | 344 | let runtime = env::var("XDG_RUNTIME_DIR") 345 | .map(|s| PathBuf::from(s).join("hover-rs")) 346 | .unwrap_or_else(|_| PathBuf::from("/tmp").join(format!("hover-rs-{allocation}"))); 347 | std::fs::create_dir_all(&runtime)?; 348 | 349 | let layer = cache.join(format!("layer-{allocation}")); 350 | fs::create_dir_all(&layer)?; 351 | let _work = cache.join(format!(".work-{allocation}")); 352 | fs::create_dir_all(&_work)?; 353 | 354 | Ok(Self { 355 | target: PathBuf::from(env::var("HOME")?), 356 | runtime, 357 | cache, 358 | allocation, 359 | layer, 360 | _work, 361 | uid: Uid::current(), 362 | gid: Gid::current(), 363 | }) 364 | } 365 | } 366 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use tracing::span; 2 | use tracing::Level; 3 | 4 | /// None but for nix's types 5 | pub const NNONE: Option<&str> = None; 6 | 7 | pub fn callback_wrapper(inner: F) -> isize 8 | where 9 | F: FnOnce() -> Result, 10 | T: std::process::Termination, 11 | E: std::fmt::Debug, 12 | { 13 | use std::process::Termination; 14 | 15 | let span = span!(Level::DEBUG, "child"); 16 | let _entered = span.enter(); 17 | 18 | let res = inner(); 19 | match res { 20 | Ok(_) => { 21 | res.report(); 22 | 0 23 | } 24 | Err(_) => { 25 | res.report(); 26 | 1 27 | } 28 | } 29 | } 30 | --------------------------------------------------------------------------------