feat: show error message when directory fails to load (#2527)

This commit is contained in:
三咲雅 · Misaki Masa 2025-03-24 23:05:42 +08:00 committed by GitHub
parent e138364102
commit 4bef557b7f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 58 additions and 36 deletions

25
Cargo.lock generated
View file

@ -1006,14 +1006,15 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "iana-time-zone"
version = "0.1.61"
version = "0.1.62"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
checksum = "b2fd658b06e56721792c5df4475705b6cda790e9298d19d2f8af083457bcd127"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"log",
"wasm-bindgen",
"windows-core 0.52.0",
]
@ -1257,9 +1258,9 @@ dependencies = [
[[package]]
name = "log"
version = "0.4.26"
version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]]
name = "loop9"
@ -2407,9 +2408,9 @@ dependencies = [
[[package]]
name = "time"
version = "0.3.40"
version = "0.3.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d9c75b47bdff86fa3334a3db91356b8d7d86a9b839dab7d0bdc5c3d3a077618"
checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40"
dependencies = [
"deranged",
"itoa",
@ -2430,9 +2431,9 @@ checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c"
[[package]]
name = "time-macros"
version = "0.2.21"
version = "0.2.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29aa485584182073ed57fd5004aa09c371f021325014694e432313345865fd04"
checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49"
dependencies = [
"num-conv",
"time-core",
@ -3390,18 +3391,18 @@ dependencies = [
[[package]]
name = "zerocopy"
version = "0.8.23"
version = "0.8.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6"
checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.23"
version = "0.8.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154"
checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be"
dependencies = [
"proc-macro2",
"quote",

View file

@ -86,8 +86,9 @@ impl Watcher {
async fn go(cwd: Url, cha: Cha) {
let Some(cha) = Files::assert_stale(&cwd, cha).await else { return };
if let Ok(files) = Files::from_dir_bulk(&cwd).await {
FilesOp::Full(cwd, files, cha).emit();
match Files::from_dir_bulk(&cwd).await {
Ok(files) => FilesOp::Full(cwd, files, cha).emit(),
Err(e) => FilesOp::issue_error(&cwd, e.kind()).await,
}
}

View file

@ -49,7 +49,11 @@ impl Preview {
self.folder_loader.take().map(|h| h.abort());
self.folder_loader = Some(tokio::spawn(async move {
let Some(new) = Files::assert_stale(&wd, dir.unwrap_or(Cha::dummy())).await else { return };
let Ok(rx) = Files::from_dir(&wd).await else { return };
let rx = match Files::from_dir(&wd).await {
Ok(rx) => rx,
Err(e) => return FilesOp::issue_error(&wd, e.kind()).await,
};
let stream =
UnboundedReceiverStream::new(rx).chunks_timeout(50000, Duration::from_millis(500));

View file

@ -1,6 +1,6 @@
use std::ops::{Deref, Range};
use mlua::{AnyUserData, Lua, UserData, UserDataFields};
use mlua::{AnyUserData, IntoLuaMulti, Lua, MetaMethod, UserData, UserDataFields, UserDataMethods};
use yazi_config::LAYOUT;
use yazi_plugin::url::Url;
@ -38,7 +38,13 @@ impl Folder {
pub(super) fn register(lua: &Lua) -> mlua::Result<()> {
lua.register_userdata_type::<yazi_fs::FolderStage>(|reg| {
reg.add_field_method_get("is_loading", |_, me| Ok(*me == yazi_fs::FolderStage::Loading));
use yazi_fs::FolderStage;
reg.add_meta_method(MetaMethod::Call, |lua, me, ()| match me {
FolderStage::Loading => false.into_lua_multi(lua),
FolderStage::Loaded => true.into_lua_multi(lua),
FolderStage::Failed(kind) => (true, yazi_plugin::Error::IoKind(*kind)).into_lua_multi(lua),
});
})?;
Ok(())

View file

@ -4,7 +4,7 @@ use tokio::{fs::{self, DirEntry}, select, sync::mpsc::{self, UnboundedReceiver}}
use yazi_shared::{Id, url::{Url, Urn, UrnBuf}};
use super::{FilesSorter, Filter};
use crate::{FILES_TICKET, File, FilesOp, SortBy, cha::Cha, maybe_exists, mounts::PARTITIONS};
use crate::{FILES_TICKET, File, FilesOp, SortBy, cha::Cha, mounts::PARTITIONS};
#[derive(Default)]
pub struct Files {
@ -81,20 +81,13 @@ impl Files {
)
}
pub async fn assert_stale(cwd: &Url, cha: Cha) -> Option<Cha> {
match fs::metadata(cwd).await.map(Cha::from) {
Ok(c) if !c.is_dir() => {
FilesOp::IOErr(cwd.clone(), std::io::ErrorKind::NotADirectory).emit();
}
pub async fn assert_stale(dir: &Url, cha: Cha) -> Option<Cha> {
use std::io::ErrorKind;
match fs::metadata(dir).await.map(Cha::from) {
Ok(c) if !c.is_dir() => FilesOp::issue_error(dir, ErrorKind::NotADirectory).await,
Ok(c) if c.hits(cha) && PARTITIONS.read().heuristic(cha) => {}
Ok(c) => return Some(c),
Err(e) => {
if maybe_exists(cwd).await {
FilesOp::IOErr(cwd.clone(), e.kind()).emit();
} else if let Some((p, n)) = cwd.pair() {
FilesOp::Deleting(p, HashSet::from_iter([n])).emit();
}
}
Err(e) => FilesOp::issue_error(dir, e.kind()).await,
}
None
}

View file

@ -3,7 +3,7 @@ use std::collections::{HashMap, HashSet};
use yazi_shared::{Id, Ids, event::Cmd, url::{Url, UrnBuf}};
use super::File;
use crate::cha::Cha;
use crate::{cha::Cha, maybe_exists};
pub static FILES_TICKET: Ids = Ids::new();
@ -118,6 +118,17 @@ impl FilesOp {
}
}
pub async fn issue_error(cwd: &Url, kind: std::io::ErrorKind) {
use std::io::ErrorKind;
if kind != ErrorKind::NotFound {
Self::IOErr(cwd.clone(), kind).emit();
} else if maybe_exists(cwd).await {
Self::IOErr(cwd.clone(), kind).emit();
} else if let Some((p, n)) = cwd.pair() {
Self::Deleting(p, HashSet::from_iter([n])).emit();
}
}
pub fn diff_recoverable(&self, contains: impl Fn(&Url) -> bool) -> (Vec<Url>, Vec<Url>) {
match self {
Self::Deleting(cwd, urns) => (urns.iter().map(|u| cwd.join(u)).collect(), vec![]),

View file

@ -11,15 +11,16 @@ function Current:new(area, tab)
end
function Current:empty()
local text
local s
if self._folder.files.filter then
text = ui.Text("No filter results")
s = "No filter results"
else
text = ui.Text(self._folder.stage.is_loading and "Loading..." or "No items")
local done, err = self._folder.stage()
s = not done and "Loading..." or not err and "No items" or string.format("Error: %s", err)
end
return {
text:area(self._area):align(ui.Text.CENTER),
ui.Text(s):area(self._area):align(ui.Text.CENTER),
}
end

View file

@ -14,8 +14,10 @@ function M:peek(job)
end
if #folder.files == 0 then
local done, err = folder.stage()
local s = not done and "Loading..." or not err and "No items" or string.format("Error: %s", err)
return ya.preview_widgets(job, {
ui.Text(folder.stage.is_loading and "Loading..." or "No items"):area(job.area):align(ui.Text.CENTER),
ui.Text(s):area(job.area):align(ui.Text.CENTER),
})
end

View file

@ -6,6 +6,7 @@ const EXPECTED: &str = "expected a Error";
pub enum Error {
Io(std::io::Error),
IoKind(std::io::ErrorKind),
Serde(serde_json::Error),
Custom(String),
}
@ -20,6 +21,7 @@ impl Error {
fn to_string(&self) -> Cow<str> {
match self {
Error::Io(e) => Cow::Owned(e.to_string()),
Error::IoKind(e) => Cow::Owned(e.to_string()),
Error::Serde(e) => Cow::Owned(e.to_string()),
Error::Custom(s) => Cow::Borrowed(s),
}
@ -30,6 +32,7 @@ impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::Io(e) => write!(f, "{e}"),
Error::IoKind(e) => write!(f, "{e}"),
Error::Serde(e) => write!(f, "{e}"),
Error::Custom(s) => write!(f, "{s}"),
}