├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENCE.txt ├── README.md ├── assets ├── basic.gif ├── file_preview.gif └── light_and_dark_theme.gif ├── examples ├── basic.rs ├── file_preview.rs └── light_and_dark_theme.rs └── src ├── file_explorer.rs ├── input ├── crossterm.rs ├── mod.rs ├── termion.rs └── termwiz.rs ├── lib.rs └── widget.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "ahash" 7 | version = "0.8.8" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "42cd52102d3df161c77a887b608d7a4897d7cc112886a9537b738a887a03aaff" 10 | dependencies = [ 11 | "cfg-if", 12 | "once_cell", 13 | "version_check", 14 | "zerocopy", 15 | ] 16 | 17 | [[package]] 18 | name = "aho-corasick" 19 | version = "1.1.2" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" 22 | dependencies = [ 23 | "memchr", 24 | ] 25 | 26 | [[package]] 27 | name = "allocator-api2" 28 | version = "0.2.16" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" 31 | 32 | [[package]] 33 | name = "anyhow" 34 | version = "1.0.79" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" 37 | 38 | [[package]] 39 | name = "atomic" 40 | version = "0.5.3" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" 43 | 44 | [[package]] 45 | name = "autocfg" 46 | version = "1.1.0" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 49 | 50 | [[package]] 51 | name = "base64" 52 | version = "0.21.7" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" 55 | 56 | [[package]] 57 | name = "bit-set" 58 | version = "0.5.3" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" 61 | dependencies = [ 62 | "bit-vec", 63 | ] 64 | 65 | [[package]] 66 | name = "bit-vec" 67 | version = "0.6.3" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" 70 | 71 | [[package]] 72 | name = "bitflags" 73 | version = "1.3.2" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 76 | 77 | [[package]] 78 | name = "bitflags" 79 | version = "2.9.0" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" 82 | 83 | [[package]] 84 | name = "block-buffer" 85 | version = "0.10.4" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 88 | dependencies = [ 89 | "generic-array", 90 | ] 91 | 92 | [[package]] 93 | name = "cassowary" 94 | version = "0.3.0" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" 97 | 98 | [[package]] 99 | name = "castaway" 100 | version = "0.2.3" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" 103 | dependencies = [ 104 | "rustversion", 105 | ] 106 | 107 | [[package]] 108 | name = "cc" 109 | version = "1.0.83" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" 112 | dependencies = [ 113 | "libc", 114 | ] 115 | 116 | [[package]] 117 | name = "cfg-if" 118 | version = "1.0.0" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 121 | 122 | [[package]] 123 | name = "compact_str" 124 | version = "0.8.0" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "6050c3a16ddab2e412160b31f2c871015704239bca62f72f6e5f0be631d3f644" 127 | dependencies = [ 128 | "castaway", 129 | "cfg-if", 130 | "itoa", 131 | "rustversion", 132 | "ryu", 133 | "static_assertions", 134 | ] 135 | 136 | [[package]] 137 | name = "cpufeatures" 138 | version = "0.2.12" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" 141 | dependencies = [ 142 | "libc", 143 | ] 144 | 145 | [[package]] 146 | name = "crossterm" 147 | version = "0.28.1" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" 150 | dependencies = [ 151 | "bitflags 2.9.0", 152 | "crossterm_winapi", 153 | "mio", 154 | "parking_lot", 155 | "rustix 0.38.34", 156 | "signal-hook", 157 | "signal-hook-mio", 158 | "winapi", 159 | ] 160 | 161 | [[package]] 162 | name = "crossterm_winapi" 163 | version = "0.9.1" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" 166 | dependencies = [ 167 | "winapi", 168 | ] 169 | 170 | [[package]] 171 | name = "crypto-common" 172 | version = "0.1.6" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 175 | dependencies = [ 176 | "generic-array", 177 | "typenum", 178 | ] 179 | 180 | [[package]] 181 | name = "csscolorparser" 182 | version = "0.6.2" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "eb2a7d3066da2de787b7f032c736763eb7ae5d355f81a68bab2675a96008b0bf" 185 | dependencies = [ 186 | "lab", 187 | "phf", 188 | ] 189 | 190 | [[package]] 191 | name = "deltae" 192 | version = "0.3.2" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "5729f5117e208430e437df2f4843f5e5952997175992d1414f94c57d61e270b4" 195 | 196 | [[package]] 197 | name = "digest" 198 | version = "0.10.7" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 201 | dependencies = [ 202 | "block-buffer", 203 | "crypto-common", 204 | ] 205 | 206 | [[package]] 207 | name = "dirs" 208 | version = "4.0.0" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" 211 | dependencies = [ 212 | "dirs-sys", 213 | ] 214 | 215 | [[package]] 216 | name = "dirs-sys" 217 | version = "0.3.7" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" 220 | dependencies = [ 221 | "libc", 222 | "redox_users", 223 | "winapi", 224 | ] 225 | 226 | [[package]] 227 | name = "educe" 228 | version = "0.6.0" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" 231 | dependencies = [ 232 | "enum-ordinalize", 233 | "proc-macro2", 234 | "quote", 235 | "syn 2.0.49", 236 | ] 237 | 238 | [[package]] 239 | name = "either" 240 | version = "1.10.0" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" 243 | 244 | [[package]] 245 | name = "enum-ordinalize" 246 | version = "4.3.0" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "fea0dcfa4e54eeb516fe454635a95753ddd39acda650ce703031c6973e315dd5" 249 | dependencies = [ 250 | "enum-ordinalize-derive", 251 | ] 252 | 253 | [[package]] 254 | name = "enum-ordinalize-derive" 255 | version = "4.3.1" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" 258 | dependencies = [ 259 | "proc-macro2", 260 | "quote", 261 | "syn 2.0.49", 262 | ] 263 | 264 | [[package]] 265 | name = "errno" 266 | version = "0.3.11" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" 269 | dependencies = [ 270 | "libc", 271 | "windows-sys", 272 | ] 273 | 274 | [[package]] 275 | name = "euclid" 276 | version = "0.22.9" 277 | source = "registry+https://github.com/rust-lang/crates.io-index" 278 | checksum = "87f253bc5c813ca05792837a0ff4b3a580336b224512d48f7eda1d7dd9210787" 279 | dependencies = [ 280 | "num-traits", 281 | ] 282 | 283 | [[package]] 284 | name = "fancy-regex" 285 | version = "0.11.0" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2" 288 | dependencies = [ 289 | "bit-set", 290 | "regex", 291 | ] 292 | 293 | [[package]] 294 | name = "fastrand" 295 | version = "2.3.0" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 298 | 299 | [[package]] 300 | name = "filedescriptor" 301 | version = "0.8.2" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "7199d965852c3bac31f779ef99cbb4537f80e952e2d6aa0ffeb30cce00f4f46e" 304 | dependencies = [ 305 | "libc", 306 | "thiserror", 307 | "winapi", 308 | ] 309 | 310 | [[package]] 311 | name = "finl_unicode" 312 | version = "1.2.0" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" 315 | 316 | [[package]] 317 | name = "fixedbitset" 318 | version = "0.4.2" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" 321 | 322 | [[package]] 323 | name = "fnv" 324 | version = "1.0.7" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 327 | 328 | [[package]] 329 | name = "generic-array" 330 | version = "0.14.7" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 333 | dependencies = [ 334 | "typenum", 335 | "version_check", 336 | ] 337 | 338 | [[package]] 339 | name = "getrandom" 340 | version = "0.2.12" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" 343 | dependencies = [ 344 | "cfg-if", 345 | "libc", 346 | "wasi 0.11.0+wasi-snapshot-preview1", 347 | ] 348 | 349 | [[package]] 350 | name = "getrandom" 351 | version = "0.3.2" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" 354 | dependencies = [ 355 | "cfg-if", 356 | "libc", 357 | "r-efi", 358 | "wasi 0.14.2+wasi-0.2.4", 359 | ] 360 | 361 | [[package]] 362 | name = "hashbrown" 363 | version = "0.14.3" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" 366 | dependencies = [ 367 | "ahash", 368 | "allocator-api2", 369 | ] 370 | 371 | [[package]] 372 | name = "heck" 373 | version = "0.5.0" 374 | source = "registry+https://github.com/rust-lang/crates.io-index" 375 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 376 | 377 | [[package]] 378 | name = "hermit-abi" 379 | version = "0.3.9" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 382 | 383 | [[package]] 384 | name = "hex" 385 | version = "0.4.3" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 388 | 389 | [[package]] 390 | name = "indoc" 391 | version = "2.0.5" 392 | source = "registry+https://github.com/rust-lang/crates.io-index" 393 | checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" 394 | 395 | [[package]] 396 | name = "instability" 397 | version = "0.3.2" 398 | source = "registry+https://github.com/rust-lang/crates.io-index" 399 | checksum = "b23a0c8dfe501baac4adf6ebbfa6eddf8f0c07f56b058cc1288017e32397846c" 400 | dependencies = [ 401 | "quote", 402 | "syn 2.0.49", 403 | ] 404 | 405 | [[package]] 406 | name = "itertools" 407 | version = "0.13.0" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" 410 | dependencies = [ 411 | "either", 412 | ] 413 | 414 | [[package]] 415 | name = "itoa" 416 | version = "1.0.10" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" 419 | 420 | [[package]] 421 | name = "lab" 422 | version = "0.11.0" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "bf36173d4167ed999940f804952e6b08197cae5ad5d572eb4db150ce8ad5d58f" 425 | 426 | [[package]] 427 | name = "lazy_static" 428 | version = "1.4.0" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 431 | 432 | [[package]] 433 | name = "libc" 434 | version = "0.2.172" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" 437 | 438 | [[package]] 439 | name = "libredox" 440 | version = "0.0.2" 441 | source = "registry+https://github.com/rust-lang/crates.io-index" 442 | checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607" 443 | dependencies = [ 444 | "bitflags 2.9.0", 445 | "libc", 446 | "redox_syscall", 447 | ] 448 | 449 | [[package]] 450 | name = "libredox" 451 | version = "0.1.3" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" 454 | dependencies = [ 455 | "bitflags 2.9.0", 456 | "libc", 457 | ] 458 | 459 | [[package]] 460 | name = "linux-raw-sys" 461 | version = "0.4.13" 462 | source = "registry+https://github.com/rust-lang/crates.io-index" 463 | checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" 464 | 465 | [[package]] 466 | name = "linux-raw-sys" 467 | version = "0.9.4" 468 | source = "registry+https://github.com/rust-lang/crates.io-index" 469 | checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" 470 | 471 | [[package]] 472 | name = "lock_api" 473 | version = "0.4.11" 474 | source = "registry+https://github.com/rust-lang/crates.io-index" 475 | checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" 476 | dependencies = [ 477 | "autocfg", 478 | "scopeguard", 479 | ] 480 | 481 | [[package]] 482 | name = "log" 483 | version = "0.4.20" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" 486 | 487 | [[package]] 488 | name = "lru" 489 | version = "0.12.2" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | checksum = "db2c024b41519440580066ba82aab04092b333e09066a5eb86c7c4890df31f22" 492 | dependencies = [ 493 | "hashbrown", 494 | ] 495 | 496 | [[package]] 497 | name = "mac_address" 498 | version = "1.1.5" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "4863ee94f19ed315bf3bc00299338d857d4b5bc856af375cc97d237382ad3856" 501 | dependencies = [ 502 | "nix 0.23.2", 503 | "winapi", 504 | ] 505 | 506 | [[package]] 507 | name = "memchr" 508 | version = "2.7.1" 509 | source = "registry+https://github.com/rust-lang/crates.io-index" 510 | checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" 511 | 512 | [[package]] 513 | name = "memmem" 514 | version = "0.1.1" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | checksum = "a64a92489e2744ce060c349162be1c5f33c6969234104dbd99ddb5feb08b8c15" 517 | 518 | [[package]] 519 | name = "memoffset" 520 | version = "0.6.5" 521 | source = "registry+https://github.com/rust-lang/crates.io-index" 522 | checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" 523 | dependencies = [ 524 | "autocfg", 525 | ] 526 | 527 | [[package]] 528 | name = "memoffset" 529 | version = "0.7.1" 530 | source = "registry+https://github.com/rust-lang/crates.io-index" 531 | checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" 532 | dependencies = [ 533 | "autocfg", 534 | ] 535 | 536 | [[package]] 537 | name = "minimal-lexical" 538 | version = "0.2.1" 539 | source = "registry+https://github.com/rust-lang/crates.io-index" 540 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 541 | 542 | [[package]] 543 | name = "mio" 544 | version = "1.0.2" 545 | source = "registry+https://github.com/rust-lang/crates.io-index" 546 | checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" 547 | dependencies = [ 548 | "hermit-abi", 549 | "libc", 550 | "log", 551 | "wasi 0.11.0+wasi-snapshot-preview1", 552 | "windows-sys", 553 | ] 554 | 555 | [[package]] 556 | name = "nix" 557 | version = "0.23.2" 558 | source = "registry+https://github.com/rust-lang/crates.io-index" 559 | checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" 560 | dependencies = [ 561 | "bitflags 1.3.2", 562 | "cc", 563 | "cfg-if", 564 | "libc", 565 | "memoffset 0.6.5", 566 | ] 567 | 568 | [[package]] 569 | name = "nix" 570 | version = "0.26.4" 571 | source = "registry+https://github.com/rust-lang/crates.io-index" 572 | checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" 573 | dependencies = [ 574 | "bitflags 1.3.2", 575 | "cfg-if", 576 | "libc", 577 | "memoffset 0.7.1", 578 | "pin-utils", 579 | ] 580 | 581 | [[package]] 582 | name = "nom" 583 | version = "7.1.3" 584 | source = "registry+https://github.com/rust-lang/crates.io-index" 585 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 586 | dependencies = [ 587 | "memchr", 588 | "minimal-lexical", 589 | ] 590 | 591 | [[package]] 592 | name = "num-derive" 593 | version = "0.3.3" 594 | source = "registry+https://github.com/rust-lang/crates.io-index" 595 | checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" 596 | dependencies = [ 597 | "proc-macro2", 598 | "quote", 599 | "syn 1.0.109", 600 | ] 601 | 602 | [[package]] 603 | name = "num-traits" 604 | version = "0.2.18" 605 | source = "registry+https://github.com/rust-lang/crates.io-index" 606 | checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" 607 | dependencies = [ 608 | "autocfg", 609 | ] 610 | 611 | [[package]] 612 | name = "numtoa" 613 | version = "0.1.0" 614 | source = "registry+https://github.com/rust-lang/crates.io-index" 615 | checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" 616 | 617 | [[package]] 618 | name = "once_cell" 619 | version = "1.19.0" 620 | source = "registry+https://github.com/rust-lang/crates.io-index" 621 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 622 | 623 | [[package]] 624 | name = "ordered-float" 625 | version = "4.2.0" 626 | source = "registry+https://github.com/rust-lang/crates.io-index" 627 | checksum = "a76df7075c7d4d01fdcb46c912dd17fba5b60c78ea480b475f2b6ab6f666584e" 628 | dependencies = [ 629 | "num-traits", 630 | ] 631 | 632 | [[package]] 633 | name = "parking_lot" 634 | version = "0.12.1" 635 | source = "registry+https://github.com/rust-lang/crates.io-index" 636 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 637 | dependencies = [ 638 | "lock_api", 639 | "parking_lot_core", 640 | ] 641 | 642 | [[package]] 643 | name = "parking_lot_core" 644 | version = "0.9.9" 645 | source = "registry+https://github.com/rust-lang/crates.io-index" 646 | checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" 647 | dependencies = [ 648 | "cfg-if", 649 | "libc", 650 | "redox_syscall", 651 | "smallvec", 652 | "windows-targets 0.48.5", 653 | ] 654 | 655 | [[package]] 656 | name = "paste" 657 | version = "1.0.14" 658 | source = "registry+https://github.com/rust-lang/crates.io-index" 659 | checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" 660 | 661 | [[package]] 662 | name = "pest" 663 | version = "2.7.7" 664 | source = "registry+https://github.com/rust-lang/crates.io-index" 665 | checksum = "219c0dcc30b6a27553f9cc242972b67f75b60eb0db71f0b5462f38b058c41546" 666 | dependencies = [ 667 | "memchr", 668 | "thiserror", 669 | "ucd-trie", 670 | ] 671 | 672 | [[package]] 673 | name = "pest_derive" 674 | version = "2.7.7" 675 | source = "registry+https://github.com/rust-lang/crates.io-index" 676 | checksum = "22e1288dbd7786462961e69bfd4df7848c1e37e8b74303dbdab82c3a9cdd2809" 677 | dependencies = [ 678 | "pest", 679 | "pest_generator", 680 | ] 681 | 682 | [[package]] 683 | name = "pest_generator" 684 | version = "2.7.7" 685 | source = "registry+https://github.com/rust-lang/crates.io-index" 686 | checksum = "1381c29a877c6d34b8c176e734f35d7f7f5b3adaefe940cb4d1bb7af94678e2e" 687 | dependencies = [ 688 | "pest", 689 | "pest_meta", 690 | "proc-macro2", 691 | "quote", 692 | "syn 2.0.49", 693 | ] 694 | 695 | [[package]] 696 | name = "pest_meta" 697 | version = "2.7.7" 698 | source = "registry+https://github.com/rust-lang/crates.io-index" 699 | checksum = "d0934d6907f148c22a3acbda520c7eed243ad7487a30f51f6ce52b58b7077a8a" 700 | dependencies = [ 701 | "once_cell", 702 | "pest", 703 | "sha2", 704 | ] 705 | 706 | [[package]] 707 | name = "phf" 708 | version = "0.11.2" 709 | source = "registry+https://github.com/rust-lang/crates.io-index" 710 | checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" 711 | dependencies = [ 712 | "phf_macros", 713 | "phf_shared", 714 | ] 715 | 716 | [[package]] 717 | name = "phf_codegen" 718 | version = "0.11.2" 719 | source = "registry+https://github.com/rust-lang/crates.io-index" 720 | checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" 721 | dependencies = [ 722 | "phf_generator", 723 | "phf_shared", 724 | ] 725 | 726 | [[package]] 727 | name = "phf_generator" 728 | version = "0.11.2" 729 | source = "registry+https://github.com/rust-lang/crates.io-index" 730 | checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" 731 | dependencies = [ 732 | "phf_shared", 733 | "rand", 734 | ] 735 | 736 | [[package]] 737 | name = "phf_macros" 738 | version = "0.11.2" 739 | source = "registry+https://github.com/rust-lang/crates.io-index" 740 | checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" 741 | dependencies = [ 742 | "phf_generator", 743 | "phf_shared", 744 | "proc-macro2", 745 | "quote", 746 | "syn 2.0.49", 747 | ] 748 | 749 | [[package]] 750 | name = "phf_shared" 751 | version = "0.11.2" 752 | source = "registry+https://github.com/rust-lang/crates.io-index" 753 | checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" 754 | dependencies = [ 755 | "siphasher", 756 | ] 757 | 758 | [[package]] 759 | name = "pin-utils" 760 | version = "0.1.0" 761 | source = "registry+https://github.com/rust-lang/crates.io-index" 762 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 763 | 764 | [[package]] 765 | name = "proc-macro2" 766 | version = "1.0.78" 767 | source = "registry+https://github.com/rust-lang/crates.io-index" 768 | checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" 769 | dependencies = [ 770 | "unicode-ident", 771 | ] 772 | 773 | [[package]] 774 | name = "quote" 775 | version = "1.0.35" 776 | source = "registry+https://github.com/rust-lang/crates.io-index" 777 | checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" 778 | dependencies = [ 779 | "proc-macro2", 780 | ] 781 | 782 | [[package]] 783 | name = "r-efi" 784 | version = "5.2.0" 785 | source = "registry+https://github.com/rust-lang/crates.io-index" 786 | checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" 787 | 788 | [[package]] 789 | name = "rand" 790 | version = "0.8.5" 791 | source = "registry+https://github.com/rust-lang/crates.io-index" 792 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 793 | dependencies = [ 794 | "rand_core", 795 | ] 796 | 797 | [[package]] 798 | name = "rand_core" 799 | version = "0.6.4" 800 | source = "registry+https://github.com/rust-lang/crates.io-index" 801 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 802 | 803 | [[package]] 804 | name = "ratatui" 805 | version = "0.29.0" 806 | source = "registry+https://github.com/rust-lang/crates.io-index" 807 | checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" 808 | dependencies = [ 809 | "bitflags 2.9.0", 810 | "cassowary", 811 | "compact_str", 812 | "crossterm", 813 | "indoc", 814 | "instability", 815 | "itertools", 816 | "lru", 817 | "paste", 818 | "strum", 819 | "termion", 820 | "termwiz", 821 | "unicode-segmentation", 822 | "unicode-truncate", 823 | "unicode-width 0.2.0", 824 | ] 825 | 826 | [[package]] 827 | name = "ratatui-explorer" 828 | version = "0.2.1" 829 | dependencies = [ 830 | "educe", 831 | "ratatui", 832 | ] 833 | 834 | [[package]] 835 | name = "redox_syscall" 836 | version = "0.4.1" 837 | source = "registry+https://github.com/rust-lang/crates.io-index" 838 | checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" 839 | dependencies = [ 840 | "bitflags 1.3.2", 841 | ] 842 | 843 | [[package]] 844 | name = "redox_termios" 845 | version = "0.1.3" 846 | source = "registry+https://github.com/rust-lang/crates.io-index" 847 | checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb" 848 | 849 | [[package]] 850 | name = "redox_users" 851 | version = "0.4.6" 852 | source = "registry+https://github.com/rust-lang/crates.io-index" 853 | checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" 854 | dependencies = [ 855 | "getrandom 0.2.12", 856 | "libredox 0.1.3", 857 | "thiserror", 858 | ] 859 | 860 | [[package]] 861 | name = "regex" 862 | version = "1.10.3" 863 | source = "registry+https://github.com/rust-lang/crates.io-index" 864 | checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" 865 | dependencies = [ 866 | "aho-corasick", 867 | "memchr", 868 | "regex-automata", 869 | "regex-syntax", 870 | ] 871 | 872 | [[package]] 873 | name = "regex-automata" 874 | version = "0.4.5" 875 | source = "registry+https://github.com/rust-lang/crates.io-index" 876 | checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" 877 | dependencies = [ 878 | "aho-corasick", 879 | "memchr", 880 | "regex-syntax", 881 | ] 882 | 883 | [[package]] 884 | name = "regex-syntax" 885 | version = "0.8.2" 886 | source = "registry+https://github.com/rust-lang/crates.io-index" 887 | checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" 888 | 889 | [[package]] 890 | name = "rustix" 891 | version = "0.38.34" 892 | source = "registry+https://github.com/rust-lang/crates.io-index" 893 | checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" 894 | dependencies = [ 895 | "bitflags 2.9.0", 896 | "errno", 897 | "libc", 898 | "linux-raw-sys 0.4.13", 899 | "windows-sys", 900 | ] 901 | 902 | [[package]] 903 | name = "rustix" 904 | version = "1.0.5" 905 | source = "registry+https://github.com/rust-lang/crates.io-index" 906 | checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" 907 | dependencies = [ 908 | "bitflags 2.9.0", 909 | "errno", 910 | "libc", 911 | "linux-raw-sys 0.9.4", 912 | "windows-sys", 913 | ] 914 | 915 | [[package]] 916 | name = "rustversion" 917 | version = "1.0.14" 918 | source = "registry+https://github.com/rust-lang/crates.io-index" 919 | checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" 920 | 921 | [[package]] 922 | name = "ryu" 923 | version = "1.0.16" 924 | source = "registry+https://github.com/rust-lang/crates.io-index" 925 | checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" 926 | 927 | [[package]] 928 | name = "scopeguard" 929 | version = "1.2.0" 930 | source = "registry+https://github.com/rust-lang/crates.io-index" 931 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 932 | 933 | [[package]] 934 | name = "semver" 935 | version = "0.11.0" 936 | source = "registry+https://github.com/rust-lang/crates.io-index" 937 | checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" 938 | dependencies = [ 939 | "semver-parser", 940 | ] 941 | 942 | [[package]] 943 | name = "semver-parser" 944 | version = "0.10.3" 945 | source = "registry+https://github.com/rust-lang/crates.io-index" 946 | checksum = "9900206b54a3527fdc7b8a938bffd94a568bac4f4aa8113b209df75a09c0dec2" 947 | dependencies = [ 948 | "pest", 949 | ] 950 | 951 | [[package]] 952 | name = "sha2" 953 | version = "0.10.8" 954 | source = "registry+https://github.com/rust-lang/crates.io-index" 955 | checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" 956 | dependencies = [ 957 | "cfg-if", 958 | "cpufeatures", 959 | "digest", 960 | ] 961 | 962 | [[package]] 963 | name = "signal-hook" 964 | version = "0.3.17" 965 | source = "registry+https://github.com/rust-lang/crates.io-index" 966 | checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" 967 | dependencies = [ 968 | "libc", 969 | "signal-hook-registry", 970 | ] 971 | 972 | [[package]] 973 | name = "signal-hook-mio" 974 | version = "0.2.4" 975 | source = "registry+https://github.com/rust-lang/crates.io-index" 976 | checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" 977 | dependencies = [ 978 | "libc", 979 | "mio", 980 | "signal-hook", 981 | ] 982 | 983 | [[package]] 984 | name = "signal-hook-registry" 985 | version = "1.4.1" 986 | source = "registry+https://github.com/rust-lang/crates.io-index" 987 | checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" 988 | dependencies = [ 989 | "libc", 990 | ] 991 | 992 | [[package]] 993 | name = "siphasher" 994 | version = "0.3.11" 995 | source = "registry+https://github.com/rust-lang/crates.io-index" 996 | checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" 997 | 998 | [[package]] 999 | name = "smallvec" 1000 | version = "1.13.1" 1001 | source = "registry+https://github.com/rust-lang/crates.io-index" 1002 | checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" 1003 | 1004 | [[package]] 1005 | name = "static_assertions" 1006 | version = "1.1.0" 1007 | source = "registry+https://github.com/rust-lang/crates.io-index" 1008 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 1009 | 1010 | [[package]] 1011 | name = "strsim" 1012 | version = "0.11.1" 1013 | source = "registry+https://github.com/rust-lang/crates.io-index" 1014 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 1015 | 1016 | [[package]] 1017 | name = "strum" 1018 | version = "0.26.3" 1019 | source = "registry+https://github.com/rust-lang/crates.io-index" 1020 | checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" 1021 | dependencies = [ 1022 | "strum_macros", 1023 | ] 1024 | 1025 | [[package]] 1026 | name = "strum_macros" 1027 | version = "0.26.4" 1028 | source = "registry+https://github.com/rust-lang/crates.io-index" 1029 | checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" 1030 | dependencies = [ 1031 | "heck", 1032 | "proc-macro2", 1033 | "quote", 1034 | "rustversion", 1035 | "syn 2.0.49", 1036 | ] 1037 | 1038 | [[package]] 1039 | name = "syn" 1040 | version = "1.0.109" 1041 | source = "registry+https://github.com/rust-lang/crates.io-index" 1042 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 1043 | dependencies = [ 1044 | "proc-macro2", 1045 | "quote", 1046 | "unicode-ident", 1047 | ] 1048 | 1049 | [[package]] 1050 | name = "syn" 1051 | version = "2.0.49" 1052 | source = "registry+https://github.com/rust-lang/crates.io-index" 1053 | checksum = "915aea9e586f80826ee59f8453c1101f9d1c4b3964cd2460185ee8e299ada496" 1054 | dependencies = [ 1055 | "proc-macro2", 1056 | "quote", 1057 | "unicode-ident", 1058 | ] 1059 | 1060 | [[package]] 1061 | name = "tempfile" 1062 | version = "3.19.1" 1063 | source = "registry+https://github.com/rust-lang/crates.io-index" 1064 | checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" 1065 | dependencies = [ 1066 | "fastrand", 1067 | "getrandom 0.3.2", 1068 | "once_cell", 1069 | "rustix 1.0.5", 1070 | "windows-sys", 1071 | ] 1072 | 1073 | [[package]] 1074 | name = "terminfo" 1075 | version = "0.8.0" 1076 | source = "registry+https://github.com/rust-lang/crates.io-index" 1077 | checksum = "666cd3a6681775d22b200409aad3b089c5b99fb11ecdd8a204d9d62f8148498f" 1078 | dependencies = [ 1079 | "dirs", 1080 | "fnv", 1081 | "nom", 1082 | "phf", 1083 | "phf_codegen", 1084 | ] 1085 | 1086 | [[package]] 1087 | name = "termion" 1088 | version = "4.0.2" 1089 | source = "registry+https://github.com/rust-lang/crates.io-index" 1090 | checksum = "1ccce68e518d1173e80876edd54760b60b792750d0cab6444a79101c6ea03848" 1091 | dependencies = [ 1092 | "libc", 1093 | "libredox 0.0.2", 1094 | "numtoa", 1095 | "redox_termios", 1096 | ] 1097 | 1098 | [[package]] 1099 | name = "termios" 1100 | version = "0.3.3" 1101 | source = "registry+https://github.com/rust-lang/crates.io-index" 1102 | checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b" 1103 | dependencies = [ 1104 | "libc", 1105 | ] 1106 | 1107 | [[package]] 1108 | name = "termwiz" 1109 | version = "0.22.0" 1110 | source = "registry+https://github.com/rust-lang/crates.io-index" 1111 | checksum = "5a75313e21da5d4406ea31402035b3b97aa74c04356bdfafa5d1043ab4e551d1" 1112 | dependencies = [ 1113 | "anyhow", 1114 | "base64", 1115 | "bitflags 2.9.0", 1116 | "fancy-regex", 1117 | "filedescriptor", 1118 | "finl_unicode", 1119 | "fixedbitset", 1120 | "hex", 1121 | "lazy_static", 1122 | "libc", 1123 | "log", 1124 | "memmem", 1125 | "nix 0.26.4", 1126 | "num-derive", 1127 | "num-traits", 1128 | "ordered-float", 1129 | "pest", 1130 | "pest_derive", 1131 | "phf", 1132 | "semver", 1133 | "sha2", 1134 | "signal-hook", 1135 | "siphasher", 1136 | "tempfile", 1137 | "terminfo", 1138 | "termios", 1139 | "thiserror", 1140 | "ucd-trie", 1141 | "unicode-segmentation", 1142 | "vtparse", 1143 | "wezterm-bidi", 1144 | "wezterm-blob-leases", 1145 | "wezterm-color-types", 1146 | "wezterm-dynamic", 1147 | "wezterm-input-types", 1148 | "winapi", 1149 | ] 1150 | 1151 | [[package]] 1152 | name = "thiserror" 1153 | version = "1.0.57" 1154 | source = "registry+https://github.com/rust-lang/crates.io-index" 1155 | checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" 1156 | dependencies = [ 1157 | "thiserror-impl", 1158 | ] 1159 | 1160 | [[package]] 1161 | name = "thiserror-impl" 1162 | version = "1.0.57" 1163 | source = "registry+https://github.com/rust-lang/crates.io-index" 1164 | checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" 1165 | dependencies = [ 1166 | "proc-macro2", 1167 | "quote", 1168 | "syn 2.0.49", 1169 | ] 1170 | 1171 | [[package]] 1172 | name = "typenum" 1173 | version = "1.17.0" 1174 | source = "registry+https://github.com/rust-lang/crates.io-index" 1175 | checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" 1176 | 1177 | [[package]] 1178 | name = "ucd-trie" 1179 | version = "0.1.6" 1180 | source = "registry+https://github.com/rust-lang/crates.io-index" 1181 | checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" 1182 | 1183 | [[package]] 1184 | name = "unicode-ident" 1185 | version = "1.0.12" 1186 | source = "registry+https://github.com/rust-lang/crates.io-index" 1187 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 1188 | 1189 | [[package]] 1190 | name = "unicode-segmentation" 1191 | version = "1.11.0" 1192 | source = "registry+https://github.com/rust-lang/crates.io-index" 1193 | checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" 1194 | 1195 | [[package]] 1196 | name = "unicode-truncate" 1197 | version = "1.1.0" 1198 | source = "registry+https://github.com/rust-lang/crates.io-index" 1199 | checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" 1200 | dependencies = [ 1201 | "itertools", 1202 | "unicode-segmentation", 1203 | "unicode-width 0.1.13", 1204 | ] 1205 | 1206 | [[package]] 1207 | name = "unicode-width" 1208 | version = "0.1.13" 1209 | source = "registry+https://github.com/rust-lang/crates.io-index" 1210 | checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" 1211 | 1212 | [[package]] 1213 | name = "unicode-width" 1214 | version = "0.2.0" 1215 | source = "registry+https://github.com/rust-lang/crates.io-index" 1216 | checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" 1217 | 1218 | [[package]] 1219 | name = "utf8parse" 1220 | version = "0.2.1" 1221 | source = "registry+https://github.com/rust-lang/crates.io-index" 1222 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" 1223 | 1224 | [[package]] 1225 | name = "uuid" 1226 | version = "1.7.0" 1227 | source = "registry+https://github.com/rust-lang/crates.io-index" 1228 | checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" 1229 | dependencies = [ 1230 | "atomic", 1231 | "getrandom 0.2.12", 1232 | ] 1233 | 1234 | [[package]] 1235 | name = "version_check" 1236 | version = "0.9.4" 1237 | source = "registry+https://github.com/rust-lang/crates.io-index" 1238 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1239 | 1240 | [[package]] 1241 | name = "vtparse" 1242 | version = "0.6.2" 1243 | source = "registry+https://github.com/rust-lang/crates.io-index" 1244 | checksum = "6d9b2acfb050df409c972a37d3b8e08cdea3bddb0c09db9d53137e504cfabed0" 1245 | dependencies = [ 1246 | "utf8parse", 1247 | ] 1248 | 1249 | [[package]] 1250 | name = "wasi" 1251 | version = "0.11.0+wasi-snapshot-preview1" 1252 | source = "registry+https://github.com/rust-lang/crates.io-index" 1253 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1254 | 1255 | [[package]] 1256 | name = "wasi" 1257 | version = "0.14.2+wasi-0.2.4" 1258 | source = "registry+https://github.com/rust-lang/crates.io-index" 1259 | checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" 1260 | dependencies = [ 1261 | "wit-bindgen-rt", 1262 | ] 1263 | 1264 | [[package]] 1265 | name = "wezterm-bidi" 1266 | version = "0.2.3" 1267 | source = "registry+https://github.com/rust-lang/crates.io-index" 1268 | checksum = "0c0a6e355560527dd2d1cf7890652f4f09bb3433b6aadade4c9b5ed76de5f3ec" 1269 | dependencies = [ 1270 | "log", 1271 | "wezterm-dynamic", 1272 | ] 1273 | 1274 | [[package]] 1275 | name = "wezterm-blob-leases" 1276 | version = "0.1.0" 1277 | source = "registry+https://github.com/rust-lang/crates.io-index" 1278 | checksum = "8e5a5e0adf7eed68976410def849a4bdab6f6e9f6163f152de9cb89deea9e60b" 1279 | dependencies = [ 1280 | "getrandom 0.2.12", 1281 | "mac_address", 1282 | "once_cell", 1283 | "sha2", 1284 | "thiserror", 1285 | "uuid", 1286 | ] 1287 | 1288 | [[package]] 1289 | name = "wezterm-color-types" 1290 | version = "0.3.0" 1291 | source = "registry+https://github.com/rust-lang/crates.io-index" 1292 | checksum = "7de81ef35c9010270d63772bebef2f2d6d1f2d20a983d27505ac850b8c4b4296" 1293 | dependencies = [ 1294 | "csscolorparser", 1295 | "deltae", 1296 | "lazy_static", 1297 | "wezterm-dynamic", 1298 | ] 1299 | 1300 | [[package]] 1301 | name = "wezterm-dynamic" 1302 | version = "0.2.1" 1303 | source = "registry+https://github.com/rust-lang/crates.io-index" 1304 | checksum = "5f2ab60e120fd6eaa68d9567f3226e876684639d22a4219b313ff69ec0ccd5ac" 1305 | dependencies = [ 1306 | "log", 1307 | "ordered-float", 1308 | "strsim", 1309 | "thiserror", 1310 | "wezterm-dynamic-derive", 1311 | ] 1312 | 1313 | [[package]] 1314 | name = "wezterm-dynamic-derive" 1315 | version = "0.1.1" 1316 | source = "registry+https://github.com/rust-lang/crates.io-index" 1317 | checksum = "46c0cf2d539c645b448eaffec9ec494b8b19bd5077d9e58cb1ae7efece8d575b" 1318 | dependencies = [ 1319 | "proc-macro2", 1320 | "quote", 1321 | "syn 1.0.109", 1322 | ] 1323 | 1324 | [[package]] 1325 | name = "wezterm-input-types" 1326 | version = "0.1.0" 1327 | source = "registry+https://github.com/rust-lang/crates.io-index" 1328 | checksum = "7012add459f951456ec9d6c7e6fc340b1ce15d6fc9629f8c42853412c029e57e" 1329 | dependencies = [ 1330 | "bitflags 1.3.2", 1331 | "euclid", 1332 | "lazy_static", 1333 | "wezterm-dynamic", 1334 | ] 1335 | 1336 | [[package]] 1337 | name = "winapi" 1338 | version = "0.3.9" 1339 | source = "registry+https://github.com/rust-lang/crates.io-index" 1340 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1341 | dependencies = [ 1342 | "winapi-i686-pc-windows-gnu", 1343 | "winapi-x86_64-pc-windows-gnu", 1344 | ] 1345 | 1346 | [[package]] 1347 | name = "winapi-i686-pc-windows-gnu" 1348 | version = "0.4.0" 1349 | source = "registry+https://github.com/rust-lang/crates.io-index" 1350 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1351 | 1352 | [[package]] 1353 | name = "winapi-x86_64-pc-windows-gnu" 1354 | version = "0.4.0" 1355 | source = "registry+https://github.com/rust-lang/crates.io-index" 1356 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1357 | 1358 | [[package]] 1359 | name = "windows-sys" 1360 | version = "0.52.0" 1361 | source = "registry+https://github.com/rust-lang/crates.io-index" 1362 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1363 | dependencies = [ 1364 | "windows-targets 0.52.0", 1365 | ] 1366 | 1367 | [[package]] 1368 | name = "windows-targets" 1369 | version = "0.48.5" 1370 | source = "registry+https://github.com/rust-lang/crates.io-index" 1371 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 1372 | dependencies = [ 1373 | "windows_aarch64_gnullvm 0.48.5", 1374 | "windows_aarch64_msvc 0.48.5", 1375 | "windows_i686_gnu 0.48.5", 1376 | "windows_i686_msvc 0.48.5", 1377 | "windows_x86_64_gnu 0.48.5", 1378 | "windows_x86_64_gnullvm 0.48.5", 1379 | "windows_x86_64_msvc 0.48.5", 1380 | ] 1381 | 1382 | [[package]] 1383 | name = "windows-targets" 1384 | version = "0.52.0" 1385 | source = "registry+https://github.com/rust-lang/crates.io-index" 1386 | checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" 1387 | dependencies = [ 1388 | "windows_aarch64_gnullvm 0.52.0", 1389 | "windows_aarch64_msvc 0.52.0", 1390 | "windows_i686_gnu 0.52.0", 1391 | "windows_i686_msvc 0.52.0", 1392 | "windows_x86_64_gnu 0.52.0", 1393 | "windows_x86_64_gnullvm 0.52.0", 1394 | "windows_x86_64_msvc 0.52.0", 1395 | ] 1396 | 1397 | [[package]] 1398 | name = "windows_aarch64_gnullvm" 1399 | version = "0.48.5" 1400 | source = "registry+https://github.com/rust-lang/crates.io-index" 1401 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 1402 | 1403 | [[package]] 1404 | name = "windows_aarch64_gnullvm" 1405 | version = "0.52.0" 1406 | source = "registry+https://github.com/rust-lang/crates.io-index" 1407 | checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" 1408 | 1409 | [[package]] 1410 | name = "windows_aarch64_msvc" 1411 | version = "0.48.5" 1412 | source = "registry+https://github.com/rust-lang/crates.io-index" 1413 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 1414 | 1415 | [[package]] 1416 | name = "windows_aarch64_msvc" 1417 | version = "0.52.0" 1418 | source = "registry+https://github.com/rust-lang/crates.io-index" 1419 | checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" 1420 | 1421 | [[package]] 1422 | name = "windows_i686_gnu" 1423 | version = "0.48.5" 1424 | source = "registry+https://github.com/rust-lang/crates.io-index" 1425 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 1426 | 1427 | [[package]] 1428 | name = "windows_i686_gnu" 1429 | version = "0.52.0" 1430 | source = "registry+https://github.com/rust-lang/crates.io-index" 1431 | checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" 1432 | 1433 | [[package]] 1434 | name = "windows_i686_msvc" 1435 | version = "0.48.5" 1436 | source = "registry+https://github.com/rust-lang/crates.io-index" 1437 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 1438 | 1439 | [[package]] 1440 | name = "windows_i686_msvc" 1441 | version = "0.52.0" 1442 | source = "registry+https://github.com/rust-lang/crates.io-index" 1443 | checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" 1444 | 1445 | [[package]] 1446 | name = "windows_x86_64_gnu" 1447 | version = "0.48.5" 1448 | source = "registry+https://github.com/rust-lang/crates.io-index" 1449 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 1450 | 1451 | [[package]] 1452 | name = "windows_x86_64_gnu" 1453 | version = "0.52.0" 1454 | source = "registry+https://github.com/rust-lang/crates.io-index" 1455 | checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" 1456 | 1457 | [[package]] 1458 | name = "windows_x86_64_gnullvm" 1459 | version = "0.48.5" 1460 | source = "registry+https://github.com/rust-lang/crates.io-index" 1461 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 1462 | 1463 | [[package]] 1464 | name = "windows_x86_64_gnullvm" 1465 | version = "0.52.0" 1466 | source = "registry+https://github.com/rust-lang/crates.io-index" 1467 | checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" 1468 | 1469 | [[package]] 1470 | name = "windows_x86_64_msvc" 1471 | version = "0.48.5" 1472 | source = "registry+https://github.com/rust-lang/crates.io-index" 1473 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 1474 | 1475 | [[package]] 1476 | name = "windows_x86_64_msvc" 1477 | version = "0.52.0" 1478 | source = "registry+https://github.com/rust-lang/crates.io-index" 1479 | checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" 1480 | 1481 | [[package]] 1482 | name = "wit-bindgen-rt" 1483 | version = "0.39.0" 1484 | source = "registry+https://github.com/rust-lang/crates.io-index" 1485 | checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" 1486 | dependencies = [ 1487 | "bitflags 2.9.0", 1488 | ] 1489 | 1490 | [[package]] 1491 | name = "zerocopy" 1492 | version = "0.7.32" 1493 | source = "registry+https://github.com/rust-lang/crates.io-index" 1494 | checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" 1495 | dependencies = [ 1496 | "zerocopy-derive", 1497 | ] 1498 | 1499 | [[package]] 1500 | name = "zerocopy-derive" 1501 | version = "0.7.32" 1502 | source = "registry+https://github.com/rust-lang/crates.io-index" 1503 | checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" 1504 | dependencies = [ 1505 | "proc-macro2", 1506 | "quote", 1507 | "syn 2.0.49", 1508 | ] 1509 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ratatui-explorer" 3 | version = "0.2.1" 4 | edition = "2021" 5 | authors = ["Tatounee"] 6 | description = "ratatui-explorer is a small, but highly customizable, file explorer widget for ratatui." 7 | license = "MIT" 8 | readme = "README.md" 9 | homepage = "https://github.com/tatounee/ratatui-explorer" 10 | repository = "https://github.com/tatounee/ratatui-explorer.git" 11 | keywords = ["tui", "ratatui", "terminal", "file", "explorer"] 12 | categories = ["command-line-interface", "filesystem"] 13 | 14 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 15 | 16 | [dependencies] 17 | ratatui = { version = "0.29", features = ["unstable-widget-ref"] } 18 | educe = { version = "0.6.0", features = [ 19 | "Debug", 20 | "PartialEq", 21 | "Eq", 22 | "Hash", 23 | ], default-features = false } 24 | 25 | 26 | [features] 27 | default = ["crossterm"] 28 | crossterm = ["ratatui/crossterm"] 29 | termion = ["ratatui/termion"] 30 | termwiz = ["ratatui/termwiz"] 31 | 32 | [package.metadata.docs.rs] 33 | cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"] 34 | -------------------------------------------------------------------------------- /LICENCE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2024 tatounee 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ratatui-explorer 2 | 3 | [ratatui-explorer](https://crates.io/crates/ratatui-explorer) is a simple library for creating file explorers for [ratatui](https://github.com/ratatui-org/ratatui). 4 | 5 | Features: 6 | - File explorer functionality. 7 | - Input handling (from [crossterm](https://docs.rs/crossterm/latest/crossterm/), [termion](https://docs.rs/termion/latest/termion/), [termwiz](https://docs.rs/termwiz/latest/termwiz/) and your own backend). 8 | - Customizable widget theming. 9 | 10 | # Examples 11 | 12 | Run `cargo run --example` to try the different example available. 13 | 14 | ## [Basic usage](examples/basic.rs) 15 | The simplest use of [ratatui-explorer](https://crates.io/crates/ratatui-explorer) with the [crossterm](https://docs.rs/crossterm/latest/crossterm/) backend. 16 | 17 | 18 | ```shell 19 | cargo run --example basic 20 | ``` 21 | 22 | ![basic usage demonstration](https://raw.githubusercontent.com/tatounee/ratatui-explorer/master/assets/basic.gif) 23 | 24 | --- 25 | 26 | ## [Light and dark theme](examples/light_and_dark_theme.rs) 27 | Switching custom themes while running. 28 | 29 | ```shell 30 | cargo run --example light_and_dark_theme 31 | ``` 32 | 33 | ![theme switching demonstration](https://raw.githubusercontent.com/tatounee/ratatui-explorer/master/assets/light_and_dark_theme.gif) 34 | 35 | --- 36 | 37 | ## [File preview](examples/file_preview.rs) 38 | Adapt the interface depending on the selected file. 39 | 40 | ```shell 41 | cargo run --example file_preview 42 | ``` 43 | 44 | ![file preview demonstration](https://raw.githubusercontent.com/tatounee/ratatui-explorer/master/assets/file_preview.gif) 45 | 46 | 47 | # Basic usage 48 | Install the libraries in your `Cargo.toml` file: 49 | ```plaintext 50 | cargo add ratatui ratatui-explorer crossterm 51 | ``` 52 | Then inside your `main.rs` file: 53 | ```rust no_run 54 | use std::io::{self, stdout}; 55 | 56 | use crossterm::{ 57 | event::{read, Event, KeyCode}, 58 | terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, 59 | ExecutableCommand, 60 | }; 61 | use ratatui::prelude::*; 62 | 63 | use ratatui_explorer::{FileExplorer, Theme}; 64 | 65 | fn main() -> io::Result<()> { 66 | enable_raw_mode()?; 67 | stdout().execute(EnterAlternateScreen)?; 68 | 69 | let mut terminal = Terminal::new(CrosstermBackend::new(stdout()))?; 70 | 71 | // Create a new file explorer with the default theme and title. 72 | let theme = Theme::default().add_default_title(); 73 | let mut file_explorer = FileExplorer::with_theme(theme)?; 74 | 75 | loop { 76 | // Render the file explorer widget. 77 | terminal.draw(|f| { 78 | f.render_widget(&file_explorer.widget(), f.area()); 79 | })?; 80 | 81 | // Read the next event from the terminal. 82 | let event = read()?; 83 | if let Event::Key(key) = event { 84 | if key.code == KeyCode::Char('q') { 85 | break; 86 | } 87 | } 88 | // Handle the event in the file explorer. 89 | file_explorer.handle(&event)?; 90 | } 91 | 92 | disable_raw_mode()?; 93 | stdout().execute(LeaveAlternateScreen)?; 94 | Ok(()) 95 | } 96 | ``` 97 | 98 | ## Customizing the theme 99 | You can customize the theme of the file explorer widget by using the `Theme` struct. 100 | ```rust 101 | use ratatui::{prelude::*, widgets::*}; 102 | use ratatui_explorer::Theme; 103 | 104 | let theme = Theme::default() 105 | .add_default_title() 106 | .with_title_bottom(|fe| format!("[{} files]", fe.files().len()).into()) 107 | .with_block(Block::default().borders(Borders::ALL).border_type(BorderType::Rounded)) 108 | .with_highlight_item_style(Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)) 109 | .with_highlight_dir_style(Style::default().fg(Color::Red).add_modifier(Modifier::BOLD)) 110 | .with_highlight_symbol("> ".into()); 111 | ``` 112 | 113 | # Bindings 114 | 115 | The following bindings are used by default for [crossterm](https://docs.rs/crossterm/latest/crossterm/), 116 | [termion](https://docs.rs/termion/latest/termion/) and [termwiz](https://docs.rs/termwiz/latest/termwiz/). 117 | 118 | | Binding | Action | 119 | |-----------------------------------|----------------------------| 120 | | `j`, `` | Move the selection down | 121 | | `k`, `` | Move the selection up | 122 | | `h`, ``, `` | Go to the parent directory | 123 | | `l`, ``, `` | Go to the child directory* | 124 | | `Home` | Select the first entry | 125 | | `End` | Select the last entry | 126 | | `PageUp` | Scroll the selection up | 127 | | `PageDown` | Scroll the selection down | 128 | 129 | _*if the selected item is a directory_ 130 | -------------------------------------------------------------------------------- /assets/basic.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tatounee/ratatui-explorer/8d506485989cb438a710f235630a586c72d19d38/assets/basic.gif -------------------------------------------------------------------------------- /assets/file_preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tatounee/ratatui-explorer/8d506485989cb438a710f235630a586c72d19d38/assets/file_preview.gif -------------------------------------------------------------------------------- /assets/light_and_dark_theme.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tatounee/ratatui-explorer/8d506485989cb438a710f235630a586c72d19d38/assets/light_and_dark_theme.gif -------------------------------------------------------------------------------- /examples/basic.rs: -------------------------------------------------------------------------------- 1 | use std::io::{self, stdout}; 2 | 3 | use ratatui::crossterm; 4 | use crossterm::{ 5 | event::{read, Event, KeyCode}, 6 | terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, 7 | ExecutableCommand, 8 | }; 9 | use ratatui::prelude::*; 10 | 11 | use ratatui_explorer::{FileExplorer, Theme}; 12 | 13 | fn main() -> io::Result<()> { 14 | enable_raw_mode()?; 15 | stdout().execute(EnterAlternateScreen)?; 16 | 17 | let mut terminal = Terminal::new(CrosstermBackend::new(stdout()))?; 18 | 19 | // Create a new file explorer with the default theme and title. 20 | let theme = Theme::default().add_default_title(); 21 | let mut file_explorer = FileExplorer::with_theme(theme)?; 22 | 23 | loop { 24 | // Render the file explorer widget. 25 | terminal.draw(|f| { 26 | f.render_widget(&file_explorer.widget(), f.area()); 27 | })?; 28 | 29 | // Read the next event from the terminal. 30 | let event = read()?; 31 | if let Event::Key(key) = event { 32 | if key.code == KeyCode::Char('q') { 33 | break; 34 | } 35 | } 36 | // Handle the event in the file explorer. 37 | file_explorer.handle(&event)?; 38 | } 39 | 40 | disable_raw_mode()?; 41 | stdout().execute(LeaveAlternateScreen)?; 42 | Ok(()) 43 | } 44 | -------------------------------------------------------------------------------- /examples/file_preview.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | borrow::Cow, 3 | fs::read_to_string, 4 | io::{self, stdout}, 5 | }; 6 | 7 | use ratatui::crossterm; 8 | use crossterm::{ 9 | event::{read, Event, KeyCode}, 10 | terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, 11 | ExecutableCommand, 12 | }; 13 | use ratatui::{prelude::*, widgets::*}; 14 | 15 | use ratatui_explorer::{File, FileExplorer, Theme}; 16 | 17 | fn main() -> io::Result<()> { 18 | enable_raw_mode()?; 19 | stdout().execute(EnterAlternateScreen)?; 20 | 21 | let mut terminal = Terminal::new(CrosstermBackend::new(stdout()))?; 22 | let layout = Layout::horizontal([Constraint::Ratio(1, 3), Constraint::Ratio(2, 3)]); 23 | 24 | // Create a new file explorer with the default theme and title. 25 | let theme = get_theme(); 26 | let mut file_explorer = FileExplorer::with_theme(theme)?; 27 | 28 | loop { 29 | // Get the content of the current selected file (if it's indeed a file). 30 | let file_content = get_file_content(file_explorer.current()); 31 | 32 | let file_content = match file_content { 33 | Ok(file_content) => file_content, 34 | _ => "Couldn't load file.".into(), 35 | }; 36 | 37 | // Render the file explorer widget and the file content. 38 | terminal.draw(|f| { 39 | let chunks = layout.split(f.area()); 40 | 41 | f.render_widget(&file_explorer.widget(), chunks[0]); 42 | f.render_widget(Clear, chunks[1]); 43 | f.render_widget( 44 | Paragraph::new(file_content).block( 45 | Block::default() 46 | .borders(Borders::ALL) 47 | .border_type(BorderType::Double), 48 | ), 49 | chunks[1], 50 | ); 51 | })?; 52 | 53 | // Read the next event from the terminal. 54 | let event = read()?; 55 | if let Event::Key(key) = event { 56 | if key.code == KeyCode::Char('q') { 57 | break; 58 | } 59 | } 60 | // Handle the event in the file explorer. 61 | file_explorer.handle(&event)?; 62 | } 63 | 64 | disable_raw_mode()?; 65 | stdout().execute(LeaveAlternateScreen)?; 66 | Ok(()) 67 | } 68 | 69 | fn get_file_content(file: &File) -> io::Result> { 70 | // If the path is a file, read its content. 71 | if file.is_file() { 72 | read_to_string(file.path()).map(Into::into) 73 | } else if file.is_dir() { 74 | Ok("".into()) 75 | } else { 76 | Ok("".into()) 77 | } 78 | } 79 | 80 | fn get_theme() -> Theme { 81 | Theme::default() 82 | .with_block(Block::default().borders(Borders::ALL)) 83 | .with_dir_style( 84 | Style::default() 85 | .fg(Color::White) 86 | .add_modifier(Modifier::BOLD), 87 | ) 88 | .with_highlight_dir_style( 89 | Style::default() 90 | .fg(Color::White) 91 | .add_modifier(Modifier::BOLD) 92 | .bg(Color::DarkGray), 93 | ) 94 | .with_scroll_padding(1) 95 | } 96 | -------------------------------------------------------------------------------- /examples/light_and_dark_theme.rs: -------------------------------------------------------------------------------- 1 | use std::io::{self, stdout}; 2 | 3 | use ratatui::crossterm; 4 | use crossterm::{ 5 | event::{read, Event, KeyCode}, 6 | terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, 7 | ExecutableCommand, 8 | }; 9 | use ratatui::{ 10 | prelude::*, 11 | widgets::{Block, BorderType, Borders}, 12 | }; 13 | 14 | use ratatui_explorer::{FileExplorer, Theme}; 15 | 16 | fn main() -> io::Result<()> { 17 | enable_raw_mode()?; 18 | stdout().execute(EnterAlternateScreen)?; 19 | 20 | let mut terminal = Terminal::new(CrosstermBackend::new(stdout()))?; 21 | 22 | // Create a new file explorer with the light theme. 23 | let mut dark_theme = false; 24 | let mut file_explorer = FileExplorer::with_theme(get_light_theme())?; 25 | 26 | loop { 27 | // Render the file explorer widget. 28 | terminal.draw(|f| { 29 | f.render_widget(&file_explorer.widget(), f.area()); 30 | })?; 31 | 32 | // Read the next event from the terminal. 33 | let event = read()?; 34 | // If the user presses `Ctrl + s`, switch the theme. 35 | // If the user presses `Ctrl + q`, quit the application. 36 | if let Event::Key(key) = event { 37 | if key.modifiers == crossterm::event::KeyModifiers::CONTROL { 38 | match key.code { 39 | KeyCode::Char('s') => { 40 | dark_theme = !dark_theme; 41 | if dark_theme { 42 | file_explorer.set_theme(get_dark_theme()); 43 | } else { 44 | file_explorer.set_theme(get_light_theme()); 45 | } 46 | } 47 | KeyCode::Char('q') => { 48 | break; 49 | } 50 | _ => {} 51 | } 52 | } 53 | } 54 | // Handle the event in the file explorer. 55 | file_explorer.handle(&event)?; 56 | } 57 | 58 | disable_raw_mode()?; 59 | stdout().execute(LeaveAlternateScreen)?; 60 | Ok(()) 61 | } 62 | 63 | fn get_light_theme() -> Theme { 64 | Theme::new() 65 | .with_block( 66 | Block::default() 67 | .borders(Borders::ALL) 68 | .border_type(BorderType::Rounded) 69 | .style(Style::default().fg(Color::Black).bg(Color::White)), 70 | ) 71 | .with_item_style(Style::default().fg(Color::Yellow)) 72 | .with_dir_style( 73 | Style::default() 74 | .fg(Color::Cyan) 75 | .add_modifier(Modifier::BOLD), 76 | ) 77 | .with_highlight_symbol("> ") 78 | .add_default_title() 79 | .with_title_top(|_| Line::from(" ☀ Theme ").right_aligned()) 80 | .with_title_bottom(|_| " ^q Quit | ^s Switch theme ".into()) 81 | } 82 | 83 | fn get_dark_theme() -> Theme { 84 | Theme::new() 85 | .with_block( 86 | Block::default() 87 | .borders(Borders::ALL) 88 | .border_type(BorderType::Rounded) 89 | .style(Style::default().fg(Color::White).bg(Color::Black)), 90 | ) 91 | .with_item_style(Style::default().fg(Color::Yellow)) 92 | .with_dir_style( 93 | Style::default() 94 | .fg(Color::Cyan) 95 | .add_modifier(Modifier::BOLD), 96 | ) 97 | .with_highlight_symbol("> ") 98 | .add_default_title() 99 | .with_title_top(|_| Line::from(" ☾ Theme ").right_aligned()) 100 | .with_title_bottom(|_| " ^q Quit | ^s Switch theme ".into()) 101 | } 102 | -------------------------------------------------------------------------------- /src/file_explorer.rs: -------------------------------------------------------------------------------- 1 | use std::{fs::FileType, io::Result, path::PathBuf}; 2 | 3 | use ratatui::widgets::WidgetRef; 4 | 5 | use crate::{input::Input, widget::Renderer, Theme}; 6 | 7 | /// A file explorer that allows browsing and selecting files and directories. 8 | /// 9 | /// The `FileExplorer` struct represents a file explorer widget that can be used to navigate 10 | /// through the file system. 11 | /// You can obtain a renderable widget from it with the [`widget`](#method.widget) method. 12 | /// It provides methods for handling user input from [crossterm](https://crates.io/crates/crossterm), 13 | /// [termion](https://crates.io/crates/termion) and [termwiz](https://crates.io/crates/termwiz) (depending on what feature is enabled). 14 | /// 15 | /// # Examples 16 | /// 17 | /// Creating a new `FileExplorer` widget: 18 | /// 19 | /// ```no_run 20 | /// use ratatui_explorer::FileExplorer; 21 | /// 22 | /// let file_explorer = FileExplorer::new().unwrap(); 23 | /// let widget = file_explorer.widget(); 24 | /// ``` 25 | /// 26 | /// Handling user input: 27 | /// 28 | /// ```no_run 29 | /// # fn get_event() -> ratatui_explorer::Input { 30 | /// # unimplemented!() 31 | /// # } 32 | /// use ratatui_explorer::FileExplorer; 33 | /// 34 | /// let mut file_explorer = FileExplorer::new().unwrap(); 35 | /// let event = get_event(); // Get the event from the terminal (with crossterm, termion or termwiz) 36 | /// file_explorer.handle(event).unwrap(); 37 | /// ``` 38 | /// 39 | /// Accessing information about the current file selected and or the current working directory: 40 | /// 41 | /// ```no_run 42 | /// use ratatui_explorer::FileExplorer; 43 | /// 44 | /// let file_explorer = FileExplorer::new().unwrap(); 45 | /// 46 | /// let current_file = file_explorer.current(); 47 | /// let current_working_directory = file_explorer.cwd(); 48 | /// println!("Current Directory: {}", current_working_directory.display()); 49 | /// println!("Name: {}", current_file.name()); 50 | /// ``` 51 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 52 | pub struct FileExplorer { 53 | cwd: PathBuf, 54 | files: Vec, 55 | selected: usize, 56 | theme: Theme, 57 | } 58 | 59 | impl FileExplorer { 60 | /// Creates a new instance of `FileExplorer`. 61 | /// 62 | /// This method initializes a `FileExplorer` with the current working directory. 63 | /// 64 | /// # Errors 65 | /// 66 | /// Will return `Err` if the current working directory can not be listed. See [`current_dir`](https://doc.rust-lang.org/stable/std/env/fn.current_dir.html) for more information. 67 | /// 68 | /// # Examples 69 | /// Suppose you have this tree file and your current working directory is `/Documents`: 70 | /// ```plaintext 71 | /// / 72 | /// ├── .git 73 | /// └── Documents <- current working directory 74 | /// ├── passport.png 75 | /// └── resume.pdf 76 | /// ``` 77 | /// You can create a new `FileExplorer` like this: 78 | /// ```no_run 79 | /// use ratatui_explorer::FileExplorer; 80 | /// 81 | /// let file_explorer = FileExplorer::new().unwrap(); 82 | /// assert_eq!(file_explorer.cwd().display().to_string(), "/Documents"); 83 | /// ``` 84 | pub fn new() -> Result { 85 | let cwd = std::env::current_dir()?; 86 | 87 | let mut file_explorer = Self { 88 | cwd, 89 | files: vec![], 90 | selected: 0, 91 | theme: Theme::default(), 92 | }; 93 | 94 | file_explorer.get_and_set_files()?; 95 | 96 | Ok(file_explorer) 97 | } 98 | 99 | /// Creates a new instance of `FileExplorer` with a specific theme. 100 | /// 101 | /// This method initializes a `FileExplorer` with the current working directory. 102 | /// 103 | /// # Errors 104 | /// 105 | /// Will return `Err` if the current working directory can not be listed. 106 | /// 107 | /// # Examples 108 | /// 109 | /// ```no_run 110 | /// use ratatui_explorer::{FileExplorer, Theme}; 111 | /// 112 | /// let file_explorer = FileExplorer::with_theme(Theme::default().add_default_title()).unwrap(); 113 | /// ``` 114 | #[inline] 115 | pub fn with_theme(theme: Theme) -> Result { 116 | let mut file_explorer = Self::new()?; 117 | 118 | file_explorer.theme = theme; 119 | 120 | Ok(file_explorer) 121 | } 122 | 123 | /// Build a ratatui widget to render the file explorer. The widget can then 124 | /// be rendered with [`Frame::render_widget`](https://docs.rs/ratatui/latest/ratatui/terminal/struct.Frame.html#method.render_widget). 125 | /// 126 | /// # Examples 127 | /// 128 | /// ```no_run 129 | /// use ratatui::{Terminal, backend::CrosstermBackend}; 130 | /// use ratatui_explorer::FileExplorer; 131 | /// 132 | /// let mut file_explorer = FileExplorer::new().unwrap(); 133 | /// 134 | /// let mut terminal = Terminal::new(CrosstermBackend::new(std::io::stdout())).unwrap(); 135 | /// 136 | /// loop { 137 | /// terminal.draw(|f| { 138 | /// let widget = file_explorer.widget(); // Get the widget to render the file explorer 139 | /// f.render_widget(&widget, f.area()); 140 | /// }).unwrap(); 141 | /// 142 | /// // ... 143 | /// } 144 | /// ``` 145 | #[inline] 146 | #[must_use] 147 | pub const fn widget(&self) -> impl WidgetRef + '_ { 148 | Renderer(self) 149 | } 150 | 151 | /// Handles input from user and updates the state of the file explorer. 152 | /// The different inputs are interpreted as follows: 153 | /// - `Up`: Move the selection up. 154 | /// - `Down`: Move the selection down. 155 | /// - `Left`: Move to the parent directory. 156 | /// - `Right`: Move to the selected directory. 157 | /// - `Home`: Select the first entry. 158 | /// - `End`: Select the last entry. 159 | /// - `PageUp`: Scroll the selection up. 160 | /// - `PageDown`: Scroll the selection down. 161 | /// - `None`: Do nothing. 162 | /// 163 | /// [`Input`](crate::input::Input) implement [`From`](https://doc.rust-lang.org/stable/std/convert/trait.From.html) 164 | /// for `Event` from [crossterm](https://docs.rs/crossterm/latest/crossterm/event/enum.Event.html), 165 | /// [termion](https://docs.rs/termion/latest/termion/event/enum.Event.html) 166 | /// and [termwiz](https://docs.rs/termwiz/latest/termwiz/input/enum.InputEvent.html) (`InputEvent` in this case). 167 | /// 168 | /// # Errors 169 | /// 170 | /// Will return `Err` if the new current working directory can not be listed. 171 | /// 172 | /// # Examples 173 | /// 174 | /// Suppose you have this tree file, with `passport.png` selected inside `file_explorer`: 175 | /// ```plaintext 176 | /// / 177 | /// ├── .git 178 | /// └── Documents 179 | /// ├── passport.png <- selected 180 | /// └── resume.pdf 181 | /// ``` 182 | /// You can handle input like this: 183 | /// ```no_run 184 | /// use ratatui_explorer::{FileExplorer, Input}; 185 | /// 186 | /// let mut file_explorer = FileExplorer::new().unwrap(); 187 | /// 188 | /// /* user select `password.png` */ 189 | /// 190 | /// file_explorer.handle(Input::Down).unwrap(); 191 | /// assert_eq!(file_explorer.current().name(), "resume.pdf"); 192 | /// 193 | /// file_explorer.handle(Input::Up).unwrap(); 194 | /// file_explorer.handle(Input::Up).unwrap(); 195 | /// assert_eq!(file_explorer.current().name(), "Documents"); 196 | /// 197 | /// file_explorer.handle(Input::Left).unwrap(); 198 | /// assert_eq!(file_explorer.cwd().display().to_string(), "/"); 199 | /// 200 | /// file_explorer.handle(Input::Right).unwrap(); 201 | /// assert_eq!(file_explorer.cwd().display().to_string(), "/Documents"); 202 | /// ``` 203 | pub fn handle>(&mut self, input: I) -> Result<()> { 204 | const SCROLL_COUNT: usize = 12; 205 | 206 | let input = input.into(); 207 | 208 | match input { 209 | Input::Up => { 210 | self.selected = self.selected.wrapping_sub(1).min(self.files.len() - 1); 211 | } 212 | Input::Down => { 213 | self.selected = (self.selected + 1) % self.files.len(); 214 | } 215 | Input::Home => { 216 | self.selected = 0; 217 | } 218 | Input::End => { 219 | self.selected = self.files.len() - 1; 220 | } 221 | Input::PageUp => { 222 | self.selected = self.selected.saturating_sub(SCROLL_COUNT); 223 | } 224 | Input::PageDown => { 225 | self.selected = (self.selected + SCROLL_COUNT).min(self.files.len() - 1); 226 | } 227 | Input::Left => { 228 | let parent = self.cwd.parent(); 229 | 230 | if let Some(parent) = parent { 231 | self.cwd = parent.to_path_buf(); 232 | self.get_and_set_files()?; 233 | self.selected = 0; 234 | } 235 | } 236 | Input::Right => { 237 | if self.files[self.selected].path.is_dir() { 238 | self.cwd = self.files.swap_remove(self.selected).path; 239 | self.get_and_set_files()?; 240 | self.selected = 0; 241 | } 242 | } 243 | Input::None => (), 244 | } 245 | 246 | Ok(()) 247 | } 248 | 249 | /// Sets the current working directory of the file explorer. 250 | /// 251 | /// # Errors 252 | /// 253 | /// Will return `Err` if the directory `cwd` can not be listed. 254 | /// 255 | /// # Examples 256 | /// 257 | /// ```no_run 258 | /// use ratatui_explorer::FileExplorer; 259 | /// 260 | /// let mut file_explorer = FileExplorer::new().unwrap(); 261 | /// 262 | /// file_explorer.set_cwd("/Documents").unwrap(); 263 | /// assert_eq!(file_explorer.cwd().display().to_string(), "/Documents"); 264 | /// ``` 265 | #[inline] 266 | pub fn set_cwd>(&mut self, cwd: P) -> Result<()> { 267 | self.cwd = cwd.into(); 268 | self.get_and_set_files()?; 269 | self.selected = 0; 270 | 271 | Ok(()) 272 | } 273 | 274 | /// Sets the theme of the file explorer. 275 | /// 276 | /// # Examples 277 | /// 278 | /// ```no_run 279 | /// use ratatui_explorer::{FileExplorer, Theme}; 280 | /// 281 | /// let mut file_explorer = FileExplorer::new().unwrap(); 282 | /// 283 | /// file_explorer.set_theme(Theme::default().add_default_title()); 284 | /// ``` 285 | #[inline] 286 | pub fn set_theme(&mut self, theme: Theme) { 287 | self.theme = theme; 288 | } 289 | 290 | /// Sets the selected file or directory index inside the current [`Vec`](https://doc.rust-lang.org/stable/std/vec/struct.Vec.html) of files 291 | /// and directories if the file explorer. 292 | /// 293 | /// The file explorer add the parent directory at the beginning of the 294 | /// [`Vec`](https://doc.rust-lang.org/stable/std/vec/struct.Vec.html) of files, so setting the selected index to 0 will select the parent directory 295 | /// (if the current working directory not the root directory). 296 | /// 297 | /// # Panics 298 | /// 299 | /// Panics if `selected` is greater or equal to the number of files (plus the parent directory if it exist) in the current 300 | /// working directory. 301 | /// 302 | /// # Examples 303 | /// 304 | /// Suppose you have this tree file, with `passport.png` selected inside `file_explorer`: 305 | /// ```plaintext 306 | /// / 307 | /// ├── .git 308 | /// └── Documents 309 | /// ├── passport.png <- selected (index 2) 310 | /// └── resume.pdf 311 | /// ``` 312 | /// You can set the selected index like this: 313 | /// ```no_run 314 | /// use ratatui_explorer::FileExplorer; 315 | /// 316 | /// let mut file_explorer = FileExplorer::new().unwrap(); 317 | /// 318 | /// /* user select `password.png` */ 319 | /// 320 | /// // Because the file explorer add the parent directory at the beginning 321 | /// // of the [`Vec`](https://doc.rust-lang.org/stable/std/vec/struct.Vec.html) of files, index 0 is indeed the parent directory. 322 | /// file_explorer.set_selected_idx(0); 323 | /// assert_eq!(file_explorer.current().path().display().to_string(), "/"); 324 | /// 325 | /// file_explorer.set_selected_idx(1); 326 | /// assert_eq!(file_explorer.current().path().display().to_string(), "/Documents"); 327 | /// 328 | /// #[test] 329 | /// #[should_panic] 330 | /// fn index_out_of_bound() { 331 | /// let mut file_explorer = FileExplorer::new().unwrap(); 332 | /// file_explorer.set_selected_idx(4); 333 | /// } 334 | /// ``` 335 | #[inline] 336 | pub fn set_selected_idx(&mut self, selected: usize) { 337 | assert!(selected < self.files.len()); 338 | self.selected = selected; 339 | } 340 | 341 | /// Returns the current file or directory selected. 342 | /// 343 | /// # Examples 344 | /// 345 | /// Suppose you have this tree file, with `passport.png` selected inside `file_explorer`: 346 | /// ```plaintext 347 | /// / 348 | /// ├── .git 349 | /// └── Documents 350 | /// ├── passport.png <- selected 351 | /// └── resume.pdf 352 | /// ``` 353 | /// You can get the current file like this: 354 | /// ```no_run 355 | /// use ratatui_explorer::FileExplorer; 356 | /// 357 | /// let file_explorer = FileExplorer::new().unwrap(); 358 | /// 359 | /// /* user select `password.png` */ 360 | /// 361 | /// let file = file_explorer.current(); 362 | /// assert_eq!(file.name(), "passport.png"); 363 | /// ``` 364 | #[inline] 365 | #[must_use] 366 | pub fn current(&self) -> &File { 367 | &self.files[self.selected] 368 | } 369 | 370 | /// Returns the current working directory of the file explorer. 371 | /// 372 | /// # Examples 373 | /// 374 | /// Suppose you have this tree file, with `passport.png` selected inside `file_explorer`: 375 | /// ```plaintext 376 | /// / 377 | /// ├── .git 378 | /// └── Documents 379 | /// ├── passport.png <- selected 380 | /// └── resume.pdf 381 | /// ``` 382 | /// You can get the current working directory like this: 383 | /// ```no_run 384 | /// use ratatui_explorer::FileExplorer; 385 | /// 386 | /// let file_explorer = FileExplorer::new().unwrap(); 387 | /// 388 | /// /* user select `password.png` */ 389 | /// 390 | /// let cwd = file_explorer.cwd(); 391 | /// assert_eq!(cwd.display().to_string(), "/Documents"); 392 | /// ``` 393 | #[inline] 394 | #[must_use] 395 | pub const fn cwd(&self) -> &PathBuf { 396 | &self.cwd 397 | } 398 | 399 | /// Returns the a [`Vec`](https://doc.rust-lang.org/stable/std/vec/struct.Vec.html) of files and directories in the current working directory 400 | /// of the file explorer, plus the parent directory if it exist. 401 | /// 402 | /// # Examples 403 | /// 404 | /// Suppose you have this tree file, with `passport.png` selected inside `file_explorer`: 405 | /// ```plaintext 406 | /// / 407 | /// ├── .git 408 | /// └── Documents 409 | /// ├── passport.png <- selected 410 | /// └── resume.pdf 411 | /// ``` 412 | /// You can get the [`Vec`](https://doc.rust-lang.org/stable/std/vec/struct.Vec.html) of files and directories like this: 413 | /// ```no_run 414 | /// use ratatui_explorer::FileExplorer; 415 | /// 416 | /// let file_explorer = FileExplorer::new().unwrap(); 417 | /// 418 | /// /* user select `password.png` */ 419 | /// 420 | /// let files = file_explorer.files(); 421 | /// assert_eq!(files.len(), 4); // 3 files/directory and the parent directory 422 | /// ``` 423 | #[inline] 424 | #[must_use] 425 | pub const fn files(&self) -> &Vec { 426 | &self.files 427 | } 428 | 429 | /// Returns the index of the selected file or directory in the current [`Vec`](https://doc.rust-lang.org/stable/std/vec/struct.Vec.html) of files 430 | /// and directories in the current working directory of the file explorer. 431 | /// 432 | /// # Examples 433 | /// 434 | /// Suppose you have this tree file, with `passport.png` selected inside `file_explorer`: 435 | /// ```plaintext 436 | /// / 437 | /// ├── .git 438 | /// └── Documents 439 | /// ├── passport.png <- selected (index 2) 440 | /// └── resume.pdf 441 | /// ``` 442 | /// You can get the selected index like this: 443 | /// ```no_run 444 | /// use ratatui_explorer::FileExplorer; 445 | /// 446 | /// let file_explorer = FileExplorer::new().unwrap(); 447 | /// 448 | /// /* user select `password.png` */ 449 | /// 450 | /// let selected_idx = file_explorer.selected_idx(); 451 | /// 452 | /// // Because the file explorer add the parent directory at the beginning 453 | /// // of the [`Vec`](https://doc.rust-lang.org/stable/std/vec/struct.Vec.html) of files, the selected index will be 2. 454 | /// assert_eq!(selected_idx, 2); 455 | /// ``` 456 | #[inline] 457 | #[must_use] 458 | pub const fn selected_idx(&self) -> usize { 459 | self.selected 460 | } 461 | 462 | /// Returns the theme of the file explorer. 463 | /// 464 | /// # Examples 465 | /// 466 | /// ```no_run 467 | /// use ratatui_explorer::{FileExplorer, Theme}; 468 | /// 469 | /// let file_explorer = FileExplorer::new().unwrap(); 470 | /// 471 | /// assert_eq!(file_explorer.theme(), &Theme::default()); 472 | /// ``` 473 | #[inline] 474 | #[must_use] 475 | pub const fn theme(&self) -> &Theme { 476 | &self.theme 477 | } 478 | 479 | /// Get the files and directories in the current working directory and set them in the file explorer. 480 | /// It add the parent directory at the beginning of the [`Vec`](https://doc.rust-lang.org/stable/std/vec/struct.Vec.html) of files if it exist. 481 | fn get_and_set_files(&mut self) -> Result<()> { 482 | let (mut dirs, mut none_dirs): (Vec<_>, Vec<_>) = std::fs::read_dir(&self.cwd)? 483 | .filter_map(|entry| { 484 | entry.ok().map(|e| { 485 | let path = e.path(); 486 | let file_type = path.metadata().map(|m| m.file_type()).ok(); 487 | let is_dir = file_type.is_some_and(|f| f.is_dir()); 488 | let name = if is_dir { 489 | format!("{}/", e.file_name().to_string_lossy()) 490 | } else { 491 | e.file_name().to_string_lossy().into_owned() 492 | }; 493 | 494 | File { 495 | name, 496 | path, 497 | is_dir, 498 | file_type, 499 | } 500 | }) 501 | }) 502 | .partition(File::is_dir); 503 | 504 | dirs.sort_unstable_by(|f1, f2| f1.name.cmp(&f2.name)); 505 | none_dirs.sort_unstable_by(|f1, f2| f1.name.cmp(&f2.name)); 506 | 507 | if let Some(parent) = self.cwd.parent() { 508 | let mut files = Vec::with_capacity(1 + dirs.len() + none_dirs.len()); 509 | 510 | files.push(File { 511 | name: "../".to_owned(), 512 | path: parent.to_path_buf(), 513 | is_dir: true, 514 | file_type: None, 515 | }); 516 | 517 | files.extend(dirs); 518 | files.extend(none_dirs); 519 | 520 | self.files = files; 521 | } else { 522 | let mut files = Vec::with_capacity(dirs.len() + none_dirs.len()); 523 | 524 | files.extend(dirs); 525 | files.extend(none_dirs); 526 | 527 | self.files = files; 528 | }; 529 | 530 | Ok(()) 531 | } 532 | } 533 | 534 | /// A file or directory in the file explorer. 535 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 536 | pub struct File { 537 | name: String, 538 | path: PathBuf, 539 | is_dir: bool, 540 | file_type: Option, 541 | } 542 | 543 | impl File { 544 | /// Returns the name of the file or directory. 545 | /// 546 | /// # Examples 547 | /// Suppose you have this tree file, with `passport.png` selected inside `file_explorer`: 548 | /// ```plaintext 549 | /// / 550 | /// ├── .git 551 | /// └── Documents 552 | /// ├── passport.png <- selected 553 | /// └── resume.pdf 554 | /// ``` 555 | /// You can get the name of the selected file like this: 556 | /// ```no_run 557 | /// use ratatui_explorer::FileExplorer; 558 | /// 559 | /// let file_explorer = FileExplorer::new().unwrap(); 560 | /// 561 | /// /* user select `password.png` */ 562 | /// 563 | /// let file = file_explorer.current(); 564 | /// assert_eq!(file.name(), "passport.png"); 565 | /// ``` 566 | #[inline] 567 | #[must_use] 568 | pub fn name(&self) -> &str { 569 | &self.name 570 | } 571 | 572 | /// Returns the path of the file or directory. 573 | /// 574 | /// # Examples 575 | /// Suppose you have this tree file, with `passport.png` selected inside `file_explorer`: 576 | /// ```plaintext 577 | /// / 578 | /// ├── .git 579 | /// └── Documents 580 | /// ├── passport.png <- selected 581 | /// └── resume.pdf 582 | /// ``` 583 | /// You can get the path of the selected file like this: 584 | /// ```no_run 585 | /// use ratatui_explorer::FileExplorer; 586 | /// 587 | /// let file_explorer = FileExplorer::new().unwrap(); 588 | /// 589 | /// /* user select `password.png` */ 590 | /// 591 | /// let file = file_explorer.current(); 592 | /// assert_eq!(file.path().display().to_string(), "/Documents/passport.png"); 593 | /// ``` 594 | #[inline] 595 | #[must_use] 596 | pub const fn path(&self) -> &PathBuf { 597 | &self.path 598 | } 599 | 600 | /// Returns `true` is the file is a directory. 601 | /// 602 | /// # Examples 603 | /// Suppose you have this tree file, with `passport.png` selected inside `file_explorer`: 604 | /// ```plaintext 605 | /// / 606 | /// ├── .git 607 | /// └── Documents 608 | /// ├── passport.png <- selected 609 | /// └── resume.pdf 610 | /// ``` 611 | /// You can know if the selected file is a directory like this: 612 | /// ```no_run 613 | /// use ratatui_explorer::FileExplorer; 614 | /// 615 | /// let file_explorer = FileExplorer::new().unwrap(); 616 | /// 617 | /// /* user select `password.png` */ 618 | /// 619 | /// let file = file_explorer.current(); 620 | /// assert_eq!(file.is_dir(), false); 621 | /// 622 | /// /* user select `Documents` */ 623 | /// 624 | /// let file = file_explorer.current(); 625 | /// assert_eq!(file.is_dir(), true); 626 | /// ``` 627 | #[inline] 628 | #[must_use] 629 | pub const fn is_dir(&self) -> bool { 630 | self.is_dir 631 | } 632 | 633 | /// Returns `true` is the file is a regular file. 634 | /// 635 | /// # Examples 636 | /// Suppose you have this tree file, with `passport.png` selected inside `file_explorer`: 637 | /// ```plaintext 638 | /// / 639 | /// ├── .git 640 | /// └── Documents 641 | /// ├── passport.png <- selected 642 | /// └── resume.pdf 643 | /// ``` 644 | /// You can know if the selected file is a directory like this: 645 | /// ```no_run 646 | /// use ratatui_explorer::FileExplorer; 647 | /// 648 | /// let file_explorer = FileExplorer::new().unwrap(); 649 | /// 650 | /// /* user select `password.png` */ 651 | /// 652 | /// let file = file_explorer.current(); 653 | /// assert_eq!(file.is_file(), true); 654 | /// 655 | /// /* user select `Documents` */ 656 | /// 657 | /// let file = file_explorer.current(); 658 | /// assert_eq!(file.is_file(), false); 659 | /// ``` 660 | #[inline] 661 | #[must_use] 662 | pub fn is_file(&self) -> bool { 663 | self.file_type.is_some_and(|f| f.is_file()) 664 | } 665 | 666 | /// Returns the `FileType` of the file, when available. 667 | /// 668 | /// # Examples 669 | /// Suppose you have this tree file, with `passport.png` selected inside `file_explorer`: 670 | /// ```plaintext 671 | /// / 672 | /// ├── .git 673 | /// └── Documents 674 | /// ├── passport.png <- selected 675 | /// └── resume.pdf 676 | /// ``` 677 | /// You can know if the selected file is a directory like this: 678 | /// ```no_run 679 | /// use std::os::unix::fs::FileTypeExt; 680 | /// 681 | /// use ratatui_explorer::FileExplorer; 682 | /// 683 | /// let file_explorer = FileExplorer::new().unwrap(); 684 | /// 685 | /// /* user select `password.png` */ 686 | /// 687 | /// let file = file_explorer.current(); 688 | /// assert_eq!(file.file_type().unwrap().is_file(), true); 689 | /// assert_eq!(file.file_type().unwrap().is_socket(), false); 690 | /// 691 | /// /* user select `Documents` */ 692 | /// 693 | /// let file = file_explorer.current(); 694 | /// assert_eq!(file.file_type().unwrap().is_file(), false); 695 | /// assert_eq!(file.file_type().unwrap().is_socket(), false); 696 | /// ``` 697 | #[inline] 698 | #[must_use] 699 | pub const fn file_type(&self) -> Option { 700 | self.file_type 701 | } 702 | } 703 | -------------------------------------------------------------------------------- /src/input/crossterm.rs: -------------------------------------------------------------------------------- 1 | use ratatui::crossterm; 2 | use crossterm::event::{Event, KeyCode}; 3 | 4 | use super::Input; 5 | 6 | impl From<&Event> for Input { 7 | /// Convert crossterm [`Event`](https://docs.rs/crossterm/latest/crossterm/event/enum.Event.html) to [`Input`]. 8 | /// 9 | /// **Note:** This implementation is only available when the `crossterm` feature is enabled. 10 | fn from(value: &Event) -> Self { 11 | if let Event::Key(key) = value { 12 | if matches!( 13 | key.kind, 14 | crossterm::event::KeyEventKind::Press | crossterm::event::KeyEventKind::Repeat 15 | ) { 16 | let input = match key.code { 17 | KeyCode::Char('j') | KeyCode::Down => Input::Down, 18 | KeyCode::Char('k') | KeyCode::Up => Input::Up, 19 | KeyCode::Char('h') | KeyCode::Left | KeyCode::Backspace => Input::Left, 20 | KeyCode::Char('l') | KeyCode::Right | KeyCode::Enter => Input::Right, 21 | KeyCode::Home => Input::Home, 22 | KeyCode::End => Input::End, 23 | KeyCode::PageUp => Input::PageUp, 24 | KeyCode::PageDown => Input::PageDown, 25 | _ => Input::None, 26 | }; 27 | 28 | return input; 29 | } 30 | } 31 | 32 | Input::None 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/input/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "crossterm")] 2 | mod crossterm; 3 | 4 | #[cfg(feature = "termion")] 5 | mod termion; 6 | 7 | #[cfg(feature = "termwiz")] 8 | mod termwiz; 9 | 10 | /// Input enum to represent the fours different actions available inside a [`FileExplorer`](crate::FileExplorer). 11 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 12 | pub enum Input { 13 | /// Move the selection up. 14 | Up, 15 | /// Move the selection down. 16 | Down, 17 | /// Select the first entry. 18 | Home, 19 | /// Select the last entry. 20 | End, 21 | /// Scroll several entries up. 22 | PageUp, 23 | /// Scroll several entries down. 24 | PageDown, 25 | /// Go to the parent directory. 26 | Left, 27 | /// Go to the child directory (if the selected item is a directory). 28 | Right, 29 | /// Do nothing (used for converting events from other libraries, like 30 | /// [crossterm](https://docs.rs/crossterm/latest/crossterm/event/enum.Event.html), 31 | /// [termion](https://docs.rs/termion/latest/termion/event/enum.Event.html) and 32 | /// [termwiz](https://docs.rs/termwiz/latest/termwiz/input/enum.InputEvent.html) to [`Input`]). 33 | None, 34 | } 35 | -------------------------------------------------------------------------------- /src/input/termion.rs: -------------------------------------------------------------------------------- 1 | use ratatui::termion; 2 | use ratatui::termion::event::{Event, Key}; 3 | 4 | use super::Input; 5 | 6 | impl From<&Event> for Input { 7 | /// Convert termion [`Event`](https://docs.rs/termion/latest/termion/event/enum.Event.html) to [`Input`]. 8 | /// 9 | /// **Note:** This implementation is only available when the `termion` feature is enabled. 10 | fn from(value: &Event) -> Self { 11 | match value { 12 | Event::Key(key) => match key { 13 | Key::Char('j') | Key::Down => Input::Down, 14 | Key::Char('k') | Key::Up => Input::Up, 15 | Key::Char('h') | Key::Left | Key::Backspace => Input::Left, 16 | Key::Char('l') | Key::Right | Key::Char('\n') => Input::Right, 17 | Key::Home => Input::Home, 18 | Key::End => Input::End, 19 | Key::PageUp => Input::PageUp, 20 | Key::PageDown => Input::PageDown, 21 | _ => Input::None, 22 | }, 23 | _ => Input::None, 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/input/termwiz.rs: -------------------------------------------------------------------------------- 1 | use ratatui::termwiz; 2 | use ratatui::termwiz::{input::InputEvent, input::KeyCode}; 3 | 4 | use super::Input; 5 | 6 | impl From<&InputEvent> for Input { 7 | /// Convert termwiz [`InputEvent`](https://docs.rs/termwiz/latest/termwiz/input/enum.InputEvent.html) to [`Input`]. 8 | /// 9 | /// **Note:** This implementation is only available when the `termwiz` feature is enabled. 10 | fn from(value: &InputEvent) -> Self { 11 | match value { 12 | InputEvent::Key(key) => match key.key { 13 | KeyCode::Char('j') | KeyCode::DownArrow => Input::Down, 14 | KeyCode::Char('k') | KeyCode::UpArrow => Input::Up, 15 | KeyCode::Char('h') | KeyCode::LeftArrow | KeyCode::Backspace => Input::Left, 16 | KeyCode::Char('l') | KeyCode::RightArrow | KeyCode::Enter => Input::Right, 17 | KeyCode::Home => Input::Home, 18 | KeyCode::End => Input::End, 19 | KeyCode::PageUp => Input::PageUp, 20 | KeyCode::PageDown => Input::PageDown, 21 | _ => Input::None, 22 | }, 23 | _ => Input::None, 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | //! # Features 3 | //! - `crossterm` (default): Enables the [`From<&Event>`](enum.Input.html#method.from-2) implementation for [`Input`]. 4 | //! - `termion`: Enables the [`From<&Event>`](enum.Input.html#method.from-1) implementation for [`Input`]. 5 | //! - `termwiz`: Enables the [`From<&InputEvent>`](enum.Input.html#method.from) implementation for [`Input`]. 6 | 7 | #![forbid(unsafe_code)] 8 | #![warn(missing_docs)] 9 | #![warn(rustdoc::missing_crate_level_docs)] 10 | #![warn(rustdoc::unescaped_backticks)] 11 | mod file_explorer; 12 | mod input; 13 | mod widget; 14 | 15 | pub use file_explorer::{File, FileExplorer}; 16 | pub use input::Input; 17 | pub use widget::Theme; 18 | -------------------------------------------------------------------------------- /src/widget.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use ratatui::{ 4 | buffer::Buffer, 5 | layout::Rect, 6 | style::{Color, Style}, 7 | text::{Line, Span, Text}, 8 | widgets::{Block, Borders, HighlightSpacing, List, ListState, WidgetRef}, 9 | }; 10 | 11 | use crate::{File, FileExplorer}; 12 | 13 | type LineFactory = Arc Line<'static> + Send + Sync>; 14 | 15 | pub struct Renderer<'a>(pub(crate) &'a FileExplorer); 16 | 17 | impl WidgetRef for Renderer<'_> { 18 | fn render_ref(&self, area: Rect, buf: &mut Buffer) 19 | where 20 | Self: Sized, 21 | { 22 | let mut state = ListState::default().with_selected(Some(self.0.selected_idx())); 23 | 24 | let highlight_style = if self.0.current().is_dir() { 25 | self.0.theme().highlight_dir_style 26 | } else { 27 | self.0.theme().highlight_item_style 28 | }; 29 | 30 | let mut list = List::new(self.0.files().iter().map(|file| file.text(self.0.theme()))) 31 | .style(self.0.theme().style) 32 | .highlight_spacing(self.0.theme().highlight_spacing.clone()) 33 | .highlight_style(highlight_style) 34 | .scroll_padding(self.0.theme().scroll_padding); 35 | 36 | if let Some(symbol) = self.0.theme().highlight_symbol.as_deref() { 37 | list = list.highlight_symbol(symbol); 38 | } 39 | 40 | if let Some(block) = self.0.theme().block.as_ref() { 41 | let mut block = block.clone(); 42 | 43 | for title_top in self.0.theme().title_top(self.0) { 44 | block = block.title_top(title_top); 45 | } 46 | for title_bottom in self.0.theme().title_bottom(self.0) { 47 | block = block.title_bottom(title_bottom); 48 | } 49 | 50 | list = list.block(block); 51 | } 52 | 53 | ratatui::widgets::StatefulWidgetRef::render_ref(&list, area, buf, &mut state); 54 | } 55 | } 56 | 57 | impl File { 58 | /// Returns the text with the appropriate style to be displayed for the file. 59 | fn text(&self, theme: &Theme) -> Text<'_> { 60 | let style = if self.is_dir() { 61 | *theme.dir_style() 62 | } else { 63 | *theme.item_style() 64 | }; 65 | Span::styled(self.name(), style).into() 66 | } 67 | } 68 | 69 | /// The theme of the file explorer. 70 | /// 71 | /// This struct is used to customize the look of the file explorer. 72 | /// It allows to set the style of the widget and the style of the files. 73 | /// You can also wrap the widget in a block with the [`Theme::with_block`](#method.block) 74 | /// method and add customizable titles to it with [`Theme::with_title_top`](#method.title_top) 75 | /// and [`Theme::with_title_bottom`](#method.title_bottom). 76 | #[derive(Clone, educe::Educe)] 77 | #[educe(Debug, PartialEq, Eq, Hash)] 78 | pub struct Theme { 79 | block: Option>, 80 | #[educe(Debug(ignore), PartialEq(ignore), Hash(ignore))] 81 | title_top: Vec, 82 | #[educe(Debug(ignore), PartialEq(ignore), Hash(ignore))] 83 | title_bottom: Vec, 84 | style: Style, 85 | item_style: Style, 86 | dir_style: Style, 87 | highlight_spacing: HighlightSpacing, 88 | highlight_item_style: Style, 89 | highlight_dir_style: Style, 90 | highlight_symbol: Option, 91 | scroll_padding: usize, 92 | } 93 | 94 | impl Theme { 95 | /// Create a new empty theme. 96 | /// 97 | /// The theme will not have any style set. To get a theme with the default style, use [`Theme::default`](#method.default). 98 | /// 99 | /// # Example 100 | /// ```no_run 101 | /// # use ratatui_explorer::Theme; 102 | /// let theme = Theme::new(); 103 | /// ``` 104 | #[must_use] 105 | pub const fn new() -> Self { 106 | Self { 107 | block: None, 108 | title_top: Vec::new(), 109 | title_bottom: Vec::new(), 110 | style: Style::new(), 111 | item_style: Style::new(), 112 | dir_style: Style::new(), 113 | highlight_spacing: HighlightSpacing::WhenSelected, 114 | highlight_item_style: Style::new(), 115 | highlight_dir_style: Style::new(), 116 | highlight_symbol: None, 117 | scroll_padding: 0, 118 | } 119 | } 120 | 121 | /// Add a top title to the theme. 122 | /// The title is the current working directory. 123 | /// 124 | /// # Example 125 | /// Suppose you have this tree file, with `passport.png` selected inside `file_explorer`: 126 | /// ```plaintext 127 | /// / 128 | /// ├── .git 129 | /// └── Documents 130 | /// ├── passport.png <- selected 131 | /// └── resume.pdf 132 | /// ``` 133 | /// You will end up with something like this: 134 | /// ```plaintext 135 | /// ┌/Documents────────────────────────┐ 136 | /// │ ../ │ 137 | /// │ passport.png │ 138 | /// │ resume.pdf │ 139 | /// └──────────────────────────────────┘ 140 | /// ``` 141 | /// With this code: 142 | /// ```no_run 143 | /// use ratatui::widgets::*; 144 | /// use ratatui_explorer::{FileExplorer, Theme}; 145 | /// 146 | /// let theme = Theme::default() 147 | /// .with_block(Block::default().borders(Borders::ALL)) 148 | /// .add_default_title(); 149 | /// 150 | /// let file_explorer = FileExplorer::with_theme(theme).unwrap(); 151 | /// 152 | /// /* user select `password.png` */ 153 | /// 154 | /// let widget = file_explorer.widget(); 155 | /// /* render the widget */ 156 | /// ``` 157 | #[inline] 158 | #[must_use = "method moves the value of self and returns the modified value"] 159 | pub fn add_default_title(self) -> Self { 160 | self.with_title_top(|file_explorer: &FileExplorer| { 161 | Line::from(file_explorer.cwd().display().to_string()) 162 | }) 163 | } 164 | 165 | /// Wrap the file explorer with a custom [`Block`](https://docs.rs/ratatui/latest/ratatui/widgets/block/struct.Block.html) widget. 166 | /// 167 | /// Behind the scene, it use the [`List::block`](https://docs.rs/ratatui/latest/ratatui/widgets/struct.List.html#method.block) method. See its documentation for more. 168 | /// 169 | /// You can use [`Theme::with_title_top`](#method.title_top) and [`Theme::with_title_bottom`](#method.title_bottom) 170 | /// to add customizable titles to the block. 171 | /// 172 | /// # Example 173 | /// ```no_run 174 | /// # use ratatui::widgets::*; 175 | /// # use ratatui_explorer::Theme; 176 | /// let theme = Theme::default().with_block(Block::default().borders(Borders::ALL)); 177 | /// ``` 178 | #[inline] 179 | #[must_use = "method moves the value of self and returns the modified value"] 180 | pub fn with_block(mut self, block: Block<'static>) -> Self { 181 | self.block = Some(block); 182 | self 183 | } 184 | 185 | /// Set the style of the widget. 186 | /// 187 | /// Behind the scene, it use the [`List::style`](https://docs.rs/ratatui/latest/ratatui/widgets/struct.List.html#method.style) method. See its documentation for more. 188 | /// 189 | /// # Example 190 | /// ```no_run 191 | /// # use ratatui::prelude::*; 192 | /// # use ratatui_explorer::Theme; 193 | /// let theme = Theme::default().with_style(Style::default().fg(Color::Yellow)); 194 | /// ``` 195 | #[inline] 196 | #[must_use = "method moves the value of self and returns the modified value"] 197 | pub fn with_style>(mut self, style: S) -> Self { 198 | self.style = style.into(); 199 | self 200 | } 201 | 202 | /// Set the style of all non directories items. To set the style of the directories, use [`Theme::with_dir_style`](#method.dir_style). 203 | /// 204 | /// Behind the scene, it use the [`Span::styled`](https://docs.rs/ratatui/latest/ratatui/text/struct.Span.html#method.styled) method. See its documentation for more. 205 | /// 206 | /// # Example 207 | /// ```no_run 208 | /// # use ratatui::prelude::*; 209 | /// # use ratatui_explorer::Theme; 210 | /// let theme = Theme::default().with_item_style(Style::default().fg(Color::White)); 211 | /// ``` 212 | #[inline] 213 | #[must_use = "method moves the value of self and returns the modified value"] 214 | pub fn with_item_style>(mut self, item_style: S) -> Self { 215 | self.item_style = item_style.into(); 216 | self 217 | } 218 | 219 | /// Set the style of all directories items. To set the style of the non directories, use [`Theme::with_item_style`](#method.item_style). 220 | /// 221 | /// Behind the scene, it use the [`Span::styled`](https://docs.rs/ratatui/latest/ratatui/text/struct.Span.html#method.styled) method. See its documentation for more. 222 | /// 223 | /// # Example 224 | /// ```no_run 225 | /// # use ratatui::prelude::*; 226 | /// # use ratatui_explorer::Theme; 227 | /// let theme = Theme::default().with_dir_style(Style::default().fg(Color::Blue)); 228 | /// ``` 229 | #[inline] 230 | #[must_use = "method moves the value of self and returns the modified value"] 231 | pub fn with_dir_style>(mut self, dir_style: S) -> Self { 232 | self.dir_style = dir_style.into(); 233 | self 234 | } 235 | 236 | /// Set the style of all highlighted non directories items. To set the style of the highlighted directories, use [`Theme::with_highlight_dir_style`](#method.highlight_dir_style). 237 | /// 238 | /// Behind the scene, it use the [`List::highlight_style`](https://docs.rs/ratatui/latest/ratatui/widgets/struct.List.html#method.highlight_style) method. See its documentation for more. 239 | /// 240 | /// # Example 241 | /// ```no_run 242 | /// # use ratatui::prelude::*; 243 | /// # use ratatui_explorer::Theme; 244 | /// let theme = Theme::default().with_highlight_item_style(Style::default().add_modifier(Modifier::BOLD)); 245 | /// ``` 246 | #[inline] 247 | #[must_use = "method moves the value of self and returns the modified value"] 248 | pub fn with_highlight_item_style>(mut self, highlight_item_style: S) -> Self { 249 | self.highlight_item_style = highlight_item_style.into(); 250 | self 251 | } 252 | 253 | /// Set the style of all highlighted directories items. To set the style of the highlighted non directories, use [`Theme::with_highlight_item_style`](#method.highlight_item_style). 254 | /// 255 | /// Behind the scene, it use the [`List::highlight_style`](https://docs.rs/ratatui/latest/ratatui/widgets/struct.List.html#method.highlight_style) method. See its documentation for more. 256 | /// 257 | /// # Example 258 | /// ```no_run 259 | /// # use ratatui::prelude::*; 260 | /// # use ratatui_explorer::Theme; 261 | /// let theme = Theme::default().with_highlight_dir_style(Style::default().fg(Color::Blue).add_modifier(Modifier::BOLD)); 262 | /// ``` 263 | #[inline] 264 | #[must_use = "method moves the value of self and returns the modified value"] 265 | pub fn with_highlight_dir_style>(mut self, highlight_dir_style: S) -> Self { 266 | self.highlight_dir_style = highlight_dir_style.into(); 267 | self 268 | } 269 | 270 | /// Set the symbol used to highlight the selected item. 271 | /// 272 | /// Behind the scene, it use the [List::highlight_symbol](https://docs.rs/ratatui/latest/ratatui/widgets/struct.List.html#method.highlight_symbol) method. See its documentation for more. 273 | /// 274 | /// # Example 275 | /// ```no_run 276 | /// # use ratatui_explorer::Theme; 277 | /// let theme = Theme::default().with_highlight_symbol("> "); 278 | /// ``` 279 | #[inline] 280 | #[must_use = "method moves the value of self and returns the modified value"] 281 | pub fn with_highlight_symbol(mut self, highlight_symbol: &str) -> Self { 282 | self.highlight_symbol = Some(highlight_symbol.to_owned()); 283 | self 284 | } 285 | 286 | /// Set the spacing between the highlighted item and the other items. 287 | /// 288 | /// Behind the scene, it use the [`List::highlight_spacing`](https://docs.rs/ratatui/latest/ratatui/widgets/struct.List.html#method.highlight_spacing) method. See its documentation for more. 289 | /// 290 | /// # Example 291 | /// ```no_run 292 | /// # use ratatui::widgets::*; 293 | /// # use ratatui_explorer::Theme; 294 | /// let theme = Theme::default().with_highlight_spacing(HighlightSpacing::Never); 295 | /// ``` 296 | #[inline] 297 | #[must_use = "method moves the value of self and returns the modified value"] 298 | pub fn with_highlight_spacing(mut self, highlight_spacing: HighlightSpacing) -> Self { 299 | self.highlight_spacing = highlight_spacing; 300 | self 301 | } 302 | 303 | /// Sets the number of items around the currently selected item that should be kept visible. 304 | /// 305 | /// /// Behind the scene, it use the [List::scroll_padding](https://docs.rs/ratatui/latest/ratatui/widgets/struct.List.html#method.scroll_padding) method. See its documentation for more. 306 | /// 307 | /// # Example 308 | /// ```no_run 309 | /// # use ratatui::widgets::*; 310 | /// # use ratatui_explorer::Theme; 311 | /// let theme = Theme::default().with_scroll_padding(1); 312 | /// ``` 313 | #[inline] 314 | #[must_use = "method moves the value of self and returns the modified value"] 315 | pub fn with_scroll_padding(mut self, scroll_padding: usize) -> Self { 316 | self.scroll_padding = scroll_padding; 317 | self 318 | } 319 | 320 | /// Add a top title factory to the theme. 321 | /// 322 | /// `title_top` is a function that take a reference to the current [`FileExplorer`] and returns 323 | /// a [`Line`](https://docs.rs/ratatui/latest/ratatui/text/struct.Line.html) 324 | /// to be displayed as a title at the top of the wrapping block (if it exist) of the file explorer. You can call 325 | /// this function multiple times to add multiple titles. 326 | /// 327 | /// Behind the scene, it use the [`Block::title_top`](https://docs.rs/ratatui/latest/ratatui/widgets/block/struct.Block.html#method.title_top) method. See its documentation for more. 328 | /// 329 | /// # Example 330 | /// ```no_run 331 | /// use ratatui::prelude::*; 332 | /// # use ratatui_explorer::{FileExplorer, Theme}; 333 | /// let theme = Theme::default() 334 | /// .with_title_top(|file_explorer: &FileExplorer| { 335 | /// Line::from(format!("cwd - {}", file_explorer.cwd().display())) 336 | /// }) 337 | /// .with_title_top(|file_explorer: &FileExplorer| { 338 | /// Line::from(format!("{} files", file_explorer.files().len() - 1)).right_aligned() 339 | /// }); 340 | /// ``` 341 | #[inline] 342 | #[must_use = "method moves the value of self and returns the modified value"] 343 | pub fn with_title_top( 344 | mut self, 345 | title_top: impl Fn(&FileExplorer) -> Line<'static> + 'static + Send + Sync, 346 | ) -> Self { 347 | self.title_top.push(Arc::new(title_top)); 348 | self 349 | } 350 | 351 | /// Add a bottom title factory to the theme. 352 | /// 353 | /// `title_bottom` is a function that take a reference to the current [`FileExplorer`] and returns 354 | /// a [`Line`](https://docs.rs/ratatui/latest/ratatui/text/struct.Line.html) 355 | /// to be displayed as a title at the bottom of the wrapping block (if it exist) of the file explorer. You can call 356 | /// this function multiple times to add multiple titles. 357 | /// 358 | /// Behind the scene, it use the [`Block::title_bottom`](https://docs.rs/ratatui/latest/ratatui/widgets/block/struct.Block.html#method.title_bottom) method. See its documentation for more. 359 | /// 360 | /// # Example 361 | /// ```no_run 362 | /// # use ratatui::prelude::*; 363 | /// # use ratatui_explorer::{FileExplorer, Theme}; 364 | /// let theme = Theme::default() 365 | /// .with_title_bottom(|file_explorer: &FileExplorer| { 366 | /// Line::from(format!("cwd - {}", file_explorer.cwd().display())) 367 | /// }) 368 | /// .with_title_bottom(|file_explorer: &FileExplorer| { 369 | /// Line::from(format!("{} files", file_explorer.files().len() - 1)).right_aligned() 370 | /// }); 371 | /// ``` 372 | #[inline] 373 | #[must_use = "method moves the value of self and returns the modified value"] 374 | pub fn with_title_bottom( 375 | mut self, 376 | title_bottom: impl Fn(&FileExplorer) -> Line<'static> + 'static + Send + Sync, 377 | ) -> Self { 378 | self.title_bottom.push(Arc::new(title_bottom)); 379 | self 380 | } 381 | 382 | /// Returns the wrapping block (if it exist) of the file explorer of the theme. 383 | #[inline] 384 | #[must_use] 385 | pub const fn block(&self) -> Option<&Block<'static>> { 386 | self.block.as_ref() 387 | } 388 | 389 | /// Returns the style of the widget of the theme. 390 | #[inline] 391 | #[must_use] 392 | pub const fn style(&self) -> &Style { 393 | &self.style 394 | } 395 | 396 | /// Returns the style of the non directories items of the theme. 397 | #[inline] 398 | #[must_use] 399 | pub const fn item_style(&self) -> &Style { 400 | &self.item_style 401 | } 402 | 403 | /// Returns the style of the directories items of the theme. 404 | #[inline] 405 | #[must_use] 406 | pub const fn dir_style(&self) -> &Style { 407 | &self.dir_style 408 | } 409 | 410 | /// Returns the style of the highlighted non directories items of the theme. 411 | #[inline] 412 | #[must_use] 413 | pub const fn highlight_item_style(&self) -> &Style { 414 | &self.highlight_item_style 415 | } 416 | 417 | /// Returns the style of the highlighted directories items of the theme. 418 | #[inline] 419 | #[must_use] 420 | pub const fn highlight_dir_style(&self) -> &Style { 421 | &self.highlight_dir_style 422 | } 423 | 424 | /// Returns the symbol used to highlight the selected item of the theme. 425 | #[inline] 426 | #[must_use] 427 | pub fn highlight_symbol(&self) -> Option<&str> { 428 | self.highlight_symbol.as_deref() 429 | } 430 | 431 | /// Returns the spacing between the highlighted item and the other items of the theme. 432 | #[inline] 433 | #[must_use] 434 | pub const fn highlight_spacing(&self) -> &HighlightSpacing { 435 | &self.highlight_spacing 436 | } 437 | 438 | /// Returns the number of items around the currently selected item that should be kept visible. 439 | #[inline] 440 | #[must_use] 441 | pub const fn scroll_padding(&self) -> usize { 442 | self.scroll_padding 443 | } 444 | 445 | /// Returns the generated top titles of the theme. 446 | #[inline] 447 | #[must_use] 448 | pub fn title_top(&self, file_explorer: &FileExplorer) -> Vec { 449 | self.title_top 450 | .iter() 451 | .map(|title_top| title_top(file_explorer)) 452 | .collect() 453 | } 454 | 455 | /// Returns the generated bottom titles of the theme. 456 | #[inline] 457 | #[must_use] 458 | pub fn title_bottom(&self, file_explorer: &FileExplorer) -> Vec { 459 | self.title_bottom 460 | .iter() 461 | .map(|title_bottom| title_bottom(file_explorer)) 462 | .collect() 463 | } 464 | } 465 | 466 | impl Default for Theme { 467 | /// Return a slightly customized default theme. To get a theme with no style set, use [`Theme::new`](#method.new). 468 | /// 469 | /// The theme will have a block with all borders, a white style for the items, a light blue style for the directories, 470 | /// a dark gray background for all the highlighted items. 471 | /// 472 | /// # Example 473 | /// ```no_run 474 | /// # use ratatui_explorer::Theme; 475 | /// let theme = Theme::default(); 476 | /// ``` 477 | fn default() -> Self { 478 | Self { 479 | block: Some(Block::default().borders(Borders::ALL)), 480 | title_top: Vec::new(), 481 | title_bottom: Vec::new(), 482 | style: Style::default(), 483 | item_style: Style::default().fg(Color::White), 484 | dir_style: Style::default().fg(Color::LightBlue), 485 | highlight_spacing: HighlightSpacing::Always, 486 | highlight_item_style: Style::default().fg(Color::White).bg(Color::DarkGray), 487 | highlight_dir_style: Style::default().fg(Color::LightBlue).bg(Color::DarkGray), 488 | highlight_symbol: None, 489 | scroll_padding: 0, 490 | } 491 | } 492 | } 493 | --------------------------------------------------------------------------------