This commit is contained in:
JustForFun88 2025-04-30 03:10:25 +08:00 committed by GitHub
commit 2dadd80406
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 801 additions and 311 deletions

View file

@ -7,7 +7,6 @@ on:
branches: [main] branches: [main]
env: env:
SCCACHE_GHA_ENABLED: true
RUSTC_WRAPPER: sccache RUSTC_WRAPPER: sccache
jobs: jobs:

View file

@ -7,7 +7,6 @@ on:
branches: [main] branches: [main]
env: env:
SCCACHE_GHA_ENABLED: true
RUSTC_WRAPPER: sccache RUSTC_WRAPPER: sccache
CARGO_TERM_COLOR: always CARGO_TERM_COLOR: always

297
Cargo.lock generated
View file

@ -145,6 +145,12 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "arrayref"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
[[package]] [[package]]
name = "arrayvec" name = "arrayvec"
version = "0.7.6" version = "0.7.6"
@ -505,6 +511,15 @@ version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 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]] [[package]]
name = "crc32fast" name = "crc32fast"
version = "1.4.2" version = "1.4.2"
@ -645,6 +660,12 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "data-url"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a"
[[package]] [[package]]
name = "deranged" name = "deranged"
version = "0.4.0" version = "0.4.0"
@ -874,6 +895,12 @@ dependencies = [
"miniz_oxide", "miniz_oxide",
] ]
[[package]]
name = "float-cmp"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"
[[package]] [[package]]
name = "fnv" name = "fnv"
version = "1.0.7" version = "1.0.7"
@ -886,6 +913,29 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" 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]] [[package]]
name = "fsevent-sys" name = "fsevent-sys"
version = "4.1.0" version = "4.1.0"
@ -1134,6 +1184,12 @@ dependencies = [
"quick-error", "quick-error",
] ]
[[package]]
name = "imagesize"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285"
[[package]] [[package]]
name = "imgref" name = "imgref"
version = "1.11.0" version = "1.11.0"
@ -1271,6 +1327,16 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "kurbo"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89234b2cc610a7dd927ebde6b41dd1a5d4214cffaef4cf1fb2195d592f92518f"
dependencies = [
"arrayvec",
"smallvec",
]
[[package]] [[package]]
name = "lazy_static" name = "lazy_static"
version = "1.5.0" version = "1.5.0"
@ -1299,6 +1365,12 @@ dependencies = [
"cc", "cc",
] ]
[[package]]
name = "libm"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa"
[[package]] [[package]]
name = "libredox" name = "libredox"
version = "0.1.3" version = "0.1.3"
@ -1433,6 +1505,15 @@ version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "memmap2"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "minimal-lexical" name = "minimal-lexical"
version = "0.2.1" version = "0.2.1"
@ -1773,6 +1854,12 @@ version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "pico-args"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
[[package]] [[package]]
name = "pin-project-lite" name = "pin-project-lite"
version = "0.2.16" version = "0.2.16"
@ -2095,11 +2182,37 @@ version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 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]] [[package]]
name = "rgb" name = "rgb"
version = "0.8.50" version = "0.8.50"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a"
dependencies = [
"bytemuck",
]
[[package]]
name = "roxmltree"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97"
[[package]] [[package]]
name = "rustc-demangle" name = "rustc-demangle"
@ -2154,6 +2267,24 @@ version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" 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]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.20" version = "1.0.20"
@ -2310,6 +2441,21 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" 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]] [[package]]
name = "slab" name = "slab"
version = "0.4.9" version = "0.4.9"
@ -2319,6 +2465,15 @@ dependencies = [
"autocfg", "autocfg",
] ]
[[package]]
name = "slotmap"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a"
dependencies = [
"version_check",
]
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.15.0" version = "1.15.0"
@ -2347,6 +2502,15 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 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]] [[package]]
name = "strsim" name = "strsim"
version = "0.11.1" version = "0.11.1"
@ -2375,6 +2539,16 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "svgtypes"
version = "0.15.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68c7541fff44b35860c1a7a47a7cadf3e4a304c457b58f9870d9706ece028afc"
dependencies = [
"kurbo",
"siphasher",
]
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.101" version = "2.0.101"
@ -2540,6 +2714,47 @@ dependencies = [
"time-core", "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]] [[package]]
name = "tokio" name = "tokio"
version = "1.44.2" version = "1.44.2"
@ -2725,6 +2940,15 @@ dependencies = [
"windows", "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]] [[package]]
name = "twox-hash" name = "twox-hash"
version = "2.1.0" version = "2.1.0"
@ -2746,6 +2970,24 @@ version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" 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]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.18" version = "1.0.18"
@ -2758,6 +3000,18 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" 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]] [[package]]
name = "unicode-segmentation" name = "unicode-segmentation"
version = "1.12.0" version = "1.12.0"
@ -2775,6 +3029,12 @@ dependencies = [
"unicode-width 0.1.14", "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]] [[package]]
name = "unicode-width" name = "unicode-width"
version = "0.1.14" version = "0.1.14"
@ -2793,6 +3053,33 @@ version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" 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]] [[package]]
name = "utf8parse" name = "utf8parse"
version = "0.2.2" version = "0.2.2"
@ -3227,6 +3514,12 @@ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.0",
] ]
[[package]]
name = "xmlwriter"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
[[package]] [[package]]
name = "yazi-adapter" name = "yazi-adapter"
version = "25.4.8" version = "25.4.8"
@ -3238,6 +3531,7 @@ dependencies = [
"crossterm 0.29.0", "crossterm 0.29.0",
"image", "image",
"ratatui", "ratatui",
"resvg",
"scopeguard", "scopeguard",
"tokio", "tokio",
"tracing", "tracing",
@ -3475,12 +3769,12 @@ dependencies = [
"crossterm 0.29.0", "crossterm 0.29.0",
"futures", "futures",
"globset", "globset",
"libc",
"md-5", "md-5",
"mlua", "mlua",
"parking_lot", "parking_lot",
"paste", "paste",
"ratatui", "ratatui",
"resvg",
"serde_json", "serde_json",
"syntect", "syntect",
"tokio", "tokio",
@ -3490,7 +3784,6 @@ dependencies = [
"twox-hash", "twox-hash",
"unicode-width 0.2.0", "unicode-width 0.2.0",
"uzers", "uzers",
"windows-sys 0.59.0",
"yazi-adapter", "yazi-adapter",
"yazi-binding", "yazi-binding",
"yazi-boot", "yazi-boot",

View file

@ -41,6 +41,7 @@ parking_lot = "0.12.3"
paste = "1.0.15" paste = "1.0.15"
ratatui = { version = "0.29.0", features = [ "unstable-rendered-line-info" ] } ratatui = { version = "0.29.0", features = [ "unstable-rendered-line-info" ] }
regex = "1.11.1" regex = "1.11.1"
resvg = { version = "0.45.0" }
scopeguard = "1.2.0" scopeguard = "1.2.0"
serde = { version = "1.0.219", features = [ "derive" ] } serde = { version = "1.0.219", features = [ "derive" ] }
serde_json = "1.0.140" serde_json = "1.0.140"

View file

@ -21,6 +21,7 @@ color_quant = "1.1.0"
crossterm = { workspace = true } 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" ] } 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 } ratatui = { workspace = true }
resvg = { workspace = true }
scopeguard = { workspace = true } scopeguard = { workspace = true }
tokio = { workspace = true } tokio = { workspace = true }
tracing = { workspace = true } tracing = { workspace = true }

View file

@ -1,27 +1,24 @@
use std::path::{Path, PathBuf}; use std::{path::{Path, PathBuf}, sync::{LazyLock, RwLock}};
use anyhow::Result; 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 ratatui::layout::Rect;
use resvg::{tiny_skia::{Pixmap, Transform}, usvg::{Options, Tree}};
use yazi_config::YAZI; use yazi_config::YAZI;
use crate::Dimension; use crate::Dimension;
pub static GLOBAL_OPTIONS: LazyLock<RwLock<Options<'static>>> =
LazyLock::new(|| RwLock::new(Options::default()));
pub struct Image; pub struct Image;
impl Image { impl Image {
pub async fn precache(path: &Path, cache: PathBuf) -> Result<()> { pub async fn precache(path: &Path, cache: PathBuf) -> Result<()> {
let (mut img, orientation, icc) = Self::decode_from(path).await?; let (img, icc) =
let (w, h) = Self::flip_size(orientation, (YAZI.preview.max_width, YAZI.preview.max_height)); Self::decode_to_fit_from(path, YAZI.preview.max_width, YAZI.preview.max_height).await?;
let buf = tokio::task::spawn_blocking(move || { 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(); let mut buf = Vec::new();
if img.color().has_alpha() { if img.color().has_alpha() {
let rgba = img.into_rgba8(); let rgba = img.into_rgba8();
@ -42,25 +39,8 @@ impl Image {
} }
pub(super) async fn downscale(path: &Path, rect: Rect) -> Result<DynamicImage> { pub(super) async fn downscale(path: &Path, rect: Rect) -> Result<DynamicImage> {
let (mut img, orientation, _) = Self::decode_from(path).await?; let (width, height) = Self::max_pixel(rect);
let (w, h) = Self::flip_size(orientation, Self::max_pixel(rect)); let (img, _) = Self::decode_to_fit_from(path, width, height).await?;
// 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?;
Ok(img) 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(); let mut limits = Limits::no_limits();
if YAZI.tasks.image_alloc > 0 { if YAZI.tasks.image_alloc > 0 {
limits.max_alloc = Some(YAZI.tasks.image_alloc as u64); limits.max_alloc = Some(YAZI.tasks.image_alloc as u64);
@ -107,20 +134,76 @@ impl Image {
if YAZI.tasks.image_bound[1] > 0 { if YAZI.tasks.image_bound[1] > 0 {
limits.max_image_height = Some(YAZI.tasks.image_bound[1] as u32); limits.max_image_height = Some(YAZI.tasks.image_bound[1] as u32);
} }
limits
}
let path = path.to_owned(); #[inline]
tokio::task::spawn_blocking(move || { fn try_decode_svg(
let mut reader = ImageReader::open(path)?; path: &Path,
reader.limits(limits); 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()?; for rgba in container.chunks_exact_mut(4) {
let orientation = decoder.orientation().unwrap_or(Orientation::NoTransforms); let alpha = rgba[3];
let icc = decoder.icc_profile().unwrap_or_default(); 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)) let img = ImageBuffer::from_raw(width, height, container)
}) .ok_or_else(|| anyhow::anyhow!("Failed to create image buffer"))?;
.await
.map_err(|e| ImageError::IoError(e.into()))? 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) { fn flip_size(orientation: Orientation, (w, h): (u32, u32)) -> (u32, u32) {

View file

@ -1,6 +1,9 @@
use std::path::Path; use std::{collections::HashSet, path::Path};
use image::{ImageDecoder, ImageError}; use image::{ImageDecoder, ImageError};
use resvg::usvg::{Font, Group, ImageKind, Node, Tree};
use super::image::GLOBAL_OPTIONS;
pub type ImageFormat = image::ImageFormat; pub type ImageFormat = image::ImageFormat;
pub type ImageColor = image::ColorType; pub type ImageColor = image::ColorType;
@ -42,3 +45,77 @@ impl ImageInfo {
.map_err(|e| ImageError::IoError(e.into()))? .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);
});
}
}

View file

@ -35,6 +35,7 @@ mlua = { workspace = true }
parking_lot = { workspace = true } parking_lot = { workspace = true }
paste = { workspace = true } paste = { workspace = true }
ratatui = { workspace = true } ratatui = { workspace = true }
resvg = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }
syntect = { workspace = true } syntect = { workspace = true }
tokio = { workspace = true } tokio = { workspace = true }
@ -46,12 +47,10 @@ unicode-width = { workspace = true }
yazi-prebuilt = "0.1.0" yazi-prebuilt = "0.1.0"
[target."cfg(unix)".dependencies] [target."cfg(unix)".dependencies]
libc = { workspace = true }
uzers = { workspace = true } uzers = { workspace = true }
[target."cfg(windows)".dependencies] [target."cfg(windows)".dependencies]
clipboard-win = "5.4.0" 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] [target.'cfg(target_os = "macos")'.dependencies]
crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] } crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] }

View file

@ -1,71 +1,57 @@
local M = {} local M = {}
function M:peek(job) function M:peek(job)
local start, cache = os.clock(), ya.file_cache(job) local start = os.clock()
if not cache then
return
end
local ok, err = self:preload(job)
if not ok or err then
return
end
ya.sleep(math.max(0, rt.preview.image_delay / 1000 + 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, {}) ya.preview_widgets(job, {})
end end
function M:seek() end function M:seek() end
function M:preload(job) function M:preload(job) end
local cache = ya.file_cache(job)
if not cache or fs.cha(cache) then
return true
end
-- stylua: ignore function M:spot(job)
local cmd = Command("resvg"):args { local rows = self:spot_base(job)
"-w", rt.preview.max_width, "-h", rt.preview.max_height, rows[#rows + 1] = ui.Row {}
"--image-rendering", "optimizeSpeed",
tostring(job.file.url), tostring(cache)
}
if rt.tasks.image_alloc > 0 then
cmd = cmd:memory(rt.tasks.image_alloc)
end
local child, err = cmd:spawn() ya.spot_table(
if not child then job,
return true, Err("Failed to start `resvg`, error: %s", err) ui.Table(ya.list_merge(rows, require("file"):spot_base(job)))
end :area(ui.Pos { "center", w = 60, h = 20 })
:row(job.skip)
local status, err :row(1)
while true do :col(1)
ya.sleep(0.2) :col_style(th.spot.tbl_col)
:cell_style(th.spot.tbl_cell)
status, err = child:try_wait() :widths { ui.Constraint.Length(14), ui.Constraint.Fill(1) }
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
end 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 return M

View file

@ -1,6 +1,7 @@
use std::ops::Deref; use std::{fmt::Write, ops::Deref};
use mlua::{MetaMethod, UserData}; use mlua::{MetaMethod, UserData};
use resvg::usvg;
pub struct ImageInfo(yazi_adapter::ImageInfo); 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()
))
});
}
}

View file

@ -1,7 +1,7 @@
use std::{ops::DerefMut, process::ExitStatus, time::Duration}; use std::{ops::DerefMut, time::Duration};
use futures::future::try_join3; 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 tokio::{io::{self, AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader, BufWriter}, process::{ChildStderr, ChildStdin, ChildStdout}, select};
use yazi_binding::Error; use yazi_binding::Error;
@ -9,94 +9,39 @@ use super::Status;
use crate::process::Output; use crate::process::Output;
pub struct Child { pub struct Child {
inner: tokio::process::Child, inner: tokio::process::Child,
stdin: Option<BufWriter<ChildStdin>>, stdin: Option<BufWriter<ChildStdin>>,
stdout: Option<BufReader<ChildStdout>>, stdout: Option<BufReader<ChildStdout>>,
stderr: Option<BufReader<ChildStderr>>, 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) };
}
}
} }
impl Child { impl Child {
pub fn new( pub fn new(mut inner: tokio::process::Child) -> Self {
mut inner: tokio::process::Child,
#[cfg(windows)] job_handle: Option<std::os::windows::io::RawHandle>,
) -> Self {
let stdin = inner.stdin.take().map(BufWriter::new); let stdin = inner.stdin.take().map(BufWriter::new);
let stdout = inner.stdout.take().map(BufReader::new); let stdout = inner.stdout.take().map(BufReader::new);
let stderr = inner.stderr.take().map(BufReader::new); let stderr = inner.stderr.take().map(BufReader::new);
Self { Self { inner, stdin, stdout, stderr }
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 })
} }
} }
impl UserData for Child { impl UserData for Child {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) { fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
methods.add_method("id", |_, me, ()| Ok(me.inner.id())); #[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 { 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>> { 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 { 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), (Some(b), event) => (lua.create_string(b)?, event).into_lua_multi(&lua),
(None, event) => (Value::Nil, 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 { 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 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); return (Value::Nil, 3u8).into_lua_multi(&lua);
}; };
match result { match result {
@ -154,21 +98,38 @@ impl UserData for Child {
}); });
methods.add_async_method_mut("wait", |lua, mut me, ()| async move { 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), Ok(status) => (Status::new(status), Value::Nil).into_lua_multi(&lua),
Err(e) => (Value::Nil, Error::Io(e)).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 { methods.add_async_function("wait_with_output", |lua, ud: AnyUserData| async move {
match ud.take::<Self>()?.wait_with_output().await { async fn read_to_end(r: &mut Option<impl AsyncBufReadExt + Unpin>) -> io::Result<Vec<u8>> {
Ok(output) => (Output::new(output), Value::Nil).into_lua_multi(&lua), let mut vec = Vec::new();
Err(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua), 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 { let mut me = ud.take::<Self>()?;
match me.inner.try_wait() { let mut stdout_pipe = me.stdout.take();
Ok(Some(status)) => (Status::new(status), Value::Nil).into_lua_multi(&lua), let mut stderr_pipe = me.stderr.take();
Ok(None) => (Value::Nil, Value::Nil).into_lua_multi(&lua),
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), Err(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua),
} }
}); });

View file

@ -1,4 +1,4 @@
use std::{io, process::Stdio}; use std::process::Stdio;
use mlua::{AnyUserData, ExternalError, IntoLuaMulti, Lua, MetaMethod, Table, UserData, Value}; use mlua::{AnyUserData, ExternalError, IntoLuaMulti, Lua, MetaMethod, Table, UserData, Value};
use tokio::process::{ChildStderr, ChildStdin, ChildStdout}; use tokio::process::{ChildStderr, ChildStdin, ChildStdout};
@ -8,8 +8,7 @@ use super::{Child, output::Output};
use crate::process::Status; use crate::process::Status;
pub struct Command { pub struct Command {
inner: tokio::process::Command, inner: tokio::process::Command,
memory: Option<usize>,
} }
const NULL: u8 = 0; const NULL: u8 = 0;
@ -22,7 +21,7 @@ impl Command {
let mut inner = tokio::process::Command::new(program); let mut inner = tokio::process::Command::new(program);
inner.kill_on_drop(true).stdin(Stdio::null()).stdout(Stdio::null()).stderr(Stdio::null()); 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([ let command = lua.create_table_from([
@ -36,74 +35,6 @@ impl Command {
lua.globals().raw_set("Command", 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 { impl UserData for Command {
@ -171,22 +102,18 @@ impl UserData for Command {
ud.borrow_mut::<Self>()?.inner.stderr(make_stdio(stdio)?); ud.borrow_mut::<Self>()?.inner.stderr(make_stdio(stdio)?);
Ok(ud) Ok(ud)
}); });
methods.add_function_mut("memory", |_, (ud, max): (AnyUserData, usize)| { methods.add_method_mut("spawn", |lua, me, ()| match me.inner.spawn() {
ud.borrow_mut::<Self>()?.memory = Some(max); Ok(child) => (Child::new(child), Value::Nil).into_lua_multi(lua),
Ok(ud)
});
methods.add_method_mut("spawn", |lua, me, ()| match me.spawn() {
Ok(child) => (child, Value::Nil).into_lua_multi(lua),
Err(e) => (Value::Nil, Error::Io(e)).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 { 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), Ok(output) => (Output::new(output), Value::Nil).into_lua_multi(&lua),
Err(e) => (Value::Nil, Error::Io(e)).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 { 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), Ok(status) => (Status::new(status), Value::Nil).into_lua_multi(&lua),
Err(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua), Err(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua),
} }

View file

@ -1,5 +1,5 @@
#![allow(clippy::module_inception)] #![allow(clippy::module_inception)]
yazi_macro::mod_flat!( 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
); );

View file

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

View 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))
}
}

View file

@ -46,13 +46,17 @@ pub fn compose(lua: &Lua, isolate: bool) -> mlua::Result<Value> {
b"preview_code" => Utils::preview_code(lua)?, b"preview_code" => Utils::preview_code(lua)?,
b"preview_widgets" => Utils::preview_widgets(lua)?, b"preview_widgets" => Utils::preview_widgets(lua)?,
// Process
b"proc_info" => Utils::proc_info(lua)?,
// Spot // Spot
b"spot_table" => Utils::spot_table(lua)?, b"spot_table" => Utils::spot_table(lua)?,
b"spot_widgets" => Utils::spot_widgets(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 // Sync
b"sync" => Utils::sync(lua, isolate)?, b"sync" => Utils::sync(lua, isolate)?,
b"chan" => Utils::chan(lua)?, b"chan" => Utils::chan(lua)?,