feat: show IO errors for files in the UI

Resolves #1137.
This commit is contained in:
Lauri Niskanen 2024-07-10 00:57:02 +03:00
parent d6081fbe6f
commit 4d858ae1c2
No known key found for this signature in database
GPG key ID: 807DCEAC3AEB1848
7 changed files with 49 additions and 17 deletions

View file

@ -46,7 +46,11 @@ impl Deref for Files {
impl Files { impl Files {
pub async fn from_dir(url: &Url) -> std::io::Result<UnboundedReceiver<File>> { pub async fn from_dir(url: &Url) -> std::io::Result<UnboundedReceiver<File>> {
let mut it = fs::read_dir(url).await?; let mut it = fs::read_dir(url).await.map_err(|e| {
FilesOp::IOErr(url.clone(), e.kind(), e.to_string()).emit();
e
})?;
let (tx, rx) = mpsc::unbounded_channel(); let (tx, rx) = mpsc::unbounded_channel();
tokio::spawn(async move { tokio::spawn(async move {
@ -100,13 +104,18 @@ impl Files {
match fs::metadata(url).await { match fs::metadata(url).await {
Ok(m) if !m.is_dir() => { Ok(m) if !m.is_dir() => {
// FIXME: use `ErrorKind::NotADirectory` instead once it gets stabilized // FIXME: use `ErrorKind::NotADirectory` instead once it gets stabilized
FilesOp::IOErr(url.clone(), std::io::ErrorKind::AlreadyExists).emit(); FilesOp::IOErr(
url.clone(),
std::io::ErrorKind::AlreadyExists,
"Not a directory".to_string(),
)
.emit();
} }
Ok(m) if mtime == m.modified().ok() => {} Ok(m) if mtime == m.modified().ok() => {}
Ok(m) => return Some(m), Ok(m) => return Some(m),
Err(e) => { Err(e) => {
if maybe_exists(url).await { if maybe_exists(url).await {
FilesOp::IOErr(url.clone(), e.kind()).emit(); FilesOp::IOErr(url.clone(), e.kind(), e.to_string()).emit();
} else if let Some(p) = url.parent_url() { } else if let Some(p) = url.parent_url() {
FilesOp::Deleting(p, vec![url.clone()]).emit(); FilesOp::Deleting(p, vec![url.clone()]).emit();
} }

View file

@ -28,7 +28,7 @@ impl From<&Url> for Folder {
impl Folder { impl Folder {
pub fn update(&mut self, op: FilesOp) -> bool { pub fn update(&mut self, op: FilesOp) -> bool {
let (stage, revision) = (self.stage, self.files.revision); let (stage, revision) = (self.stage.clone(), self.files.revision);
match op { match op {
FilesOp::Full(_, _, mtime) => { FilesOp::Full(_, _, mtime) => {
(self.mtime, self.stage) = (mtime, FolderStage::Loaded); (self.mtime, self.stage) = (mtime, FolderStage::Loaded);
@ -39,8 +39,8 @@ impl Folder {
FilesOp::Done(_, mtime, ticket) if ticket == self.files.ticket() => { FilesOp::Done(_, mtime, ticket) if ticket == self.files.ticket() => {
(self.mtime, self.stage) = (mtime, FolderStage::Loaded); (self.mtime, self.stage) = (mtime, FolderStage::Loaded);
} }
FilesOp::IOErr(_, kind) => { FilesOp::IOErr(_, kind, ref msg) => {
(self.mtime, self.stage) = (None, FolderStage::Failed(kind)); (self.mtime, self.stage) = (None, FolderStage::Failed(kind, msg.clone()));
} }
_ => {} _ => {}
} }
@ -59,7 +59,7 @@ impl Folder {
} }
self.arrow(0); self.arrow(0);
(stage, revision) != (self.stage, self.files.revision) (stage, revision) != (self.stage.clone(), self.files.revision)
} }
pub fn arrow(&mut self, step: impl Into<Step>) -> bool { pub fn arrow(&mut self, step: impl Into<Step>) -> bool {

View file

@ -1,7 +1,7 @@
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] #[derive(Clone, Debug, Default, Eq, PartialEq)]
pub enum FolderStage { pub enum FolderStage {
#[default] #[default]
Loading, Loading,
Loaded, Loaded,
Failed(std::io::ErrorKind), Failed(std::io::ErrorKind, String),
} }

View file

@ -40,7 +40,7 @@ impl Folder {
lua.register_userdata_type::<Self>(|reg| { lua.register_userdata_type::<Self>(|reg| {
reg.add_field_method_get("cwd", |lua, me| Url::cast(lua, me.cwd.clone())); reg.add_field_method_get("cwd", |lua, me| Url::cast(lua, me.cwd.clone()));
reg.add_field_method_get("files", |_, me| Files::make(0..me.files.len(), me, me.tab())); reg.add_field_method_get("files", |_, me| Files::make(0..me.files.len(), me, me.tab()));
reg.add_field_method_get("stage", |lua, me| lua.create_any_userdata(me.stage)); reg.add_field_method_get("stage", |lua, me| lua.create_any_userdata(me.stage.clone()));
reg.add_field_method_get("window", |_, me| Files::make(me.window.clone(), me, me.tab())); reg.add_field_method_get("window", |_, me| Files::make(me.window.clone(), me, me.tab()));
reg.add_field_method_get("offset", |_, me| Ok(me.offset)); reg.add_field_method_get("offset", |_, me| Ok(me.offset));
@ -53,12 +53,23 @@ impl Folder {
lua.register_userdata_type::<yazi_core::folder::FolderStage>(|reg| { lua.register_userdata_type::<yazi_core::folder::FolderStage>(|reg| {
reg.add_meta_method(MetaMethod::ToString, |lua, me, ()| { reg.add_meta_method(MetaMethod::ToString, |lua, me, ()| {
use yazi_core::folder::FolderStage::{Failed, Loaded, Loading}; use yazi_core::folder::FolderStage::{Failed, Loaded, Loading};
lua.create_string(match me { lua.create_string(match me {
Loading => "loading", Loading => "loading",
Loaded => "loaded", Loaded => "loaded",
Failed(_) => "failed", Failed(..) => "failed",
}) })
}); });
reg.add_field_method_get("error", |lua, me| {
use yazi_core::folder::FolderStage::{Failed, Loaded, Loading};
match me {
Loading => Ok(None),
Loaded => Ok(None),
Failed(_, msg) => Some(lua.create_string(msg)).transpose(),
}
});
})?; })?;
Ok(()) Ok(())

View file

@ -14,8 +14,12 @@ function Current:empty()
local line local line
if self._folder.files.filter then if self._folder.files.filter then
line = ui.Line("No filter results") line = ui.Line("No filter results")
elseif tostring(self._folder.stage) == "loading" then
line = ui.Line("Loading...")
elseif tostring(self._folder.stage) == "failed" then
line = ui.Line(self._folder.stage.error)
else else
line = ui.Line(self._folder.stage == "loading" and "Loading..." or "No items") line = ui.Line("No items")
end end
return { return {

View file

@ -12,9 +12,17 @@ function M:peek()
end end
if #folder.files == 0 then if #folder.files == 0 then
local line
if tostring(folder.stage) == "loading" then
line = ui.Line("Loading...")
elseif tostring(folder.stage) == "failed" then
line = ui.Line(folder.stage.error)
else
line = ui.Line("No items")
end
return ya.preview_widgets(self, { return ya.preview_widgets(self, {
ui.Paragraph(self.area, { ui.Line(folder.stage == "loading" and "Loading..." or "No items") }) ui.Paragraph(self.area, { line }):align(ui.Paragraph.CENTER),
:align(ui.Paragraph.CENTER),
}) })
end end

View file

@ -11,7 +11,7 @@ pub enum FilesOp {
Part(Url, Vec<File>, u64), Part(Url, Vec<File>, u64),
Done(Url, Option<SystemTime>, u64), Done(Url, Option<SystemTime>, u64),
Size(Url, HashMap<Url, u64>), Size(Url, HashMap<Url, u64>),
IOErr(Url, std::io::ErrorKind), IOErr(Url, std::io::ErrorKind, String),
Creating(Url, Vec<File>), Creating(Url, Vec<File>),
Deleting(Url, Vec<Url>), Deleting(Url, Vec<Url>),
@ -27,7 +27,7 @@ impl FilesOp {
Self::Part(url, ..) => url, Self::Part(url, ..) => url,
Self::Done(url, ..) => url, Self::Done(url, ..) => url,
Self::Size(url, _) => url, Self::Size(url, _) => url,
Self::IOErr(url, _) => url, Self::IOErr(url, ..) => url,
Self::Creating(url, _) => url, Self::Creating(url, _) => url,
Self::Deleting(url, _) => url, Self::Deleting(url, _) => url,
@ -83,7 +83,7 @@ impl FilesOp {
Self::Part(_, files, ticket) => Self::Part(u, files!(files), *ticket), Self::Part(_, files, ticket) => Self::Part(u, files!(files), *ticket),
Self::Done(_, mtime, ticket) => Self::Done(u, *mtime, *ticket), Self::Done(_, mtime, ticket) => Self::Done(u, *mtime, *ticket),
Self::Size(_, map) => Self::Size(u, map.iter().map(|(k, v)| (new!(k), *v)).collect()), Self::Size(_, map) => Self::Size(u, map.iter().map(|(k, v)| (new!(k), *v)).collect()),
Self::IOErr(_, err) => Self::IOErr(u, *err), Self::IOErr(_, err, msg) => Self::IOErr(u, *err, msg.clone()),
Self::Creating(_, files) => Self::Creating(u, files!(files)), Self::Creating(_, files) => Self::Creating(u, files!(files)),
Self::Deleting(_, urls) => Self::Deleting(u, urls.iter().map(|u| new!(u)).collect()), Self::Deleting(_, urls) => Self::Deleting(u, urls.iter().map(|u| new!(u)).collect()),