feat: make the preview scrollable

This commit is contained in:
sxyazi 2023-08-25 12:37:56 +08:00
parent 388ad43537
commit 395c04a852
No known key found for this signature in database
25 changed files with 542 additions and 372 deletions

218
Cargo.lock generated
View file

@ -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]]
@ -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.186"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c"
checksum = "9f5db24220c009de9bd45e69fb2938f4b6d2df856aa9304ce377b3180f83b7c1"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.183"
version = "1.0.186"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816"
checksum = "5ad697f7e0b65af4983a4ce8f56ed5b357e8d3c36651bf6a7e13639c17b8e670"
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",
]

View file

@ -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();
}

View file

@ -88,6 +88,7 @@ impl App {
fn dispatch_resize(&mut self) {
self.cx.manager.current_mut().set_page(true);
self.cx.manager.active_mut().preview_reset_image();
// TODO: use peek
self.cx.manager.preview(self.cx.image_layer());
emit!(Render);
}
@ -100,6 +101,7 @@ impl App {
} else {
self.term = Some(Term::start().unwrap());
self.signals.stop_term(false);
// TODO: use peek
self.cx.manager.preview(self.cx.image_layer());
emit!(Render);
emit!(Hover);
@ -165,6 +167,9 @@ impl App {
manager.update_preview(path, mime, data);
emit!(Render);
}
Event::Peek(path, skip) => {
manager.update_peek(path, skip);
}
Event::Select(opt, tx) => {
self.cx.select.show(opt, tx);

View file

@ -61,6 +61,10 @@ 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().peek(step)
}
"leave" => cx.manager.active_mut().leave(),
"enter" => cx.manager.active_mut().enter(),
"back" => cx.manager.active_mut().back(),

View file

@ -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()

View file

@ -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" ] }

View file

@ -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" },

View file

@ -64,17 +64,10 @@ impl Default for 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]

View file

@ -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"

View file

@ -27,6 +27,7 @@ pub enum Event {
Mimetype(BTreeMap<PathBuf, String>),
Hover(Option<File>),
Preview(PathBuf, String, PreviewData),
Peek(PathBuf, usize),
// Input
Select(SelectOpt, oneshot::Sender<Result<usize>>),
@ -90,6 +91,9 @@ macro_rules! emit {
(Preview($path:expr, $mime:expr, $data:expr)) => {
$crate::Event::Preview($path, $mime, $data).emit();
};
(Peek($path:expr, $skip:expr)) => {
$crate::Event::Peek($path, $skip).emit();
};
(Select($opt:expr)) => {{
let (tx, rx) = tokio::sync::oneshot::channel();

View file

@ -1,22 +1,28 @@
use std::path::Path;
use anyhow::{bail, Result};
use config::PREVIEW;
use shared::PagedError;
use tokio::process::Command;
pub async fn ffmpegthumbnailer(src: &Path, dest: &Path) -> Result<()> {
pub async fn ffmpegthumbnailer(src: &Path, dest: &Path, skip: usize) -> Result<(), PagedError> {
let percentage = 5 + skip;
if percentage >= 100 {
return Err(PagedError::Exceed(94));
}
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(())
}

View file

@ -1,10 +1,11 @@
use std::{path::Path, process::Stdio};
use anyhow::{bail, Result};
use anyhow::Result;
use config::PREVIEW;
use shared::PagedError;
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, PagedError> {
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(PagedError::Exceed(i.saturating_sub(limit)))
} else {
Ok(lines)
}
Ok(output)
}

View file

@ -1,8 +1,9 @@
use std::path::Path;
use anyhow::{bail, Result};
use anyhow::anyhow;
use serde::Deserialize;
use serde_json::Value;
use shared::PagedError;
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>, PagedError> {
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);
}
if skip > 0 && files.len() < limit {
Err(PagedError::Exceed(i.saturating_sub(limit)))
} else {
Ok(files)
}
}

View file

@ -1,19 +1,29 @@
use std::path::Path;
use adaptor::Image;
use anyhow::{bail, Result};
use regex::Regex;
use shared::PagedError;
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<(), PagedError> {
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(PagedError::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?)
}

View file

@ -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 }

View file

@ -63,8 +63,8 @@ impl Manager {
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));
if let Some(offset) = self.active().history(&hovered.path).map(|f| f.offset()) {
emit!(Peek(hovered.path, offset));
}
} else if let Some(mime) = self.mimetype.get(&hovered.path).cloned() {
self.active_mut().preview.go(&hovered.path, &mime, show_image);
@ -407,6 +407,26 @@ impl Manager {
preview.data = data;
true
}
pub fn update_peek(&mut self, path: PathBuf, skip: usize) -> bool {
if !matches!(self.hovered(), Some(f) if f.path == path) {
return false;
}
if self.active().preview.lock.is_some() {
self.active_mut().preview.peek(skip);
return false;
}
if self.active().history(&path).is_some() {
let preview = &mut self.active_mut().preview;
preview.lock = Some((path, MIME_DIR.to_string()));
preview.data = PreviewData::Folder;
}
self.active_mut().preview.peek(skip);
false
}
}
impl Manager {

View file

@ -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(&regions, 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)
}
}

View file

@ -0,0 +1,5 @@
mod preview;
mod provider;
pub use preview::*;
use provider::*;

View file

@ -0,0 +1,119 @@
use std::{mem, path::{Path, PathBuf}, sync::atomic::Ordering};
use adaptor::Adaptor;
use config::MANAGER;
use shared::{MimeKind, PagedError};
use tokio::task::JoinHandle;
use super::{provider::INCR, Provider};
use crate::emit;
#[derive(Default)]
pub struct Preview {
pub lock: Option<(PathBuf, String)>,
pub data: PreviewData,
skip: usize,
handle: Option<JoinHandle<()>>,
}
#[derive(Debug, Default, PartialEq, Eq)]
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, skip) = (path.to_path_buf(), mime.to_owned(), self.skip);
self.handle = Some(tokio::spawn(async move {
let result = Provider::auto(kind, &path, skip).await;
emit!(Preview(path, mime, result.unwrap_or_default()));
}));
}
pub fn peek(&mut self, skip: usize) {
let Some((path, kind, mime)) = self.lock.clone().map(|(p, m)| (p, MimeKind::new(&m), m)) else {
return;
};
self.handle.take().map(|h| h.abort());
INCR.fetch_add(1, Ordering::Relaxed);
Adaptor::image_hide(MANAGER.layout.preview_rect()).ok();
self.skip = skip;
self.handle = Some(tokio::spawn(async move {
match Provider::auto(kind, &path, skip).await {
Ok(result) => {
emit!(Preview(path, mime, result));
}
Err(PagedError::Exceed(max)) => {
emit!(Peek(path, max));
}
_ => {}
};
}));
}
#[inline]
pub fn peek_step(&mut self, step: isize) -> usize {
let Some(kind) = self.lock.clone().map(|(_, m)| MimeKind::new(&m)) else {
return 0;
};
Provider::step_size(kind, step.unsigned_abs())
}
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();
self.lock = None;
self.skip = 0;
!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());
INCR.fetch_add(1, Ordering::Relaxed);
Adaptor::image_hide(MANAGER.layout.preview_rect()).ok();
if self.data == PreviewData::Image {
self.lock = None;
self.data = PreviewData::None;
self.skip = 0;
}
false
}
}
impl Preview {
#[inline]
pub fn skip(&self) -> usize { self.skip }
#[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)
}
}

View file

@ -0,0 +1,137 @@
use std::{io::BufRead, path::Path, sync::atomic::{AtomicUsize, Ordering}};
use adaptor::Adaptor;
use anyhow::anyhow;
use config::{BOOT, MANAGER, PREVIEW};
use shared::{MimeKind, PagedError};
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, PagedError> {
match kind {
MimeKind::Empty => Ok(PreviewData::None),
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, PagedError> {
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, PagedError> {
Adaptor::image_show(path, MANAGER.layout.preview_rect()).await?;
Ok(PreviewData::Image)
}
pub(super) async fn video(path: &Path, skip: usize) -> Result<PreviewData, PagedError> {
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, PagedError> {
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, PagedError> {
external::jq(path, skip, MANAGER.layout.preview_height()).await
}
pub(super) async fn archive(path: &Path, skip: usize) -> Result<String, PagedError> {
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, PagedError> {
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, PagedError> {
let mut h = HighlightFile::new(path, syntaxes, theme)?;
let mut line = String::new();
let mut buf = String::new();
let mut i = 0;
let mut limit = MANAGER.layout.preview_height();
while limit > 0 && h.reader.read_line(&mut line)? > 0 {
if tick != INCR.load(Ordering::Relaxed) {
return Err("Preview cancelled".into());
}
i += 1;
if i <= skip {
line.clear();
continue;
}
limit -= 1;
line = line.replace('\t', &spaces);
let regions = h.highlight_lines.highlight_line(&line, syntaxes).map_err(|e| anyhow!(e))?;
buf.push_str(&as_24_bit_terminal_escaped(&regions, false));
line.clear();
}
if buf.is_empty() {
return Err(PagedError::Exceed(i));
}
buf.push_str("\x1b[0m");
Ok(buf)
})
.await?
}
}

View file

@ -1,9 +1,9 @@
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};
@ -76,6 +76,32 @@ impl Tab {
true
}
pub fn peek(&mut self, step: isize) -> bool {
let size = self.preview.peek_step(step);
let old = self.preview.skip();
let new = if step < 0 { old.saturating_sub(size) } else { old + size };
if new == old {
return false;
}
if !matches!(&self.preview.lock, Some((_, m)) if m == MIME_DIR) {
self.preview.peek(new);
return false;
};
let path = self.preview.lock.as_ref().unwrap().0.clone();
if let Some(dir) = self.history(&path) {
let max = dir.files.len().saturating_sub(MANAGER.layout.preview_height());
if new > max {
emit!(Peek(path, max));
return false;
}
}
self.preview.peek(new);
false
}
pub async fn cd(&mut self, mut target: PathBuf) -> bool {
let Ok(file) = File::from(&target).await else {
return false;

View file

@ -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))?;
}
}

34
shared/src/errors.rs Normal file
View file

@ -0,0 +1,34 @@
use std::{error::Error, fmt::{self, Display}};
#[derive(Debug)]
pub enum PagedError {
Exceed(usize),
Unexpected(String),
}
impl Display for PagedError {
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 PagedError {}
impl From<String> for PagedError {
fn from(error: String) -> Self { Self::Unexpected(error) }
}
impl From<&str> for PagedError {
fn from(error: &str) -> Self { Self::from(error.to_owned()) }
}
impl From<anyhow::Error> for PagedError {
fn from(error: anyhow::Error) -> Self { Self::from(error.to_string()) }
}
impl From<std::io::Error> for PagedError {
fn from(error: std::io::Error) -> Self { Self::from(error.to_string()) }
}
impl From<tokio::task::JoinError> for PagedError {
fn from(error: tokio::task::JoinError) -> Self { Self::from(error.to_string()) }
}

View file

@ -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::*;