├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE.md ├── README.md ├── src ├── app │ ├── app.rs │ └── mod.rs ├── configuration │ ├── configuration.rs │ └── mod.rs ├── main.rs └── ui │ ├── display │ ├── block.rs │ ├── bookmarks.rs │ ├── contents.rs │ ├── details.rs │ ├── files_dirs.rs │ ├── help.rs │ ├── inputs.rs │ ├── mod.rs │ ├── navs.rs │ ├── ops.rs │ ├── pane.rs │ └── render.rs │ ├── input │ ├── bookmark.rs │ ├── extract.rs │ ├── file_ops.rs │ ├── help.rs │ ├── mod.rs │ ├── movement.rs │ ├── nav.rs │ ├── run_app.rs │ ├── stateful_list.rs │ └── submit.rs │ └── mod.rs └── traverse.gif /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /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 = "adler" 7 | version = "1.0.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 10 | 11 | [[package]] 12 | name = "aes" 13 | version = "0.8.3" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" 16 | dependencies = [ 17 | "cfg-if", 18 | "cipher", 19 | "cpufeatures", 20 | ] 21 | 22 | [[package]] 23 | name = "android-tzdata" 24 | version = "0.1.1" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 27 | 28 | [[package]] 29 | name = "android_system_properties" 30 | version = "0.1.5" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 33 | dependencies = [ 34 | "libc", 35 | ] 36 | 37 | [[package]] 38 | name = "anyhow" 39 | version = "1.0.75" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" 42 | 43 | [[package]] 44 | name = "autocfg" 45 | version = "1.1.0" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 48 | 49 | [[package]] 50 | name = "base-x" 51 | version = "0.2.11" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" 54 | 55 | [[package]] 56 | name = "base64ct" 57 | version = "1.6.0" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" 60 | 61 | [[package]] 62 | name = "bitflags" 63 | version = "1.3.2" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 66 | 67 | [[package]] 68 | name = "block-buffer" 69 | version = "0.10.4" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 72 | dependencies = [ 73 | "generic-array", 74 | ] 75 | 76 | [[package]] 77 | name = "bumpalo" 78 | version = "3.13.0" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" 81 | 82 | [[package]] 83 | name = "byteorder" 84 | version = "1.4.3" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 87 | 88 | [[package]] 89 | name = "bzip2" 90 | version = "0.4.4" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" 93 | dependencies = [ 94 | "bzip2-sys", 95 | "libc", 96 | ] 97 | 98 | [[package]] 99 | name = "bzip2-sys" 100 | version = "0.1.11+1.0.8" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" 103 | dependencies = [ 104 | "cc", 105 | "libc", 106 | "pkg-config", 107 | ] 108 | 109 | [[package]] 110 | name = "cassowary" 111 | version = "0.3.0" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" 114 | 115 | [[package]] 116 | name = "cc" 117 | version = "1.0.83" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" 120 | dependencies = [ 121 | "jobserver", 122 | "libc", 123 | ] 124 | 125 | [[package]] 126 | name = "cfg-if" 127 | version = "1.0.0" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 130 | 131 | [[package]] 132 | name = "chrono" 133 | version = "0.4.26" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" 136 | dependencies = [ 137 | "android-tzdata", 138 | "iana-time-zone", 139 | "num-traits", 140 | "winapi", 141 | ] 142 | 143 | [[package]] 144 | name = "cipher" 145 | version = "0.4.4" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" 148 | dependencies = [ 149 | "crypto-common", 150 | "inout", 151 | ] 152 | 153 | [[package]] 154 | name = "const_fn" 155 | version = "0.4.9" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | checksum = "fbdcdcb6d86f71c5e97409ad45898af11cbc995b4ee8112d59095a28d376c935" 158 | 159 | [[package]] 160 | name = "constant_time_eq" 161 | version = "0.1.5" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" 164 | 165 | [[package]] 166 | name = "core-foundation-sys" 167 | version = "0.8.4" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" 170 | 171 | [[package]] 172 | name = "cpufeatures" 173 | version = "0.2.9" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" 176 | dependencies = [ 177 | "libc", 178 | ] 179 | 180 | [[package]] 181 | name = "crc32fast" 182 | version = "1.3.2" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" 185 | dependencies = [ 186 | "cfg-if", 187 | ] 188 | 189 | [[package]] 190 | name = "crossbeam-channel" 191 | version = "0.5.8" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" 194 | dependencies = [ 195 | "cfg-if", 196 | "crossbeam-utils", 197 | ] 198 | 199 | [[package]] 200 | name = "crossbeam-deque" 201 | version = "0.8.3" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" 204 | dependencies = [ 205 | "cfg-if", 206 | "crossbeam-epoch", 207 | "crossbeam-utils", 208 | ] 209 | 210 | [[package]] 211 | name = "crossbeam-epoch" 212 | version = "0.9.15" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" 215 | dependencies = [ 216 | "autocfg", 217 | "cfg-if", 218 | "crossbeam-utils", 219 | "memoffset", 220 | "scopeguard", 221 | ] 222 | 223 | [[package]] 224 | name = "crossbeam-utils" 225 | version = "0.8.16" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" 228 | dependencies = [ 229 | "cfg-if", 230 | ] 231 | 232 | [[package]] 233 | name = "crossterm" 234 | version = "0.26.1" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13" 237 | dependencies = [ 238 | "bitflags", 239 | "crossterm_winapi", 240 | "libc", 241 | "mio", 242 | "parking_lot", 243 | "signal-hook", 244 | "signal-hook-mio", 245 | "winapi", 246 | ] 247 | 248 | [[package]] 249 | name = "crossterm_winapi" 250 | version = "0.9.1" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" 253 | dependencies = [ 254 | "winapi", 255 | ] 256 | 257 | [[package]] 258 | name = "crypto-common" 259 | version = "0.1.6" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 262 | dependencies = [ 263 | "generic-array", 264 | "typenum", 265 | ] 266 | 267 | [[package]] 268 | name = "deranged" 269 | version = "0.3.8" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" 272 | 273 | [[package]] 274 | name = "digest" 275 | version = "0.10.7" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 278 | dependencies = [ 279 | "block-buffer", 280 | "crypto-common", 281 | "subtle", 282 | ] 283 | 284 | [[package]] 285 | name = "dirs" 286 | version = "5.0.1" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" 289 | dependencies = [ 290 | "dirs-sys", 291 | ] 292 | 293 | [[package]] 294 | name = "dirs-sys" 295 | version = "0.4.1" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" 298 | dependencies = [ 299 | "libc", 300 | "option-ext", 301 | "redox_users", 302 | "windows-sys", 303 | ] 304 | 305 | [[package]] 306 | name = "discard" 307 | version = "1.0.4" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" 310 | 311 | [[package]] 312 | name = "distance" 313 | version = "0.4.0" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "6d9d8664cf849d7d0f3114a3a387d2f5e4303176d746d5a951aaddc66dfe9240" 316 | 317 | [[package]] 318 | name = "either" 319 | version = "1.9.0" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" 322 | 323 | [[package]] 324 | name = "filetime" 325 | version = "0.2.22" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" 328 | dependencies = [ 329 | "cfg-if", 330 | "libc", 331 | "redox_syscall 0.3.5", 332 | "windows-sys", 333 | ] 334 | 335 | [[package]] 336 | name = "flate2" 337 | version = "1.0.27" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010" 340 | dependencies = [ 341 | "crc32fast", 342 | "miniz_oxide", 343 | ] 344 | 345 | [[package]] 346 | name = "form_urlencoded" 347 | version = "1.2.0" 348 | source = "registry+https://github.com/rust-lang/crates.io-index" 349 | checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" 350 | dependencies = [ 351 | "percent-encoding", 352 | ] 353 | 354 | [[package]] 355 | name = "generic-array" 356 | version = "0.14.7" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 359 | dependencies = [ 360 | "typenum", 361 | "version_check", 362 | ] 363 | 364 | [[package]] 365 | name = "getrandom" 366 | version = "0.2.10" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" 369 | dependencies = [ 370 | "cfg-if", 371 | "libc", 372 | "wasi", 373 | ] 374 | 375 | [[package]] 376 | name = "hermit-abi" 377 | version = "0.3.2" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" 380 | 381 | [[package]] 382 | name = "hmac" 383 | version = "0.12.1" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" 386 | dependencies = [ 387 | "digest", 388 | ] 389 | 390 | [[package]] 391 | name = "iana-time-zone" 392 | version = "0.1.57" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" 395 | dependencies = [ 396 | "android_system_properties", 397 | "core-foundation-sys", 398 | "iana-time-zone-haiku", 399 | "js-sys", 400 | "wasm-bindgen", 401 | "windows 0.48.0", 402 | ] 403 | 404 | [[package]] 405 | name = "iana-time-zone-haiku" 406 | version = "0.1.2" 407 | source = "registry+https://github.com/rust-lang/crates.io-index" 408 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 409 | dependencies = [ 410 | "cc", 411 | ] 412 | 413 | [[package]] 414 | name = "idna" 415 | version = "0.4.0" 416 | source = "registry+https://github.com/rust-lang/crates.io-index" 417 | checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" 418 | dependencies = [ 419 | "unicode-bidi", 420 | "unicode-normalization", 421 | ] 422 | 423 | [[package]] 424 | name = "inout" 425 | version = "0.1.3" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" 428 | dependencies = [ 429 | "generic-array", 430 | ] 431 | 432 | [[package]] 433 | name = "itoa" 434 | version = "1.0.9" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" 437 | 438 | [[package]] 439 | name = "jobserver" 440 | version = "0.1.26" 441 | source = "registry+https://github.com/rust-lang/crates.io-index" 442 | checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" 443 | dependencies = [ 444 | "libc", 445 | ] 446 | 447 | [[package]] 448 | name = "js-sys" 449 | version = "0.3.64" 450 | source = "registry+https://github.com/rust-lang/crates.io-index" 451 | checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" 452 | dependencies = [ 453 | "wasm-bindgen", 454 | ] 455 | 456 | [[package]] 457 | name = "libc" 458 | version = "0.2.159" 459 | source = "registry+https://github.com/rust-lang/crates.io-index" 460 | checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" 461 | 462 | [[package]] 463 | name = "lock_api" 464 | version = "0.4.10" 465 | source = "registry+https://github.com/rust-lang/crates.io-index" 466 | checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" 467 | dependencies = [ 468 | "autocfg", 469 | "scopeguard", 470 | ] 471 | 472 | [[package]] 473 | name = "log" 474 | version = "0.4.20" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" 477 | 478 | [[package]] 479 | name = "malloc_buf" 480 | version = "0.0.6" 481 | source = "registry+https://github.com/rust-lang/crates.io-index" 482 | checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" 483 | dependencies = [ 484 | "libc", 485 | ] 486 | 487 | [[package]] 488 | name = "memoffset" 489 | version = "0.9.0" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" 492 | dependencies = [ 493 | "autocfg", 494 | ] 495 | 496 | [[package]] 497 | name = "miniz_oxide" 498 | version = "0.7.1" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" 501 | dependencies = [ 502 | "adler", 503 | ] 504 | 505 | [[package]] 506 | name = "mio" 507 | version = "0.8.11" 508 | source = "registry+https://github.com/rust-lang/crates.io-index" 509 | checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" 510 | dependencies = [ 511 | "libc", 512 | "log", 513 | "wasi", 514 | "windows-sys", 515 | ] 516 | 517 | [[package]] 518 | name = "ntapi" 519 | version = "0.4.1" 520 | source = "registry+https://github.com/rust-lang/crates.io-index" 521 | checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" 522 | dependencies = [ 523 | "winapi", 524 | ] 525 | 526 | [[package]] 527 | name = "num-traits" 528 | version = "0.2.16" 529 | source = "registry+https://github.com/rust-lang/crates.io-index" 530 | checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" 531 | dependencies = [ 532 | "autocfg", 533 | ] 534 | 535 | [[package]] 536 | name = "num_cpus" 537 | version = "1.16.0" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" 540 | dependencies = [ 541 | "hermit-abi", 542 | "libc", 543 | ] 544 | 545 | [[package]] 546 | name = "objc" 547 | version = "0.2.7" 548 | source = "registry+https://github.com/rust-lang/crates.io-index" 549 | checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" 550 | dependencies = [ 551 | "malloc_buf", 552 | ] 553 | 554 | [[package]] 555 | name = "once_cell" 556 | version = "1.18.0" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" 559 | 560 | [[package]] 561 | name = "option-ext" 562 | version = "0.2.0" 563 | source = "registry+https://github.com/rust-lang/crates.io-index" 564 | checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" 565 | 566 | [[package]] 567 | name = "parking_lot" 568 | version = "0.12.1" 569 | source = "registry+https://github.com/rust-lang/crates.io-index" 570 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 571 | dependencies = [ 572 | "lock_api", 573 | "parking_lot_core", 574 | ] 575 | 576 | [[package]] 577 | name = "parking_lot_core" 578 | version = "0.9.8" 579 | source = "registry+https://github.com/rust-lang/crates.io-index" 580 | checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" 581 | dependencies = [ 582 | "cfg-if", 583 | "libc", 584 | "redox_syscall 0.3.5", 585 | "smallvec", 586 | "windows-targets 0.48.5", 587 | ] 588 | 589 | [[package]] 590 | name = "password-hash" 591 | version = "0.4.2" 592 | source = "registry+https://github.com/rust-lang/crates.io-index" 593 | checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" 594 | dependencies = [ 595 | "base64ct", 596 | "rand_core", 597 | "subtle", 598 | ] 599 | 600 | [[package]] 601 | name = "pbkdf2" 602 | version = "0.11.0" 603 | source = "registry+https://github.com/rust-lang/crates.io-index" 604 | checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" 605 | dependencies = [ 606 | "digest", 607 | "hmac", 608 | "password-hash", 609 | "sha2", 610 | ] 611 | 612 | [[package]] 613 | name = "percent-encoding" 614 | version = "2.3.0" 615 | source = "registry+https://github.com/rust-lang/crates.io-index" 616 | checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" 617 | 618 | [[package]] 619 | name = "pkg-config" 620 | version = "0.3.27" 621 | source = "registry+https://github.com/rust-lang/crates.io-index" 622 | checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" 623 | 624 | [[package]] 625 | name = "proc-macro-hack" 626 | version = "0.5.20+deprecated" 627 | source = "registry+https://github.com/rust-lang/crates.io-index" 628 | checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" 629 | 630 | [[package]] 631 | name = "proc-macro2" 632 | version = "1.0.66" 633 | source = "registry+https://github.com/rust-lang/crates.io-index" 634 | checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" 635 | dependencies = [ 636 | "unicode-ident", 637 | ] 638 | 639 | [[package]] 640 | name = "quote" 641 | version = "1.0.33" 642 | source = "registry+https://github.com/rust-lang/crates.io-index" 643 | checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" 644 | dependencies = [ 645 | "proc-macro2", 646 | ] 647 | 648 | [[package]] 649 | name = "rand_core" 650 | version = "0.6.4" 651 | source = "registry+https://github.com/rust-lang/crates.io-index" 652 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 653 | 654 | [[package]] 655 | name = "ratatui" 656 | version = "0.20.1" 657 | source = "registry+https://github.com/rust-lang/crates.io-index" 658 | checksum = "dcc0d032bccba900ee32151ec0265667535c230169f5a011154cdcd984e16829" 659 | dependencies = [ 660 | "bitflags", 661 | "cassowary", 662 | "crossterm", 663 | "unicode-segmentation", 664 | "unicode-width", 665 | ] 666 | 667 | [[package]] 668 | name = "rayon" 669 | version = "1.7.0" 670 | source = "registry+https://github.com/rust-lang/crates.io-index" 671 | checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" 672 | dependencies = [ 673 | "either", 674 | "rayon-core", 675 | ] 676 | 677 | [[package]] 678 | name = "rayon-core" 679 | version = "1.11.0" 680 | source = "registry+https://github.com/rust-lang/crates.io-index" 681 | checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" 682 | dependencies = [ 683 | "crossbeam-channel", 684 | "crossbeam-deque", 685 | "crossbeam-utils", 686 | "num_cpus", 687 | ] 688 | 689 | [[package]] 690 | name = "redox_syscall" 691 | version = "0.2.16" 692 | source = "registry+https://github.com/rust-lang/crates.io-index" 693 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 694 | dependencies = [ 695 | "bitflags", 696 | ] 697 | 698 | [[package]] 699 | name = "redox_syscall" 700 | version = "0.3.5" 701 | source = "registry+https://github.com/rust-lang/crates.io-index" 702 | checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" 703 | dependencies = [ 704 | "bitflags", 705 | ] 706 | 707 | [[package]] 708 | name = "redox_users" 709 | version = "0.4.3" 710 | source = "registry+https://github.com/rust-lang/crates.io-index" 711 | checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" 712 | dependencies = [ 713 | "getrandom", 714 | "redox_syscall 0.2.16", 715 | "thiserror", 716 | ] 717 | 718 | [[package]] 719 | name = "rst-traverse" 720 | version = "2.0.1" 721 | dependencies = [ 722 | "anyhow", 723 | "crossterm", 724 | "dirs", 725 | "distance", 726 | "flate2", 727 | "ratatui", 728 | "sublime_fuzzy", 729 | "sysinfo", 730 | "tar", 731 | "time 0.2.27", 732 | "trash", 733 | "walkdir", 734 | "zip-extract", 735 | ] 736 | 737 | [[package]] 738 | name = "rustc_version" 739 | version = "0.2.3" 740 | source = "registry+https://github.com/rust-lang/crates.io-index" 741 | checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" 742 | dependencies = [ 743 | "semver", 744 | ] 745 | 746 | [[package]] 747 | name = "ryu" 748 | version = "1.0.15" 749 | source = "registry+https://github.com/rust-lang/crates.io-index" 750 | checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" 751 | 752 | [[package]] 753 | name = "same-file" 754 | version = "1.0.6" 755 | source = "registry+https://github.com/rust-lang/crates.io-index" 756 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 757 | dependencies = [ 758 | "winapi-util", 759 | ] 760 | 761 | [[package]] 762 | name = "scopeguard" 763 | version = "1.2.0" 764 | source = "registry+https://github.com/rust-lang/crates.io-index" 765 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 766 | 767 | [[package]] 768 | name = "semver" 769 | version = "0.9.0" 770 | source = "registry+https://github.com/rust-lang/crates.io-index" 771 | checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 772 | dependencies = [ 773 | "semver-parser", 774 | ] 775 | 776 | [[package]] 777 | name = "semver-parser" 778 | version = "0.7.0" 779 | source = "registry+https://github.com/rust-lang/crates.io-index" 780 | checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 781 | 782 | [[package]] 783 | name = "serde" 784 | version = "1.0.188" 785 | source = "registry+https://github.com/rust-lang/crates.io-index" 786 | checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" 787 | dependencies = [ 788 | "serde_derive", 789 | ] 790 | 791 | [[package]] 792 | name = "serde_derive" 793 | version = "1.0.188" 794 | source = "registry+https://github.com/rust-lang/crates.io-index" 795 | checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" 796 | dependencies = [ 797 | "proc-macro2", 798 | "quote", 799 | "syn 2.0.29", 800 | ] 801 | 802 | [[package]] 803 | name = "serde_json" 804 | version = "1.0.105" 805 | source = "registry+https://github.com/rust-lang/crates.io-index" 806 | checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" 807 | dependencies = [ 808 | "itoa", 809 | "ryu", 810 | "serde", 811 | ] 812 | 813 | [[package]] 814 | name = "sha1" 815 | version = "0.6.1" 816 | source = "registry+https://github.com/rust-lang/crates.io-index" 817 | checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770" 818 | dependencies = [ 819 | "sha1_smol", 820 | ] 821 | 822 | [[package]] 823 | name = "sha1" 824 | version = "0.10.5" 825 | source = "registry+https://github.com/rust-lang/crates.io-index" 826 | checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" 827 | dependencies = [ 828 | "cfg-if", 829 | "cpufeatures", 830 | "digest", 831 | ] 832 | 833 | [[package]] 834 | name = "sha1_smol" 835 | version = "1.0.0" 836 | source = "registry+https://github.com/rust-lang/crates.io-index" 837 | checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" 838 | 839 | [[package]] 840 | name = "sha2" 841 | version = "0.10.7" 842 | source = "registry+https://github.com/rust-lang/crates.io-index" 843 | checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" 844 | dependencies = [ 845 | "cfg-if", 846 | "cpufeatures", 847 | "digest", 848 | ] 849 | 850 | [[package]] 851 | name = "signal-hook" 852 | version = "0.3.17" 853 | source = "registry+https://github.com/rust-lang/crates.io-index" 854 | checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" 855 | dependencies = [ 856 | "libc", 857 | "signal-hook-registry", 858 | ] 859 | 860 | [[package]] 861 | name = "signal-hook-mio" 862 | version = "0.2.3" 863 | source = "registry+https://github.com/rust-lang/crates.io-index" 864 | checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" 865 | dependencies = [ 866 | "libc", 867 | "mio", 868 | "signal-hook", 869 | ] 870 | 871 | [[package]] 872 | name = "signal-hook-registry" 873 | version = "1.4.1" 874 | source = "registry+https://github.com/rust-lang/crates.io-index" 875 | checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" 876 | dependencies = [ 877 | "libc", 878 | ] 879 | 880 | [[package]] 881 | name = "smallvec" 882 | version = "1.11.0" 883 | source = "registry+https://github.com/rust-lang/crates.io-index" 884 | checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" 885 | 886 | [[package]] 887 | name = "standback" 888 | version = "0.2.17" 889 | source = "registry+https://github.com/rust-lang/crates.io-index" 890 | checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff" 891 | dependencies = [ 892 | "version_check", 893 | ] 894 | 895 | [[package]] 896 | name = "stdweb" 897 | version = "0.4.20" 898 | source = "registry+https://github.com/rust-lang/crates.io-index" 899 | checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" 900 | dependencies = [ 901 | "discard", 902 | "rustc_version", 903 | "stdweb-derive", 904 | "stdweb-internal-macros", 905 | "stdweb-internal-runtime", 906 | "wasm-bindgen", 907 | ] 908 | 909 | [[package]] 910 | name = "stdweb-derive" 911 | version = "0.5.3" 912 | source = "registry+https://github.com/rust-lang/crates.io-index" 913 | checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" 914 | dependencies = [ 915 | "proc-macro2", 916 | "quote", 917 | "serde", 918 | "serde_derive", 919 | "syn 1.0.109", 920 | ] 921 | 922 | [[package]] 923 | name = "stdweb-internal-macros" 924 | version = "0.2.9" 925 | source = "registry+https://github.com/rust-lang/crates.io-index" 926 | checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" 927 | dependencies = [ 928 | "base-x", 929 | "proc-macro2", 930 | "quote", 931 | "serde", 932 | "serde_derive", 933 | "serde_json", 934 | "sha1 0.6.1", 935 | "syn 1.0.109", 936 | ] 937 | 938 | [[package]] 939 | name = "stdweb-internal-runtime" 940 | version = "0.1.5" 941 | source = "registry+https://github.com/rust-lang/crates.io-index" 942 | checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" 943 | 944 | [[package]] 945 | name = "sublime_fuzzy" 946 | version = "0.7.0" 947 | source = "registry+https://github.com/rust-lang/crates.io-index" 948 | checksum = "fa7986063f7c0ab374407e586d7048a3d5aac94f103f751088bf398e07cd5400" 949 | 950 | [[package]] 951 | name = "subtle" 952 | version = "2.5.0" 953 | source = "registry+https://github.com/rust-lang/crates.io-index" 954 | checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" 955 | 956 | [[package]] 957 | name = "syn" 958 | version = "1.0.109" 959 | source = "registry+https://github.com/rust-lang/crates.io-index" 960 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 961 | dependencies = [ 962 | "proc-macro2", 963 | "quote", 964 | "unicode-ident", 965 | ] 966 | 967 | [[package]] 968 | name = "syn" 969 | version = "2.0.29" 970 | source = "registry+https://github.com/rust-lang/crates.io-index" 971 | checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" 972 | dependencies = [ 973 | "proc-macro2", 974 | "quote", 975 | "unicode-ident", 976 | ] 977 | 978 | [[package]] 979 | name = "sysinfo" 980 | version = "0.29.9" 981 | source = "registry+https://github.com/rust-lang/crates.io-index" 982 | checksum = "a8d0e9cc2273cc8d31377bdd638d72e3ac3e5607b18621062b169d02787f1bab" 983 | dependencies = [ 984 | "cfg-if", 985 | "core-foundation-sys", 986 | "libc", 987 | "ntapi", 988 | "once_cell", 989 | "rayon", 990 | "winapi", 991 | ] 992 | 993 | [[package]] 994 | name = "tar" 995 | version = "0.4.40" 996 | source = "registry+https://github.com/rust-lang/crates.io-index" 997 | checksum = "b16afcea1f22891c49a00c751c7b63b2233284064f11a200fc624137c51e2ddb" 998 | dependencies = [ 999 | "filetime", 1000 | "libc", 1001 | "xattr", 1002 | ] 1003 | 1004 | [[package]] 1005 | name = "thiserror" 1006 | version = "1.0.47" 1007 | source = "registry+https://github.com/rust-lang/crates.io-index" 1008 | checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f" 1009 | dependencies = [ 1010 | "thiserror-impl", 1011 | ] 1012 | 1013 | [[package]] 1014 | name = "thiserror-impl" 1015 | version = "1.0.47" 1016 | source = "registry+https://github.com/rust-lang/crates.io-index" 1017 | checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b" 1018 | dependencies = [ 1019 | "proc-macro2", 1020 | "quote", 1021 | "syn 2.0.29", 1022 | ] 1023 | 1024 | [[package]] 1025 | name = "time" 1026 | version = "0.2.27" 1027 | source = "registry+https://github.com/rust-lang/crates.io-index" 1028 | checksum = "4752a97f8eebd6854ff91f1c1824cd6160626ac4bd44287f7f4ea2035a02a242" 1029 | dependencies = [ 1030 | "const_fn", 1031 | "libc", 1032 | "standback", 1033 | "stdweb", 1034 | "time-macros", 1035 | "version_check", 1036 | "winapi", 1037 | ] 1038 | 1039 | [[package]] 1040 | name = "time" 1041 | version = "0.3.28" 1042 | source = "registry+https://github.com/rust-lang/crates.io-index" 1043 | checksum = "17f6bb557fd245c28e6411aa56b6403c689ad95061f50e4be16c274e70a17e48" 1044 | dependencies = [ 1045 | "deranged", 1046 | "serde", 1047 | "time-core", 1048 | ] 1049 | 1050 | [[package]] 1051 | name = "time-core" 1052 | version = "0.1.1" 1053 | source = "registry+https://github.com/rust-lang/crates.io-index" 1054 | checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" 1055 | 1056 | [[package]] 1057 | name = "time-macros" 1058 | version = "0.1.1" 1059 | source = "registry+https://github.com/rust-lang/crates.io-index" 1060 | checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1" 1061 | dependencies = [ 1062 | "proc-macro-hack", 1063 | "time-macros-impl", 1064 | ] 1065 | 1066 | [[package]] 1067 | name = "time-macros-impl" 1068 | version = "0.1.2" 1069 | source = "registry+https://github.com/rust-lang/crates.io-index" 1070 | checksum = "fd3c141a1b43194f3f56a1411225df8646c55781d5f26db825b3d98507eb482f" 1071 | dependencies = [ 1072 | "proc-macro-hack", 1073 | "proc-macro2", 1074 | "quote", 1075 | "standback", 1076 | "syn 1.0.109", 1077 | ] 1078 | 1079 | [[package]] 1080 | name = "tinyvec" 1081 | version = "1.6.0" 1082 | source = "registry+https://github.com/rust-lang/crates.io-index" 1083 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 1084 | dependencies = [ 1085 | "tinyvec_macros", 1086 | ] 1087 | 1088 | [[package]] 1089 | name = "tinyvec_macros" 1090 | version = "0.1.1" 1091 | source = "registry+https://github.com/rust-lang/crates.io-index" 1092 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 1093 | 1094 | [[package]] 1095 | name = "trash" 1096 | version = "3.0.6" 1097 | source = "registry+https://github.com/rust-lang/crates.io-index" 1098 | checksum = "af3663fb8f476d674b9c61d1d2796acec725bef6bec4b41402a904252a25971e" 1099 | dependencies = [ 1100 | "chrono", 1101 | "libc", 1102 | "log", 1103 | "objc", 1104 | "once_cell", 1105 | "scopeguard", 1106 | "url", 1107 | "windows 0.44.0", 1108 | ] 1109 | 1110 | [[package]] 1111 | name = "typenum" 1112 | version = "1.16.0" 1113 | source = "registry+https://github.com/rust-lang/crates.io-index" 1114 | checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" 1115 | 1116 | [[package]] 1117 | name = "unicode-bidi" 1118 | version = "0.3.13" 1119 | source = "registry+https://github.com/rust-lang/crates.io-index" 1120 | checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" 1121 | 1122 | [[package]] 1123 | name = "unicode-ident" 1124 | version = "1.0.11" 1125 | source = "registry+https://github.com/rust-lang/crates.io-index" 1126 | checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" 1127 | 1128 | [[package]] 1129 | name = "unicode-normalization" 1130 | version = "0.1.22" 1131 | source = "registry+https://github.com/rust-lang/crates.io-index" 1132 | checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" 1133 | dependencies = [ 1134 | "tinyvec", 1135 | ] 1136 | 1137 | [[package]] 1138 | name = "unicode-segmentation" 1139 | version = "1.10.1" 1140 | source = "registry+https://github.com/rust-lang/crates.io-index" 1141 | checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" 1142 | 1143 | [[package]] 1144 | name = "unicode-width" 1145 | version = "0.1.10" 1146 | source = "registry+https://github.com/rust-lang/crates.io-index" 1147 | checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" 1148 | 1149 | [[package]] 1150 | name = "url" 1151 | version = "2.4.1" 1152 | source = "registry+https://github.com/rust-lang/crates.io-index" 1153 | checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" 1154 | dependencies = [ 1155 | "form_urlencoded", 1156 | "idna", 1157 | "percent-encoding", 1158 | ] 1159 | 1160 | [[package]] 1161 | name = "version_check" 1162 | version = "0.9.4" 1163 | source = "registry+https://github.com/rust-lang/crates.io-index" 1164 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1165 | 1166 | [[package]] 1167 | name = "walkdir" 1168 | version = "2.3.3" 1169 | source = "registry+https://github.com/rust-lang/crates.io-index" 1170 | checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" 1171 | dependencies = [ 1172 | "same-file", 1173 | "winapi-util", 1174 | ] 1175 | 1176 | [[package]] 1177 | name = "wasi" 1178 | version = "0.11.0+wasi-snapshot-preview1" 1179 | source = "registry+https://github.com/rust-lang/crates.io-index" 1180 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1181 | 1182 | [[package]] 1183 | name = "wasm-bindgen" 1184 | version = "0.2.87" 1185 | source = "registry+https://github.com/rust-lang/crates.io-index" 1186 | checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" 1187 | dependencies = [ 1188 | "cfg-if", 1189 | "wasm-bindgen-macro", 1190 | ] 1191 | 1192 | [[package]] 1193 | name = "wasm-bindgen-backend" 1194 | version = "0.2.87" 1195 | source = "registry+https://github.com/rust-lang/crates.io-index" 1196 | checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" 1197 | dependencies = [ 1198 | "bumpalo", 1199 | "log", 1200 | "once_cell", 1201 | "proc-macro2", 1202 | "quote", 1203 | "syn 2.0.29", 1204 | "wasm-bindgen-shared", 1205 | ] 1206 | 1207 | [[package]] 1208 | name = "wasm-bindgen-macro" 1209 | version = "0.2.87" 1210 | source = "registry+https://github.com/rust-lang/crates.io-index" 1211 | checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" 1212 | dependencies = [ 1213 | "quote", 1214 | "wasm-bindgen-macro-support", 1215 | ] 1216 | 1217 | [[package]] 1218 | name = "wasm-bindgen-macro-support" 1219 | version = "0.2.87" 1220 | source = "registry+https://github.com/rust-lang/crates.io-index" 1221 | checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" 1222 | dependencies = [ 1223 | "proc-macro2", 1224 | "quote", 1225 | "syn 2.0.29", 1226 | "wasm-bindgen-backend", 1227 | "wasm-bindgen-shared", 1228 | ] 1229 | 1230 | [[package]] 1231 | name = "wasm-bindgen-shared" 1232 | version = "0.2.87" 1233 | source = "registry+https://github.com/rust-lang/crates.io-index" 1234 | checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" 1235 | 1236 | [[package]] 1237 | name = "winapi" 1238 | version = "0.3.9" 1239 | source = "registry+https://github.com/rust-lang/crates.io-index" 1240 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1241 | dependencies = [ 1242 | "winapi-i686-pc-windows-gnu", 1243 | "winapi-x86_64-pc-windows-gnu", 1244 | ] 1245 | 1246 | [[package]] 1247 | name = "winapi-i686-pc-windows-gnu" 1248 | version = "0.4.0" 1249 | source = "registry+https://github.com/rust-lang/crates.io-index" 1250 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1251 | 1252 | [[package]] 1253 | name = "winapi-util" 1254 | version = "0.1.5" 1255 | source = "registry+https://github.com/rust-lang/crates.io-index" 1256 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1257 | dependencies = [ 1258 | "winapi", 1259 | ] 1260 | 1261 | [[package]] 1262 | name = "winapi-x86_64-pc-windows-gnu" 1263 | version = "0.4.0" 1264 | source = "registry+https://github.com/rust-lang/crates.io-index" 1265 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1266 | 1267 | [[package]] 1268 | name = "windows" 1269 | version = "0.44.0" 1270 | source = "registry+https://github.com/rust-lang/crates.io-index" 1271 | checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b" 1272 | dependencies = [ 1273 | "windows-targets 0.42.2", 1274 | ] 1275 | 1276 | [[package]] 1277 | name = "windows" 1278 | version = "0.48.0" 1279 | source = "registry+https://github.com/rust-lang/crates.io-index" 1280 | checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" 1281 | dependencies = [ 1282 | "windows-targets 0.48.5", 1283 | ] 1284 | 1285 | [[package]] 1286 | name = "windows-sys" 1287 | version = "0.48.0" 1288 | source = "registry+https://github.com/rust-lang/crates.io-index" 1289 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 1290 | dependencies = [ 1291 | "windows-targets 0.48.5", 1292 | ] 1293 | 1294 | [[package]] 1295 | name = "windows-targets" 1296 | version = "0.42.2" 1297 | source = "registry+https://github.com/rust-lang/crates.io-index" 1298 | checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" 1299 | dependencies = [ 1300 | "windows_aarch64_gnullvm 0.42.2", 1301 | "windows_aarch64_msvc 0.42.2", 1302 | "windows_i686_gnu 0.42.2", 1303 | "windows_i686_msvc 0.42.2", 1304 | "windows_x86_64_gnu 0.42.2", 1305 | "windows_x86_64_gnullvm 0.42.2", 1306 | "windows_x86_64_msvc 0.42.2", 1307 | ] 1308 | 1309 | [[package]] 1310 | name = "windows-targets" 1311 | version = "0.48.5" 1312 | source = "registry+https://github.com/rust-lang/crates.io-index" 1313 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 1314 | dependencies = [ 1315 | "windows_aarch64_gnullvm 0.48.5", 1316 | "windows_aarch64_msvc 0.48.5", 1317 | "windows_i686_gnu 0.48.5", 1318 | "windows_i686_msvc 0.48.5", 1319 | "windows_x86_64_gnu 0.48.5", 1320 | "windows_x86_64_gnullvm 0.48.5", 1321 | "windows_x86_64_msvc 0.48.5", 1322 | ] 1323 | 1324 | [[package]] 1325 | name = "windows_aarch64_gnullvm" 1326 | version = "0.42.2" 1327 | source = "registry+https://github.com/rust-lang/crates.io-index" 1328 | checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" 1329 | 1330 | [[package]] 1331 | name = "windows_aarch64_gnullvm" 1332 | version = "0.48.5" 1333 | source = "registry+https://github.com/rust-lang/crates.io-index" 1334 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 1335 | 1336 | [[package]] 1337 | name = "windows_aarch64_msvc" 1338 | version = "0.42.2" 1339 | source = "registry+https://github.com/rust-lang/crates.io-index" 1340 | checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" 1341 | 1342 | [[package]] 1343 | name = "windows_aarch64_msvc" 1344 | version = "0.48.5" 1345 | source = "registry+https://github.com/rust-lang/crates.io-index" 1346 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 1347 | 1348 | [[package]] 1349 | name = "windows_i686_gnu" 1350 | version = "0.42.2" 1351 | source = "registry+https://github.com/rust-lang/crates.io-index" 1352 | checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" 1353 | 1354 | [[package]] 1355 | name = "windows_i686_gnu" 1356 | version = "0.48.5" 1357 | source = "registry+https://github.com/rust-lang/crates.io-index" 1358 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 1359 | 1360 | [[package]] 1361 | name = "windows_i686_msvc" 1362 | version = "0.42.2" 1363 | source = "registry+https://github.com/rust-lang/crates.io-index" 1364 | checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" 1365 | 1366 | [[package]] 1367 | name = "windows_i686_msvc" 1368 | version = "0.48.5" 1369 | source = "registry+https://github.com/rust-lang/crates.io-index" 1370 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 1371 | 1372 | [[package]] 1373 | name = "windows_x86_64_gnu" 1374 | version = "0.42.2" 1375 | source = "registry+https://github.com/rust-lang/crates.io-index" 1376 | checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" 1377 | 1378 | [[package]] 1379 | name = "windows_x86_64_gnu" 1380 | version = "0.48.5" 1381 | source = "registry+https://github.com/rust-lang/crates.io-index" 1382 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 1383 | 1384 | [[package]] 1385 | name = "windows_x86_64_gnullvm" 1386 | version = "0.42.2" 1387 | source = "registry+https://github.com/rust-lang/crates.io-index" 1388 | checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" 1389 | 1390 | [[package]] 1391 | name = "windows_x86_64_gnullvm" 1392 | version = "0.48.5" 1393 | source = "registry+https://github.com/rust-lang/crates.io-index" 1394 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 1395 | 1396 | [[package]] 1397 | name = "windows_x86_64_msvc" 1398 | version = "0.42.2" 1399 | source = "registry+https://github.com/rust-lang/crates.io-index" 1400 | checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" 1401 | 1402 | [[package]] 1403 | name = "windows_x86_64_msvc" 1404 | version = "0.48.5" 1405 | source = "registry+https://github.com/rust-lang/crates.io-index" 1406 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 1407 | 1408 | [[package]] 1409 | name = "xattr" 1410 | version = "1.0.1" 1411 | source = "registry+https://github.com/rust-lang/crates.io-index" 1412 | checksum = "f4686009f71ff3e5c4dbcf1a282d0a44db3f021ba69350cd42086b3e5f1c6985" 1413 | dependencies = [ 1414 | "libc", 1415 | ] 1416 | 1417 | [[package]] 1418 | name = "zip" 1419 | version = "0.6.6" 1420 | source = "registry+https://github.com/rust-lang/crates.io-index" 1421 | checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" 1422 | dependencies = [ 1423 | "aes", 1424 | "byteorder", 1425 | "bzip2", 1426 | "constant_time_eq", 1427 | "crc32fast", 1428 | "crossbeam-utils", 1429 | "flate2", 1430 | "hmac", 1431 | "pbkdf2", 1432 | "sha1 0.10.5", 1433 | "time 0.3.28", 1434 | "zstd", 1435 | ] 1436 | 1437 | [[package]] 1438 | name = "zip-extract" 1439 | version = "0.1.2" 1440 | source = "registry+https://github.com/rust-lang/crates.io-index" 1441 | checksum = "bb654964c003959ed64cbd0d7b329bcdcbd9690facd50c8617748d3622543972" 1442 | dependencies = [ 1443 | "log", 1444 | "thiserror", 1445 | "zip", 1446 | ] 1447 | 1448 | [[package]] 1449 | name = "zstd" 1450 | version = "0.11.2+zstd.1.5.2" 1451 | source = "registry+https://github.com/rust-lang/crates.io-index" 1452 | checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" 1453 | dependencies = [ 1454 | "zstd-safe", 1455 | ] 1456 | 1457 | [[package]] 1458 | name = "zstd-safe" 1459 | version = "5.0.2+zstd.1.5.2" 1460 | source = "registry+https://github.com/rust-lang/crates.io-index" 1461 | checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" 1462 | dependencies = [ 1463 | "libc", 1464 | "zstd-sys", 1465 | ] 1466 | 1467 | [[package]] 1468 | name = "zstd-sys" 1469 | version = "2.0.8+zstd.1.5.5" 1470 | source = "registry+https://github.com/rust-lang/crates.io-index" 1471 | checksum = "5556e6ee25d32df2586c098bbfa278803692a20d0ab9565e049480d52707ec8c" 1472 | dependencies = [ 1473 | "cc", 1474 | "libc", 1475 | "pkg-config", 1476 | ] 1477 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rst-traverse" 3 | version = "2.0.1" 4 | edition = "2021" 5 | description = "A terminal based file manager." 6 | authors = ["dmcg310"] 7 | readme = "README.md" 8 | license = "MIT" 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [dependencies] 13 | anyhow = "1.0.71" 14 | time = "0.2.23" 15 | crossterm = "0.26" 16 | distance = "0.4.0" 17 | ratatui = "0.20" 18 | sysinfo = "0.29.0" 19 | trash = "3.0.2" 20 | walkdir = "2.3.3" 21 | flate2 = "1.0.26" 22 | tar = "0.4.38" 23 | zip-extract = "0.1.2" 24 | dirs = "5.0.1" 25 | sublime_fuzzy = "0.7.0" 26 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023, dmcg310 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rust-Traverse 2 | 3 | Rust traverse is a terminal based file explorer. It is inspired by the [NNN](https://github.com/jarun/nnn) file manager. It uses [Ratatui](https://github.com/tui-rs-revival/ratatui) for the terminal UI, with Crossterm for the terminal backend. 4 | 5 | > To traverse or not to traverse? 6 | 7 | ![Rust-Traverse](traverse.gif) 8 | 9 | ## Features 10 | 11 | - [x] Full CRUD operations on files and directories. 12 | - [x] Move and copy files and directories. 13 | - [x] Keyboard shortcuts for navigation and operations, to make sure you don't have to leave the keyboard. 14 | - [x] Traverse directly to a directory by typing its path. 15 | - [x] Configurable. 16 | - [x] Extract tar.gz, or zip archives. 17 | - [x] Bookmarks for your favourite directories. 18 | - [x] Fuzzy finder for files in your current directory. 19 | - [x] Preview files in the terminal. 20 | - [x] Blazingly fast. 21 | 22 | ## Installation 23 | 24 | ### From source 25 | 26 | 1. Install [Rust](https://www.rust-lang.org/tools/install). 27 | 2. Clone the repository. 28 | 3. Run `cargo build --release`. 29 | 4. The binary will be in `target/release/rst-traverse`. 30 | 5. Add the binary to your path. 31 | 32 | ### From Cargo 33 | 34 | 1. Install [Rust](https://www.rust-lang.org/tools/install). 35 | 2. Run `cargo install rst-traverse`. 36 | 3. Make sure cargo's bin directory is in your path. 37 | 4. (Optional) Rename the binary to whatever suits you. 38 | 39 | ### From Binary 40 | 41 | 1. Download the binary from the releases page. 42 | 2. `chmod +x rst-traverse` (linux only) within the same directory as it, to make it executable. 43 | 3. Move the resulting binary to your path. 44 | 45 | ## Usage 46 | 47 | Run `rst-traverse` in your terminal. 48 | 49 | ### Keyboard Shortcuts 50 | 51 | #### Navigation 52 | 53 | - `ESC` or `q`: Quit the application. 54 | - `1`: Select the Files pane. 55 | - `2`: Select the Directories pane. 56 | - `j`: Select the next item in the current pane. 57 | - `k`: Select the previous item in the current pane. 58 | 59 | #### File and Directory Operations 60 | 61 | - `n`: Create a new file or directory, depending on the current pane. 62 | - `CTRL + d`: Delete the selected file or directory, (to bin). 63 | - `r`: Rename the selected file or directory. 64 | - `f`: Navigate to a directory using a relative or absolute path. 65 | - `x`: Extract the selected archive, to the current directory. 66 | 67 | #### Move/Copy Operations 68 | 69 | - `c`: Append the selected file or directory to the move/copy buffer. 70 | - `p`: Opens the move/copy buffer menu, (enter on any option is in relation to your current directory). 71 | 72 | #### Fuzzy Finder Operations 73 | 74 | - `w`: Toggle fzf. 75 | - `CTRL + n`: 'Next' item in results. 76 | - `CTRL + p`: 'Previous' item in results. 77 | 78 | #### Bookmark Operations 79 | 80 | - `b`: Shows bookmarks menu. 81 | - `z`: Add current directory to bookmarks. 82 | - `CTRL + n`: 'Next' bookmark in menu. 83 | - `CTRL + p`: 'Previous' bookmark in menu. 84 | 85 | #### Help 86 | 87 | - `?`: Shows help menu. 88 | 89 | ## Configuration 90 | 91 | The configuration file is located at `/traverse/config.txt`. The default configuration is as follows: 92 | 93 | ``` 94 | show_hidden=false 95 | excluded_directories=.git,.idea,.vscode,target 96 | ``` 97 | 98 | The excluded directories are directories that will not be searched when using the FZF. 99 | 100 | The bookmarks file is located at `/traverse/bookmarks.txt`. 101 | -------------------------------------------------------------------------------- /src/app/app.rs: -------------------------------------------------------------------------------- 1 | use crate::configuration::configuration::read_config; 2 | use crate::ui::display::{pane::get_du, pane::get_pwd}; 3 | use crate::ui::input::{run_app::Command, stateful_list::StatefulList}; 4 | use ratatui::{ 5 | buffer::Buffer, 6 | layout::Rect, 7 | style::Style, 8 | widgets::{ListState, Widget}, 9 | }; 10 | use std::fs::{self, read_dir, File}; 11 | 12 | pub struct App { 13 | pub files: StatefulList<(String, String)>, 14 | pub dirs: StatefulList<(String, String)>, 15 | pub content: StatefulList, 16 | pub cur_du: String, 17 | pub cur_dir: String, 18 | pub show_popup: bool, 19 | pub show_nav: bool, 20 | pub show_fzf: bool, 21 | pub show_help: bool, 22 | pub show_bookmark: bool, 23 | pub fzf_results: StatefulList, 24 | pub selected_fzf_result: usize, 25 | pub selected_item_state: ListState, 26 | pub last_command: Option, 27 | pub bookmarked_dirs: StatefulList, 28 | pub excluded_directories: Vec, 29 | pub show_hidden: bool, 30 | pub show_ops_menu: bool, 31 | pub selected_files: Vec, 32 | pub selected_dirs: Vec, 33 | pub ops_menu: StatefulList, 34 | } 35 | 36 | impl App { 37 | pub fn new() -> App { 38 | let mut files = StatefulList::with_items(vec![]); 39 | for entry in read_dir("./").unwrap() { 40 | let entry = entry.unwrap(); 41 | if entry.metadata().unwrap().is_file() { 42 | let temp = entry.file_name().into_string().unwrap(); 43 | files.items.push((temp.clone(), temp)); 44 | } 45 | } 46 | 47 | let mut dirs = StatefulList::with_items(vec![(("../".to_string(), "../".to_string()))]); 48 | for entry in read_dir("./").unwrap() { 49 | let entry = entry.unwrap(); 50 | 51 | if entry.metadata().unwrap().is_dir() { 52 | let temp = entry.file_name().into_string().unwrap(); 53 | dirs.items.push((temp.clone(), temp)); 54 | } 55 | } 56 | 57 | let cur_dir = get_pwd(); 58 | let cur_du = get_du(); 59 | 60 | App { 61 | files, 62 | dirs, 63 | cur_du, 64 | cur_dir, 65 | content: StatefulList::with_items(vec![]), 66 | show_popup: false, 67 | show_nav: false, 68 | show_fzf: false, 69 | show_bookmark: false, 70 | show_help: false, 71 | fzf_results: StatefulList::with_items(vec![]), 72 | selected_fzf_result: 0, 73 | selected_item_state: ListState::default(), 74 | last_command: None, 75 | bookmarked_dirs: StatefulList::with_items(vec![]), 76 | excluded_directories: vec![], 77 | show_hidden: false, 78 | show_ops_menu: false, 79 | selected_files: vec![], 80 | selected_dirs: vec![], 81 | ops_menu: StatefulList::with_items(vec![]), 82 | } 83 | } 84 | 85 | pub fn op_menu_init(&mut self) { 86 | self.ops_menu.items.push("Copy here".to_string()); 87 | self.ops_menu.items.push("Move here".to_string()); 88 | self.ops_menu.items.push("Clear selection".to_string()); 89 | } 90 | 91 | pub fn read_config(&mut self) { 92 | read_config(self); 93 | } 94 | 95 | pub fn update_files(&mut self) { 96 | self.read_config(); 97 | self.files.items.clear(); 98 | 99 | let mut file_entries: Vec<(String, String)> = vec![]; 100 | 101 | for entry in read_dir("./").unwrap() { 102 | let entry = entry.unwrap(); 103 | if entry.metadata().unwrap().is_file() { 104 | let temp = entry.file_name().into_string().unwrap(); 105 | if temp == "swapfile" { 106 | // previewing this file devastates the terminal, 107 | // mine anyway 108 | continue; 109 | } 110 | 111 | if temp.starts_with(".") && !self.show_hidden { 112 | continue; 113 | } 114 | 115 | file_entries.push((temp.clone(), temp)); 116 | } 117 | } 118 | 119 | file_entries.sort_by(|a, b| { 120 | let a_starts_with_dot = a.0.starts_with("."); 121 | let b_starts_with_dot = b.0.starts_with("."); 122 | 123 | if a_starts_with_dot && !b_starts_with_dot { 124 | std::cmp::Ordering::Greater 125 | } else if !a_starts_with_dot && b_starts_with_dot { 126 | std::cmp::Ordering::Less 127 | } else { 128 | a.0.cmp(&b.0) 129 | } 130 | }); 131 | 132 | for file in file_entries { 133 | self.files.items.push(file); 134 | } 135 | } 136 | 137 | pub fn update_dirs(&mut self) { 138 | self.dirs.items.clear(); 139 | self.dirs.items.push(("../".to_string(), "../".to_string())); 140 | 141 | let mut dir_entries: Vec<(String, String)> = vec![]; 142 | 143 | for entry in read_dir("./").unwrap() { 144 | let entry = entry.unwrap(); 145 | 146 | if entry.metadata().unwrap().is_dir() { 147 | let temp = entry.file_name().into_string().unwrap(); 148 | 149 | if temp.starts_with(".") && !self.show_hidden { 150 | continue; 151 | } 152 | 153 | dir_entries.push((temp.clone(), temp.clone())); 154 | } 155 | } 156 | 157 | dir_entries.sort_by(|a, b| { 158 | let a_starts_with_dot = a.0.starts_with("."); 159 | let b_starts_with_dot = b.0.starts_with("."); 160 | 161 | if a_starts_with_dot && !b_starts_with_dot { 162 | std::cmp::Ordering::Greater 163 | } else if !a_starts_with_dot && b_starts_with_dot { 164 | std::cmp::Ordering::Less 165 | } else { 166 | a.0.cmp(&b.0) 167 | } 168 | }); 169 | 170 | for dir in dir_entries { 171 | self.dirs.items.push(dir); 172 | } 173 | } 174 | 175 | pub fn update_bookmarks(&mut self) { 176 | self.show_bookmark = true; 177 | } 178 | 179 | pub fn create_file(input: &str) -> bool { 180 | if File::create(input).is_ok() { 181 | true 182 | } else { 183 | false 184 | } 185 | } 186 | 187 | pub fn create_dir(input: &str) -> bool { 188 | format!("./{}", input); 189 | if fs::create_dir(input).is_ok() { 190 | true 191 | } else { 192 | false 193 | } 194 | } 195 | } 196 | 197 | pub struct InputBox<'a> { 198 | text: &'a str, 199 | style: Style, 200 | } 201 | 202 | impl<'a> Widget for InputBox<'a> { 203 | fn render(self, area: Rect, buf: &mut Buffer) { 204 | buf.set_stringn(area.x, area.y, self.text, area.width as usize, self.style); 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /src/app/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod app; 2 | -------------------------------------------------------------------------------- /src/configuration/configuration.rs: -------------------------------------------------------------------------------- 1 | use crate::app::app::App; 2 | use dirs::config_dir; 3 | use std::fs; 4 | use std::io::BufRead; 5 | use std::io::Write; 6 | 7 | pub fn read_config(app: &mut App) { 8 | let config_path = config_dir().unwrap().join("traverse/config.txt"); 9 | 10 | if !config_path.exists() { 11 | if let Some(parent) = config_path.parent() { 12 | if !parent.exists() { 13 | fs::create_dir_all(parent).unwrap_or_else(|_| { 14 | panic!("Failed to create directory at {}", parent.display()) 15 | }); 16 | } 17 | } 18 | 19 | let file = fs::File::create(&config_path).unwrap_or_else(|_| { 20 | panic!("Failed to create config file at {}", config_path.display()) 21 | }); 22 | 23 | let mut writer = std::io::BufWriter::new(file); 24 | writer.write_all(b"show_hidden=false").unwrap(); 25 | writer 26 | .write_all(b"\nexcluded_directories=.git,.idea,.vscode,target") 27 | .unwrap(); 28 | } 29 | 30 | let file = fs::File::open(config_path).unwrap(); 31 | let reader = std::io::BufReader::new(file); 32 | 33 | for line in reader.lines() { 34 | let line = line.unwrap(); 35 | 36 | if line.contains("show_hidden") { 37 | let mut split = line.split("="); 38 | let value = split.nth(1).unwrap().trim().to_string(); 39 | 40 | if value.eq_ignore_ascii_case("true") { 41 | app.show_hidden = true; 42 | } else { 43 | app.show_hidden = false; 44 | } 45 | } 46 | 47 | if line.contains("excluded_directories") { 48 | let mut split = line.split("="); 49 | let value = split.nth(1).unwrap().trim().to_string(); 50 | 51 | if value.contains(',') { 52 | let values = value.split(","); 53 | 54 | for val in values { 55 | app.excluded_directories.push(val.trim().to_string()); 56 | } 57 | } else { 58 | app.excluded_directories.push(value); 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/configuration/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod configuration; 2 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod app; 2 | mod ui; 3 | mod configuration; 4 | 5 | use ui::display::render::init; 6 | 7 | fn main() { 8 | init().unwrap(); 9 | } 10 | -------------------------------------------------------------------------------- /src/ui/display/block.rs: -------------------------------------------------------------------------------- 1 | use crate::app::app::App; 2 | 3 | // block binds when a popup is shown 4 | pub fn block_binds(app: &mut App) -> bool { 5 | if app.show_nav 6 | || app.show_fzf 7 | || app.show_help 8 | || app.show_popup 9 | || app.show_bookmark 10 | || app.show_ops_menu 11 | { 12 | return true; 13 | } 14 | 15 | false 16 | } 17 | -------------------------------------------------------------------------------- /src/ui/display/bookmarks.rs: -------------------------------------------------------------------------------- 1 | use crate::app::app::App; 2 | use crate::ui::input::nav::abbreviate_path; 3 | use ratatui::backend::Backend; 4 | use ratatui::layout::Alignment; 5 | use ratatui::widgets::Clear; 6 | use ratatui::widgets::ListItem; 7 | use ratatui::{ 8 | layout::Rect, 9 | style::{Color, Modifier, Style}, 10 | widgets::{Block, Borders, List}, 11 | Frame, 12 | }; 13 | 14 | pub fn render_bookmark(f: &mut Frame, app: &mut App, size: Rect) { 15 | if app.show_bookmark { 16 | let block_width = f.size().width / 3; 17 | let block_height = f.size().height / 3; 18 | let block_x = (size.width - block_width) / 2; 19 | let block_y = (size.height - block_height) / 2; 20 | 21 | let area = Rect::new(block_x, block_y, block_width, block_height); 22 | 23 | let bookmark_block = Block::default() 24 | .style(Style::default().add_modifier(Modifier::BOLD)) 25 | .border_style( 26 | Style::default() 27 | .fg(Color::LightYellow) 28 | .add_modifier(Modifier::BOLD), 29 | ) 30 | .borders(Borders::ALL) 31 | .title_alignment(Alignment::Center); 32 | 33 | f.render_widget(Clear, area); 34 | f.render_widget(bookmark_block, area); 35 | 36 | let bookmark_text = app 37 | .bookmarked_dirs 38 | .items 39 | .iter() 40 | .map(|i| ListItem::new(abbreviate_path(i))) 41 | .collect::>(); 42 | 43 | let bookmark_list = List::new(bookmark_text) 44 | .block( 45 | Block::default() 46 | .borders(Borders::ALL) 47 | .title("Bookmarks") 48 | .title_alignment(Alignment::Center), 49 | ) 50 | .highlight_style( 51 | Style::default() 52 | .add_modifier(Modifier::BOLD) 53 | .fg(Color::LightGreen), 54 | ) 55 | .highlight_symbol("> "); 56 | 57 | let bookmark_list_area = 58 | Rect::new(block_x + 1, block_y + 1, block_width - 2, block_height - 2); 59 | 60 | f.render_stateful_widget( 61 | bookmark_list, 62 | bookmark_list_area, 63 | &mut app.bookmarked_dirs.state, 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/ui/display/contents.rs: -------------------------------------------------------------------------------- 1 | use crate::app::app::App; 2 | use ratatui::backend::Backend; 3 | use ratatui::widgets::Paragraph; 4 | use ratatui::{ 5 | layout::Rect, 6 | style::Style, 7 | widgets::{Block, Borders, List, ListItem}, 8 | Frame, 9 | }; 10 | use std::fs::File; 11 | use std::io::BufRead; 12 | use std::io::BufReader; 13 | use std::io::{Read, Seek, SeekFrom}; 14 | 15 | pub fn render_contents(f: &mut Frame, app: &mut App, chunks: &[Rect]) { 16 | let contents_block = Block::default().borders(Borders::ALL).title("Preview"); 17 | f.render_widget(contents_block, chunks[0]); 18 | 19 | let selected_file = match app.files.state.selected() { 20 | Some(i) => match app.files.items.get(i) { 21 | Some(item) => &item.0, 22 | None => "", 23 | }, 24 | None => "", 25 | }; 26 | 27 | let mut content = String::new(); 28 | let max_lines = chunks[0].height as usize - 2; 29 | 30 | if !selected_file.is_empty() { 31 | let metadata = match std::fs::metadata(selected_file) { 32 | Ok(metadata) => metadata, 33 | Err(err) => { 34 | println!("Error getting metadata for file: {}", err); 35 | return; 36 | } 37 | }; 38 | 39 | if !metadata.is_file() { 40 | println!("Not a regular file: {}", selected_file); 41 | return; 42 | } 43 | 44 | let mut file = match File::open(selected_file) { 45 | Ok(file) => file, 46 | Err(err) => { 47 | println!("Error opening file: {}", err); 48 | return; 49 | } 50 | }; 51 | 52 | if is_binary(&mut file).unwrap_or(false) { 53 | return; 54 | } 55 | 56 | let reader = BufReader::new(file); 57 | for (num, line) in reader.lines().enumerate() { 58 | if num >= max_lines { 59 | break; 60 | } 61 | 62 | match line { 63 | Ok(line) => { 64 | content.push_str(&line); 65 | content.push('\n'); 66 | } 67 | #[allow(unused_variables)] 68 | Err(err) => { 69 | continue; 70 | } 71 | } 72 | } 73 | } 74 | 75 | let items = List::new(vec![ListItem::new(content)]) 76 | .block(Block::default().borders(Borders::ALL).title("Preview")); 77 | 78 | f.render_stateful_widget(items, chunks[0], &mut app.files.state); 79 | 80 | if selected_file.is_empty() { 81 | let placeholder = Paragraph::new("No file selected") 82 | .style(Style::default()) 83 | .block(Block::default().borders(Borders::ALL).title("Preview")); 84 | f.render_widget(placeholder, chunks[0]); 85 | } 86 | } 87 | 88 | fn is_binary(file: &mut File) -> std::io::Result { 89 | let mut buffer = vec![0; 1024]; 90 | file.read(&mut buffer)?; 91 | 92 | let total_bytes = buffer.len(); 93 | let ascii_bytes = buffer.iter().filter(|b| b.is_ascii()).count(); 94 | let result = (ascii_bytes as f32 / total_bytes as f32) < 0.9; 95 | 96 | file.seek(SeekFrom::Start(0))?; 97 | 98 | Ok(result) 99 | } 100 | -------------------------------------------------------------------------------- /src/ui/display/details.rs: -------------------------------------------------------------------------------- 1 | use crate::app::app::App; 2 | use crate::ui::display::pane::selected_pane_content; 3 | use ratatui::backend::Backend; 4 | use ratatui::layout::Alignment; 5 | use ratatui::widgets::Paragraph; 6 | use ratatui::{ 7 | layout::{Constraint, Direction, Layout, Rect}, 8 | style::{Color, Style}, 9 | text::Spans, 10 | widgets::{Block, Borders, List, ListItem}, 11 | Frame, 12 | }; 13 | 14 | pub fn render_details( 15 | f: &mut Frame, 16 | app: &mut App, 17 | chunks: &[Rect], 18 | cur_dir: String, 19 | cur_du: String, 20 | ) { 21 | let details_chunks = Layout::default() 22 | .direction(Direction::Horizontal) 23 | .constraints( 24 | [ 25 | Constraint::Percentage(33), 26 | Constraint::Percentage(33), 27 | Constraint::Percentage(33), 28 | ] 29 | .as_ref(), 30 | ) 31 | .split(chunks[0]); 32 | 33 | // to fit the path in the pane 34 | let mut cur_dir = cur_dir; 35 | let components: Vec<&str> = cur_dir.split("/").collect(); 36 | if components.len() > 4 { 37 | let last_three: Vec<&str> = components.into_iter().rev().take(3).collect(); 38 | cur_dir = format!( 39 | ".../{}", 40 | last_three 41 | .into_iter() 42 | .rev() 43 | .collect::>() 44 | .join("/") 45 | ); 46 | } 47 | 48 | let selected_file = match app.files.state.selected() { 49 | Some(i) => match app.files.items.get(i) { 50 | Some(item) => &item.0, 51 | None => "", 52 | }, 53 | None => "", 54 | }; 55 | 56 | let selected_dir = match app.dirs.state.selected() { 57 | Some(i) => &app.dirs.items[i].0, 58 | None => "", 59 | }; 60 | 61 | let selected_item = if !selected_file.is_empty() { 62 | selected_pane_content(&selected_file.to_string()) 63 | } else if !selected_dir.is_empty() { 64 | selected_pane_content(&selected_dir.to_string()) 65 | } else { 66 | vec![ListItem::new(Spans::from("No file selected"))] 67 | }; 68 | 69 | let items = List::new(selected_item).block( 70 | Block::default() 71 | .borders(Borders::ALL) 72 | .border_style(Style::default().fg(Color::LightYellow)) 73 | .title("Details") 74 | .title_alignment(Alignment::Left), 75 | ); 76 | f.render_widget(items, details_chunks[0]); 77 | 78 | let pwd_paragraph = Paragraph::new(cur_dir) 79 | .block( 80 | Block::default() 81 | .borders(Borders::ALL) 82 | .border_style(Style::default().fg(Color::LightYellow)) 83 | .title_alignment(Alignment::Center) 84 | .title("Current Directory"), 85 | ) 86 | .alignment(Alignment::Center); 87 | f.render_widget(pwd_paragraph, details_chunks[1]); 88 | 89 | let du_paragraph = Paragraph::new(cur_du) 90 | .block( 91 | Block::default() 92 | .borders(Borders::ALL) 93 | .border_style(Style::default().fg(Color::LightYellow)) 94 | .title("Disk Usage") 95 | .title_alignment(Alignment::Right), 96 | ) 97 | .alignment(Alignment::Right); 98 | f.render_widget(du_paragraph, details_chunks[2]); 99 | } 100 | -------------------------------------------------------------------------------- /src/ui/display/files_dirs.rs: -------------------------------------------------------------------------------- 1 | use super::pane::get_pwd; 2 | use crate::app::app::App; 3 | use ratatui::backend::Backend; 4 | use ratatui::layout::Alignment; 5 | use ratatui::{ 6 | layout::Rect, 7 | style::{Color, Modifier, Style}, 8 | widgets::{Block, Borders, List, ListItem}, 9 | Frame, 10 | }; 11 | 12 | pub fn render_files(f: &mut Frame, app: &mut App, chunks: &[Rect]) { 13 | let files_block = Block::default() 14 | .borders(Borders::ALL) 15 | .title("Files") 16 | .title_alignment(Alignment::Center); 17 | f.render_widget(files_block, chunks[0]); 18 | 19 | app.update_files(); 20 | 21 | let files = app 22 | .files 23 | .items 24 | .iter() 25 | .map(|i| ListItem::new(i.0.clone())) 26 | .collect::>(); 27 | 28 | let items = List::new(files) 29 | .block( 30 | Block::default() 31 | .borders(Borders::ALL) 32 | .title("Files") 33 | .title_alignment(Alignment::Center), 34 | ) 35 | .highlight_symbol("> ") 36 | .highlight_style( 37 | Style::default() 38 | .fg(Color::LightGreen) 39 | .add_modifier(Modifier::BOLD), 40 | ); 41 | 42 | if app.files.items.len() == 0 { 43 | let empty = vec![ListItem::new("No files in this directory")]; 44 | let empty_list = List::new(empty) 45 | .block(Block::default().borders(Borders::ALL).title("Files")) 46 | .highlight_symbol("> ") 47 | .highlight_style( 48 | Style::default() 49 | .fg(Color::LightGreen) 50 | .add_modifier(Modifier::BOLD), 51 | ); 52 | f.render_stateful_widget(empty_list, chunks[0], &mut app.files.state); 53 | return; 54 | } 55 | 56 | f.render_stateful_widget(items, chunks[0], &mut app.files.state); 57 | 58 | if app.files.state.selected().is_some() { 59 | let files_block = Block::default() 60 | .borders(Borders::ALL) 61 | .title("Files") 62 | .title_alignment(Alignment::Center) 63 | .border_style(Style::default().fg(Color::LightBlue)); 64 | f.render_widget(files_block, chunks[0]); 65 | } else { 66 | let files_block = Block::default() 67 | .borders(Borders::ALL) 68 | .title("Files") 69 | .title_alignment(Alignment::Center) 70 | .border_style(Style::default().fg(Color::White)); 71 | f.render_widget(files_block, chunks[0]); 72 | } 73 | } 74 | 75 | pub fn render_dirs(f: &mut Frame, app: &mut App, chunks: &[Rect]) { 76 | app.cur_dir = get_pwd(); 77 | 78 | let dirs_block = Block::default() 79 | .borders(Borders::ALL) 80 | .title("Directories") 81 | .title_alignment(Alignment::Center); 82 | f.render_widget(dirs_block, chunks[0]); 83 | 84 | let dirs = app 85 | .dirs 86 | .items 87 | .iter() 88 | .map(|i| ListItem::new(i.0.clone())) 89 | .collect::>(); 90 | 91 | app.update_dirs(); 92 | 93 | let items = List::new(dirs) 94 | .block( 95 | Block::default() 96 | .borders(Borders::ALL) 97 | .title("Directories") 98 | .title_alignment(Alignment::Center), 99 | ) 100 | .highlight_symbol("> ") 101 | .highlight_style( 102 | Style::default() 103 | .fg(Color::LightGreen) 104 | .add_modifier(Modifier::BOLD), 105 | ); 106 | 107 | f.render_stateful_widget(items, chunks[0], &mut app.dirs.state); 108 | 109 | if app.dirs.state.selected().is_some() { 110 | let dirs_block = Block::default() 111 | .borders(Borders::ALL) 112 | .title("Directories") 113 | .title_alignment(Alignment::Center) 114 | .border_style(Style::default().fg(Color::LightBlue)); 115 | f.render_widget(dirs_block, chunks[0]); 116 | } else { 117 | let dirs_block = Block::default() 118 | .borders(Borders::ALL) 119 | .title("Directories") 120 | .title_alignment(Alignment::Center) 121 | .border_style(Style::default().fg(Color::White)); 122 | f.render_widget(dirs_block, chunks[0]); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/ui/display/help.rs: -------------------------------------------------------------------------------- 1 | use crate::app::app::App; 2 | use ratatui::backend::Backend; 3 | use ratatui::layout::Alignment; 4 | use ratatui::widgets::Clear; 5 | use ratatui::widgets::Paragraph; 6 | use ratatui::{ 7 | layout::Rect, 8 | style::{Color, Modifier, Style}, 9 | widgets::{Block, Borders}, 10 | Frame, 11 | }; 12 | 13 | pub fn render_help(f: &mut Frame, app: &mut App, size: Rect) { 14 | if app.show_help { 15 | let block_width = f.size().width / 2; 16 | let block_height = f.size().height; 17 | let block_x = (size.width - block_width) / 2; 18 | let block_y = (size.height - block_height) / 2; 19 | 20 | let area = Rect::new(block_x, block_y, block_width, block_height); 21 | 22 | let help_block = Block::default() 23 | .style(Style::default().add_modifier(Modifier::BOLD)) 24 | .border_style( 25 | Style::default() 26 | .fg(Color::LightYellow) 27 | .add_modifier(Modifier::BOLD), 28 | ) 29 | .borders(Borders::ALL) 30 | .title_alignment(Alignment::Center); 31 | 32 | f.render_widget(Clear, area); 33 | f.render_widget(help_block, area); 34 | 35 | let mut help_text = String::new(); 36 | // formatted like this because tui rs doesn't render it nicely 37 | help_text.push_str( 38 | "Traverse 2023 39 | ESC | q: Quit the application. 40 | 1: Select the Files pane. 41 | 2: Select the Directories pane. 42 | 43 | j: Select the next item in the current pane. 44 | k: Select the previous item in the current pane. 45 | 46 | n: Create a new file or directory, depending on the current pane. 47 | CTRL + d: Delete the selected file or directory, (to bin). 48 | r: Rename the selected file or directory. 49 | 50 | f: Navigate to a directory using a relative or absolute path. 51 | x: Extract the selected archive, to the current directory. 52 | w: Open fzf. 53 | 54 | c: Append the selected file or directory to the move/copy buffer. 55 | p: Opens the move/copy buffer menu, (enter on any option is in 56 | relation to your current directory). 57 | 58 | b: Shows bookmarks menu. 59 | z: Add current directory to bookmarks. 60 | 61 | CTRL + n: 'Next' item in results. 62 | CTRL + p: 'Previous' item in results.", 63 | ); 64 | 65 | let help_para = Paragraph::new(help_text) 66 | .block( 67 | Block::default() 68 | .borders(Borders::ALL) 69 | .title("Bindings") 70 | .title_alignment(Alignment::Center), 71 | ) 72 | .alignment(Alignment::Center); 73 | 74 | f.render_widget(help_para, area); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/ui/display/inputs.rs: -------------------------------------------------------------------------------- 1 | use crate::app::app::App; 2 | use ratatui::backend::Backend; 3 | use ratatui::layout::Alignment; 4 | use ratatui::widgets::{Clear, Paragraph}; 5 | use ratatui::{ 6 | layout::Rect, 7 | style::{Color, Modifier, Style}, 8 | widgets::{Block, Borders}, 9 | Frame, 10 | }; 11 | 12 | pub fn render_input(f: &mut Frame, app: &mut App, size: Rect, input: &mut String) { 13 | if app.show_popup { 14 | let block = Block::default() 15 | .title("Name") 16 | .borders(Borders::ALL) 17 | .title_alignment(Alignment::Center); 18 | 19 | let input_box_width = 30; 20 | let input_box_height = 3; 21 | let input_box_x = (size.width - input_box_width) / 4 + 3; 22 | let input_box_y = (size.height - input_box_height) / 1; 23 | 24 | let area = Rect::new(input_box_x, input_box_y, input_box_width, input_box_height); 25 | 26 | f.render_widget(Clear, area); 27 | f.render_widget(block, area); 28 | 29 | let input_box = Paragraph::new(input.clone()) 30 | .style(Style::default()) 31 | .block( 32 | Block::default() 33 | .title("Input") 34 | .borders(Borders::ALL) 35 | .border_style(Style::default().fg(Color::LightBlue)), 36 | ) 37 | .style(Style::default().add_modifier(Modifier::BOLD)) 38 | .alignment(Alignment::Left); 39 | f.render_widget(input_box, area); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/ui/display/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod bookmarks; 2 | pub mod contents; 3 | pub mod details; 4 | pub mod files_dirs; 5 | pub mod inputs; 6 | pub mod navs; 7 | pub mod pane; 8 | pub mod render; 9 | pub mod help; 10 | pub mod block; 11 | pub mod ops; 12 | -------------------------------------------------------------------------------- /src/ui/display/navs.rs: -------------------------------------------------------------------------------- 1 | use crate::app::app::App; 2 | use ratatui::backend::Backend; 3 | use ratatui::layout::Alignment; 4 | use ratatui::widgets::ListItem; 5 | use ratatui::widgets::{Clear, Paragraph}; 6 | use ratatui::{ 7 | layout::Rect, 8 | style::{Color, Modifier, Style}, 9 | widgets::{Block, Borders, List}, 10 | Frame, 11 | }; 12 | 13 | pub fn render_navigator( 14 | f: &mut Frame, 15 | app: &mut App, 16 | size: Rect, 17 | input: &mut String, 18 | ) { 19 | if app.show_nav { 20 | let block = Block::default() 21 | .title("Navigator") 22 | .borders(Borders::ALL) 23 | .title_alignment(Alignment::Center); 24 | 25 | let input_box_width = 30; 26 | let input_box_height = 3; 27 | let input_box_x = (size.width - input_box_width) / 4 + 3; 28 | let input_box_y = (size.height - input_box_height) / 1; 29 | 30 | let area = Rect::new(input_box_x, input_box_y, input_box_width, input_box_height); 31 | 32 | f.render_widget(Clear, area); 33 | f.render_widget(block, area); 34 | 35 | let input_box = Paragraph::new(input.clone()) 36 | .style(Style::default()) 37 | .block(Block::default().title("Navigator").borders(Borders::ALL)) 38 | .style( 39 | Style::default() 40 | .fg(Color::LightBlue) 41 | .add_modifier(Modifier::BOLD), 42 | ) 43 | .alignment(Alignment::Left); 44 | f.render_widget(input_box, area); 45 | } 46 | } 47 | 48 | pub fn render_fzf(f: &mut Frame, app: &mut App, size: Rect) { 49 | if app.show_fzf { 50 | let block_width = f.size().width / 1; 51 | let block_height = f.size().height / 2; 52 | let block_x = (size.width - block_width) / 2; 53 | let block_y = (size.height - block_height) / 2; 54 | 55 | let area = Rect::new(block_x, block_y, block_width, block_height); 56 | 57 | let results_block = Block::default() 58 | .style(Style::default().add_modifier(Modifier::BOLD)) 59 | .title("FZF") 60 | .border_style( 61 | Style::default() 62 | .fg(Color::LightYellow) 63 | .add_modifier(Modifier::BOLD), 64 | ) 65 | .borders(Borders::ALL) 66 | .title_alignment(Alignment::Center); 67 | 68 | f.render_widget(Clear, area); 69 | f.render_widget(results_block, area); 70 | 71 | let results_text = app 72 | .fzf_results 73 | .items 74 | .iter() 75 | .map(|i| ListItem::new(i.clone())) 76 | .collect::>(); 77 | 78 | let results_list = List::new(results_text) 79 | .block( 80 | Block::default() 81 | .borders(Borders::ALL) 82 | .title("Results") 83 | .title_alignment(Alignment::Center), 84 | ) 85 | .highlight_style( 86 | Style::default() 87 | .add_modifier(Modifier::BOLD) 88 | .fg(Color::LightGreen), 89 | ) 90 | .highlight_symbol("> "); 91 | 92 | let results_list_area = 93 | Rect::new(block_x + 1, block_y + 1, block_width - 2, block_height - 2); 94 | 95 | f.render_stateful_widget(results_list, results_list_area, &mut app.fzf_results.state); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/ui/display/ops.rs: -------------------------------------------------------------------------------- 1 | use crate::app::app::App; 2 | use crate::ui::input::nav::abbreviate_path; 3 | use ratatui::backend::Backend; 4 | use ratatui::layout::Alignment; 5 | use ratatui::widgets::Clear; 6 | use ratatui::widgets::ListItem; 7 | use ratatui::{ 8 | layout::Rect, 9 | style::{Color, Modifier, Style}, 10 | widgets::{Block, Borders, List}, 11 | Frame, 12 | }; 13 | 14 | pub fn render_ops_menu(f: &mut Frame, app: &mut App, size: Rect) { 15 | if app.show_ops_menu { 16 | let block_width = f.size().width / 2; 17 | let block_height = f.size().height / 3; 18 | let block_x = (size.width - block_width) / 2; 19 | let block_y = (size.height - block_height) / 2; 20 | 21 | let area = Rect::new(block_x, block_y, block_width, block_height); 22 | let half_area = Rect::new(block_x, block_y, block_width / 2, block_height); 23 | 24 | let ops_menu_block = Block::default() 25 | .style(Style::default().add_modifier(Modifier::BOLD)) 26 | .border_style( 27 | Style::default() 28 | .fg(Color::LightYellow) 29 | .add_modifier(Modifier::BOLD), 30 | ) 31 | .title_alignment(Alignment::Center); 32 | 33 | f.render_widget(Clear, area); 34 | f.render_widget(ops_menu_block, half_area); 35 | 36 | let ops_text = app 37 | .ops_menu 38 | .items 39 | .iter() 40 | .map(|i| ListItem::new(i.clone())) 41 | .collect::>(); 42 | 43 | let ops_list = List::new(ops_text) 44 | .block( 45 | Block::default() 46 | .borders(Borders::ALL) 47 | .title("Operations") 48 | .border_style( 49 | Style::default() 50 | .fg(Color::LightYellow) 51 | .add_modifier(Modifier::BOLD), 52 | ) 53 | .title_alignment(Alignment::Center), 54 | ) 55 | .highlight_style( 56 | Style::default() 57 | .add_modifier(Modifier::BOLD) 58 | .fg(Color::LightGreen), 59 | ) 60 | .highlight_symbol("> "); 61 | 62 | let ops_menu_list_area = Rect::new( 63 | block_x + 1, 64 | block_y + 1, 65 | (block_width / 2) - 2, 66 | block_height - 2, 67 | ); 68 | 69 | f.render_stateful_widget(ops_list, ops_menu_list_area, &mut app.ops_menu.state); 70 | 71 | let mut selected_files_clone = app.selected_files.clone(); 72 | 73 | if selected_files_clone.is_empty() { 74 | selected_files_clone.push("No files staged for operation".to_string()); 75 | } 76 | 77 | let selected_files_text = selected_files_clone 78 | .iter() 79 | .map(|i| ListItem::new(abbreviate_path(i))) 80 | .collect::>(); 81 | 82 | let selected_files_list = List::new(selected_files_text).block( 83 | Block::default() 84 | .style(Style::default().add_modifier(Modifier::BOLD)) 85 | .title("Currently Selected Files/Dirs") 86 | .border_style( 87 | Style::default() 88 | .fg(Color::LightYellow) 89 | .add_modifier(Modifier::BOLD), 90 | ) 91 | .borders(Borders::ALL) 92 | .title_alignment(Alignment::Center), 93 | ); 94 | 95 | let selected_files_list_area = Rect::new( 96 | block_x + block_width / 2 + 1, 97 | block_y + 1, 98 | block_width / 2 - 2, 99 | block_height - 2, 100 | ); 101 | 102 | f.render_stateful_widget( 103 | selected_files_list, 104 | selected_files_list_area, 105 | &mut app.ops_menu.state, 106 | ); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/ui/display/pane.rs: -------------------------------------------------------------------------------- 1 | use ratatui::{text::Spans, widgets::ListItem}; 2 | use std::path::Path; 3 | use std::process::Command; 4 | use sysinfo::{DiskExt, System, SystemExt}; 5 | 6 | #[allow(dead_code)] 7 | enum PaneState { 8 | Selected, 9 | NotSelected, 10 | } 11 | 12 | #[allow(dead_code)] 13 | struct SelectedPane { 14 | pub state: PaneState, 15 | pub items: Vec, 16 | } 17 | 18 | pub fn selected_pane_content(input: &String) -> Vec> { 19 | let file = Path::new(&input); 20 | 21 | if let Some(ext) = file.extension() { 22 | if ext == "png" || ext == "jpg" { 23 | let output = Command::new("file") 24 | .arg(file) 25 | .output() 26 | .expect("failed to execute process"); 27 | 28 | let output_str = String::from_utf8_lossy(&output.stdout); 29 | let mut items = Vec::new(); 30 | 31 | for line in output_str.lines() { 32 | items.push(ListItem::new(Spans::from(line.to_string()))); 33 | } 34 | 35 | return items; 36 | } 37 | 38 | if ext == "mp4" || ext == "mp3" { 39 | let output = Command::new("ffprobe") 40 | .arg(file) 41 | .output() 42 | .expect("failed to execute process"); 43 | 44 | if output.stdout.is_empty() { 45 | return vec![ListItem::new(Spans::from("Cannot get details of file"))]; 46 | } 47 | 48 | let output_str = String::from_utf8_lossy(&output.stdout); 49 | let mut items = Vec::new(); 50 | 51 | for line in output_str.lines() { 52 | items.push(ListItem::new(Spans::from(line.to_string()))); 53 | } 54 | 55 | return items; 56 | } 57 | } 58 | 59 | if file.is_dir() { 60 | let mut items = Vec::new(); 61 | let output = Command::new("ls") 62 | .arg("-ld") 63 | .arg(file) 64 | .output() 65 | .expect("failed to execute process"); 66 | 67 | let output_str = String::from_utf8_lossy(&output.stdout); 68 | let output_vec = output_str.split_whitespace().collect::>(); 69 | 70 | let perms = output_vec[0]; 71 | let owner = output_vec[2]; 72 | let size = output_vec[4]; 73 | let date = output_vec[5]; 74 | let day = output_vec[6]; 75 | let time = output_vec[7]; 76 | 77 | if output.stdout.is_empty() { 78 | return vec![ListItem::new(Spans::from("No directory selected"))]; 79 | } 80 | 81 | #[allow(unused_variables)] 82 | for line in output_str.lines() { 83 | items.push(ListItem::new(Spans::from(format!( 84 | "{} {} {} {} {}/{}", 85 | perms, owner, size, date, day, time 86 | )))); 87 | } 88 | 89 | return items; 90 | } 91 | 92 | if file.is_file() { 93 | let mut items = Vec::new(); 94 | let output = Command::new("ls") 95 | .arg("-lh") 96 | .arg(file) 97 | .output() 98 | .expect("failed to execute process"); 99 | 100 | let output_str = String::from_utf8_lossy(&output.stdout); 101 | let output_vec: Vec<&str> = output_str.split_whitespace().collect(); 102 | 103 | if output.stdout.is_empty() { 104 | return vec![ListItem::new(Spans::from("No file selected"))]; 105 | } 106 | 107 | let perms = output_vec[0]; 108 | let owner = output_vec[2]; 109 | let size = output_vec[4]; 110 | let date = output_vec[5]; 111 | let day = output_vec[6]; 112 | let time = output_vec[7]; 113 | 114 | #[allow(unused_variables)] 115 | for line in output_str.lines() { 116 | items.push(ListItem::new(Spans::from(format!( 117 | "{} {} {} {} {}/{}", 118 | perms, owner, size, date, day, time 119 | )))); 120 | } 121 | return items; 122 | } 123 | 124 | vec![ListItem::new(Spans::from("No file selected"))] 125 | } 126 | 127 | pub fn get_pwd() -> String { 128 | let output = Command::new("pwd") 129 | .output() 130 | .expect("failed to execute process"); 131 | 132 | let output_str = String::from_utf8_lossy(&output.stdout); 133 | format!("{}", output_str) 134 | } 135 | 136 | pub fn get_du() -> String { 137 | let mut sys = System::new_all(); 138 | sys.refresh_all(); 139 | 140 | if let Some(disk) = sys.disks().get(0) { 141 | let total = disk.total_space(); 142 | let free = disk.available_space(); 143 | let used = total - free; 144 | 145 | return format!( 146 | "{} used / {} total / {} free ", 147 | convert_bytes(used), 148 | convert_bytes(total), 149 | convert_bytes(free), 150 | ); 151 | } else { 152 | return String::from("No disk found"); 153 | } 154 | } 155 | 156 | fn convert_bytes(bytes: u64) -> String { 157 | let mut bytes = bytes; 158 | let mut unit = 0; 159 | 160 | while bytes > 1024 { 161 | bytes /= 1024; 162 | unit += 1; 163 | } 164 | 165 | let unit = match unit { 166 | 0 => "B", 167 | 1 => "KB", 168 | 2 => "MB", 169 | 3 => "GB", 170 | 4 => "TB", 171 | _ => "PB", 172 | }; 173 | 174 | format!("{} {}", bytes, unit) 175 | } 176 | -------------------------------------------------------------------------------- /src/ui/display/render.rs: -------------------------------------------------------------------------------- 1 | use crate::app::app::App; 2 | use crate::ui::display::*; 3 | use crate::ui::input::run_app::run_app; 4 | use anyhow::Result; 5 | use crossterm::{ 6 | event::{DisableMouseCapture, EnableMouseCapture}, 7 | execute, 8 | terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, 9 | }; 10 | use ratatui::backend::Backend; 11 | use ratatui::{ 12 | backend::CrosstermBackend, 13 | layout::{Constraint, Direction, Layout, Rect}, 14 | terminal::Terminal, 15 | Frame, 16 | }; 17 | use std::io; 18 | use std::time::Duration; 19 | 20 | pub fn init() -> Result<()> { 21 | enable_raw_mode()?; 22 | 23 | let stdout = io::stdout(); 24 | execute!(io::stdout(), EnterAlternateScreen, EnableMouseCapture,)?; 25 | 26 | let backend = CrosstermBackend::new(stdout); 27 | let mut terminal = Terminal::new(backend)?; 28 | let tick_rate = Duration::from_millis(250); 29 | let mut app = App::new(); 30 | app.op_menu_init(); 31 | let res = run_app(&mut terminal, app, tick_rate); 32 | 33 | disable_raw_mode()?; 34 | 35 | execute!( 36 | terminal.backend_mut(), 37 | LeaveAlternateScreen, 38 | DisableMouseCapture, 39 | )?; 40 | 41 | terminal.show_cursor()?; 42 | 43 | if let Err(e) = res { 44 | eprintln!("{}", e); 45 | } 46 | 47 | Ok(()) 48 | } 49 | 50 | pub fn render(f: &mut Frame, app: &mut App, input: &mut String) { 51 | let cur_dir = app.cur_dir.clone(); 52 | let cur_du = app.cur_du.clone(); 53 | 54 | let size = f.size(); 55 | let fifty_percent = (size.width as f32 * 0.5) as u16; 56 | let ninety_percent = (size.height as f32 * 0.9) as u16; 57 | 58 | let chunks = Layout::default() 59 | .direction(Direction::Horizontal) 60 | .constraints([Constraint::Length(fifty_percent), Constraint::Min(1)]) 61 | .split(size); 62 | 63 | let left_chunks = Layout::default() 64 | .direction(Direction::Vertical) 65 | .constraints([Constraint::Length(ninety_percent), Constraint::Min(1)]) 66 | .split(chunks[0]); 67 | 68 | let right_chunks = Layout::default() 69 | .direction(Direction::Vertical) 70 | .constraints([ 71 | Constraint::Percentage(45), 72 | Constraint::Percentage(45), 73 | Constraint::Percentage(10), 74 | ]) 75 | .split(chunks[1]); 76 | 77 | let bottom_chunks = bottom_chunks(f); 78 | 79 | contents::render_contents(f, app, &left_chunks); 80 | files_dirs::render_files(f, app, &[right_chunks[0]]); 81 | files_dirs::render_dirs(f, app, &[right_chunks[1]]); 82 | details::render_details(f, app, &bottom_chunks, cur_dir, cur_du); 83 | inputs::render_input(f, app, size, input); 84 | navs::render_navigator(f, app, size, input); 85 | navs::render_fzf(f, app, size); 86 | help::render_help(f, app, size); 87 | bookmarks::render_bookmark(f, app, size); 88 | ops::render_ops_menu(f, app, size); 89 | } 90 | 91 | fn bottom_chunks(f: &mut Frame) -> Vec { 92 | let size = f.size(); 93 | let ninety_percent = (size.height as f32 * 0.9) as u16; 94 | 95 | let chunks = Layout::default() 96 | .direction(Direction::Vertical) 97 | .constraints([Constraint::Length(ninety_percent), Constraint::Min(1)]) 98 | .split(size); 99 | 100 | let bottom_chunks = Layout::default() 101 | .direction(Direction::Horizontal) 102 | .constraints([Constraint::Percentage(f.size().width / 2)]) 103 | .split(chunks[1]); 104 | 105 | (bottom_chunks).to_vec() 106 | } 107 | -------------------------------------------------------------------------------- /src/ui/input/bookmark.rs: -------------------------------------------------------------------------------- 1 | use super::run_app::Command; 2 | use crate::app::app::App; 3 | use dirs::config_dir; 4 | use std::fs::OpenOptions; 5 | use std::io::prelude::*; 6 | 7 | pub fn handle_bookmark(app: &mut App) { 8 | if app.last_command != Some(Command::Bookmark) { 9 | read_bookmark(app); 10 | app.show_bookmark = true; 11 | app.last_command = Some(Command::Bookmark); 12 | } 13 | } 14 | 15 | pub fn read_bookmark(app: &mut App) { 16 | if !config_dir() 17 | .unwrap() 18 | .join("traverse/bookmarks.txt") 19 | .exists() 20 | { 21 | return; 22 | } 23 | 24 | let file = std::fs::File::open(config_dir().unwrap().join("traverse/bookmarks.txt")).unwrap(); 25 | let reader = std::io::BufReader::new(file); 26 | 27 | for line in reader.lines() { 28 | let line = line.unwrap(); 29 | 30 | if app.bookmarked_dirs.items.contains(&line) { 31 | continue; 32 | } else { 33 | app.bookmarked_dirs.items.push(line); 34 | } 35 | } 36 | 37 | if app.bookmarked_dirs.items.len() > 0 { 38 | app.bookmarked_dirs.state.select(Some(0)); 39 | } 40 | 41 | app.bookmarked_dirs.items.sort(); 42 | } 43 | 44 | pub fn add_bookmark(app: &mut App) { 45 | let path = std::env::current_dir().unwrap(); 46 | let dirs = app.bookmarked_dirs.items.clone(); 47 | 48 | if dirs.contains(&path.to_str().unwrap().to_string()) { 49 | return; 50 | } else { 51 | app.bookmarked_dirs 52 | .items 53 | .push(path.to_str().unwrap().to_string()); 54 | 55 | let mut data = path.to_str().unwrap().to_string(); 56 | data = format!("{}\n", data); 57 | 58 | if !config_dir() 59 | .unwrap() 60 | .join("traverse/bookmarks.txt") 61 | .exists() 62 | { 63 | std::fs::create_dir_all(config_dir().unwrap().join("traverse")).unwrap(); 64 | std::fs::File::create(config_dir().unwrap().join("traverse/bookmarks.txt")).unwrap(); 65 | } 66 | 67 | let mut file = OpenOptions::new() 68 | .append(true) 69 | .open(config_dir().unwrap().join("traverse/bookmarks.txt")) 70 | .expect("Unable to open file"); 71 | 72 | file.write_all(data.as_bytes()) 73 | .expect("Unable to write data"); 74 | } 75 | 76 | if app.bookmarked_dirs.items.len() > 0 { 77 | app.bookmarked_dirs.state.select(Some(0)); 78 | } 79 | 80 | app.update_bookmarks(); 81 | } 82 | 83 | pub fn delete_bookmark(app: &mut App) { 84 | let index = app.bookmarked_dirs.state.selected().unwrap(); 85 | let path = std::env::current_dir().unwrap(); 86 | let dirs = app.bookmarked_dirs.items.clone(); 87 | 88 | if dirs.contains(&path.to_str().unwrap().to_string()) { 89 | app.bookmarked_dirs.items.remove(index); 90 | 91 | let mut file = OpenOptions::new() 92 | .write(true) 93 | .truncate(true) 94 | .open(config_dir().unwrap().join("traverse/bookmarks.txt")) 95 | .expect("Unable to open file"); 96 | 97 | for dir in &app.bookmarked_dirs.items { 98 | let mut data = dir.to_string(); 99 | data = format!("{}\n", data); 100 | 101 | file.write_all(data.as_bytes()) 102 | .expect("Unable to write data"); 103 | } 104 | 105 | file.sync_all().expect("Unable to sync data"); 106 | } 107 | 108 | app.update_bookmarks(); 109 | } 110 | -------------------------------------------------------------------------------- /src/ui/input/extract.rs: -------------------------------------------------------------------------------- 1 | use crate::app::app::App; 2 | use flate2::read::GzDecoder; 3 | use std::io::Read; 4 | use std::{fs::File, io::Cursor}; 5 | use tar::Archive; 6 | 7 | pub fn extract_tar(app: &mut App, file: &str) -> Result<(), std::io::Error> { 8 | let path = std::env::current_dir().unwrap().join(file); 9 | 10 | let tar_gz = File::open(path)?; 11 | let tar = GzDecoder::new(tar_gz); 12 | let mut archive = Archive::new(tar); 13 | archive.unpack(".")?; 14 | 15 | app.update_files(); 16 | app.update_dirs(); 17 | 18 | Ok(()) 19 | } 20 | 21 | pub fn extract_zip(app: &mut App, file: &str) -> Result<(), std::io::Error> { 22 | let target_dir = std::env::current_dir().unwrap(); 23 | 24 | let mut file = File::open(file)?; 25 | let mut buffer = Vec::new(); 26 | file.read_to_end(&mut buffer)?; 27 | 28 | let reader = Cursor::new(buffer); 29 | 30 | zip_extract::extract(reader, &target_dir, true).unwrap(); 31 | 32 | app.update_files(); 33 | app.update_dirs(); 34 | 35 | Ok(()) 36 | } 37 | -------------------------------------------------------------------------------- /src/ui/input/file_ops.rs: -------------------------------------------------------------------------------- 1 | use super::{extract::*, run_app::Command}; 2 | use crate::{app::app::App, ui::display::block::block_binds}; 3 | 4 | pub fn handle_new_file(app: &mut App, input_active: &mut bool) { 5 | if app.files.state.selected().is_some() { 6 | if (*input_active == false && app.last_command != Some(Command::CreateFile)) 7 | || (*input_active == true && app.last_command.is_none()) 8 | { 9 | *input_active = true; 10 | app.show_popup = true; 11 | app.last_command = Some(Command::CreateFile); 12 | } 13 | } else if app.dirs.state.selected().is_some() { 14 | if (*input_active == false && app.last_command != Some(Command::CreateDir)) 15 | || (*input_active == true && app.last_command.is_none()) 16 | { 17 | *input_active = true; 18 | app.show_popup = true; 19 | app.last_command = Some(Command::CreateDir); 20 | } 21 | } 22 | } 23 | 24 | pub fn handle_delete(app: &mut App) { 25 | if let Some(selected) = app.files.state.selected() { 26 | if selected == 0 && app.files.items.len() == 0 { 27 | return; 28 | } else { 29 | let file = app.files.items[selected].0.clone(); 30 | 31 | trash::delete(&file).unwrap(); 32 | app.update_files(); 33 | 34 | if selected >= app.files.items.len() { 35 | app.files 36 | .state 37 | .select(Some(app.files.items.len().saturating_sub(1))); 38 | } 39 | } 40 | } else if let Some(selected) = app.dirs.state.selected() { 41 | let dir = app.dirs.items[selected].0.clone(); 42 | 43 | if dir == "../" { 44 | return; 45 | } else { 46 | trash::delete(&dir).unwrap(); 47 | app.update_dirs(); 48 | 49 | if selected >= app.dirs.items.len() { 50 | app.dirs 51 | .state 52 | .select(Some(app.dirs.items.len().saturating_sub(1))); 53 | } 54 | } 55 | } 56 | } 57 | 58 | pub fn handle_rename(app: &mut App, input: &mut String, input_active: &mut bool) { 59 | if block_binds(app) { 60 | return; 61 | } 62 | 63 | if app.files.state.selected().is_some() { 64 | if *input_active == false && app.last_command != Some(Command::RenameFile) { 65 | *input_active = true; 66 | app.show_popup = true; 67 | app.last_command = Some(Command::RenameFile); 68 | 69 | *input = app.files.items[app.files.state.selected().unwrap()] 70 | .0 71 | .clone(); 72 | } 73 | } else if app.dirs.state.selected().is_some() { 74 | if app.dirs.items[app.dirs.state.selected().unwrap()].0 == "../" { 75 | return; 76 | } else { 77 | if *input_active == false && app.last_command != Some(Command::RenameDir) { 78 | *input_active = true; 79 | app.show_popup = true; 80 | app.last_command = Some(Command::RenameDir); 81 | *input = app.dirs.items[app.dirs.state.selected().unwrap()].0.clone(); 82 | } 83 | } 84 | } 85 | } 86 | 87 | pub fn extract(app: &mut App) { 88 | if app.files.state.selected().is_some() { 89 | let file = app.files.items[app.files.state.selected().unwrap()] 90 | .0 91 | .clone(); 92 | 93 | if file.ends_with(".tar.gz") { 94 | extract_tar(app, &file).expect("Failed to extract tar file"); 95 | } else if file.ends_with(".zip") { 96 | extract_zip(app, &file).expect("Failed to extract zip file"); 97 | } 98 | } 99 | } 100 | 101 | fn add_dir(app: &mut App) { 102 | let selected = app.dirs.state.selected().unwrap(); 103 | let cur_dir = std::env::current_dir().unwrap(); 104 | 105 | app.selected_files.push(format!( 106 | "{}/{}", 107 | cur_dir.display(), 108 | app.dirs.items[selected].0 109 | )); 110 | } 111 | 112 | fn add_file(app: &mut App) { 113 | let selected = app.files.state.selected().unwrap(); 114 | let cur_dir = std::env::current_dir().unwrap(); 115 | let selected = format!("{}/{}", cur_dir.display(), app.files.items[selected].0); 116 | 117 | for file in app.selected_files.clone() { 118 | if file == selected { 119 | return; 120 | } 121 | } 122 | 123 | app.selected_files.push(selected); 124 | } 125 | 126 | pub fn add_to_selected(app: &mut App) { 127 | if app.dirs.state.selected().is_some() { 128 | add_dir(app); 129 | } else if app.files.state.selected().is_some() { 130 | add_file(app); 131 | } 132 | } 133 | 134 | pub fn handle_paste_or_move(app: &mut App) { 135 | // TODO: 136 | // copying files into directories where they already exist 137 | // (error box maybe for global error handling) 138 | if app.selected_files.len() == 0 { 139 | if app.selected_dirs.len() == 0 { 140 | return; 141 | } 142 | } 143 | 144 | if let Some(selected) = app.ops_menu.state.selected() { 145 | let mut cur_dir = std::env::current_dir().unwrap(); 146 | match selected { 147 | 0 => { 148 | // copy 149 | for file in app.selected_files.clone() { 150 | for cur_files in app.files.items.clone() { 151 | if file == cur_files.0 { 152 | continue; 153 | } 154 | 155 | std::process::Command::new("cp") 156 | .arg("-r") 157 | .arg(&file) 158 | .arg(&cur_dir) 159 | .spawn() 160 | .expect("Failed to copy file"); 161 | 162 | app.show_ops_menu = false; 163 | app.last_command = None; 164 | app.selected_files = vec![]; 165 | app.selected_dirs = vec![]; 166 | 167 | app.update_files(); 168 | app.update_dirs(); 169 | 170 | app.files 171 | .state 172 | .select(Some(app.files.items.len().saturating_sub(1))); 173 | } 174 | 175 | cur_dir = std::env::current_dir().unwrap(); 176 | } 177 | } 178 | 1 => { 179 | // move 180 | for file in app.selected_files.clone() { 181 | for cur_files in app.files.items.clone() { 182 | if file == cur_files.0 { 183 | continue; 184 | } 185 | 186 | std::process::Command::new("mv") 187 | .arg(&file) 188 | .arg(&cur_dir) 189 | .spawn() 190 | .expect("Failed to move file"); 191 | 192 | app.show_ops_menu = false; 193 | app.last_command = None; 194 | app.selected_files = vec![]; 195 | app.selected_dirs = vec![]; 196 | 197 | app.update_files(); 198 | app.update_dirs(); 199 | 200 | app.files 201 | .state 202 | .select(Some(app.files.items.len().saturating_sub(1))); 203 | 204 | cur_dir = std::env::current_dir().unwrap(); 205 | } 206 | } 207 | } 208 | 2 => { 209 | // clear selection 210 | app.last_command = None; 211 | app.show_ops_menu = false; 212 | 213 | app.selected_files = vec![]; 214 | app.selected_dirs = vec![]; 215 | 216 | app.update_files(); 217 | app.update_dirs(); 218 | } 219 | _ => {} 220 | } 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /src/ui/input/help.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::app::app::App; 3 | use crate::ui::display::block::block_binds; 4 | use run_app::Command; 5 | 6 | pub fn handle_help(app: &mut App) { 7 | if block_binds(app) { 8 | return; 9 | } 10 | 11 | if app.last_command != Some(Command::ShowHelp) { 12 | app.show_help = true; 13 | app.last_command = Some(Command::ShowHelp); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/ui/input/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod bookmark; 2 | pub mod extract; 3 | pub mod file_ops; 4 | pub mod help; 5 | pub mod movement; 6 | pub mod nav; 7 | pub mod run_app; 8 | pub mod stateful_list; 9 | pub mod submit; 10 | -------------------------------------------------------------------------------- /src/ui/input/movement.rs: -------------------------------------------------------------------------------- 1 | use crate::{app::app::App, ui::display::block::block_binds}; 2 | 3 | pub fn handle_movement(app: &mut App, key: char) { 4 | if block_binds(app) { 5 | return; 6 | } 7 | 8 | if app.files.state.selected().is_some() { 9 | if app.files.items.len() > 1 { 10 | if key == 'j' { 11 | app.files.next(); 12 | } else { 13 | app.files.previous(); 14 | } 15 | } 16 | } else if app.dirs.state.selected().is_some() { 17 | if app.dirs.items.len() > 1 { 18 | if key == 'j' { 19 | app.dirs.next(); 20 | } else { 21 | app.dirs.previous(); 22 | } 23 | } 24 | } 25 | } 26 | 27 | pub fn handle_fzf_movement(app: &mut App, idx: isize) { 28 | let results = app.fzf_results.items.len(); 29 | 30 | if results > 0 { 31 | if app.fzf_results.state.selected().is_none() { 32 | app.fzf_results.state.select(Some(0)); 33 | } else { 34 | let selected = app.fzf_results.state.selected().unwrap() as isize; 35 | let new_selected = (selected + idx).rem_euclid(results as isize) as usize; 36 | 37 | app.fzf_results.state.select(Some(new_selected)); 38 | } 39 | } 40 | } 41 | 42 | pub fn handle_bookmark_movement(app: &mut App, idx: isize) { 43 | let results = app.bookmarked_dirs.items.len(); 44 | 45 | if results > 0 { 46 | if app.bookmarked_dirs.state.selected().is_none() { 47 | app.bookmarked_dirs.state.select(Some(0)); 48 | } else { 49 | let selected = app.bookmarked_dirs.state.selected().unwrap() as isize; 50 | let new_selected = (selected + idx).rem_euclid(results as isize) as usize; 51 | 52 | app.bookmarked_dirs.state.select(Some(new_selected)); 53 | } 54 | } 55 | } 56 | 57 | pub fn handle_pane_switching(app: &mut App, key: u8) { 58 | if block_binds(app) { 59 | return; 60 | } 61 | 62 | if key == 1 { 63 | app.files.state.select(Some(0)); 64 | app.dirs.state.select(None); 65 | } else if key == 2 { 66 | app.dirs.state.select(Some(0)); 67 | app.files.state.select(None); 68 | } 69 | } 70 | 71 | pub fn handle_ops_menu_movement(app: &mut App, idx: isize) { 72 | let results = app.ops_menu.items.len(); 73 | 74 | if results > 0 { 75 | if app.ops_menu.state.selected().is_none() { 76 | app.ops_menu.state.select(Some(0)); 77 | } else { 78 | let selected = app.ops_menu.state.selected().unwrap() as isize; 79 | let new_selected = (selected + idx).rem_euclid(results as isize) as usize; 80 | 81 | app.ops_menu.state.select(Some(new_selected)); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/ui/input/nav.rs: -------------------------------------------------------------------------------- 1 | use super::stateful_list::StatefulList; 2 | use super::*; 3 | use crate::app::app::App; 4 | use crate::ui::display::pane::get_pwd; 5 | use crossterm::{ 6 | cursor::MoveTo, cursor::Show, execute, style::Print, style::ResetColor, terminal::Clear, 7 | terminal::ClearType, 8 | }; 9 | use run_app::Command; 10 | use std::io::stdout; 11 | use std::io::Write; 12 | use std::path::PathBuf; 13 | use std::process::exit; 14 | use sublime_fuzzy::best_match; 15 | use walkdir::WalkDir; 16 | 17 | pub fn handle_nav(app: &mut App, input_active: &mut bool) { 18 | if !*input_active { 19 | app.show_nav = true; 20 | *input_active = true; 21 | app.last_command = Some(Command::ShowNav); 22 | } 23 | } 24 | 25 | fn fzf(app: &mut App, input: &mut String) -> Vec { 26 | let query = input.clone(); 27 | let dir = app.cur_dir.clone(); 28 | let dir = dir.trim_end_matches('\n'); 29 | 30 | let mut result = Vec::new(); 31 | 32 | for entry in WalkDir::new(dir) { 33 | let entry = entry.unwrap(); 34 | 35 | if entry.file_type().is_file() { 36 | let mut should_exclude = false; 37 | 38 | for dir in &app.excluded_directories { 39 | if entry.path().to_str().unwrap().contains(dir) { 40 | should_exclude = true; 41 | break; 42 | } 43 | } 44 | 45 | if should_exclude { 46 | continue; 47 | } 48 | 49 | if entry.path().to_str().unwrap().contains(".git") || !app.show_hidden { 50 | if !app.show_hidden { 51 | if entry.file_name().to_str().unwrap().starts_with('.') { 52 | continue; 53 | } 54 | } else { 55 | continue; 56 | } 57 | } 58 | 59 | let filename = entry.file_name().to_str().unwrap().to_string(); 60 | 61 | if let Some(matched) = best_match(&query, &filename) { 62 | if matched.score() > 0 { 63 | result.push(entry.path().to_path_buf()); 64 | } 65 | } 66 | } 67 | } 68 | 69 | result 70 | } 71 | 72 | pub fn handle_fzf(app: &mut App, input: &mut String, input_active: &mut bool) { 73 | app.show_fzf = true; 74 | app.show_popup = true; 75 | app.last_command = Some(Command::ShowFzf); 76 | 77 | *input_active = true; 78 | 79 | let result = fzf(app, input); 80 | 81 | app.fzf_results = StatefulList::with_items( 82 | result 83 | .iter() 84 | .map(|x| x.to_str().unwrap().to_string()) 85 | .collect(), 86 | ); 87 | } 88 | 89 | pub fn abbreviate_path(path: &str) -> String { 90 | let components: Vec<&str> = path.split("/").collect(); 91 | if components.len() > 4 { 92 | let last_three: Vec<&str> = components.into_iter().rev().take(3).collect(); 93 | format!( 94 | ".../{}", 95 | last_three 96 | .into_iter() 97 | .rev() 98 | .collect::>() 99 | .join("/") 100 | ) 101 | } else { 102 | path.to_string() 103 | } 104 | } 105 | 106 | pub fn output_cur_dir() { 107 | crossterm::terminal::disable_raw_mode().unwrap(); 108 | 109 | let dir = get_pwd(); 110 | 111 | execute!( 112 | stdout(), 113 | Clear(ClearType::All), 114 | ResetColor, 115 | Show, 116 | MoveTo(0, 0), 117 | Print(format!( 118 | "To navigate to traverse's last directory: cd {}", 119 | dir 120 | )) 121 | ) 122 | .unwrap(); 123 | 124 | stdout().flush().unwrap(); 125 | exit(0); 126 | } 127 | -------------------------------------------------------------------------------- /src/ui/input/run_app.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::app::app::App; 3 | use crate::ui::display::block::block_binds; 4 | use crate::ui::display::render::render; 5 | use anyhow::Result; 6 | use crossterm::event::{self, Event, KeyCode, KeyEventKind}; 7 | use ratatui::backend::Backend; 8 | use ratatui::terminal::Terminal; 9 | use std::process::Command as SysCommand; 10 | use std::time::Duration; 11 | 12 | #[derive(PartialEq)] 13 | pub enum Command { 14 | CreateFile, 15 | CreateDir, 16 | RenameFile, 17 | RenameDir, 18 | ShowNav, 19 | ShowFzf, 20 | ShowHelp, 21 | Bookmark, 22 | } 23 | 24 | pub fn run_app( 25 | terminal: &mut Terminal, 26 | mut app: App, 27 | tick_rate: Duration, 28 | ) -> Result<()> { 29 | let mut last_tick = std::time::Instant::now(); 30 | let mut input = String::new(); 31 | let mut input_active = false; 32 | 33 | loop { 34 | terminal.draw(|f| render(f, &mut app, &mut input))?; 35 | 36 | let timeout = tick_rate 37 | .checked_sub(last_tick.elapsed()) 38 | .unwrap_or_else(|| Duration::from_secs(0)); 39 | 40 | if crossterm::event::poll(timeout)? { 41 | if let Event::Key(key) = event::read()? { 42 | if key.kind == KeyEventKind::Press { 43 | match key.code { 44 | // EXIT 45 | KeyCode::Char('c') 46 | if key.modifiers.contains(event::KeyModifiers::CONTROL) => 47 | { 48 | SysCommand::new("reset").status().unwrap_or_else(|_| { 49 | panic!("Failed to reset terminal"); 50 | }); 51 | nav::output_cur_dir(); 52 | 53 | return Ok(()); 54 | } 55 | KeyCode::Esc => { 56 | if app.show_popup 57 | || app.show_nav 58 | || app.show_fzf 59 | || app.show_bookmark 60 | || app.show_help 61 | || app.show_ops_menu 62 | { 63 | input_active = false; 64 | app.show_popup = false; 65 | app.show_nav = false; 66 | app.show_fzf = false; 67 | app.last_command = None; 68 | app.show_bookmark = false; 69 | app.show_help = false; 70 | app.show_ops_menu = false; 71 | input.clear(); 72 | } else { 73 | SysCommand::new("reset").status().unwrap_or_else(|_| { 74 | panic!("Failed to reset terminal"); 75 | }); 76 | nav::output_cur_dir(); 77 | 78 | return Ok(()); 79 | } 80 | } 81 | KeyCode::Char('q') => { 82 | if app.show_fzf || app.show_nav || input_active { 83 | input.push('q'); 84 | } else { 85 | if app.show_popup 86 | || app.show_nav 87 | || app.show_fzf 88 | || app.show_bookmark 89 | || app.show_help 90 | || app.show_ops_menu 91 | { 92 | input_active = false; 93 | app.show_popup = false; 94 | app.show_nav = false; 95 | app.show_fzf = false; 96 | app.last_command = None; 97 | app.show_bookmark = false; 98 | app.show_help = false; 99 | app.show_ops_menu = false; 100 | input.clear(); 101 | } else { 102 | SysCommand::new("reset").status().unwrap_or_else(|_| { 103 | panic!("Failed to reset terminal"); 104 | }); 105 | 106 | nav::output_cur_dir(); 107 | return Ok(()); 108 | } 109 | } 110 | } 111 | 112 | // PANE SWITCHING 113 | KeyCode::Char('1') => { 114 | if input_active { 115 | input.push('1'); 116 | } else { 117 | movement::handle_pane_switching(&mut app, 1); 118 | } 119 | } 120 | KeyCode::Char('2') => { 121 | if input_active { 122 | input.push('2'); 123 | } else { 124 | movement::handle_pane_switching(&mut app, 2); 125 | } 126 | } 127 | 128 | // MOVEMENT 129 | KeyCode::Char('j') | KeyCode::Down => { 130 | if input_active { 131 | input.push('j'); 132 | } else { 133 | movement::handle_movement(&mut app, 'j'); 134 | } 135 | } 136 | KeyCode::Char('k') | KeyCode::Up => { 137 | if input_active { 138 | input.push('k'); 139 | } else { 140 | movement::handle_movement(&mut app, 'k'); 141 | } 142 | } 143 | KeyCode::Char('n') 144 | if key.modifiers.contains(event::KeyModifiers::CONTROL) => 145 | { 146 | if app.show_fzf && block_binds(&mut app) { 147 | movement::handle_fzf_movement(&mut app, 1); 148 | } else if app.show_bookmark { 149 | movement::handle_bookmark_movement(&mut app, 1); 150 | } else if app.show_ops_menu { 151 | movement::handle_ops_menu_movement(&mut app, 1); 152 | } 153 | } 154 | KeyCode::Char('p') 155 | if key.modifiers.contains(event::KeyModifiers::CONTROL) => 156 | { 157 | if app.show_fzf && block_binds(&mut app) { 158 | movement::handle_fzf_movement(&mut app, -1); 159 | } else if app.show_bookmark { 160 | movement::handle_bookmark_movement(&mut app, -1); 161 | } else if app.show_ops_menu { 162 | movement::handle_ops_menu_movement(&mut app, -1); 163 | } 164 | } 165 | 166 | // BOOKMARKS 167 | KeyCode::Char('z') => { 168 | if input_active { 169 | input.push('z'); 170 | } else { 171 | bookmark::add_bookmark(&mut app); 172 | } 173 | } 174 | KeyCode::Char('b') => { 175 | if input_active { 176 | input.push('b'); 177 | } else { 178 | bookmark::handle_bookmark(&mut app); 179 | } 180 | } 181 | 182 | // FILE OPS 183 | KeyCode::Char('n') => { 184 | if input_active { 185 | input.push('n'); 186 | } else { 187 | file_ops::handle_new_file(&mut app, &mut input_active); 188 | } 189 | } 190 | KeyCode::Char('d') 191 | if key.modifiers.contains(event::KeyModifiers::CONTROL) => 192 | { 193 | if app.show_bookmark { 194 | bookmark::delete_bookmark(&mut app); 195 | } else { 196 | file_ops::handle_delete(&mut app); 197 | } 198 | } 199 | KeyCode::Char('c') => { 200 | if input_active { 201 | input.push('c'); 202 | } else { 203 | file_ops::add_to_selected(&mut app); 204 | } 205 | } 206 | KeyCode::Char('p') => { 207 | if input_active { 208 | input.push('p'); 209 | } else { 210 | if app.files.state.selected().is_some() 211 | || app.dirs.state.selected().is_some() 212 | { 213 | app.show_ops_menu = true; 214 | } 215 | } 216 | } 217 | KeyCode::Char('x') => { 218 | if input_active { 219 | input.push('x'); 220 | } else { 221 | file_ops::extract(&mut app); 222 | } 223 | } 224 | KeyCode::Char('r') => { 225 | if input_active { 226 | input.push('r'); 227 | } else { 228 | file_ops::handle_rename(&mut app, &mut input, &mut input_active); 229 | } 230 | } 231 | 232 | // HELP MENU 233 | KeyCode::Char('?') => { 234 | if input_active { 235 | input.push('?'); 236 | } else if app.show_help { 237 | app.show_help = false; 238 | app.last_command = None; 239 | } else { 240 | help::handle_help(&mut app); 241 | } 242 | } 243 | 244 | // FZF & NAV 245 | KeyCode::Char('w') => { 246 | if input_active { 247 | input.push('w'); 248 | } else { 249 | nav::handle_fzf(&mut app, &mut input, &mut input_active); 250 | } 251 | } 252 | KeyCode::Char('f') => { 253 | if input_active { 254 | input.push('f'); 255 | } else { 256 | nav::handle_nav(&mut app, &mut input_active); 257 | } 258 | } 259 | 260 | // SUBMIT 261 | KeyCode::Enter => { 262 | if app.show_fzf { 263 | submit::handle_open_fzf_result( 264 | &mut app, 265 | &mut input, 266 | &mut input_active, 267 | ); 268 | } else if input_active { 269 | submit::handle_submit(&mut app, &mut input, &mut input_active); 270 | } else if app.show_bookmark { 271 | submit::handle_open_bookmark(&mut app); 272 | } else if app.show_ops_menu { 273 | if app.ops_menu.state.selected().is_none() { 274 | app.show_ops_menu = false; 275 | app.last_command = None; 276 | } else { 277 | file_ops::handle_paste_or_move(&mut app); 278 | } 279 | } else { 280 | submit::handle_submit(&mut app, &mut input, &mut input_active); 281 | } 282 | } 283 | 284 | // BACKSPACE 285 | KeyCode::Backspace => { 286 | if input_active { 287 | input.pop(); 288 | if app.show_fzf { 289 | nav::handle_fzf(&mut app, &mut input, &mut input_active); 290 | } 291 | } 292 | } 293 | 294 | // OTHER CHARACTERS 295 | KeyCode::Char(c) => { 296 | if input_active { 297 | input.push(c); 298 | 299 | if app.last_command == Some(Command::ShowFzf) { 300 | nav::handle_fzf(&mut app, &mut input, &mut input_active); 301 | } 302 | 303 | if app.show_fzf { 304 | nav::handle_fzf(&mut app, &mut input, &mut input_active); 305 | } 306 | } 307 | } 308 | _ => {} 309 | } 310 | } 311 | } 312 | } 313 | 314 | if last_tick.elapsed() >= tick_rate { 315 | last_tick = std::time::Instant::now(); 316 | } 317 | } 318 | } 319 | -------------------------------------------------------------------------------- /src/ui/input/stateful_list.rs: -------------------------------------------------------------------------------- 1 | use ratatui::widgets::ListState; 2 | 3 | pub struct StatefulList { 4 | pub state: ListState, 5 | pub items: Vec, 6 | } 7 | 8 | impl StatefulList { 9 | pub fn with_items(items: Vec) -> StatefulList { 10 | StatefulList { 11 | state: ListState::default(), 12 | items, 13 | } 14 | } 15 | 16 | pub fn next(&mut self) { 17 | let i = match self.state.selected() { 18 | Some(i) => { 19 | if i >= self.items.len() - 1 { 20 | 0 21 | } else { 22 | i + 1 23 | } 24 | } 25 | None => 0, 26 | }; 27 | 28 | self.state.select(Some(i)); 29 | } 30 | 31 | pub fn previous(&mut self) { 32 | let i = match self.state.selected() { 33 | Some(i) => { 34 | if i == 0 { 35 | self.items.len() - 1 36 | } else { 37 | i - 1 38 | } 39 | } 40 | None => 0, 41 | }; 42 | 43 | self.state.select(Some(i)); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/ui/input/submit.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::app::app::App; 3 | use crate::ui::display::pane::get_pwd; 4 | use run_app::Command; 5 | use std::path::PathBuf; 6 | 7 | pub fn handle_submit(app: &mut App, input: &mut String, input_active: &mut bool) { 8 | if *input_active { 9 | if app.last_command == Some(Command::CreateFile) { 10 | App::create_file(&input); 11 | app.update_files(); 12 | app.update_dirs(); 13 | app.last_command = None; 14 | } else if app.last_command == Some(Command::CreateDir) { 15 | App::create_dir(&input); 16 | app.update_dirs(); 17 | app.update_files(); 18 | app.last_command = None; 19 | } else if app.last_command == Some(Command::RenameFile) { 20 | let file = app.files.items[app.files.state.selected().unwrap()] 21 | .0 22 | .clone(); 23 | 24 | std::fs::rename(file, input.clone()).unwrap(); 25 | app.update_files(); 26 | app.update_dirs(); 27 | app.last_command = None; 28 | } else if app.last_command == Some(Command::RenameDir) { 29 | let dir = app.dirs.items[app.dirs.state.selected().unwrap()].0.clone(); 30 | 31 | std::fs::rename(dir, input.clone()).unwrap(); 32 | app.update_dirs(); 33 | app.update_files(); 34 | app.last_command = None; 35 | } else if app.last_command == Some(Command::ShowNav) { 36 | let path = Some(PathBuf::from(input.clone())); 37 | 38 | if path.is_some() { 39 | std::env::set_current_dir(path.unwrap()).unwrap(); 40 | 41 | app.cur_dir = std::env::current_dir() 42 | .unwrap() 43 | .to_str() 44 | .unwrap() 45 | .to_string(); 46 | 47 | app.update_files(); 48 | app.update_dirs(); 49 | 50 | app.show_popup = false; 51 | app.show_nav = false; 52 | app.last_command = None; 53 | } else { 54 | app.show_popup = false; 55 | app.show_nav = false; 56 | app.last_command = None; 57 | } 58 | } 59 | 60 | input.clear(); 61 | app.show_popup = false; 62 | *input_active = false; 63 | app.update_files(); 64 | app.update_dirs(); 65 | } else { 66 | if app.dirs.state.selected().is_some() { 67 | if app.dirs.items[app.dirs.state.selected().unwrap()].0 == "../" { 68 | let mut path = std::env::current_dir().unwrap(); 69 | path.pop(); 70 | 71 | std::env::set_current_dir(path).unwrap(); 72 | app.cur_dir = get_pwd(); 73 | } else { 74 | let dir = app.dirs.items[app.dirs.state.selected().unwrap()].0.clone(); 75 | 76 | std::env::set_current_dir(dir).unwrap(); 77 | app.cur_dir = get_pwd(); 78 | } 79 | app.update_files(); 80 | app.update_dirs(); 81 | 82 | if let Some(selected) = app.files.state.selected() { 83 | if selected >= app.files.items.len() { 84 | if !app.files.items.is_empty() { 85 | app.files 86 | .state 87 | .select(Some(app.files.items.len().saturating_sub(1))); 88 | } else { 89 | app.files.state.select(None); 90 | } 91 | } 92 | } 93 | app.dirs.state.select(Some(0)); 94 | } 95 | } 96 | } 97 | 98 | pub fn handle_open_fzf_result(app: &mut App, input: &mut String, input_active: &mut bool) { 99 | if app.fzf_results.state.selected().is_none() { 100 | return; 101 | } else { 102 | if app.fzf_results.items[app.fzf_results.state.selected().unwrap()] 103 | .clone() 104 | .is_ascii() 105 | { 106 | let path = app.fzf_results.items[app.fzf_results.state.selected().unwrap()].clone(); 107 | let path = PathBuf::from(path).parent().unwrap().to_path_buf(); 108 | std::env::set_current_dir(path).unwrap(); 109 | 110 | app.update_files(); 111 | app.update_dirs(); 112 | 113 | app.show_fzf = false; 114 | app.show_popup = false; 115 | app.last_command = None; 116 | 117 | input.clear(); 118 | *input_active = false; 119 | 120 | app.fzf_results.state.select(None); 121 | app.selected_fzf_result = 0; 122 | 123 | app.files.state.select(Some(0)); 124 | app.dirs.state.select(None); 125 | 126 | app.cur_dir = get_pwd(); 127 | } 128 | } 129 | } 130 | 131 | pub fn handle_open_bookmark(app: &mut App) { 132 | if app.bookmarked_dirs.state.selected().is_none() { 133 | return; 134 | } else { 135 | if app.bookmarked_dirs.items[app.bookmarked_dirs.state.selected().unwrap()] 136 | .clone() 137 | .is_ascii() 138 | { 139 | let path = 140 | app.bookmarked_dirs.items[app.bookmarked_dirs.state.selected().unwrap()].clone(); 141 | let path = PathBuf::from(path); 142 | std::env::set_current_dir(path).unwrap(); 143 | 144 | app.update_files(); 145 | app.update_dirs(); 146 | 147 | app.show_bookmark = false; 148 | app.show_popup = false; 149 | app.last_command = None; 150 | 151 | app.files.state.select(Some(0)); 152 | app.dirs.state.select(None); 153 | 154 | app.cur_dir = get_pwd(); 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/ui/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod display; 2 | pub mod input; 3 | -------------------------------------------------------------------------------- /traverse.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmcg310/navoxide/0c693b26dd446c2b5dd870936ebac9443b9ac71d/traverse.gif --------------------------------------------------------------------------------