mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
Merge f2df5803ba into 9d92cdbf79
This commit is contained in:
commit
2dadd80406
16 changed files with 801 additions and 311 deletions
1
.github/workflows/check.yml
vendored
1
.github/workflows/check.yml
vendored
|
|
@ -7,7 +7,6 @@ on:
|
|||
branches: [main]
|
||||
|
||||
env:
|
||||
SCCACHE_GHA_ENABLED: true
|
||||
RUSTC_WRAPPER: sccache
|
||||
|
||||
jobs:
|
||||
|
|
|
|||
1
.github/workflows/test.yml
vendored
1
.github/workflows/test.yml
vendored
|
|
@ -7,7 +7,6 @@ on:
|
|||
branches: [main]
|
||||
|
||||
env:
|
||||
SCCACHE_GHA_ENABLED: true
|
||||
RUSTC_WRAPPER: sccache
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
|
|
|
|||
297
Cargo.lock
generated
297
Cargo.lock
generated
|
|
@ -145,6 +145,12 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.6"
|
||||
|
|
@ -505,6 +511,15 @@ version = "0.8.7"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "core_maths"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30"
|
||||
dependencies = [
|
||||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.4.2"
|
||||
|
|
@ -645,6 +660,12 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "data-url"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a"
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.4.0"
|
||||
|
|
@ -874,6 +895,12 @@ dependencies = [
|
|||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "float-cmp"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
|
|
@ -886,6 +913,29 @@ version = "0.1.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||
|
||||
[[package]]
|
||||
name = "fontconfig-parser"
|
||||
version = "0.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1fcfcd44ca6e90c921fee9fa665d530b21ef1327a4c1a6c5250ea44b776ada7"
|
||||
dependencies = [
|
||||
"roxmltree",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fontdb"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "457e789b3d1202543297a350643cf459f836cade38934e7a4cf6a39e7cde2905"
|
||||
dependencies = [
|
||||
"fontconfig-parser",
|
||||
"log",
|
||||
"memmap2",
|
||||
"slotmap",
|
||||
"tinyvec",
|
||||
"ttf-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fsevent-sys"
|
||||
version = "4.1.0"
|
||||
|
|
@ -1134,6 +1184,12 @@ dependencies = [
|
|||
"quick-error",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "imagesize"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285"
|
||||
|
||||
[[package]]
|
||||
name = "imgref"
|
||||
version = "1.11.0"
|
||||
|
|
@ -1271,6 +1327,16 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kurbo"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89234b2cc610a7dd927ebde6b41dd1a5d4214cffaef4cf1fb2195d592f92518f"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
|
|
@ -1299,6 +1365,12 @@ dependencies = [
|
|||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa"
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.3"
|
||||
|
|
@ -1433,6 +1505,15 @@ version = "2.7.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "memmap2"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
|
|
@ -1773,6 +1854,12 @@ version = "2.3.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||
|
||||
[[package]]
|
||||
name = "pico-args"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.16"
|
||||
|
|
@ -2095,11 +2182,37 @@ version = "0.8.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
|
||||
[[package]]
|
||||
name = "resvg"
|
||||
version = "0.45.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd43d1c474e9dadf09a8fdf22d713ba668b499b5117b9b9079500224e26b5b29"
|
||||
dependencies = [
|
||||
"gif",
|
||||
"image-webp",
|
||||
"log",
|
||||
"pico-args",
|
||||
"rgb",
|
||||
"svgtypes",
|
||||
"tiny-skia",
|
||||
"usvg",
|
||||
"zune-jpeg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rgb"
|
||||
version = "0.8.50"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roxmltree"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
|
|
@ -2154,6 +2267,24 @@ version = "1.0.20"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
|
||||
|
||||
[[package]]
|
||||
name = "rustybuzz"
|
||||
version = "0.20.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd3c7c96f8a08ee34eff8857b11b49b07d71d1c3f4e88f8a88d4c9e9f90b1702"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"bytemuck",
|
||||
"core_maths",
|
||||
"log",
|
||||
"smallvec",
|
||||
"ttf-parser",
|
||||
"unicode-bidi-mirroring",
|
||||
"unicode-ccc",
|
||||
"unicode-properties",
|
||||
"unicode-script",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.20"
|
||||
|
|
@ -2310,6 +2441,21 @@ version = "0.1.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
|
||||
|
||||
[[package]]
|
||||
name = "simplecss"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a9c6883ca9c3c7c90e888de77b7a5c849c779d25d74a1269b0218b14e8b136c"
|
||||
dependencies = [
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.9"
|
||||
|
|
@ -2319,6 +2465,15 @@ dependencies = [
|
|||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slotmap"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a"
|
||||
dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.0"
|
||||
|
|
@ -2347,6 +2502,15 @@ version = "1.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "strict-num"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
|
||||
dependencies = [
|
||||
"float-cmp",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
|
|
@ -2375,6 +2539,16 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "svgtypes"
|
||||
version = "0.15.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68c7541fff44b35860c1a7a47a7cadf3e4a304c457b58f9870d9706ece028afc"
|
||||
dependencies = [
|
||||
"kurbo",
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.101"
|
||||
|
|
@ -2540,6 +2714,47 @@ dependencies = [
|
|||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiny-skia"
|
||||
version = "0.11.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"arrayvec",
|
||||
"bytemuck",
|
||||
"cfg-if",
|
||||
"log",
|
||||
"png",
|
||||
"tiny-skia-path",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiny-skia-path"
|
||||
version = "0.11.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"bytemuck",
|
||||
"strict-num",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71"
|
||||
dependencies = [
|
||||
"tinyvec_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec_macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.44.2"
|
||||
|
|
@ -2725,6 +2940,15 @@ dependencies = [
|
|||
"windows",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ttf-parser"
|
||||
version = "0.25.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31"
|
||||
dependencies = [
|
||||
"core_maths",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "twox-hash"
|
||||
version = "2.1.0"
|
||||
|
|
@ -2746,6 +2970,24 @@ version = "1.18.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi-mirroring"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5dfa6e8c60bb66d49db113e0125ee8711b7647b5579dc7f5f19c42357ed039fe"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ccc"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce61d488bcdc9bc8b5d1772c404828b17fc481c0a582b5581e95fb233aef503e"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
|
|
@ -2758,6 +3000,18 @@ version = "0.1.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-properties"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-script"
|
||||
version = "0.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fb421b350c9aff471779e262955939f565ec18b86c15364e6bdf0d662ca7c1f"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.12.0"
|
||||
|
|
@ -2775,6 +3029,12 @@ dependencies = [
|
|||
"unicode-width 0.1.14",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-vo"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.14"
|
||||
|
|
@ -2793,6 +3053,33 @@ version = "2.1.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
|
||||
|
||||
[[package]]
|
||||
name = "usvg"
|
||||
version = "0.45.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ac8e0e3e4696253dc06167990b3fe9a2668ab66270adf949a464db4088cb354"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"data-url",
|
||||
"flate2",
|
||||
"fontdb",
|
||||
"imagesize",
|
||||
"kurbo",
|
||||
"log",
|
||||
"pico-args",
|
||||
"roxmltree",
|
||||
"rustybuzz",
|
||||
"simplecss",
|
||||
"siphasher",
|
||||
"strict-num",
|
||||
"svgtypes",
|
||||
"tiny-skia-path",
|
||||
"unicode-bidi",
|
||||
"unicode-script",
|
||||
"unicode-vo",
|
||||
"xmlwriter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
|
|
@ -3227,6 +3514,12 @@ dependencies = [
|
|||
"bitflags 2.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xmlwriter"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
|
||||
|
||||
[[package]]
|
||||
name = "yazi-adapter"
|
||||
version = "25.4.8"
|
||||
|
|
@ -3238,6 +3531,7 @@ dependencies = [
|
|||
"crossterm 0.29.0",
|
||||
"image",
|
||||
"ratatui",
|
||||
"resvg",
|
||||
"scopeguard",
|
||||
"tokio",
|
||||
"tracing",
|
||||
|
|
@ -3475,12 +3769,12 @@ dependencies = [
|
|||
"crossterm 0.29.0",
|
||||
"futures",
|
||||
"globset",
|
||||
"libc",
|
||||
"md-5",
|
||||
"mlua",
|
||||
"parking_lot",
|
||||
"paste",
|
||||
"ratatui",
|
||||
"resvg",
|
||||
"serde_json",
|
||||
"syntect",
|
||||
"tokio",
|
||||
|
|
@ -3490,7 +3784,6 @@ dependencies = [
|
|||
"twox-hash",
|
||||
"unicode-width 0.2.0",
|
||||
"uzers",
|
||||
"windows-sys 0.59.0",
|
||||
"yazi-adapter",
|
||||
"yazi-binding",
|
||||
"yazi-boot",
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ parking_lot = "0.12.3"
|
|||
paste = "1.0.15"
|
||||
ratatui = { version = "0.29.0", features = [ "unstable-rendered-line-info" ] }
|
||||
regex = "1.11.1"
|
||||
resvg = { version = "0.45.0" }
|
||||
scopeguard = "1.2.0"
|
||||
serde = { version = "1.0.219", features = [ "derive" ] }
|
||||
serde_json = "1.0.140"
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ color_quant = "1.1.0"
|
|||
crossterm = { workspace = true }
|
||||
image = { version = "0.25.6", default-features = false, features = [ "avif", "bmp", "dds", "exr", "ff", "gif", "hdr", "ico", "jpeg", "png", "pnm", "qoi", "tga", "webp" ] }
|
||||
ratatui = { workspace = true }
|
||||
resvg = { workspace = true }
|
||||
scopeguard = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -1,27 +1,24 @@
|
|||
use std::path::{Path, PathBuf};
|
||||
use std::{path::{Path, PathBuf}, sync::{LazyLock, RwLock}};
|
||||
|
||||
use anyhow::Result;
|
||||
use image::{DynamicImage, ExtendedColorType, ImageDecoder, ImageEncoder, ImageError, ImageReader, ImageResult, Limits, codecs::{jpeg::JpegEncoder, png::PngEncoder}, imageops::FilterType, metadata::Orientation};
|
||||
use image::{DynamicImage, ExtendedColorType, ImageBuffer, ImageDecoder, ImageEncoder, ImageError, ImageReader, ImageResult, Limits, codecs::{jpeg::JpegEncoder, png::PngEncoder}, error::UnsupportedErrorKind, imageops::FilterType, metadata::Orientation};
|
||||
use ratatui::layout::Rect;
|
||||
use resvg::{tiny_skia::{Pixmap, Transform}, usvg::{Options, Tree}};
|
||||
use yazi_config::YAZI;
|
||||
|
||||
use crate::Dimension;
|
||||
|
||||
pub static GLOBAL_OPTIONS: LazyLock<RwLock<Options<'static>>> =
|
||||
LazyLock::new(|| RwLock::new(Options::default()));
|
||||
|
||||
pub struct Image;
|
||||
|
||||
impl Image {
|
||||
pub async fn precache(path: &Path, cache: PathBuf) -> Result<()> {
|
||||
let (mut img, orientation, icc) = Self::decode_from(path).await?;
|
||||
let (w, h) = Self::flip_size(orientation, (YAZI.preview.max_width, YAZI.preview.max_height));
|
||||
let (img, icc) =
|
||||
Self::decode_to_fit_from(path, YAZI.preview.max_width, YAZI.preview.max_height).await?;
|
||||
|
||||
let buf = tokio::task::spawn_blocking(move || {
|
||||
if img.width() > w || img.height() > h {
|
||||
img = img.resize(w, h, Self::filter());
|
||||
}
|
||||
if orientation != Orientation::NoTransforms {
|
||||
img.apply_orientation(orientation);
|
||||
}
|
||||
|
||||
let mut buf = Vec::new();
|
||||
if img.color().has_alpha() {
|
||||
let rgba = img.into_rgba8();
|
||||
|
|
@ -42,25 +39,8 @@ impl Image {
|
|||
}
|
||||
|
||||
pub(super) async fn downscale(path: &Path, rect: Rect) -> Result<DynamicImage> {
|
||||
let (mut img, orientation, _) = Self::decode_from(path).await?;
|
||||
let (w, h) = Self::flip_size(orientation, Self::max_pixel(rect));
|
||||
|
||||
// Fast path.
|
||||
if img.width() <= w && img.height() <= h && orientation == Orientation::NoTransforms {
|
||||
return Ok(img);
|
||||
}
|
||||
|
||||
let img = tokio::task::spawn_blocking(move || {
|
||||
if img.width() > w || img.height() > h {
|
||||
img = img.resize(w, h, Self::filter())
|
||||
}
|
||||
if orientation != Orientation::NoTransforms {
|
||||
img.apply_orientation(orientation);
|
||||
}
|
||||
img
|
||||
})
|
||||
.await?;
|
||||
|
||||
let (width, height) = Self::max_pixel(rect);
|
||||
let (img, _) = Self::decode_to_fit_from(path, width, height).await?;
|
||||
Ok(img)
|
||||
}
|
||||
|
||||
|
|
@ -96,7 +76,54 @@ impl Image {
|
|||
}
|
||||
}
|
||||
|
||||
async fn decode_from(path: &Path) -> ImageResult<(DynamicImage, Orientation, Option<Vec<u8>>)> {
|
||||
async fn decode_to_fit_from(
|
||||
path: &Path,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<(DynamicImage, Option<Vec<u8>>)> {
|
||||
let path = path.to_owned();
|
||||
|
||||
tokio::task::spawn_blocking(move || {
|
||||
Self::try_decode_raster(&path, width, height).or_else(|err| match err {
|
||||
ImageError::Unsupported(ref unsupported) => match unsupported.kind() {
|
||||
UnsupportedErrorKind::Format(_) => Self::try_decode_svg(&path, width, height),
|
||||
_ => Err(err.into()),
|
||||
},
|
||||
_ => Err(err.into()),
|
||||
})
|
||||
})
|
||||
.await
|
||||
.map_err(|e| ImageError::IoError(e.into()))?
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_decode_raster(
|
||||
path: &Path,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> ImageResult<(DynamicImage, Option<Vec<u8>>)> {
|
||||
let limits = Self::build_limits();
|
||||
let mut reader = ImageReader::open(path)?;
|
||||
reader.limits(limits);
|
||||
let mut decoder = reader.with_guessed_format()?.into_decoder()?;
|
||||
let orientation = decoder.orientation().unwrap_or(Orientation::NoTransforms);
|
||||
let icc = decoder.icc_profile().unwrap_or_default();
|
||||
|
||||
let mut img = DynamicImage::from_decoder(decoder)?;
|
||||
let (w, h) = Self::flip_size(orientation, (width, height));
|
||||
|
||||
if img.width() > w || img.height() > h {
|
||||
img = img.resize(w, h, Self::filter());
|
||||
}
|
||||
if orientation != Orientation::NoTransforms {
|
||||
img.apply_orientation(orientation);
|
||||
}
|
||||
|
||||
Ok((img, icc))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn build_limits() -> Limits {
|
||||
let mut limits = Limits::no_limits();
|
||||
if YAZI.tasks.image_alloc > 0 {
|
||||
limits.max_alloc = Some(YAZI.tasks.image_alloc as u64);
|
||||
|
|
@ -107,20 +134,76 @@ impl Image {
|
|||
if YAZI.tasks.image_bound[1] > 0 {
|
||||
limits.max_image_height = Some(YAZI.tasks.image_bound[1] as u32);
|
||||
}
|
||||
limits
|
||||
}
|
||||
|
||||
let path = path.to_owned();
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let mut reader = ImageReader::open(path)?;
|
||||
reader.limits(limits);
|
||||
#[inline]
|
||||
fn try_decode_svg(
|
||||
path: &Path,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<(DynamicImage, Option<Vec<u8>>)> {
|
||||
let pixmap = Self::render_svg_to_fit_from(path, width, height)?;
|
||||
let (width, height) = (pixmap.width(), pixmap.height());
|
||||
let mut container = pixmap.take();
|
||||
|
||||
let mut decoder = reader.with_guessed_format()?.into_decoder()?;
|
||||
let orientation = decoder.orientation().unwrap_or(Orientation::NoTransforms);
|
||||
let icc = decoder.icc_profile().unwrap_or_default();
|
||||
for rgba in container.chunks_exact_mut(4) {
|
||||
let alpha = rgba[3];
|
||||
if alpha != 0xff {
|
||||
let pixel: &mut [u8; 4] = unsafe { rgba.try_into().unwrap_unchecked() };
|
||||
let a = alpha as f64 / 255.0;
|
||||
pixel[0] = (pixel[0] as f64 / a + 0.5) as u8;
|
||||
pixel[1] = (pixel[1] as f64 / a + 0.5) as u8;
|
||||
pixel[2] = (pixel[2] as f64 / a + 0.5) as u8;
|
||||
}
|
||||
}
|
||||
|
||||
Ok((DynamicImage::from_decoder(decoder)?, orientation, icc))
|
||||
})
|
||||
.await
|
||||
.map_err(|e| ImageError::IoError(e.into()))?
|
||||
let img = ImageBuffer::from_raw(width, height, container)
|
||||
.ok_or_else(|| anyhow::anyhow!("Failed to create image buffer"))?;
|
||||
|
||||
Ok((DynamicImage::ImageRgba8(img), None))
|
||||
}
|
||||
|
||||
/// Helper function to rasterize an SVG to a pixmap fitting within max_width
|
||||
/// and max_height.
|
||||
fn render_svg_to_fit_from(path: &Path, max_width: u32, max_height: u32) -> Result<Pixmap> {
|
||||
let svg = std::fs::read(path)?;
|
||||
let options_guard =
|
||||
GLOBAL_OPTIONS.read().map_err(|e| anyhow::anyhow!("RwLock poisoned: {}", e))?;
|
||||
let tree = Tree::from_data(&svg, &options_guard)?;
|
||||
let (width, height, transform) = Self::svg_size_and_scale(&tree, max_width, max_height);
|
||||
|
||||
let mut pixmap =
|
||||
Pixmap::new(width, height).ok_or_else(|| anyhow::anyhow!("Cannot create pixmap"))?;
|
||||
|
||||
resvg::render(&tree, transform, &mut pixmap.as_mut());
|
||||
|
||||
Ok(pixmap)
|
||||
}
|
||||
|
||||
fn svg_size_and_scale(tree: &Tree, max_width: u32, max_height: u32) -> (u32, u32, Transform) {
|
||||
// It is Ok. The max_width and max_height could not be larger then monitor
|
||||
// dimensions which much less then f32::MAX
|
||||
let max_width = max_width as f32;
|
||||
let max_height = max_height as f32;
|
||||
let mut width = tree.size().width();
|
||||
let mut height = tree.size().height();
|
||||
|
||||
for node in tree.root().children() {
|
||||
if let Some(bounding_box) = node.abs_layer_bounding_box() {
|
||||
width = width.max(bounding_box.width());
|
||||
height = height.max(bounding_box.height());
|
||||
}
|
||||
}
|
||||
if width <= max_width && height <= max_height {
|
||||
return (width.floor() as u32, height.floor() as u32, Transform::from_scale(1.0, 1.0));
|
||||
}
|
||||
let ratio = f32::min(max_width / width, max_height / height);
|
||||
(
|
||||
(width * ratio).floor() as u32,
|
||||
(height * ratio).floor() as u32,
|
||||
Transform::from_scale(ratio, ratio),
|
||||
)
|
||||
}
|
||||
|
||||
fn flip_size(orientation: Orientation, (w, h): (u32, u32)) -> (u32, u32) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
use std::path::Path;
|
||||
use std::{collections::HashSet, path::Path};
|
||||
|
||||
use image::{ImageDecoder, ImageError};
|
||||
use resvg::usvg::{Font, Group, ImageKind, Node, Tree};
|
||||
|
||||
use super::image::GLOBAL_OPTIONS;
|
||||
|
||||
pub type ImageFormat = image::ImageFormat;
|
||||
pub type ImageColor = image::ColorType;
|
||||
|
|
@ -42,3 +45,77 @@ impl ImageInfo {
|
|||
.map_err(|e| ImageError::IoError(e.into()))?
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SvgInfo {
|
||||
pub width: f32,
|
||||
pub height: f32,
|
||||
pub fonts: HashSet<Font>,
|
||||
pub layers: Vec<String>,
|
||||
}
|
||||
|
||||
impl SvgInfo {
|
||||
pub async fn new(path: &Path) -> anyhow::Result<Self> {
|
||||
let path = path.to_owned();
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let svg = std::fs::read(path)?;
|
||||
let options_guard =
|
||||
GLOBAL_OPTIONS.read().map_err(|e| anyhow::anyhow!("RwLock poisoned: {}", e))?;
|
||||
let tree = Tree::from_data(&svg, &options_guard)?;
|
||||
|
||||
let mut info = SvgInfo {
|
||||
width: tree.size().width().round(),
|
||||
height: tree.size().height().round(),
|
||||
fonts: HashSet::new(),
|
||||
layers: Vec::new(),
|
||||
};
|
||||
|
||||
Self::collect_group(tree.root(), &mut info);
|
||||
|
||||
Ok(info)
|
||||
})
|
||||
.await?
|
||||
}
|
||||
|
||||
fn collect_group(group: &Group, info: &mut SvgInfo) {
|
||||
if !group.id().is_empty() {
|
||||
info.layers.push(group.id().to_string());
|
||||
}
|
||||
|
||||
for node in group.children() {
|
||||
Self::collect_node(node, info);
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_node(node: &Node, info: &mut SvgInfo) {
|
||||
if let Some(bounding_box) = node.abs_layer_bounding_box() {
|
||||
info.width = info.width.max(bounding_box.width());
|
||||
info.height = info.height.max(bounding_box.height());
|
||||
}
|
||||
match node {
|
||||
Node::Group(g) => Self::collect_group(g, info),
|
||||
|
||||
Node::Path(_) => {}
|
||||
|
||||
Node::Text(t) => {
|
||||
for chunk in t.chunks() {
|
||||
for span in chunk.spans() {
|
||||
if !info.fonts.contains(span.font()) {
|
||||
info.fonts.insert(span.font().clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Node::Image(i) => {
|
||||
if let ImageKind::SVG(sub_tree) = &i.kind() {
|
||||
Self::collect_group(sub_tree.root(), info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
node.subroots(|subroot: &Group| {
|
||||
Self::collect_group(subroot, info);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ mlua = { workspace = true }
|
|||
parking_lot = { workspace = true }
|
||||
paste = { workspace = true }
|
||||
ratatui = { workspace = true }
|
||||
resvg = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
syntect = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
|
|
@ -46,12 +47,10 @@ unicode-width = { workspace = true }
|
|||
yazi-prebuilt = "0.1.0"
|
||||
|
||||
[target."cfg(unix)".dependencies]
|
||||
libc = { workspace = true }
|
||||
uzers = { workspace = true }
|
||||
|
||||
[target."cfg(windows)".dependencies]
|
||||
clipboard-win = "5.4.0"
|
||||
windows-sys = { version = "0.59.0", features = [ "Win32_Security", "Win32_System_JobObjects", "Win32_System_Threading" ] }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] }
|
||||
|
|
|
|||
|
|
@ -1,71 +1,57 @@
|
|||
local M = {}
|
||||
|
||||
function M:peek(job)
|
||||
local start, cache = os.clock(), ya.file_cache(job)
|
||||
if not cache then
|
||||
return
|
||||
end
|
||||
|
||||
local ok, err = self:preload(job)
|
||||
if not ok or err then
|
||||
return
|
||||
end
|
||||
|
||||
local start = os.clock()
|
||||
ya.sleep(math.max(0, rt.preview.image_delay / 1000 + start - os.clock()))
|
||||
ya.image_show(cache, job.area)
|
||||
ya.image_show(job.file.url, job.area)
|
||||
ya.preview_widgets(job, {})
|
||||
end
|
||||
|
||||
function M:seek() end
|
||||
|
||||
function M:preload(job)
|
||||
local cache = ya.file_cache(job)
|
||||
if not cache or fs.cha(cache) then
|
||||
return true
|
||||
end
|
||||
function M:preload(job) end
|
||||
|
||||
-- stylua: ignore
|
||||
local cmd = Command("resvg"):args {
|
||||
"-w", rt.preview.max_width, "-h", rt.preview.max_height,
|
||||
"--image-rendering", "optimizeSpeed",
|
||||
tostring(job.file.url), tostring(cache)
|
||||
}
|
||||
if rt.tasks.image_alloc > 0 then
|
||||
cmd = cmd:memory(rt.tasks.image_alloc)
|
||||
end
|
||||
function M:spot(job)
|
||||
local rows = self:spot_base(job)
|
||||
rows[#rows + 1] = ui.Row {}
|
||||
|
||||
local child, err = cmd:spawn()
|
||||
if not child then
|
||||
return true, Err("Failed to start `resvg`, error: %s", err)
|
||||
end
|
||||
|
||||
local status, err
|
||||
while true do
|
||||
ya.sleep(0.2)
|
||||
|
||||
status, err = child:try_wait()
|
||||
if status or err then
|
||||
break
|
||||
end
|
||||
|
||||
local id, mem = child:id(), nil
|
||||
if id then
|
||||
mem = ya.proc_info(id).mem_resident
|
||||
end
|
||||
if mem and mem > rt.tasks.image_alloc then
|
||||
child:start_kill()
|
||||
err = Err("memory limit exceeded, pid: %s, memory: %s", id, mem)
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if status then
|
||||
return status.success
|
||||
else
|
||||
return true, Err("Error while running `resvg`: %s", err)
|
||||
end
|
||||
ya.spot_table(
|
||||
job,
|
||||
ui.Table(ya.list_merge(rows, require("file"):spot_base(job)))
|
||||
:area(ui.Pos { "center", w = 60, h = 20 })
|
||||
:row(job.skip)
|
||||
:row(1)
|
||||
:col(1)
|
||||
:col_style(th.spot.tbl_col)
|
||||
:cell_style(th.spot.tbl_cell)
|
||||
:widths { ui.Constraint.Length(14), ui.Constraint.Fill(1) }
|
||||
)
|
||||
end
|
||||
|
||||
function M:spot(job) require("file"):spot(job) end
|
||||
function M:spot_base(job)
|
||||
local info = ya.svg_info(job.file.url)
|
||||
if not info then
|
||||
return {}
|
||||
end
|
||||
|
||||
local rows = {
|
||||
ui.Row({ "SVG Image" }):style(ui.Style():fg("green")),
|
||||
ui.Row { " Size:", string.format("%.0fx%.0f", info.w, info.h) },
|
||||
}
|
||||
|
||||
if #info.layers > 0 then
|
||||
rows[#rows + 1] = ui.Row { " Layers:", table.concat(info.layers, ", ") }
|
||||
end
|
||||
|
||||
if #info.fonts > 0 then
|
||||
rows[#rows + 1] = ui.Row { " Fonts:", tostring(info.fonts[1]) }
|
||||
|
||||
for i = 2, #info.fonts do
|
||||
rows[#rows + 1] = ui.Row { "", tostring(info.fonts[i]) }
|
||||
end
|
||||
end
|
||||
|
||||
return rows
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use std::ops::Deref;
|
||||
use std::{fmt::Write, ops::Deref};
|
||||
|
||||
use mlua::{MetaMethod, UserData};
|
||||
use resvg::usvg;
|
||||
|
||||
pub struct ImageInfo(yazi_adapter::ImageInfo);
|
||||
|
||||
|
|
@ -81,3 +82,115 @@ impl UserData for ImageColor {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SvgInfo(yazi_adapter::SvgInfo);
|
||||
|
||||
impl Deref for SvgInfo {
|
||||
type Target = yazi_adapter::SvgInfo;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.0 }
|
||||
}
|
||||
|
||||
impl From<yazi_adapter::SvgInfo> for SvgInfo {
|
||||
fn from(value: yazi_adapter::SvgInfo) -> Self { Self(value) }
|
||||
}
|
||||
|
||||
impl UserData for SvgInfo {
|
||||
fn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("w", |_, this| Ok(this.width));
|
||||
fields.add_field_method_get("h", |_, this| Ok(this.height));
|
||||
|
||||
fields.add_field_method_get("layers", |lua, this| {
|
||||
let table = lua.create_table()?;
|
||||
for (i, layer) in this.layers.iter().enumerate() {
|
||||
table.set(i + 1, layer.clone())?;
|
||||
}
|
||||
Ok(table)
|
||||
});
|
||||
|
||||
fields.add_field_method_get("fonts", |lua, this| {
|
||||
let table = lua.create_table()?;
|
||||
for (i, font) in this.fonts.iter().enumerate() {
|
||||
table.set(i + 1, Font(font.clone()))?;
|
||||
}
|
||||
Ok(table)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Font(usvg::Font);
|
||||
|
||||
impl Deref for Font {
|
||||
type Target = usvg::Font;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.0 }
|
||||
}
|
||||
|
||||
trait ToStaticStr {
|
||||
fn as_str(&self) -> &'static str;
|
||||
}
|
||||
|
||||
impl ToStaticStr for usvg::FontStyle {
|
||||
fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Normal => "Normal",
|
||||
Self::Italic => "Italic",
|
||||
Self::Oblique => "Oblique",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToStaticStr for usvg::FontStretch {
|
||||
fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::UltraCondensed => "UltraCondensed",
|
||||
Self::ExtraCondensed => "ExtraCondensed",
|
||||
Self::Condensed => "Condensed",
|
||||
Self::SemiCondensed => "SemiCondensed",
|
||||
Self::Normal => "Normal",
|
||||
Self::SemiExpanded => "SemiExpanded",
|
||||
Self::Expanded => "Expanded",
|
||||
Self::ExtraExpanded => "ExtraExpanded",
|
||||
Self::UltraExpanded => "UltraExpanded",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for Font {
|
||||
fn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("families", |_, this| {
|
||||
let mut families = String::new();
|
||||
for (i, family) in this.families().iter().enumerate() {
|
||||
if i > 0 {
|
||||
families.push_str(", ");
|
||||
}
|
||||
write!(&mut families, "{}", family).unwrap();
|
||||
}
|
||||
Ok(families)
|
||||
});
|
||||
fields.add_field_method_get("style", |_, this| Ok(this.style().as_str()));
|
||||
fields.add_field_method_get("stretch", |_, this| Ok(this.stretch().as_str()));
|
||||
|
||||
fields.add_field_method_get("weight", |_, this| Ok(this.weight()));
|
||||
}
|
||||
|
||||
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_meta_method(MetaMethod::ToString, |_, this, ()| {
|
||||
let mut families = String::new();
|
||||
for (i, family) in this.families().iter().enumerate() {
|
||||
if i > 0 {
|
||||
families.push_str(", ");
|
||||
}
|
||||
write!(&mut families, "{}", family).unwrap();
|
||||
}
|
||||
|
||||
Ok(format!(
|
||||
"{}, style: {}, stretch: {}, weight: {}",
|
||||
families,
|
||||
this.style().as_str(),
|
||||
this.stretch().as_str(),
|
||||
this.weight()
|
||||
))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use std::{ops::DerefMut, process::ExitStatus, time::Duration};
|
||||
use std::{ops::DerefMut, time::Duration};
|
||||
|
||||
use futures::future::try_join3;
|
||||
use mlua::{AnyUserData, ExternalError, IntoLua, IntoLuaMulti, Table, UserData, UserDataMethods, Value};
|
||||
use mlua::{AnyUserData, ExternalError, IntoLua, IntoLuaMulti, Table, UserData, Value};
|
||||
use tokio::{io::{self, AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader, BufWriter}, process::{ChildStderr, ChildStdin, ChildStdout}, select};
|
||||
use yazi_binding::Error;
|
||||
|
||||
|
|
@ -9,94 +9,39 @@ use super::Status;
|
|||
use crate::process::Output;
|
||||
|
||||
pub struct Child {
|
||||
inner: tokio::process::Child,
|
||||
stdin: Option<BufWriter<ChildStdin>>,
|
||||
stdout: Option<BufReader<ChildStdout>>,
|
||||
stderr: Option<BufReader<ChildStderr>>,
|
||||
#[cfg(windows)]
|
||||
job_handle: Option<std::os::windows::io::RawHandle>,
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
impl Drop for Child {
|
||||
fn drop(&mut self) {
|
||||
if let Some(h) = self.job_handle.take() {
|
||||
unsafe { windows_sys::Win32::Foundation::CloseHandle(h) };
|
||||
}
|
||||
}
|
||||
inner: tokio::process::Child,
|
||||
stdin: Option<BufWriter<ChildStdin>>,
|
||||
stdout: Option<BufReader<ChildStdout>>,
|
||||
stderr: Option<BufReader<ChildStderr>>,
|
||||
}
|
||||
|
||||
impl Child {
|
||||
pub fn new(
|
||||
mut inner: tokio::process::Child,
|
||||
#[cfg(windows)] job_handle: Option<std::os::windows::io::RawHandle>,
|
||||
) -> Self {
|
||||
pub fn new(mut inner: tokio::process::Child) -> Self {
|
||||
let stdin = inner.stdin.take().map(BufWriter::new);
|
||||
let stdout = inner.stdout.take().map(BufReader::new);
|
||||
let stderr = inner.stderr.take().map(BufReader::new);
|
||||
Self {
|
||||
inner,
|
||||
stdin,
|
||||
stdout,
|
||||
stderr,
|
||||
#[cfg(windows)]
|
||||
job_handle,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn wait(&mut self) -> io::Result<ExitStatus> {
|
||||
drop(self.stdin.take());
|
||||
self.inner.wait().await
|
||||
}
|
||||
|
||||
pub(super) async fn status(&mut self) -> io::Result<ExitStatus> {
|
||||
drop(self.stdin.take());
|
||||
drop(self.stdout.take());
|
||||
drop(self.stderr.take());
|
||||
self.inner.wait().await
|
||||
}
|
||||
|
||||
async fn read_line(&mut self) -> (Option<Vec<u8>>, u8) {
|
||||
async fn read(r: Option<impl AsyncBufReadExt + Unpin>) -> Option<Vec<u8>> {
|
||||
let mut buf = Vec::new();
|
||||
match r?.read_until(b'\n', &mut buf).await {
|
||||
Ok(0) | Err(_) => None,
|
||||
Ok(_) => Some(buf),
|
||||
}
|
||||
}
|
||||
|
||||
select! {
|
||||
r @ Some(_) = read(self.stdout.as_mut()) => (r, 0u8),
|
||||
r @ Some(_) = read(self.stderr.as_mut()) => (r, 1u8),
|
||||
else => (None, 2u8),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn wait_with_output(mut self) -> io::Result<std::process::Output> {
|
||||
async fn read(r: &mut Option<impl AsyncBufReadExt + Unpin>) -> io::Result<Vec<u8>> {
|
||||
let mut vec = Vec::new();
|
||||
if let Some(r) = r.as_mut() {
|
||||
r.read_to_end(&mut vec).await?;
|
||||
}
|
||||
Ok(vec)
|
||||
}
|
||||
|
||||
// Ensure stdin is closed so the child isn't stuck waiting on input while the
|
||||
// parent is waiting for it to exit.
|
||||
drop(self.stdin.take());
|
||||
|
||||
// Drop happens after `try_join` due to <https://github.com/tokio-rs/tokio/issues/4309>
|
||||
let mut stdout = self.stdout.take();
|
||||
let mut stderr = self.stderr.take();
|
||||
|
||||
let result = try_join3(self.inner.wait(), read(&mut stdout), read(&mut stderr)).await?;
|
||||
Ok(std::process::Output { status: result.0, stdout: result.1, stderr: result.2 })
|
||||
Self { inner, stdin, stdout, stderr }
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for Child {
|
||||
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_method("id", |_, me, ()| Ok(me.inner.id()));
|
||||
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
|
||||
#[inline]
|
||||
async fn read_line(me: &mut Child) -> (Option<Vec<u8>>, u8) {
|
||||
async fn read(r: Option<impl AsyncBufReadExt + Unpin>) -> Option<Vec<u8>> {
|
||||
let mut buf = Vec::new();
|
||||
match r?.read_until(b'\n', &mut buf).await {
|
||||
Ok(0) | Err(_) => None,
|
||||
Ok(_) => Some(buf),
|
||||
}
|
||||
}
|
||||
|
||||
select! {
|
||||
r @ Some(_) = read(me.stdout.as_mut()) => (r, 0u8),
|
||||
r @ Some(_) = read(me.stderr.as_mut()) => (r, 1u8),
|
||||
else => (None, 2u8),
|
||||
}
|
||||
}
|
||||
|
||||
methods.add_async_method_mut("read", |_, mut me, len: usize| async move {
|
||||
async fn read(r: Option<impl AsyncBufReadExt + Unpin>, len: usize) -> Option<Vec<u8>> {
|
||||
|
|
@ -117,15 +62,14 @@ impl UserData for Child {
|
|||
})
|
||||
});
|
||||
methods.add_async_method_mut("read_line", |lua, mut me, ()| async move {
|
||||
match me.read_line().await {
|
||||
match read_line(&mut me).await {
|
||||
(Some(b), event) => (lua.create_string(b)?, event).into_lua_multi(&lua),
|
||||
(None, event) => (Value::Nil, event).into_lua_multi(&lua),
|
||||
}
|
||||
});
|
||||
// TODO: deprecate this method
|
||||
methods.add_async_method_mut("read_line_with", |lua, mut me, options: Table| async move {
|
||||
let timeout = Duration::from_millis(options.raw_get("timeout")?);
|
||||
let Ok(result) = tokio::time::timeout(timeout, me.read_line()).await else {
|
||||
let Ok(result) = tokio::time::timeout(timeout, read_line(&mut me)).await else {
|
||||
return (Value::Nil, 3u8).into_lua_multi(&lua);
|
||||
};
|
||||
match result {
|
||||
|
|
@ -154,21 +98,38 @@ impl UserData for Child {
|
|||
});
|
||||
|
||||
methods.add_async_method_mut("wait", |lua, mut me, ()| async move {
|
||||
match me.wait().await {
|
||||
drop(me.stdin.take());
|
||||
match me.inner.wait().await {
|
||||
Ok(status) => (Status::new(status), Value::Nil).into_lua_multi(&lua),
|
||||
Err(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua),
|
||||
}
|
||||
});
|
||||
methods.add_async_function("wait_with_output", |lua, ud: AnyUserData| async move {
|
||||
match ud.take::<Self>()?.wait_with_output().await {
|
||||
Ok(output) => (Output::new(output), Value::Nil).into_lua_multi(&lua),
|
||||
Err(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua),
|
||||
async fn read_to_end(r: &mut Option<impl AsyncBufReadExt + Unpin>) -> io::Result<Vec<u8>> {
|
||||
let mut vec = Vec::new();
|
||||
if let Some(r) = r.as_mut() {
|
||||
r.read_to_end(&mut vec).await?;
|
||||
}
|
||||
Ok(vec)
|
||||
}
|
||||
});
|
||||
methods.add_async_method_mut("try_wait", |lua, mut me, ()| async move {
|
||||
match me.inner.try_wait() {
|
||||
Ok(Some(status)) => (Status::new(status), Value::Nil).into_lua_multi(&lua),
|
||||
Ok(None) => (Value::Nil, Value::Nil).into_lua_multi(&lua),
|
||||
|
||||
let mut me = ud.take::<Self>()?;
|
||||
let mut stdout_pipe = me.stdout.take();
|
||||
let mut stderr_pipe = me.stderr.take();
|
||||
|
||||
let stdout_fut = read_to_end(&mut stdout_pipe);
|
||||
let stderr_fut = read_to_end(&mut stderr_pipe);
|
||||
|
||||
drop(me.stdin.take());
|
||||
let result = try_join3(me.inner.wait(), stdout_fut, stderr_fut).await;
|
||||
drop(stdout_pipe);
|
||||
drop(stderr_pipe);
|
||||
|
||||
match result {
|
||||
Ok((status, stdout, stderr)) => {
|
||||
(Output::new(std::process::Output { status, stdout, stderr }), Value::Nil)
|
||||
.into_lua_multi(&lua)
|
||||
}
|
||||
Err(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua),
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::{io, process::Stdio};
|
||||
use std::process::Stdio;
|
||||
|
||||
use mlua::{AnyUserData, ExternalError, IntoLuaMulti, Lua, MetaMethod, Table, UserData, Value};
|
||||
use tokio::process::{ChildStderr, ChildStdin, ChildStdout};
|
||||
|
|
@ -8,8 +8,7 @@ use super::{Child, output::Output};
|
|||
use crate::process::Status;
|
||||
|
||||
pub struct Command {
|
||||
inner: tokio::process::Command,
|
||||
memory: Option<usize>,
|
||||
inner: tokio::process::Command,
|
||||
}
|
||||
|
||||
const NULL: u8 = 0;
|
||||
|
|
@ -22,7 +21,7 @@ impl Command {
|
|||
let mut inner = tokio::process::Command::new(program);
|
||||
inner.kill_on_drop(true).stdin(Stdio::null()).stdout(Stdio::null()).stderr(Stdio::null());
|
||||
|
||||
Ok(Self { inner, memory: None })
|
||||
Ok(Self { inner })
|
||||
})?;
|
||||
|
||||
let command = lua.create_table_from([
|
||||
|
|
@ -36,74 +35,6 @@ impl Command {
|
|||
|
||||
lua.globals().raw_set("Command", command)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn spawn(&mut self) -> io::Result<Child> {
|
||||
if let Some(max) = self.memory {
|
||||
unsafe {
|
||||
self.inner.pre_exec(move || {
|
||||
let rlp = libc::rlimit { rlim_cur: max as _, rlim_max: max as _ };
|
||||
libc::setrlimit(libc::RLIMIT_AS, &rlp);
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
}
|
||||
self.inner.spawn().map(Child::new)
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn spawn(&mut self) -> io::Result<Child> {
|
||||
use std::os::windows::io::RawHandle;
|
||||
|
||||
use windows_sys::Win32::System::JobObjects::{AssignProcessToJobObject, CreateJobObjectW, JOB_OBJECT_LIMIT_PROCESS_MEMORY, JOBOBJECT_EXTENDED_LIMIT_INFORMATION, JobObjectExtendedLimitInformation, SetInformationJobObject};
|
||||
|
||||
fn assign_job(max: usize, handle: RawHandle) -> io::Result<RawHandle> {
|
||||
unsafe {
|
||||
let job = CreateJobObjectW(std::ptr::null_mut(), std::ptr::null());
|
||||
if job.is_null() {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
|
||||
let mut info: JOBOBJECT_EXTENDED_LIMIT_INFORMATION = std::mem::zeroed();
|
||||
info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_PROCESS_MEMORY;
|
||||
info.ProcessMemoryLimit = max;
|
||||
|
||||
let result = SetInformationJobObject(
|
||||
job,
|
||||
JobObjectExtendedLimitInformation,
|
||||
&mut info as *mut _ as *mut _,
|
||||
std::mem::size_of_val(&info) as u32,
|
||||
);
|
||||
|
||||
if result == 0 {
|
||||
Err(io::Error::last_os_error())
|
||||
} else if AssignProcessToJobObject(job, handle) == 0 {
|
||||
Err(io::Error::last_os_error())
|
||||
} else {
|
||||
Ok(job)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let child = self.inner.spawn()?;
|
||||
if let (Some(max), Some(handle)) = (self.memory, child.raw_handle()) {
|
||||
if let Ok(job) = assign_job(max, handle) {
|
||||
return Ok(Child::new(child, Some(job)));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Child::new(child, None))
|
||||
}
|
||||
|
||||
async fn output(&mut self) -> io::Result<std::process::Output> {
|
||||
self.inner.stdin(Stdio::piped());
|
||||
self.inner.stdout(Stdio::piped());
|
||||
self.spawn()?.wait_with_output().await
|
||||
}
|
||||
|
||||
async fn status(&mut self) -> io::Result<std::process::ExitStatus> {
|
||||
self.spawn()?.status().await
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for Command {
|
||||
|
|
@ -171,22 +102,18 @@ impl UserData for Command {
|
|||
ud.borrow_mut::<Self>()?.inner.stderr(make_stdio(stdio)?);
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_function_mut("memory", |_, (ud, max): (AnyUserData, usize)| {
|
||||
ud.borrow_mut::<Self>()?.memory = Some(max);
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_method_mut("spawn", |lua, me, ()| match me.spawn() {
|
||||
Ok(child) => (child, Value::Nil).into_lua_multi(lua),
|
||||
methods.add_method_mut("spawn", |lua, me, ()| match me.inner.spawn() {
|
||||
Ok(child) => (Child::new(child), Value::Nil).into_lua_multi(lua),
|
||||
Err(e) => (Value::Nil, Error::Io(e)).into_lua_multi(lua),
|
||||
});
|
||||
methods.add_async_method_mut("output", |lua, mut me, ()| async move {
|
||||
match me.output().await {
|
||||
match me.inner.output().await {
|
||||
Ok(output) => (Output::new(output), Value::Nil).into_lua_multi(&lua),
|
||||
Err(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua),
|
||||
}
|
||||
});
|
||||
methods.add_async_method_mut("status", |lua, mut me, ()| async move {
|
||||
match me.status().await {
|
||||
match me.inner.status().await {
|
||||
Ok(status) => (Status::new(status), Value::Nil).into_lua_multi(&lua),
|
||||
Err(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#![allow(clippy::module_inception)]
|
||||
|
||||
yazi_macro::mod_flat!(
|
||||
app cache call image json layer log preview process spot sync target text time user utils
|
||||
app cache call image json layer log preview spot svg sync target text time user utils
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,29 +0,0 @@
|
|||
use mlua::{Function, Lua};
|
||||
|
||||
use super::Utils;
|
||||
|
||||
impl Utils {
|
||||
#[cfg(target_os = "macos")]
|
||||
pub(super) fn proc_info(lua: &Lua) -> mlua::Result<Function> {
|
||||
lua.create_function(|lua, pid: usize| {
|
||||
let info = unsafe {
|
||||
let mut info: libc::proc_taskinfo = std::mem::zeroed();
|
||||
libc::proc_pidinfo(
|
||||
pid as _,
|
||||
libc::PROC_PIDTASKINFO,
|
||||
0,
|
||||
&mut info as *mut _ as *mut _,
|
||||
std::mem::size_of_val(&info) as _,
|
||||
);
|
||||
info
|
||||
};
|
||||
|
||||
lua.create_table_from([("mem_resident", info.pti_resident_size)])
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
pub(super) fn proc_info(lua: &Lua) -> mlua::Result<Function> {
|
||||
lua.create_function(|lua, ()| lua.create_table())
|
||||
}
|
||||
}
|
||||
76
yazi-plugin/src/utils/svg.rs
Normal file
76
yazi-plugin/src/utils/svg.rs
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use mlua::{Function, IntoLua, Lua, Value};
|
||||
use resvg::usvg::fontdb::Database;
|
||||
use yazi_adapter::GLOBAL_OPTIONS;
|
||||
use yazi_binding::UrlRef;
|
||||
|
||||
use super::Utils;
|
||||
use crate::bindings::SvgInfo;
|
||||
|
||||
impl Utils {
|
||||
pub(super) fn svg_info(lua: &Lua) -> mlua::Result<Function> {
|
||||
lua.create_async_function(|lua, url: UrlRef| async move {
|
||||
if let Ok(info) = yazi_adapter::SvgInfo::new(&url).await {
|
||||
SvgInfo::from(info).into_lua(&lua)
|
||||
} else {
|
||||
Value::Nil.into_lua(&lua)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn set_svg_font_family(lua: &Lua) -> mlua::Result<Function> {
|
||||
lua.create_function(|_, (family, font_name): (mlua::String, mlua::String)| {
|
||||
let font = font_name.to_str()?;
|
||||
|
||||
Self::with_mut_fontdb(|db| {
|
||||
match family.to_str()?.as_ref() {
|
||||
"serif" => db.set_serif_family(&*font),
|
||||
"sans-serif" => db.set_sans_serif_family(&*font),
|
||||
"cursive" => db.set_cursive_family(&*font),
|
||||
"fantasy" => db.set_fantasy_family(&*font),
|
||||
"monospace" => db.set_monospace_family(&*font),
|
||||
_ => return Err(mlua::Error::external("Invalid font family")),
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn load_system_fonts(lua: &Lua) -> mlua::Result<Function> {
|
||||
lua.create_function(|_, ()| {
|
||||
Self::with_mut_fontdb(|db| {
|
||||
db.load_system_fonts();
|
||||
Ok(())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn load_font_file(lua: &Lua) -> mlua::Result<Function> {
|
||||
lua.create_function(|_, path: mlua::String| {
|
||||
Self::with_mut_fontdb(|db| {
|
||||
db.load_font_file(&*path.to_str()?)
|
||||
.map_err(|e| mlua::Error::external(format!("Failed to load font file: {e}")))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn load_fonts_dir(lua: &Lua) -> mlua::Result<Function> {
|
||||
lua.create_function(|_, dir: mlua::String| {
|
||||
Self::with_mut_fontdb(|db| {
|
||||
db.load_fonts_dir(&*dir.to_str()?);
|
||||
Ok(())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn with_mut_fontdb<F>(f: F) -> mlua::Result<()>
|
||||
where
|
||||
F: FnOnce(&mut Database) -> mlua::Result<()>,
|
||||
{
|
||||
let mut options = GLOBAL_OPTIONS
|
||||
.write()
|
||||
.map_err(|e| mlua::Error::external(format!("RwLock poisoned: {}", e)))?;
|
||||
f(Arc::make_mut(&mut options.fontdb))
|
||||
}
|
||||
}
|
||||
|
|
@ -46,13 +46,17 @@ pub fn compose(lua: &Lua, isolate: bool) -> mlua::Result<Value> {
|
|||
b"preview_code" => Utils::preview_code(lua)?,
|
||||
b"preview_widgets" => Utils::preview_widgets(lua)?,
|
||||
|
||||
// Process
|
||||
b"proc_info" => Utils::proc_info(lua)?,
|
||||
|
||||
// Spot
|
||||
b"spot_table" => Utils::spot_table(lua)?,
|
||||
b"spot_widgets" => Utils::spot_widgets(lua)?,
|
||||
|
||||
// Svg
|
||||
b"load_font_file" => Utils::load_font_file(lua)?,
|
||||
b"load_fonts_dir" => Utils::load_fonts_dir(lua)?,
|
||||
b"load_system_fonts" => Utils::load_system_fonts(lua)?,
|
||||
b"svg_info" => Utils::svg_info(lua)?,
|
||||
b"set_svg_font_family" => Utils::set_svg_font_family(lua)?,
|
||||
|
||||
// Sync
|
||||
b"sync" => Utils::sync(lua, isolate)?,
|
||||
b"chan" => Utils::chan(lua)?,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue