mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 23:01:05 +00:00
feat: make the preview scrollable (#86)
This commit is contained in:
parent
388ad43537
commit
e4096c0f25
31 changed files with 645 additions and 425 deletions
222
Cargo.lock
generated
222
Cargo.lock
generated
|
|
@ -19,9 +19,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.20.0"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3"
|
||||
checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
|
||||
dependencies = [
|
||||
"gimli",
|
||||
]
|
||||
|
|
@ -69,24 +69,23 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.3.2"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163"
|
||||
checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is-terminal",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.1"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd"
|
||||
checksum = "15c4c2c83f81532e5845a733998b6971faca23490340a418e9b72a3ec9de12ea"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
|
|
@ -103,17 +102,17 @@ version = "1.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
|
||||
dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "1.0.2"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c677ab05e09154296dd37acecd46420c17b9713e8366facafa8fc0885167cf4c"
|
||||
checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -163,9 +162,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
|||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.68"
|
||||
version = "0.3.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12"
|
||||
checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
|
||||
dependencies = [
|
||||
"addr2line",
|
||||
"cc",
|
||||
|
|
@ -250,9 +249,9 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
|
|||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.82"
|
||||
version = "1.0.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01"
|
||||
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
|
@ -277,9 +276,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.3.22"
|
||||
version = "4.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b417ae4361bca3f5de378294fc7472d3c4ed86a5ef9f49e93ae722f432aae8d2"
|
||||
checksum = "1d5f1946157a96594eb2d2c10eb7ad9a2b27518cb3000209dec700c35df9197d"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
|
|
@ -288,9 +287,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.3.22"
|
||||
version = "4.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c90dc0f0e42c64bff177ca9d7be6fcc9ddb0f26a6e062174a61c84dd6c644d4"
|
||||
checksum = "78116e32a042dd73c2901f0dc30790d20ff3447f3e3472fad359e8c3d282bcd6"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
|
|
@ -300,9 +299,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.3.12"
|
||||
version = "4.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050"
|
||||
checksum = "c9fd1a5729c4548118d7d70ff234a44868d00489a4b6597b0b020918a0e91a1a"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
|
|
@ -312,9 +311,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.5.0"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b"
|
||||
checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961"
|
||||
|
||||
[[package]]
|
||||
name = "clipboard-win"
|
||||
|
|
@ -361,7 +360,6 @@ dependencies = [
|
|||
"indexmap 2.0.0",
|
||||
"md-5",
|
||||
"ratatui",
|
||||
"regex",
|
||||
"serde",
|
||||
"shared",
|
||||
"shell-words",
|
||||
|
|
@ -385,6 +383,7 @@ dependencies = [
|
|||
"notify",
|
||||
"parking_lot",
|
||||
"ratatui",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"shared",
|
||||
|
|
@ -515,9 +514,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.3.7"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7684a49fb1af197853ef7b2ee694bc1f5b4179556f1e5710e1760c5db6f5e929"
|
||||
checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946"
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
|
|
@ -547,7 +546,7 @@ dependencies = [
|
|||
"libc",
|
||||
"option-ext",
|
||||
"redox_users",
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -562,27 +561,6 @@ version = "1.0.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f"
|
||||
dependencies = [
|
||||
"errno-dragonfly",
|
||||
"libc",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "errno-dragonfly"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "error-code"
|
||||
version = "2.3.1"
|
||||
|
|
@ -633,7 +611,7 @@ dependencies = [
|
|||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall 0.3.5",
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -807,9 +785,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.27.3"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e"
|
||||
checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0"
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
|
|
@ -954,17 +932,6 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is-terminal"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"rustix",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.9"
|
||||
|
|
@ -1042,12 +1009,6 @@ version = "0.5.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.10"
|
||||
|
|
@ -1122,7 +1083,7 @@ dependencies = [
|
|||
"libc",
|
||||
"log",
|
||||
"wasi",
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1152,19 +1113,20 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "notify"
|
||||
version = "6.0.1"
|
||||
version = "6.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5738a2795d57ea20abec2d6d76c6081186709c0024187cd5977265eda6598b51"
|
||||
checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"bitflags 2.4.0",
|
||||
"filetime",
|
||||
"fsevent-sys",
|
||||
"inotify",
|
||||
"kqueue",
|
||||
"libc",
|
||||
"log",
|
||||
"mio",
|
||||
"walkdir",
|
||||
"windows-sys 0.45.0",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1228,9 +1190,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.31.1"
|
||||
version = "0.32.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1"
|
||||
checksum = "77ac5bbd07aea88c60a577a1ce218075ffd59208b2d7ca97adf9bfc5aeb21ebe"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
|
@ -1295,7 +1257,7 @@ dependencies = [
|
|||
"libc",
|
||||
"redox_syscall 0.3.5",
|
||||
"smallvec",
|
||||
"windows-targets 0.48.3",
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1332,9 +1294,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.12"
|
||||
version = "0.2.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05"
|
||||
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
|
|
@ -1364,9 +1326,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.17.9"
|
||||
version = "0.17.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59871cc5b6cce7eaccca5a802b4173377a1c2ba90654246789a8fa2334426d11"
|
||||
checksum = "dd75bf2d8dd3702b9707cdbc56a5b9ef42cec752eb8b3bafc01234558442aa64"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"crc32fast",
|
||||
|
|
@ -1536,19 +1498,6 @@ version = "0.1.23"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f"
|
||||
dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.15"
|
||||
|
|
@ -1578,18 +1527,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
|||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.183"
|
||||
version = "1.0.188"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c"
|
||||
checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.183"
|
||||
version = "1.0.188"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816"
|
||||
checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
@ -1694,9 +1643,9 @@ checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
|
|||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.8"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d"
|
||||
checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
|
@ -1714,7 +1663,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1824,9 +1773,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.25"
|
||||
version = "0.3.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0fdd63d58b18d663fbdf70e049f00a22c8e42be082203be7f26589213cd75ea"
|
||||
checksum = "0bb39ee79a6d8de55f48f2293a830e040392f1c5f16e336bdd1788cd0aadce07"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa",
|
||||
|
|
@ -1843,9 +1792,9 @@ checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"
|
|||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.11"
|
||||
version = "0.2.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb71511c991639bb078fd5bf97757e03914361c48100d52878b8e52b46fb92cd"
|
||||
checksum = "733d258752e9303d392b94b75230d07b0b9c489350c69b851fc6c065fde3e8f9"
|
||||
dependencies = [
|
||||
"time-core",
|
||||
]
|
||||
|
|
@ -1881,7 +1830,7 @@ dependencies = [
|
|||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2258,16 +2207,7 @@ version = "0.48.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.45.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
|
||||
dependencies = [
|
||||
"windows-targets 0.42.2",
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2276,7 +2216,7 @@ version = "0.48.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.3",
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2296,17 +2236,17 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.3"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "27f51fb4c64f8b770a823c043c7fad036323e1c48f55287b7bbb7987b2fcdf3b"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.48.3",
|
||||
"windows_aarch64_msvc 0.48.3",
|
||||
"windows_i686_gnu 0.48.3",
|
||||
"windows_i686_msvc 0.48.3",
|
||||
"windows_x86_64_gnu 0.48.3",
|
||||
"windows_x86_64_gnullvm 0.48.3",
|
||||
"windows_x86_64_msvc 0.48.3",
|
||||
"windows_aarch64_gnullvm 0.48.5",
|
||||
"windows_aarch64_msvc 0.48.5",
|
||||
"windows_i686_gnu 0.48.5",
|
||||
"windows_i686_msvc 0.48.5",
|
||||
"windows_x86_64_gnu 0.48.5",
|
||||
"windows_x86_64_gnullvm 0.48.5",
|
||||
"windows_x86_64_msvc 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2317,9 +2257,9 @@ checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
|
|||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.3"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fde1bb55ae4ce76a597a8566d82c57432bc69c039449d61572a7a353da28f68c"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
|
|
@ -2329,9 +2269,9 @@ checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
|
|||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.3"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1513e8d48365a78adad7322fd6b5e4c4e99d92a69db8df2d435b25b1f1f286d4"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
|
|
@ -2341,9 +2281,9 @@ checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
|
|||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.3"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60587c0265d2b842298f5858e1a5d79d146f9ee0c37be5782e92a6eb5e1d7a83"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
|
|
@ -2353,9 +2293,9 @@ checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
|
|||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.3"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "224fe0e0ffff5d2ea6a29f82026c8f43870038a0ffc247aa95a52b47df381ac4"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
|
|
@ -2365,9 +2305,9 @@ checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
|
|||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.3"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62fc52a0f50a088de499712cbc012df7ebd94e2d6eb948435449d76a6287e7ad"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
|
|
@ -2377,9 +2317,9 @@ checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
|
|||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.3"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2093925509d91ea3d69bcd20238f4c2ecdb1a29d3c281d026a09705d0dd35f3d"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
|
|
@ -2389,15 +2329,15 @@ checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
|
|||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.3"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6ade45bc8bf02ae2aa34a9d54ba660a1a58204da34ba793c00d83ca3730b5f1"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.5.14"
|
||||
version = "0.5.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d09770118a7eb1ccaf4a594a221334119a44a814fcb0d31c5b85e83e97227a97"
|
||||
checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ pub struct Adaptor;
|
|||
|
||||
impl Adaptor {
|
||||
pub async fn image_show(mut path: &Path, rect: Rect) -> Result<()> {
|
||||
let cache = BOOT.cache(path);
|
||||
let cache = BOOT.cache(path, 0);
|
||||
if fs::metadata(&cache).await.is_ok() {
|
||||
path = cache.as_path();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,8 +87,8 @@ impl App {
|
|||
|
||||
fn dispatch_resize(&mut self) {
|
||||
self.cx.manager.current_mut().set_page(true);
|
||||
self.cx.manager.active_mut().preview_reset_image();
|
||||
self.cx.manager.preview(self.cx.image_layer());
|
||||
self.cx.manager.active_mut().preview_reset();
|
||||
self.cx.manager.peek(true, self.cx.image_layer());
|
||||
emit!(Render);
|
||||
}
|
||||
|
||||
|
|
@ -100,7 +100,6 @@ impl App {
|
|||
} else {
|
||||
self.term = Some(Term::start().unwrap());
|
||||
self.signals.stop_term(false);
|
||||
self.cx.manager.preview(self.cx.image_layer());
|
||||
emit!(Render);
|
||||
emit!(Hover);
|
||||
}
|
||||
|
|
@ -152,18 +151,24 @@ impl App {
|
|||
Event::Mimetype(mimes) => {
|
||||
if manager.update_mimetype(mimes, tasks) {
|
||||
emit!(Render);
|
||||
self.cx.manager.preview(self.cx.image_layer());
|
||||
emit!(Peek);
|
||||
}
|
||||
}
|
||||
Event::Hover(file) => {
|
||||
if file.map(|f| manager.current_mut().hover_force(f)).unwrap_or(false) {
|
||||
if manager.update_hover(file) {
|
||||
emit!(Render);
|
||||
}
|
||||
self.cx.manager.preview(self.cx.image_layer());
|
||||
emit!(Peek);
|
||||
}
|
||||
Event::Preview(path, mime, data) => {
|
||||
manager.update_preview(path, mime, data);
|
||||
emit!(Render);
|
||||
Event::Peek(skip, sequent) => {
|
||||
let b = sequent.is_some();
|
||||
manager.active_mut().update_peek(skip as isize, sequent);
|
||||
self.cx.manager.peek(b, self.cx.image_layer());
|
||||
}
|
||||
Event::Preview(lock) => {
|
||||
if manager.active_mut().update_preview(lock) {
|
||||
emit!(Render);
|
||||
}
|
||||
}
|
||||
|
||||
Event::Select(opt, tx) => {
|
||||
|
|
|
|||
|
|
@ -61,6 +61,11 @@ impl Executor {
|
|||
let step = exec.args.get(0).and_then(|s| s.parse().ok()).unwrap_or(0);
|
||||
cx.manager.active_mut().arrow(step)
|
||||
}
|
||||
"peek" => {
|
||||
let step = exec.args.get(0).and_then(|s| s.parse().ok()).unwrap_or(0);
|
||||
cx.manager.active_mut().update_peek(step, None);
|
||||
cx.manager.peek(true, cx.image_layer())
|
||||
}
|
||||
"leave" => cx.manager.active_mut().leave(),
|
||||
"enter" => cx.manager.active_mut().enter(),
|
||||
"back" => cx.manager.active_mut().back(),
|
||||
|
|
|
|||
|
|
@ -44,8 +44,14 @@ impl<'a> Folder<'a> {
|
|||
|
||||
impl<'a> Widget for Folder<'a> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
let window = self.folder.window();
|
||||
let mode = self.cx.manager.active().mode();
|
||||
let active = self.cx.manager.active();
|
||||
let mode = active.mode();
|
||||
|
||||
let window = if self.is_preview {
|
||||
self.folder.window_for(active.preview().skip())
|
||||
} else {
|
||||
self.folder.window()
|
||||
};
|
||||
|
||||
let items = window
|
||||
.iter()
|
||||
|
|
|
|||
|
|
@ -26,8 +26,7 @@ impl<'a> Widget for Preview<'a> {
|
|||
return;
|
||||
}
|
||||
|
||||
match &preview.data {
|
||||
PreviewData::None => {}
|
||||
match &preview.lock.as_ref().unwrap().data {
|
||||
PreviewData::Folder => {
|
||||
if let Some(folder) = manager.active().history(hovered) {
|
||||
Folder::new(self.cx, folder).with_preview(true).render(area, buf);
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ glob = "^0"
|
|||
indexmap = "^2"
|
||||
md-5 = "^0"
|
||||
ratatui = "^0"
|
||||
regex = "^1"
|
||||
serde = { version = "^1", features = [ "derive" ] }
|
||||
shell-words = "^1"
|
||||
toml = { version = "^0", features = [ "preserve_order" ] }
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@ keymap = [
|
|||
{ on = [ "H" ], exec = "back" },
|
||||
{ on = [ "L" ], exec = "forward" },
|
||||
|
||||
{ on = [ "<C-k>" ], exec = "peek -5" },
|
||||
{ on = [ "<C-j>" ], exec = "peek 5" },
|
||||
|
||||
{ on = [ "<Up>" ], exec = "arrow -1" },
|
||||
{ on = [ "<Down>" ], exec = "arrow 1" },
|
||||
{ on = [ "<Left>" ], exec = "leave" },
|
||||
|
|
|
|||
|
|
@ -41,13 +41,13 @@ rules = [
|
|||
{ mime = "audio/*", fg = "#F3D398" },
|
||||
|
||||
# Archives
|
||||
{ mime = "application/zip", fg = "#CD9EFC" },
|
||||
{ mime = "application/gzip", fg = "#CD9EFC" },
|
||||
{ mime = "application/x-bzip", fg = "#CD9EFC" },
|
||||
{ mime = "application/x-bzip2", fg = "#CD9EFC" },
|
||||
{ mime = "application/gzip", fg = "#CD9EFC" },
|
||||
{ mime = "application/vnd.rar", fg = "#CD9EFC" },
|
||||
{ mime = "application/x-tar", fg = "#CD9EFC" },
|
||||
{ mime = "application/zip", fg = "#CD9EFC" },
|
||||
{ mime = "application/x-7z-compressed", fg = "#CD9EFC" },
|
||||
{ mime = "application/x-rar", fg = "#CD9EFC" },
|
||||
|
||||
# Fallback
|
||||
{ name = "*", fg = "#C8D3F8" },
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::{env, fs, path::{Path, PathBuf}, time::{self, SystemTime}};
|
||||
use std::{env, fs, path::{Path, PathBuf}, process, time::{self, SystemTime}};
|
||||
|
||||
use clap::{command, Parser};
|
||||
use md5::{Digest, Md5};
|
||||
|
|
@ -30,6 +30,10 @@ struct Args {
|
|||
/// Write the selected files on open emitted by the chooser mode
|
||||
#[arg(long)]
|
||||
chooser_file: Option<PathBuf>,
|
||||
|
||||
/// Clear the cache directory
|
||||
#[arg(long, action)]
|
||||
clear_cache: bool,
|
||||
}
|
||||
|
||||
impl Default for Boot {
|
||||
|
|
@ -58,23 +62,22 @@ impl Default for Boot {
|
|||
fs::create_dir_all(&boot.state_dir).unwrap();
|
||||
}
|
||||
|
||||
if args.clear_cache {
|
||||
println!("Clearing cache directory: {:?}", boot.cache_dir);
|
||||
fs::remove_dir_all(&boot.cache_dir).unwrap();
|
||||
process::exit(0);
|
||||
}
|
||||
|
||||
boot
|
||||
}
|
||||
}
|
||||
|
||||
impl Boot {
|
||||
#[inline]
|
||||
pub fn cache(&self, path: &Path) -> PathBuf {
|
||||
#[cfg(target_os = "windows")]
|
||||
let h = Md5::new_with_prefix(path.to_string_lossy().as_bytes());
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let h = {
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
Md5::new_with_prefix(path.as_os_str().as_bytes())
|
||||
};
|
||||
|
||||
self.cache_dir.join(format!("{:x}", h.finalize()))
|
||||
pub fn cache(&self, path: &Path, skip: usize) -> PathBuf {
|
||||
self
|
||||
.cache_dir
|
||||
.join(format!("{:x}", Md5::new_with_prefix(format!("{:?}///{}", path, skip)).finalize()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ natord = "^1"
|
|||
notify = { version = "^6", default-features = false, features = [ "macos_fsevent" ] }
|
||||
parking_lot = "^0"
|
||||
ratatui = "^0"
|
||||
regex = "^1"
|
||||
serde = "^1"
|
||||
serde_json = "^1"
|
||||
syntect = "^5"
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ use crossterm::event::KeyEvent;
|
|||
use shared::RoCell;
|
||||
use tokio::sync::{mpsc::UnboundedSender, oneshot};
|
||||
|
||||
use super::{files::{File, FilesOp}, input::InputOpt, manager::PreviewData, select::SelectOpt};
|
||||
use super::{files::{File, FilesOp}, input::InputOpt, select::SelectOpt};
|
||||
use crate::manager::PreviewLock;
|
||||
|
||||
static TX: RoCell<UnboundedSender<Event>> = RoCell::new();
|
||||
|
||||
|
|
@ -26,7 +27,8 @@ pub enum Event {
|
|||
Pages(usize),
|
||||
Mimetype(BTreeMap<PathBuf, String>),
|
||||
Hover(Option<File>),
|
||||
Preview(PathBuf, String, PreviewData),
|
||||
Peek(usize, Option<PathBuf>),
|
||||
Preview(PreviewLock),
|
||||
|
||||
// Input
|
||||
Select(SelectOpt, oneshot::Sender<Result<usize>>),
|
||||
|
|
@ -87,8 +89,14 @@ macro_rules! emit {
|
|||
(Hover($file:expr)) => {
|
||||
$crate::Event::Hover(Some($file)).emit();
|
||||
};
|
||||
(Preview($path:expr, $mime:expr, $data:expr)) => {
|
||||
$crate::Event::Preview($path, $mime, $data).emit();
|
||||
(Peek) => {
|
||||
$crate::Event::Peek(0, None).emit();
|
||||
};
|
||||
(Peek($skip:expr, $path:expr)) => {
|
||||
$crate::Event::Peek($skip, Some($path)).emit();
|
||||
};
|
||||
(Preview($lock:expr)) => {
|
||||
$crate::Event::Preview($lock).emit();
|
||||
};
|
||||
|
||||
(Select($opt:expr)) => {{
|
||||
|
|
|
|||
12
core/src/external/ffmpegthumbnailer.rs
vendored
12
core/src/external/ffmpegthumbnailer.rs
vendored
|
|
@ -1,22 +1,28 @@
|
|||
use std::path::Path;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use config::PREVIEW;
|
||||
use shared::PeekError;
|
||||
use tokio::process::Command;
|
||||
|
||||
pub async fn ffmpegthumbnailer(src: &Path, dest: &Path) -> Result<()> {
|
||||
pub async fn ffmpegthumbnailer(src: &Path, dest: &Path, skip: usize) -> Result<(), PeekError> {
|
||||
let percentage = 5 + skip;
|
||||
if percentage > 95 {
|
||||
return Err(PeekError::Exceed(95 - 5));
|
||||
}
|
||||
|
||||
let output = Command::new("ffmpegthumbnailer")
|
||||
.arg("-i")
|
||||
.arg(src)
|
||||
.arg("-o")
|
||||
.arg(dest)
|
||||
.args(["-t", &percentage.to_string()])
|
||||
.args(["-q", "6", "-c", "jpeg", "-s", &PREVIEW.max_width.to_string()])
|
||||
.kill_on_drop(true)
|
||||
.output()
|
||||
.await?;
|
||||
|
||||
if !output.status.success() {
|
||||
bail!("failed to generate video thumbnail: {}", String::from_utf8_lossy(&output.stderr));
|
||||
return Err(String::from_utf8_lossy(&output.stderr).to_string().into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
25
core/src/external/jq.rs
vendored
25
core/src/external/jq.rs
vendored
|
|
@ -1,10 +1,11 @@
|
|||
use std::{path::Path, process::Stdio};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use anyhow::Result;
|
||||
use config::PREVIEW;
|
||||
use shared::PeekError;
|
||||
use tokio::{io::{AsyncBufReadExt, BufReader}, process::Command};
|
||||
|
||||
pub async fn jq(path: &Path, mut lines: usize) -> Result<String> {
|
||||
pub async fn jq(path: &Path, skip: usize, limit: usize) -> Result<String, PeekError> {
|
||||
let mut child = Command::new("jq")
|
||||
.args(["-C", "--indent", &PREVIEW.tab_size.to_string(), "."])
|
||||
.arg(path)
|
||||
|
|
@ -13,20 +14,24 @@ pub async fn jq(path: &Path, mut lines: usize) -> Result<String> {
|
|||
.kill_on_drop(true)
|
||||
.spawn()?;
|
||||
|
||||
let mut i = 0;
|
||||
let mut it = BufReader::new(child.stdout.take().unwrap()).lines();
|
||||
let mut output = String::new();
|
||||
let mut lines = String::new();
|
||||
while let Ok(Some(line)) = it.next_line().await {
|
||||
if lines < 1 {
|
||||
i += 1;
|
||||
if i > skip + limit {
|
||||
break;
|
||||
} else if i <= skip {
|
||||
continue;
|
||||
}
|
||||
|
||||
output.push_str(&line);
|
||||
output.push('\n');
|
||||
lines -= 1;
|
||||
lines.push_str(&line);
|
||||
lines.push('\n');
|
||||
}
|
||||
|
||||
if output.is_empty() {
|
||||
bail!("failed to get head of jq");
|
||||
if skip > 0 && i < skip + limit {
|
||||
Err(PeekError::Exceed(i.saturating_sub(limit)))
|
||||
} else {
|
||||
Ok(lines)
|
||||
}
|
||||
Ok(output)
|
||||
}
|
||||
|
|
|
|||
44
core/src/external/lsar.rs
vendored
44
core/src/external/lsar.rs
vendored
|
|
@ -1,8 +1,9 @@
|
|||
use std::path::Path;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use anyhow::anyhow;
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
use shared::PeekError;
|
||||
use tokio::process::Command;
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -29,12 +30,13 @@ pub struct LsarFile {
|
|||
pub attributes: Option<LsarAttr>,
|
||||
}
|
||||
|
||||
pub async fn lsar(path: &Path) -> Result<Vec<LsarFile>> {
|
||||
#[allow(clippy::manual_map)]
|
||||
pub async fn lsar(path: &Path, skip: usize, limit: usize) -> Result<Vec<LsarFile>, PeekError> {
|
||||
let output =
|
||||
Command::new("lsar").args(["-j", "-jss"]).arg(path).kill_on_drop(true).output().await?;
|
||||
|
||||
if !output.status.success() {
|
||||
bail!("failed to get json: {}", String::from_utf8_lossy(&output.stderr));
|
||||
return Err(String::from_utf8_lossy(&output.stderr).to_string().into());
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
|
@ -44,20 +46,36 @@ pub async fn lsar(path: &Path) -> Result<Vec<LsarFile>> {
|
|||
}
|
||||
|
||||
let output = String::from_utf8_lossy(&output.stdout);
|
||||
let contents = serde_json::from_str::<Outer>(output.trim())?.contents;
|
||||
let contents = serde_json::from_str::<Outer>(output.trim()).map_err(|e| anyhow!(e))?.contents;
|
||||
|
||||
let mut files = Vec::with_capacity(contents.len());
|
||||
let mut i = 0;
|
||||
let mut files = Vec::with_capacity(limit);
|
||||
for content in contents {
|
||||
let mut file = serde_json::from_value::<LsarFile>(content.clone())?;
|
||||
if let Some(p) = content.get("XADPosixPermissions").and_then(|p| p.as_u64()) {
|
||||
file.attributes = Some(LsarAttr::Posix(p as u16));
|
||||
} else if let Some(a) = content.get("XADWindowsFileAttributes").and_then(|a| a.as_u64()) {
|
||||
file.attributes = Some(LsarAttr::Windows(a as u16));
|
||||
} else if let Some(a) = content.get("XADDOSFileAttributes").and_then(|a| a.as_u64()) {
|
||||
file.attributes = Some(LsarAttr::Dos(a as u8));
|
||||
i += 1;
|
||||
if i > skip + limit {
|
||||
break;
|
||||
} else if i <= skip {
|
||||
continue;
|
||||
}
|
||||
|
||||
let attributes = if let Some(p) = content.get("XADPosixPermissions").and_then(|p| p.as_u64()) {
|
||||
Some(LsarAttr::Posix(p as u16))
|
||||
} else if let Some(a) = content.get("XADWindowsFileAttributes").and_then(|a| a.as_u64()) {
|
||||
Some(LsarAttr::Windows(a as u16))
|
||||
} else if let Some(a) = content.get("XADDOSFileAttributes").and_then(|a| a.as_u64()) {
|
||||
Some(LsarAttr::Dos(a as u8))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut file = serde_json::from_value::<LsarFile>(content).map_err(|e| anyhow!(e))?;
|
||||
file.attributes = attributes;
|
||||
files.push(file);
|
||||
}
|
||||
Ok(files)
|
||||
|
||||
if skip > 0 && files.len() < limit {
|
||||
Err(PeekError::Exceed(i.saturating_sub(limit)))
|
||||
} else {
|
||||
Ok(files)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
20
core/src/external/pdftoppm.rs
vendored
20
core/src/external/pdftoppm.rs
vendored
|
|
@ -1,19 +1,29 @@
|
|||
use std::path::Path;
|
||||
|
||||
use adaptor::Image;
|
||||
use anyhow::{bail, Result};
|
||||
use regex::Regex;
|
||||
use shared::PeekError;
|
||||
use tokio::process::Command;
|
||||
|
||||
pub async fn pdftoppm(src: &Path, dest: impl AsRef<Path>) -> Result<()> {
|
||||
pub async fn pdftoppm(src: &Path, dest: impl AsRef<Path>, skip: usize) -> Result<(), PeekError> {
|
||||
let output = Command::new("pdftoppm")
|
||||
.args(["-singlefile", "-jpeg", "-jpegopt", "quality=75"])
|
||||
.args(["-singlefile", "-jpeg", "-jpegopt", "quality=75", "-f"])
|
||||
.arg((skip + 1).to_string())
|
||||
.arg(src)
|
||||
.kill_on_drop(true)
|
||||
.output()
|
||||
.await?;
|
||||
|
||||
if !output.status.success() {
|
||||
bail!("failed to generate PDF thumbnail: {}", String::from_utf8_lossy(&output.stderr));
|
||||
let s = String::from_utf8_lossy(&output.stderr);
|
||||
let pages: usize = Regex::new(r"the last page \((\d+)\)")
|
||||
.unwrap()
|
||||
.captures(&s)
|
||||
.map(|cap| cap[1].parse().unwrap())
|
||||
.unwrap_or(0);
|
||||
|
||||
return if pages > 0 { Err(PeekError::Exceed(pages - 1)) } else { Err(s.to_string().into()) };
|
||||
}
|
||||
Image::precache_anyway(output.stdout.into(), dest).await
|
||||
|
||||
Ok(Image::precache_anyway(output.stdout.into(), dest).await?)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,6 +108,13 @@ impl Folder {
|
|||
self.files.get_range(self.offset..end).unwrap()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn window_for(&self, offset: usize) -> &Slice<PathBuf, File> {
|
||||
let start = offset.min(self.files.len().saturating_sub(1));
|
||||
let end = (offset + MANAGER.layout.folder_height()).min(self.files.len());
|
||||
self.files.get_range(start..end).unwrap()
|
||||
}
|
||||
|
||||
pub fn select(&mut self, idx: Option<usize>, state: Option<bool>) -> bool {
|
||||
let len = self.files.len();
|
||||
let mut apply = |idx: usize, state: Option<bool>| -> bool {
|
||||
|
|
@ -157,6 +164,9 @@ impl Folder {
|
|||
}
|
||||
|
||||
impl Folder {
|
||||
#[inline]
|
||||
pub fn offset(&self) -> usize { self.offset }
|
||||
|
||||
#[inline]
|
||||
pub fn cursor(&self) -> usize { self.cursor }
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use config::{BOOT, OPEN};
|
|||
use shared::{max_common_root, Defer, Term, MIME_DIR};
|
||||
use tokio::{fs::{self, OpenOptions}, io::{stdin, AsyncReadExt, AsyncWriteExt}};
|
||||
|
||||
use super::{PreviewData, Tab, Tabs, Watcher};
|
||||
use super::{Tab, Tabs, Watcher};
|
||||
use crate::{emit, external::{self, ShellOpt}, files::{File, FilesOp}, input::InputOpt, manager::Folder, select::SelectOpt, tasks::Tasks, Event, BLOCKER};
|
||||
|
||||
pub struct Manager {
|
||||
|
|
@ -52,7 +52,7 @@ impl Manager {
|
|||
self.watcher.watch(to_watch);
|
||||
}
|
||||
|
||||
pub fn preview(&mut self, show_image: bool) -> bool {
|
||||
pub fn peek(&mut self, sequent: bool, show_image: bool) -> bool {
|
||||
let Some(hovered) = self.hovered().cloned() else {
|
||||
return self.active_mut().preview.reset();
|
||||
};
|
||||
|
|
@ -61,19 +61,23 @@ impl Manager {
|
|||
self.active_mut().preview_reset_image();
|
||||
}
|
||||
|
||||
if hovered.meta.is_dir() {
|
||||
self.active_mut().preview.go(&hovered.path, MIME_DIR, show_image);
|
||||
if self.active().history(&hovered.path).is_some() {
|
||||
emit!(Preview(hovered.path, MIME_DIR.to_owned(), PreviewData::Folder));
|
||||
}
|
||||
} else if let Some(mime) = self.mimetype.get(&hovered.path).cloned() {
|
||||
self.active_mut().preview.go(&hovered.path, &mime, show_image);
|
||||
let mime = if hovered.meta.is_dir() {
|
||||
MIME_DIR.to_owned()
|
||||
} else if let Some(m) = self.mimetype.get(&hovered.path).cloned() {
|
||||
m
|
||||
} else {
|
||||
tokio::spawn(async move {
|
||||
if let Ok(mimes) = external::file(&[hovered.path]).await {
|
||||
emit!(Mimetype(mimes));
|
||||
}
|
||||
});
|
||||
return false;
|
||||
};
|
||||
|
||||
if sequent {
|
||||
self.active_mut().preview.sequent(&hovered.path, &mime, show_image);
|
||||
} else {
|
||||
self.active_mut().preview.go(&hovered.path, &mime, show_image);
|
||||
}
|
||||
false
|
||||
}
|
||||
|
|
@ -393,19 +397,16 @@ impl Manager {
|
|||
true
|
||||
}
|
||||
|
||||
pub fn update_preview(&mut self, path: PathBuf, mime: String, data: PreviewData) -> bool {
|
||||
let Some(hovered) = self.hovered().map(|h| &h.path) else {
|
||||
return self.active_mut().preview.reset();
|
||||
pub fn update_hover(&mut self, file: Option<File>) -> bool {
|
||||
let b = file.map(|f| self.current_mut().hover_force(f)).unwrap_or(false);
|
||||
let Some(hovered) = self.hovered() else {
|
||||
return b;
|
||||
};
|
||||
|
||||
if path != *hovered {
|
||||
return false;
|
||||
if hovered.meta.is_dir() {
|
||||
self.watcher.trigger_dirs(&[&hovered.path]);
|
||||
}
|
||||
|
||||
let preview = &mut self.active_mut().preview;
|
||||
preview.lock = Some((path, mime));
|
||||
preview.data = data;
|
||||
true
|
||||
b
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,174 +0,0 @@
|
|||
use std::{io::BufRead, mem, path::{Path, PathBuf}, sync::{atomic::{AtomicUsize, Ordering}, Arc}};
|
||||
|
||||
use adaptor::Adaptor;
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use config::{BOOT, MANAGER, PREVIEW};
|
||||
use shared::MimeKind;
|
||||
use syntect::{easy::HighlightFile, util::as_24_bit_terminal_escaped};
|
||||
use tokio::{fs, task::JoinHandle};
|
||||
|
||||
use crate::{emit, external, files::{Files, FilesOp}, highlighter};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Preview {
|
||||
pub lock: Option<(PathBuf, String)>,
|
||||
pub data: PreviewData,
|
||||
|
||||
incr: Arc<AtomicUsize>,
|
||||
handle: Option<JoinHandle<()>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub enum PreviewData {
|
||||
#[default]
|
||||
None,
|
||||
Folder,
|
||||
Text(String),
|
||||
Image,
|
||||
}
|
||||
|
||||
impl Preview {
|
||||
pub fn go(&mut self, path: &Path, mime: &str, show_image: bool) {
|
||||
let kind = MimeKind::new(mime);
|
||||
if !show_image && matches!(kind, MimeKind::Image | MimeKind::Video) {
|
||||
return;
|
||||
} else if self.same(path, mime) {
|
||||
return;
|
||||
} else {
|
||||
self.reset();
|
||||
}
|
||||
|
||||
let (path, mime) = (path.to_path_buf(), mime.to_owned());
|
||||
let incr = self.incr.clone();
|
||||
|
||||
self.handle = Some(tokio::spawn(async move {
|
||||
let result = match kind {
|
||||
MimeKind::Empty => Ok(PreviewData::None),
|
||||
MimeKind::Archive => Self::archive(&path).await.map(PreviewData::Text),
|
||||
MimeKind::Dir => Self::folder(&path).await,
|
||||
MimeKind::Image => Self::image(&path).await,
|
||||
MimeKind::Video => Self::video(&path).await,
|
||||
MimeKind::JSON => Self::json(&path).await.map(PreviewData::Text),
|
||||
MimeKind::PDF => Self::pdf(&path).await,
|
||||
MimeKind::Text => Self::highlight(&path, incr).await.map(PreviewData::Text),
|
||||
MimeKind::Others => Err(anyhow!("Unsupported mimetype: {mime}")),
|
||||
};
|
||||
|
||||
emit!(Preview(path, mime, result.unwrap_or_default()));
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) -> bool {
|
||||
self.handle.take().map(|h| h.abort());
|
||||
self.incr.fetch_add(1, Ordering::Relaxed);
|
||||
Adaptor::image_hide(MANAGER.layout.preview_rect()).ok();
|
||||
|
||||
self.lock = None;
|
||||
!matches!(
|
||||
mem::replace(&mut self.data, PreviewData::None),
|
||||
PreviewData::None | PreviewData::Image
|
||||
)
|
||||
}
|
||||
|
||||
pub fn reset_image(&mut self) -> bool {
|
||||
self.handle.take().map(|h| h.abort());
|
||||
self.incr.fetch_add(1, Ordering::Relaxed);
|
||||
Adaptor::image_hide(MANAGER.layout.preview_rect()).ok();
|
||||
|
||||
if matches!(self.data, PreviewData::Image) {
|
||||
self.lock = None;
|
||||
self.data = PreviewData::None;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub async fn folder(path: &Path) -> Result<PreviewData> {
|
||||
emit!(Files(match Files::read_dir(path).await {
|
||||
Ok(items) => FilesOp::Read(path.to_path_buf(), items),
|
||||
Err(_) => FilesOp::IOErr(path.to_path_buf()),
|
||||
}));
|
||||
|
||||
Ok(PreviewData::Folder)
|
||||
}
|
||||
|
||||
pub async fn image(path: &Path) -> Result<PreviewData> {
|
||||
Adaptor::image_show(path, MANAGER.layout.preview_rect()).await?;
|
||||
Ok(PreviewData::Image)
|
||||
}
|
||||
|
||||
pub async fn video(path: &Path) -> Result<PreviewData> {
|
||||
let cache = BOOT.cache(path);
|
||||
if fs::metadata(&cache).await.is_err() {
|
||||
external::ffmpegthumbnailer(path, &cache).await?;
|
||||
}
|
||||
|
||||
Self::image(&cache).await
|
||||
}
|
||||
|
||||
pub async fn pdf(path: &Path) -> Result<PreviewData> {
|
||||
let cache = BOOT.cache(path);
|
||||
if fs::metadata(&cache).await.is_err() {
|
||||
external::pdftoppm(path, &cache).await?;
|
||||
}
|
||||
|
||||
Self::image(&cache).await
|
||||
}
|
||||
|
||||
pub async fn json(path: &Path) -> Result<String> {
|
||||
external::jq(path, MANAGER.layout.preview_height()).await
|
||||
}
|
||||
|
||||
pub async fn archive(path: &Path) -> Result<String> {
|
||||
Ok(
|
||||
external::lsar(path)
|
||||
.await?
|
||||
.into_iter()
|
||||
.take(MANAGER.layout.preview_height())
|
||||
.map(|f| f.name)
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n"),
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn highlight(path: &Path, incr: Arc<AtomicUsize>) -> Result<String> {
|
||||
let tick = incr.load(Ordering::Relaxed);
|
||||
let path = path.to_path_buf();
|
||||
let spaces = " ".repeat(PREVIEW.tab_size as usize);
|
||||
|
||||
let (syntaxes, theme) = highlighter();
|
||||
tokio::task::spawn_blocking(move || -> Result<String> {
|
||||
let mut h = HighlightFile::new(path, syntaxes, theme)?;
|
||||
let mut line = String::new();
|
||||
let mut buf = String::new();
|
||||
|
||||
let mut i = MANAGER.layout.preview_height();
|
||||
while i > 0 && h.reader.read_line(&mut line)? > 0 {
|
||||
if tick != incr.load(Ordering::Relaxed) {
|
||||
bail!("Preview cancelled");
|
||||
}
|
||||
|
||||
i -= 1;
|
||||
line = line.replace('\t', &spaces);
|
||||
let regions = h.highlight_lines.highlight_line(&line, syntaxes)?;
|
||||
buf.push_str(&as_24_bit_terminal_escaped(®ions, false));
|
||||
line.clear();
|
||||
}
|
||||
|
||||
buf.push_str("\x1b[0m");
|
||||
Ok(buf)
|
||||
})
|
||||
.await?
|
||||
}
|
||||
}
|
||||
|
||||
impl Preview {
|
||||
#[inline]
|
||||
pub fn same(&self, path: &Path, mime: &str) -> bool {
|
||||
self.lock.as_ref().map(|(p, m)| p == path && m == mime).unwrap_or(false)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn same_path(&self, path: &Path) -> bool {
|
||||
self.lock.as_ref().map(|(p, _)| p == path).unwrap_or(false)
|
||||
}
|
||||
}
|
||||
5
core/src/manager/preview/mod.rs
Normal file
5
core/src/manager/preview/mod.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
mod preview;
|
||||
mod provider;
|
||||
|
||||
pub use preview::*;
|
||||
use provider::*;
|
||||
151
core/src/manager/preview/preview.rs
Normal file
151
core/src/manager/preview/preview.rs
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
use std::{path::{Path, PathBuf}, sync::atomic::Ordering};
|
||||
|
||||
use adaptor::Adaptor;
|
||||
use config::MANAGER;
|
||||
use shared::{MimeKind, PeekError};
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
use super::{Provider, INCR};
|
||||
use crate::emit;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Preview {
|
||||
pub lock: Option<PreviewLock>,
|
||||
skip: usize,
|
||||
|
||||
handle: Option<JoinHandle<()>>,
|
||||
}
|
||||
|
||||
pub struct PreviewLock {
|
||||
pub path: PathBuf,
|
||||
pub mime: String,
|
||||
pub skip: usize,
|
||||
pub data: PreviewData,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum PreviewData {
|
||||
Folder,
|
||||
Text(String),
|
||||
Image,
|
||||
}
|
||||
|
||||
impl Preview {
|
||||
pub fn go(&mut self, path: &Path, mime: &str, show_image: bool) {
|
||||
let kind = MimeKind::new(mime);
|
||||
if !show_image && kind.show_as_image() {
|
||||
return;
|
||||
} else if self.same(path, mime) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.reset();
|
||||
if !self.same_path(path) {
|
||||
self.skip = 0;
|
||||
}
|
||||
|
||||
let (path, mime, skip) = (path.to_path_buf(), mime.to_owned(), self.skip);
|
||||
self.handle = Some(tokio::spawn(async move {
|
||||
match Provider::auto(kind, &path, skip).await {
|
||||
Ok(data) => {
|
||||
emit!(Preview(PreviewLock { path, mime, skip, data }));
|
||||
}
|
||||
Err(PeekError::Exceed(max)) => {
|
||||
emit!(Peek(max, path));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn sequent(&mut self, path: &Path, mime: &str, show_image: bool) {
|
||||
let kind = MimeKind::new(mime);
|
||||
if !show_image && kind.show_as_image() {
|
||||
return;
|
||||
} else if self.same(path, mime) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.handle.take().map(|h| h.abort());
|
||||
INCR.fetch_add(1, Ordering::Relaxed);
|
||||
|
||||
let (path, mime, skip) = (path.to_path_buf(), mime.to_owned(), self.skip);
|
||||
self.handle = Some(tokio::spawn(async move {
|
||||
match Provider::auto(kind, &path, skip).await {
|
||||
Ok(data) => {
|
||||
emit!(Preview(PreviewLock { path, mime, skip, data }));
|
||||
}
|
||||
Err(PeekError::Exceed(max)) => {
|
||||
emit!(Peek(max, path));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn arrow(&mut self, step: isize, absolute: bool) -> bool {
|
||||
let old = self.skip;
|
||||
if absolute {
|
||||
self.skip = step.unsigned_abs();
|
||||
} else if let Some(kind) = self.lock.as_ref().map(|l| MimeKind::new(&l.mime)) {
|
||||
let size = Provider::step_size(kind, step.unsigned_abs());
|
||||
self.skip = if step < 0 { old.saturating_sub(size) } else { old + size };
|
||||
}
|
||||
|
||||
self.skip != old
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) -> bool {
|
||||
self.handle.take().map(|h| h.abort());
|
||||
INCR.fetch_add(1, Ordering::Relaxed);
|
||||
Adaptor::image_hide(MANAGER.layout.preview_rect()).ok();
|
||||
|
||||
let b = matches!(&self.lock, Some(l) if !l.is_image());
|
||||
self.lock = None;
|
||||
b
|
||||
}
|
||||
|
||||
pub fn reset_image(&mut self) -> bool {
|
||||
self.handle.take().map(|h| h.abort());
|
||||
INCR.fetch_add(1, Ordering::Relaxed);
|
||||
Adaptor::image_hide(MANAGER.layout.preview_rect()).ok();
|
||||
|
||||
if matches!(&self.lock, Some(l) if l.is_image()) {
|
||||
self.lock = None;
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl Preview {
|
||||
#[inline]
|
||||
pub fn lock(&self) -> &Option<PreviewLock> { &self.lock }
|
||||
|
||||
#[inline]
|
||||
pub fn skip(&self) -> usize { self.skip }
|
||||
|
||||
#[inline]
|
||||
pub fn same(&self, path: &Path, mime: &str) -> bool {
|
||||
if let Some(ref lock) = self.lock {
|
||||
return lock.path == path && lock.mime == mime && lock.skip == self.skip;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn same_path(&self, path: &Path) -> bool {
|
||||
if let Some(ref lock) = self.lock {
|
||||
return lock.path == path;
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl PreviewLock {
|
||||
#[inline]
|
||||
pub fn is_image(&self) -> bool { matches!(self.data, PreviewData::Image) }
|
||||
|
||||
#[inline]
|
||||
pub fn is_folder(&self) -> bool { matches!(self.data, PreviewData::Folder) }
|
||||
}
|
||||
138
core/src/manager/preview/provider.rs
Normal file
138
core/src/manager/preview/provider.rs
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
use std::{io::BufRead, path::Path, sync::atomic::{AtomicUsize, Ordering}};
|
||||
|
||||
use adaptor::Adaptor;
|
||||
use anyhow::anyhow;
|
||||
use config::{BOOT, MANAGER, PREVIEW};
|
||||
use shared::{MimeKind, PeekError};
|
||||
use syntect::{easy::HighlightFile, util::as_24_bit_terminal_escaped};
|
||||
use tokio::fs;
|
||||
|
||||
use super::PreviewData;
|
||||
use crate::{emit, external, files::{Files, FilesOp}, highlighter};
|
||||
|
||||
pub(super) struct Provider;
|
||||
|
||||
pub(super) static INCR: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
impl Provider {
|
||||
pub(super) async fn auto(
|
||||
kind: MimeKind,
|
||||
path: &Path,
|
||||
skip: usize,
|
||||
) -> Result<PreviewData, PeekError> {
|
||||
match kind {
|
||||
MimeKind::Empty => Err("Empty file".into()),
|
||||
MimeKind::Archive => Provider::archive(path, skip).await.map(PreviewData::Text),
|
||||
MimeKind::Dir => Provider::folder(path).await,
|
||||
MimeKind::Image => Provider::image(path).await,
|
||||
MimeKind::Video => Provider::video(path, skip).await,
|
||||
MimeKind::JSON => Provider::json(path, skip).await.map(PreviewData::Text),
|
||||
MimeKind::PDF => Provider::pdf(path, skip).await,
|
||||
MimeKind::Text => Provider::highlight(path, skip).await.map(PreviewData::Text),
|
||||
MimeKind::Others => Err("Unsupported mimetype".into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn step_size(kind: MimeKind, step: usize) -> usize {
|
||||
match kind {
|
||||
MimeKind::Empty => 0,
|
||||
MimeKind::Archive => step * MANAGER.layout.preview_height() / 10,
|
||||
MimeKind::Dir => step * MANAGER.layout.preview_height() / 10,
|
||||
MimeKind::Image => 0,
|
||||
MimeKind::Video => step,
|
||||
MimeKind::JSON => step * MANAGER.layout.preview_height() / 10,
|
||||
MimeKind::PDF => 1,
|
||||
MimeKind::Text => step * MANAGER.layout.preview_height() / 10,
|
||||
MimeKind::Others => 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn folder(path: &Path) -> Result<PreviewData, PeekError> {
|
||||
emit!(Files(match Files::read_dir(path).await {
|
||||
Ok(items) => FilesOp::Read(path.to_path_buf(), items),
|
||||
Err(_) => FilesOp::IOErr(path.to_path_buf()),
|
||||
}));
|
||||
|
||||
Ok(PreviewData::Folder)
|
||||
}
|
||||
|
||||
pub(super) async fn image(path: &Path) -> Result<PreviewData, PeekError> {
|
||||
Adaptor::image_show(path, MANAGER.layout.preview_rect()).await?;
|
||||
Ok(PreviewData::Image)
|
||||
}
|
||||
|
||||
pub(super) async fn video(path: &Path, skip: usize) -> Result<PreviewData, PeekError> {
|
||||
let cache = BOOT.cache(path, skip);
|
||||
if fs::metadata(&cache).await.is_err() {
|
||||
external::ffmpegthumbnailer(path, &cache, skip).await?;
|
||||
}
|
||||
|
||||
Self::image(&cache).await
|
||||
}
|
||||
|
||||
pub(super) async fn pdf(path: &Path, skip: usize) -> Result<PreviewData, PeekError> {
|
||||
let cache = BOOT.cache(path, skip);
|
||||
if fs::metadata(&cache).await.is_err() {
|
||||
external::pdftoppm(path, &cache, skip).await?;
|
||||
}
|
||||
|
||||
Self::image(&cache).await
|
||||
}
|
||||
|
||||
pub(super) async fn json(path: &Path, skip: usize) -> Result<String, PeekError> {
|
||||
external::jq(path, skip, MANAGER.layout.preview_height()).await
|
||||
}
|
||||
|
||||
pub(super) async fn archive(path: &Path, skip: usize) -> Result<String, PeekError> {
|
||||
Ok(
|
||||
external::lsar(path, skip, MANAGER.layout.preview_height())
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|f| f.name)
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n"),
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) async fn highlight(path: &Path, skip: usize) -> Result<String, PeekError> {
|
||||
let tick = INCR.load(Ordering::Relaxed);
|
||||
let path = path.to_path_buf();
|
||||
let spaces = " ".repeat(PREVIEW.tab_size as usize);
|
||||
|
||||
let (syntaxes, theme) = highlighter();
|
||||
tokio::task::spawn_blocking(move || -> Result<String, PeekError> {
|
||||
let mut h = HighlightFile::new(path, syntaxes, theme)?;
|
||||
let mut line = String::new();
|
||||
let mut buf = String::new();
|
||||
|
||||
let mut i = 0;
|
||||
let limit = MANAGER.layout.preview_height();
|
||||
while h.reader.read_line(&mut line)? > 0 {
|
||||
if tick != INCR.load(Ordering::Relaxed) {
|
||||
return Err("Highlighting cancelled".into());
|
||||
}
|
||||
|
||||
i += 1;
|
||||
if i > skip + limit {
|
||||
break;
|
||||
}
|
||||
|
||||
line = line.replace('\t', &spaces);
|
||||
let regions = h.highlight_lines.highlight_line(&line, syntaxes).map_err(|e| anyhow!(e))?;
|
||||
|
||||
if i > skip {
|
||||
buf.push_str(&as_24_bit_terminal_escaped(®ions, false));
|
||||
}
|
||||
line.clear();
|
||||
}
|
||||
|
||||
if skip > 0 && i < skip + limit {
|
||||
return Err(PeekError::Exceed(i.saturating_sub(limit)));
|
||||
}
|
||||
|
||||
buf.push_str("\x1b[0m");
|
||||
Ok(buf)
|
||||
})
|
||||
.await?
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
use std::{collections::{BTreeMap, BTreeSet}, ffi::{OsStr, OsString}, mem, path::{Path, PathBuf}};
|
||||
|
||||
use anyhow::{Error, Result};
|
||||
use config::open::Opener;
|
||||
use config::{open::Opener, MANAGER};
|
||||
use futures::StreamExt;
|
||||
use shared::Defer;
|
||||
use shared::{Defer, MIME_DIR};
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
use super::{Folder, Mode, Preview};
|
||||
use super::{Folder, Mode, Preview, PreviewLock};
|
||||
use crate::{emit, external::{self, FzfOpt, ZoxideOpt}, files::{File, Files, FilesOp}, input::InputOpt, Event, BLOCKER};
|
||||
|
||||
pub struct Tab {
|
||||
|
|
@ -309,6 +309,41 @@ impl Tab {
|
|||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn update_peek(&mut self, step: isize, path: Option<PathBuf>) {
|
||||
let Some(ref hovered) = self.current.hovered else {
|
||||
return;
|
||||
};
|
||||
|
||||
if path.as_ref().map(|p| *p != hovered.path).unwrap_or(false) {
|
||||
return;
|
||||
} else if !self.preview.arrow(step, path.is_some()) {
|
||||
return;
|
||||
} else if !matches!(&self.preview.lock, Some(l) if l.mime == MIME_DIR) {
|
||||
return;
|
||||
}
|
||||
|
||||
let path = &self.preview.lock.as_ref().unwrap().path;
|
||||
if let Some(folder) = self.history(path) {
|
||||
let max = folder.files.len().saturating_sub(MANAGER.layout.preview_height());
|
||||
if self.preview.skip() > max {
|
||||
self.preview.arrow(max as isize, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_preview(&mut self, lock: PreviewLock) -> bool {
|
||||
let Some(hovered) = self.current.hovered.as_ref().map(|h| &h.path) else {
|
||||
return self.preview.reset();
|
||||
};
|
||||
|
||||
if lock.path != *hovered {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.preview.lock = Some(lock);
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl Tab {
|
||||
|
|
@ -362,6 +397,9 @@ impl Tab {
|
|||
#[inline]
|
||||
pub fn preview(&self) -> &Preview { &self.preview }
|
||||
|
||||
#[inline]
|
||||
pub fn preview_reset(&mut self) -> bool { self.preview.reset() }
|
||||
|
||||
#[inline]
|
||||
pub fn preview_reset_image(&mut self) -> bool { self.preview.reset_image() }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ impl Tasks {
|
|||
|
||||
pub fn toggle(&mut self) -> bool {
|
||||
self.visible = !self.visible;
|
||||
emit!(Hover); // Show/hide preview for images
|
||||
emit!(Peek); // Show/hide preview for images
|
||||
true
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ impl Precache {
|
|||
pub(crate) async fn work(&self, task: &mut PrecacheOp) -> Result<()> {
|
||||
match task {
|
||||
PrecacheOp::Image(task) => {
|
||||
let cache = BOOT.cache(&task.target);
|
||||
let cache = BOOT.cache(&task.target, 0);
|
||||
if fs::metadata(&cache).await.is_ok() {
|
||||
return Ok(self.sch.send(TaskOp::Adv(task.id, 1, 0))?);
|
||||
}
|
||||
|
|
@ -84,21 +84,21 @@ impl Precache {
|
|||
self.sch.send(TaskOp::Adv(task.id, 1, 0))?;
|
||||
}
|
||||
PrecacheOp::Video(task) => {
|
||||
let cache = BOOT.cache(&task.target);
|
||||
let cache = BOOT.cache(&task.target, 0);
|
||||
if fs::metadata(&cache).await.is_ok() {
|
||||
return Ok(self.sch.send(TaskOp::Adv(task.id, 1, 0))?);
|
||||
}
|
||||
|
||||
external::ffmpegthumbnailer(&task.target, &cache).await.ok();
|
||||
external::ffmpegthumbnailer(&task.target, &cache, 0).await.ok();
|
||||
self.sch.send(TaskOp::Adv(task.id, 1, 0))?;
|
||||
}
|
||||
PrecacheOp::Pdf(task) => {
|
||||
let cache = BOOT.cache(&task.target);
|
||||
let cache = BOOT.cache(&task.target, 0);
|
||||
if fs::metadata(&cache).await.is_ok() {
|
||||
return Ok(self.sch.send(TaskOp::Adv(task.id, 1, 0))?);
|
||||
}
|
||||
|
||||
external::pdftoppm(&task.target, &cache).await.ok();
|
||||
external::pdftoppm(&task.target, &cache, 0).await.ok();
|
||||
self.sch.send(TaskOp::Adv(task.id, 1, 0))?;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,6 +51,6 @@ impl Which {
|
|||
#[inline]
|
||||
fn switch(&mut self, state: bool) {
|
||||
self.visible = state;
|
||||
emit!(Hover); // Show/hide preview for images
|
||||
emit!(Peek); // Show/hide preview for images
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,5 +9,5 @@ crossterm = "^0"
|
|||
futures = "^0"
|
||||
libc = "^0"
|
||||
parking_lot = "^0"
|
||||
ratatui = { version = "^0" }
|
||||
ratatui = { version = "^0" }
|
||||
tokio = { version = "^1", features = [ "parking_lot", "macros", "rt-multi-thread", "sync", "time", "fs" ] }
|
||||
|
|
|
|||
3
shared/src/errors/mod.rs
Normal file
3
shared/src/errors/mod.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
mod peek;
|
||||
|
||||
pub use peek::*;
|
||||
34
shared/src/errors/peek.rs
Normal file
34
shared/src/errors/peek.rs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
use std::{error::Error, fmt::{self, Display}};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum PeekError {
|
||||
Exceed(usize),
|
||||
Unexpected(String),
|
||||
}
|
||||
|
||||
impl Display for PeekError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Exceed(lines) => write!(f, "Exceed: {lines}"),
|
||||
Self::Unexpected(msg) => write!(f, "Unexpected error: {msg}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for PeekError {}
|
||||
|
||||
impl From<String> for PeekError {
|
||||
fn from(error: String) -> Self { Self::Unexpected(error) }
|
||||
}
|
||||
impl From<&str> for PeekError {
|
||||
fn from(error: &str) -> Self { Self::from(error.to_owned()) }
|
||||
}
|
||||
impl From<anyhow::Error> for PeekError {
|
||||
fn from(error: anyhow::Error) -> Self { Self::from(error.to_string()) }
|
||||
}
|
||||
impl From<std::io::Error> for PeekError {
|
||||
fn from(error: std::io::Error) -> Self { Self::from(error.to_string()) }
|
||||
}
|
||||
impl From<tokio::task::JoinError> for PeekError {
|
||||
fn from(error: tokio::task::JoinError) -> Self { Self::from(error.to_string()) }
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
mod chars;
|
||||
mod defer;
|
||||
mod errors;
|
||||
mod fns;
|
||||
mod fs;
|
||||
mod mime;
|
||||
|
|
@ -13,6 +14,7 @@ mod time;
|
|||
|
||||
pub use chars::*;
|
||||
pub use defer::*;
|
||||
pub use errors::*;
|
||||
pub use fns::*;
|
||||
pub use fs::*;
|
||||
pub use mime::*;
|
||||
|
|
|
|||
|
|
@ -18,21 +18,6 @@ pub enum MimeKind {
|
|||
}
|
||||
|
||||
impl MimeKind {
|
||||
pub fn valid(s: &str) -> bool {
|
||||
if s == "inode/x-empty" {
|
||||
return true;
|
||||
}
|
||||
|
||||
let parts = s.split('/').collect::<Vec<_>>();
|
||||
if parts.len() != 2 {
|
||||
return false;
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
let b = matches!(parts[0], "application" | "audio" | "example" | "font" | "image" | "message" | "model" | "multipart" | "text" | "video");
|
||||
b && !parts[1].is_empty()
|
||||
}
|
||||
|
||||
pub fn new(s: &str) -> Self {
|
||||
if s == MIME_DIR {
|
||||
Self::Dir
|
||||
|
|
@ -61,4 +46,23 @@ impl MimeKind {
|
|||
Self::Others
|
||||
}
|
||||
}
|
||||
|
||||
pub fn valid(s: &str) -> bool {
|
||||
if s == "inode/x-empty" {
|
||||
return true;
|
||||
}
|
||||
|
||||
let parts = s.split('/').collect::<Vec<_>>();
|
||||
if parts.len() != 2 {
|
||||
return false;
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
let b = matches!(parts[0], "application" | "audio" | "example" | "font" | "image" | "message" | "model" | "multipart" | "text" | "video");
|
||||
b && !parts[1].is_empty()
|
||||
}
|
||||
|
||||
pub fn show_as_image(&self) -> bool {
|
||||
matches!(self, MimeKind::Image | MimeKind::Video | MimeKind::PDF)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue