feat: fill in error messages if preview fails (#2917)

This commit is contained in:
三咲雅 misaki masa 2025-06-24 16:47:39 +08:00 committed by GitHub
parent e218f1d8bb
commit 590d336716
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 77 additions and 29 deletions

View file

@ -1,4 +1,4 @@
use std::path::{Path, PathBuf};
use std::path::Path;
use anyhow::Result;
use image::{DynamicImage, ExtendedColorType, ImageDecoder, ImageEncoder, ImageError, ImageReader, ImageResult, Limits, codecs::{jpeg::JpegEncoder, png::PngEncoder}, imageops::FilterType, metadata::Orientation};
@ -10,7 +10,7 @@ use crate::Dimension;
pub struct Image;
impl Image {
pub async fn precache(path: &Path, cache: PathBuf) -> Result<()> {
pub async fn precache(path: &Path, cache: &Path) -> 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));

View file

@ -10,13 +10,13 @@ function M:peek(job)
local ok, err = self:preload(job)
if not ok or err then
return
return ya.preview_widget(job, err)
end
ya.sleep(math.max(0, rt.preview.image_delay / 1000 + start - os.clock()))
local _, err = ya.image_show(cache, job.area)
ya.preview_widget(job, err and ui.Text(err):area(job.area):wrap(ui.Wrap.YES))
ya.preview_widget(job, err)
end
function M:seek() end
@ -45,10 +45,12 @@ function M:preload(job)
"JPG:" .. tostring(cache),
}):status()
if status then
return status.success
else
if not status then
return true, Err("Failed to start `magick`, error: %s", err)
elseif not status.success then
return false, Err("`magick` exited with error code: %s", status.code)
else
return true
end
end

View file

@ -8,13 +8,13 @@ function M:peek(job)
local ok, err = self:preload(job)
if not ok or err then
return
return ya.preview_widget(job, err)
end
ya.sleep(math.max(0, rt.preview.image_delay / 1000 + start - os.clock()))
local _, err = ya.image_show(cache, job.area)
ya.preview_widget(job, err and ui.Text(err):area(job.area):wrap(ui.Wrap.YES))
ya.preview_widget(job, err)
end
function M:seek() end
@ -38,10 +38,12 @@ function M:preload(job)
string.format("JPG:%s", cache),
}:status()
if status then
return status.success
else
if not status then
return true, Err("Failed to start `magick`, error: %s", err)
elseif not status.success then
return false, Err("`magick` exited with error code: %s", status.code)
else
return true
end
end

View file

@ -8,13 +8,13 @@ function M:peek(job)
local ok, err = self:preload(job)
if not ok or err then
return
return ya.preview_widget(job, err)
end
ya.sleep(math.max(0, rt.preview.image_delay / 1000 + start - os.clock()))
local _, err = ya.image_show(cache, job.area)
ya.preview_widget(job, err and ui.Text(err):area(job.area):wrap(ui.Wrap.YES))
ya.preview_widget(job, err)
end
function M:seek(job)

View file

@ -8,13 +8,13 @@ function M:peek(job)
local ok, err = self:preload(job)
if not ok or err then
return
return ya.preview_widget(job, err)
end
ya.sleep(math.max(0, rt.preview.image_delay / 1000 + start - os.clock()))
local _, err = ya.image_show(cache, job.area)
ya.preview_widget(job, err and ui.Text(err):area(job.area):wrap(ui.Wrap.YES))
ya.preview_widget(job, err)
end
function M:seek() end
@ -60,10 +60,12 @@ function M:preload(job)
end
end
if status then
return status.success
else
if not status then
return true, Err("Error while running `resvg`: %s", err)
elseif not status.success then
return false, Err("`resvg` exited with error code: %s", status.code)
else
return true
end
end

View file

@ -8,13 +8,13 @@ function M:peek(job)
local ok, err = self:preload(job)
if not ok or err then
return
return ya.preview_widget(job, err)
end
ya.sleep(math.max(0, rt.preview.image_delay / 1000 + start - os.clock()))
local _, err = ya.image_show(cache, job.area)
ya.preview_widget(job, err and ui.Text(err):area(job.area):wrap(ui.Wrap.YES))
ya.preview_widget(job, err)
end
function M:seek(job)
@ -76,10 +76,12 @@ function M:preload(job)
"-y", tostring(cache),
}):status()
if status then
return status.success
else
if not status then
return true, Err("Failed to start `ffmpeg`, error: %s", err)
elseif not status.success then
return false, Err("`ffmpeg` exited with error code: %s", status.code)
else
return true
end
end

View file

@ -50,6 +50,10 @@ impl Area {
}
}
impl From<Rect> for Area {
fn from(rect: Rect) -> Self { Self::Rect(rect) }
}
impl TryFrom<AnyUserData> for Area {
type Error = mlua::Error;

View file

@ -1,8 +1,10 @@
use std::any::TypeId;
use mlua::{AnyUserData, ExternalError};
use yazi_binding::Error;
use super::{Bar, Border, Clear, Gauge, Line, List, Table, Text};
use crate::elements::Rect;
#[derive(Clone, Debug)]
pub enum Renderable {
@ -35,10 +37,10 @@ impl Renderable {
}
}
impl TryFrom<AnyUserData> for Renderable {
impl TryFrom<&AnyUserData> for Renderable {
type Error = mlua::Error;
fn try_from(ud: AnyUserData) -> Result<Self, Self::Error> {
fn try_from(ud: &AnyUserData) -> Result<Self, Self::Error> {
Ok(match ud.type_id() {
Some(t) if t == TypeId::of::<Line>() => Self::Line(ud.take()?),
Some(t) if t == TypeId::of::<Text>() => Self::Text(ud.take()?),
@ -52,3 +54,20 @@ impl TryFrom<AnyUserData> for Renderable {
})
}
}
impl TryFrom<AnyUserData> for Renderable {
type Error = mlua::Error;
fn try_from(ud: AnyUserData) -> Result<Self, Self::Error> { Self::try_from(&ud) }
}
impl From<(Rect, Error)> for Renderable {
fn from((area, error): (Rect, Error)) -> Self {
Self::Text(Text {
area: area.into(),
inner: error.into_string().into(),
wrap: ratatui::widgets::Wrap { trim: false }.into(),
..Default::default()
})
}
}

View file

@ -18,6 +18,10 @@ impl Wrap {
}
}
impl From<ratatui::widgets::Wrap> for Wrap {
fn from(value: ratatui::widgets::Wrap) -> Self { Self(Some(value)) }
}
impl From<PreviewWrap> for Wrap {
fn from(value: PreviewWrap) -> Self {
Self(match value {

View file

@ -25,8 +25,11 @@ impl Utils {
}
pub(super) fn image_precache(lua: &Lua) -> mlua::Result<Function> {
lua.create_async_function(|_, (src, dist): (UrlRef, UrlRef)| async move {
Ok(Image::precache(&src, dist.to_path_buf()).await.is_ok())
lua.create_async_function(|lua, (src, dist): (UrlRef, UrlRef)| async move {
match Image::precache(&src, &dist).await {
Ok(()) => true.into_lua_multi(&lua),
Err(e) => (false, Error::Custom(e.to_string().into())).into_lua_multi(&lua),
}
})
}
}

View file

@ -1,4 +1,5 @@
use mlua::{AnyUserData, ExternalError, Function, IntoLuaMulti, Lua, Table, Value};
use yazi_binding::Error;
use yazi_config::YAZI;
use yazi_macro::emit;
use yazi_shared::{errors::PeekError, event::Cmd};
@ -69,7 +70,16 @@ impl Utils {
.sequence_values::<AnyUserData>()
.map(|ud| ud.and_then(Renderable::try_from))
.collect::<mlua::Result<_>>()?,
Value::UserData(ud) => vec![Renderable::try_from(ud)?],
Value::UserData(ud) => match Renderable::try_from(&ud) {
Ok(r) => vec![r],
Err(e) => {
if let Ok(err) = ud.take::<Error>() {
vec![(lock.area, err).into()]
} else {
Err(e)?
}
}
},
_ => Err("preview widget must be a renderable element or a table of them".into_lua_err())?,
};