mirror of
https://github.com/sxyazi/yazi.git
synced 2026-05-13 08:16:40 +00:00
feat: new null plugin as a general-purpose error handler (#3387)
This commit is contained in:
parent
76196aab70
commit
0fb652d2ec
7 changed files with 211 additions and 130 deletions
|
|
@ -14,9 +14,20 @@ pub enum Error {
|
|||
|
||||
impl Error {
|
||||
pub fn install(lua: &Lua) -> mlua::Result<()> {
|
||||
let new = lua.create_function(|_, msg: String| Ok(Self::custom(msg)))?;
|
||||
let custom = lua.create_function(|_, msg: String| Ok(Self::custom(msg)))?;
|
||||
|
||||
lua.globals().raw_set("Error", lua.create_table_from([("custom", new)])?)
|
||||
let fs = lua.create_function(|_, value: Value| {
|
||||
Ok(Self::Fs(match value {
|
||||
Value::Table(t) => yazi_fs::error::Error::custom(
|
||||
&t.raw_get::<mlua::String>("kind")?.to_str()?,
|
||||
t.raw_get("code")?,
|
||||
&t.raw_get::<mlua::String>("message")?.to_str()?,
|
||||
)?,
|
||||
_ => Err("expected a table".into_lua_err())?,
|
||||
}))
|
||||
})?;
|
||||
|
||||
lua.globals().raw_set("Error", lua.create_table_from([("custom", custom), ("fs", fs)])?)
|
||||
}
|
||||
|
||||
pub fn custom(msg: impl Into<SStr>) -> Self { Self::Custom(msg.into()) }
|
||||
|
|
@ -60,6 +71,13 @@ impl UserData for Error {
|
|||
_ => None,
|
||||
})
|
||||
});
|
||||
fields.add_field_method_get("kind", |_, me| {
|
||||
Ok(match me {
|
||||
Self::Io(e) => Some(yazi_fs::error::Error::from(e.kind()).kind_str()),
|
||||
Self::Fs(e) => Some(e.kind_str()),
|
||||
_ => None,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
||||
|
|
|
|||
|
|
@ -114,6 +114,8 @@ spotters = [
|
|||
{ mime = "video/*", run = "video" },
|
||||
# Virtual file system
|
||||
{ mime = "vfs/*", run = "vfs" },
|
||||
# Error
|
||||
{ mime = "null/*", run = "null" },
|
||||
# Fallback
|
||||
{ url = "*", run = "file" },
|
||||
]
|
||||
|
|
@ -160,6 +162,8 @@ previewers = [
|
|||
{ mime = "inode/empty", run = "empty" },
|
||||
# Virtual file system
|
||||
{ mime = "vfs/*", run = "vfs" },
|
||||
# Error
|
||||
{ mime = "null/*", run = "null" },
|
||||
# Fallback
|
||||
{ url = "*", run = "file" },
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
use std::{fmt, io, sync::Arc};
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::error::{kind_from_str, kind_to_str};
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum Error {
|
||||
Kind(io::ErrorKind),
|
||||
|
|
@ -38,6 +42,10 @@ impl fmt::Display for Error {
|
|||
}
|
||||
|
||||
impl Error {
|
||||
pub fn custom(kind: &str, code: Option<i32>, message: &str) -> Result<Self> {
|
||||
Ok(Self::Custom { kind: kind_from_str(kind)?, code, message: message.into() })
|
||||
}
|
||||
|
||||
pub fn kind(&self) -> io::ErrorKind {
|
||||
match self {
|
||||
Self::Kind(kind) => *kind,
|
||||
|
|
@ -46,6 +54,8 @@ impl Error {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn kind_str(&self) -> &'static str { kind_to_str(self.kind()) }
|
||||
|
||||
pub fn raw_os_error(&self) -> Option<i32> {
|
||||
match self {
|
||||
Self::Kind(_) => None,
|
||||
|
|
|
|||
|
|
@ -1,103 +1,104 @@
|
|||
use std::io;
|
||||
|
||||
use anyhow::{Result, bail};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
use super::Error;
|
||||
|
||||
fn kind_to_str(kind: io::ErrorKind) -> &'static str {
|
||||
pub(super) fn kind_to_str(kind: io::ErrorKind) -> &'static str {
|
||||
use std::io::ErrorKind as K;
|
||||
match kind {
|
||||
K::NotFound => "not_found",
|
||||
K::PermissionDenied => "permission_denied",
|
||||
K::ConnectionRefused => "connection_refused",
|
||||
K::ConnectionReset => "connection_reset",
|
||||
K::HostUnreachable => "host_unreachable",
|
||||
K::NetworkUnreachable => "network_unreachable",
|
||||
K::ConnectionAborted => "connection_aborted",
|
||||
K::NotConnected => "not_connected",
|
||||
K::AddrInUse => "addr_in_use",
|
||||
K::AddrNotAvailable => "addr_not_available",
|
||||
K::NetworkDown => "network_down",
|
||||
K::BrokenPipe => "broken_pipe",
|
||||
K::AlreadyExists => "already_exists",
|
||||
K::WouldBlock => "would_block",
|
||||
K::NotADirectory => "not_a_directory",
|
||||
K::IsADirectory => "is_a_directory",
|
||||
K::DirectoryNotEmpty => "directory_not_empty",
|
||||
K::ReadOnlyFilesystem => "read_only_filesystem",
|
||||
// K::FilesystemLoop => "filesystem_loop",
|
||||
K::StaleNetworkFileHandle => "stale_network_file_handle",
|
||||
K::InvalidInput => "invalid_input",
|
||||
K::InvalidData => "invalid_data",
|
||||
K::TimedOut => "timed_out",
|
||||
K::WriteZero => "write_zero",
|
||||
K::StorageFull => "storage_full",
|
||||
K::NotSeekable => "not_seekable",
|
||||
K::QuotaExceeded => "quota_exceeded",
|
||||
K::FileTooLarge => "file_too_large",
|
||||
K::ResourceBusy => "resource_busy",
|
||||
K::ExecutableFileBusy => "executable_file_busy",
|
||||
K::Deadlock => "deadlock",
|
||||
K::CrossesDevices => "crosses_devices",
|
||||
K::TooManyLinks => "too_many_links",
|
||||
K::InvalidFilename => "invalid_filename",
|
||||
K::ArgumentListTooLong => "argument_list_too_long",
|
||||
K::Interrupted => "interrupted",
|
||||
K::Unsupported => "unsupported",
|
||||
K::UnexpectedEof => "unexpected_eof",
|
||||
K::OutOfMemory => "out_of_memory",
|
||||
// K::InProgress => "in_progress",
|
||||
K::Other => "other",
|
||||
_ => "other",
|
||||
K::NotFound => "NotFound",
|
||||
K::PermissionDenied => "PermissionDenied",
|
||||
K::ConnectionRefused => "ConnectionRefused",
|
||||
K::ConnectionReset => "ConnectionReset",
|
||||
K::HostUnreachable => "HostUnreachable",
|
||||
K::NetworkUnreachable => "NetworkUnreachable",
|
||||
K::ConnectionAborted => "ConnectionAborted",
|
||||
K::NotConnected => "NotConnected",
|
||||
K::AddrInUse => "AddrInUse",
|
||||
K::AddrNotAvailable => "AddrNotAvailable",
|
||||
K::NetworkDown => "NetworkDown",
|
||||
K::BrokenPipe => "BrokenPipe",
|
||||
K::AlreadyExists => "AlreadyExists",
|
||||
K::WouldBlock => "WouldBlock",
|
||||
K::NotADirectory => "NotADirectory",
|
||||
K::IsADirectory => "IsADirectory",
|
||||
K::DirectoryNotEmpty => "DirectoryNotEmpty",
|
||||
K::ReadOnlyFilesystem => "ReadOnlyFilesystem",
|
||||
// K::FilesystemLoop => "FilesystemLoop",
|
||||
K::StaleNetworkFileHandle => "StaleNetworkFileHandle",
|
||||
K::InvalidInput => "InvalidInput",
|
||||
K::InvalidData => "InvalidData",
|
||||
K::TimedOut => "TimedOut",
|
||||
K::WriteZero => "WriteZero",
|
||||
K::StorageFull => "StorageFull",
|
||||
K::NotSeekable => "NotSeekable",
|
||||
K::QuotaExceeded => "QuotaExceeded",
|
||||
K::FileTooLarge => "FileTooLarge",
|
||||
K::ResourceBusy => "ResourceBusy",
|
||||
K::ExecutableFileBusy => "ExecutableFileBusy",
|
||||
K::Deadlock => "Deadlock",
|
||||
K::CrossesDevices => "CrossesDevices",
|
||||
K::TooManyLinks => "TooManyLinks",
|
||||
K::InvalidFilename => "InvalidFilename",
|
||||
K::ArgumentListTooLong => "ArgumentListTooLong",
|
||||
K::Interrupted => "Interrupted",
|
||||
K::Unsupported => "Unsupported",
|
||||
K::UnexpectedEof => "UnexpectedEof",
|
||||
K::OutOfMemory => "OutOfMemory",
|
||||
// K::InProgress => "InProgress",
|
||||
K::Other => "Other",
|
||||
_ => "Other",
|
||||
}
|
||||
}
|
||||
|
||||
fn kind_from_str(s: &str) -> io::ErrorKind {
|
||||
pub(super) fn kind_from_str(s: &str) -> Result<io::ErrorKind> {
|
||||
use std::io::ErrorKind as K;
|
||||
match s {
|
||||
"not_found" => K::NotFound,
|
||||
"permission_denied" => K::PermissionDenied,
|
||||
"connection_refused" => K::ConnectionRefused,
|
||||
"connection_reset" => K::ConnectionReset,
|
||||
"host_unreachable" => K::HostUnreachable,
|
||||
"network_unreachable" => K::NetworkUnreachable,
|
||||
"connection_aborted" => K::ConnectionAborted,
|
||||
"not_connected" => K::NotConnected,
|
||||
"addr_in_use" => K::AddrInUse,
|
||||
"addr_not_available" => K::AddrNotAvailable,
|
||||
"network_down" => K::NetworkDown,
|
||||
"broken_pipe" => K::BrokenPipe,
|
||||
"already_exists" => K::AlreadyExists,
|
||||
"would_block" => K::WouldBlock,
|
||||
"not_a_directory" => K::NotADirectory,
|
||||
"is_a_directory" => K::IsADirectory,
|
||||
"directory_not_empty" => K::DirectoryNotEmpty,
|
||||
"read_only_filesystem" => K::ReadOnlyFilesystem,
|
||||
// "filesystem_loop" => K::FilesystemLoop,
|
||||
"stale_network_file_handle" => K::StaleNetworkFileHandle,
|
||||
"invalid_input" => K::InvalidInput,
|
||||
"invalid_data" => K::InvalidData,
|
||||
"timed_out" => K::TimedOut,
|
||||
"write_zero" => K::WriteZero,
|
||||
"storage_full" => K::StorageFull,
|
||||
"not_seekable" => K::NotSeekable,
|
||||
"quota_exceeded" => K::QuotaExceeded,
|
||||
"file_too_large" => K::FileTooLarge,
|
||||
"resource_busy" => K::ResourceBusy,
|
||||
"executable_file_busy" => K::ExecutableFileBusy,
|
||||
"deadlock" => K::Deadlock,
|
||||
"crosses_devices" => K::CrossesDevices,
|
||||
"too_many_links" => K::TooManyLinks,
|
||||
"invalid_filename" => K::InvalidFilename,
|
||||
"argument_list_too_long" => K::ArgumentListTooLong,
|
||||
"interrupted" => K::Interrupted,
|
||||
"unsupported" => K::Unsupported,
|
||||
"unexpected_eof" => K::UnexpectedEof,
|
||||
"out_of_memory" => K::OutOfMemory,
|
||||
// "in_progress" => K::InProgress,
|
||||
"other" => K::Other,
|
||||
_ => K::Other,
|
||||
}
|
||||
Ok(match s {
|
||||
"NotFound" => K::NotFound,
|
||||
"PermissionDenied" => K::PermissionDenied,
|
||||
"ConnectionRefused" => K::ConnectionRefused,
|
||||
"ConnectionReset" => K::ConnectionReset,
|
||||
"HostUnreachable" => K::HostUnreachable,
|
||||
"NetworkUnreachable" => K::NetworkUnreachable,
|
||||
"ConnectionAborted" => K::ConnectionAborted,
|
||||
"NotConnected" => K::NotConnected,
|
||||
"AddrInUse" => K::AddrInUse,
|
||||
"AddrNotAvailable" => K::AddrNotAvailable,
|
||||
"NetworkDown" => K::NetworkDown,
|
||||
"BrokenPipe" => K::BrokenPipe,
|
||||
"AlreadyExists" => K::AlreadyExists,
|
||||
"WouldBlock" => K::WouldBlock,
|
||||
"NotADirectory" => K::NotADirectory,
|
||||
"IsADirectory" => K::IsADirectory,
|
||||
"DirectoryNotEmpty" => K::DirectoryNotEmpty,
|
||||
"ReadOnlyFilesystem" => K::ReadOnlyFilesystem,
|
||||
// "FilesystemLoop" => K::FilesystemLoop,
|
||||
"StaleNetworkFileHandle" => K::StaleNetworkFileHandle,
|
||||
"InvalidInput" => K::InvalidInput,
|
||||
"InvalidData" => K::InvalidData,
|
||||
"TimedOut" => K::TimedOut,
|
||||
"WriteZero" => K::WriteZero,
|
||||
"StorageFull" => K::StorageFull,
|
||||
"NotSeekable" => K::NotSeekable,
|
||||
"QuotaExceeded" => K::QuotaExceeded,
|
||||
"FileTooLarge" => K::FileTooLarge,
|
||||
"ResourceBusy" => K::ResourceBusy,
|
||||
"ExecutableFileBusy" => K::ExecutableFileBusy,
|
||||
"Deadlock" => K::Deadlock,
|
||||
"CrossesDevices" => K::CrossesDevices,
|
||||
"TooManyLinks" => K::TooManyLinks,
|
||||
"InvalidFilename" => K::InvalidFilename,
|
||||
"ArgumentListTooLong" => K::ArgumentListTooLong,
|
||||
"Interrupted" => K::Interrupted,
|
||||
"Unsupported" => K::Unsupported,
|
||||
"UnexpectedEof" => K::UnexpectedEof,
|
||||
"OutOfMemory" => K::OutOfMemory,
|
||||
// "InProgress" => K::InProgress,
|
||||
"Other" => K::Other,
|
||||
_ => bail!("unknown error kind: {s}"),
|
||||
})
|
||||
}
|
||||
|
||||
impl Serialize for Error {
|
||||
|
|
@ -138,15 +139,19 @@ impl<'de> Deserialize<'de> for Error {
|
|||
|
||||
let shadow = Shadow::deserialize(deserializer)?;
|
||||
Ok(match shadow {
|
||||
Shadow::Kind { kind } => Self::Kind(kind_from_str(&kind)),
|
||||
Shadow::Kind { kind } => Self::Kind(kind_from_str(&kind).map_err(serde::de::Error::custom)?),
|
||||
Shadow::Raw { code } => Self::Raw(code),
|
||||
Shadow::Dyn { kind, code, message } => {
|
||||
if !message.is_empty() {
|
||||
Self::Custom { kind: kind_from_str(&kind), code, message: message.into() }
|
||||
Self::Custom {
|
||||
kind: kind_from_str(&kind).map_err(serde::de::Error::custom)?,
|
||||
code,
|
||||
message: message.into(),
|
||||
}
|
||||
} else if let Some(code) = code {
|
||||
Self::Raw(code)
|
||||
} else {
|
||||
Self::Kind(kind_from_str(&kind))
|
||||
Self::Kind(kind_from_str(&kind).map_err(serde::de::Error::custom)?)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -3,41 +3,6 @@ local TYPE_PATS = { "text", "image", "video", "application", "audio", "font", "i
|
|||
|
||||
local M = {}
|
||||
|
||||
local function match_mimetype(line)
|
||||
for _, pat in ipairs(TYPE_PATS) do
|
||||
local typ, sub = line:match(string.format("(%s/)([+-.a-zA-Z0-9]+)%%s+$", pat))
|
||||
if not sub then
|
||||
elseif line:find(typ .. sub, 1, true) == 1 then
|
||||
return typ:gsub("^x%-", "", 1) .. sub:gsub("^x%-", "", 1):gsub("^vnd%.", "", 1)
|
||||
else
|
||||
return nil, true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function spawn_file1(paths)
|
||||
local bin = os.getenv("YAZI_FILE_ONE") or "file"
|
||||
local windows = ya.target_family() == "windows"
|
||||
|
||||
local cmd = Command(bin):arg({ "-bL", "--mime-type" }):stdout(Command.PIPED)
|
||||
if windows then
|
||||
cmd:arg({ "-f", "-" }):stdin(Command.PIPED)
|
||||
else
|
||||
cmd:arg("--"):arg(paths)
|
||||
end
|
||||
|
||||
local child, err = cmd:spawn()
|
||||
if not child then
|
||||
return nil, Err("Failed to start `%s`, error: %s", bin, err)
|
||||
elseif windows then
|
||||
child:write_all(table.concat(paths, "\n"))
|
||||
child:flush()
|
||||
ya.drop(child:take_stdin())
|
||||
end
|
||||
|
||||
return child
|
||||
end
|
||||
|
||||
function M:fetch(job)
|
||||
local urls, paths = {}, {}
|
||||
for i, file in ipairs(job.files) do
|
||||
|
|
@ -48,8 +13,9 @@ function M:fetch(job)
|
|||
end
|
||||
end
|
||||
|
||||
local child, err = spawn_file1(paths)
|
||||
local child, err = M.spawn_file1(paths)
|
||||
if not child then
|
||||
M.placeholder(err, urls, paths)
|
||||
return true, err
|
||||
end
|
||||
|
||||
|
|
@ -74,7 +40,7 @@ function M:fetch(job)
|
|||
break
|
||||
end
|
||||
|
||||
match, ignore = match_mimetype(line)
|
||||
match, ignore = M.match_mimetype(line)
|
||||
if match then
|
||||
updates[urls[i] or paths[i]], state[i], i = match, true, i + 1
|
||||
flush(false)
|
||||
|
|
@ -88,4 +54,59 @@ function M:fetch(job)
|
|||
return state
|
||||
end
|
||||
|
||||
function M.match_mimetype(line)
|
||||
for _, pat in ipairs(TYPE_PATS) do
|
||||
local typ, sub = line:match(string.format("(%s/)([+-.a-zA-Z0-9]+)%%s+$", pat))
|
||||
if not sub then
|
||||
elseif line:find(typ .. sub, 1, true) == 1 then
|
||||
return typ:gsub("^x%-", "", 1) .. sub:gsub("^x%-", "", 1):gsub("^vnd%.", "", 1)
|
||||
else
|
||||
return nil, true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function M.file1_bin() return os.getenv("YAZI_FILE_ONE") or "file" end
|
||||
|
||||
function M.spawn_file1(paths)
|
||||
local bin = M.file1_bin()
|
||||
local windows = ya.target_family() == "windows"
|
||||
|
||||
local cmd = Command(bin):arg({ "-bL", "--mime-type" }):stdout(Command.PIPED)
|
||||
if windows then
|
||||
cmd:arg({ "-f", "-" }):stdin(Command.PIPED)
|
||||
else
|
||||
cmd:arg("--"):arg(paths)
|
||||
end
|
||||
|
||||
local child, err = cmd:spawn()
|
||||
if not child then
|
||||
local e = Error.fs {
|
||||
kind = err.kind or "Other",
|
||||
code = err.code,
|
||||
message = string.format("Failed to start `%s`, error: %s", bin, err),
|
||||
}
|
||||
return nil, e
|
||||
elseif windows then
|
||||
child:write_all(table.concat(paths, "\n"))
|
||||
child:flush()
|
||||
ya.drop(child:take_stdin())
|
||||
end
|
||||
|
||||
return child
|
||||
end
|
||||
|
||||
function M.placeholder(err, urls, paths)
|
||||
if err.kind ~= "NotFound" then
|
||||
return
|
||||
end
|
||||
|
||||
local updates = {}
|
||||
for i = 1, #paths do
|
||||
updates[urls[i] or paths[i]] = "null/file1-not-found"
|
||||
end
|
||||
|
||||
ya.emit("update_mimes", { updates = updates })
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
|
|||
22
yazi-plugin/preset/plugins/null.lua
Normal file
22
yazi-plugin/preset/plugins/null.lua
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
local M = {}
|
||||
|
||||
function M:peek(job)
|
||||
local err
|
||||
if job.mime == "null/file1-not-found" then
|
||||
err = string.format(
|
||||
"Cannot find `%s` to detect the file's MIME type. Make sure it's installed and restart Yazi",
|
||||
require("mime.local").file1_bin()
|
||||
)
|
||||
else
|
||||
err = "Unknown error occurred while detecting MIME type"
|
||||
end
|
||||
|
||||
local line = ui.Line(err):reverse()
|
||||
ya.preview_widget(job, ui.Text(line):area(job.area):wrap(ui.Wrap.YES))
|
||||
end
|
||||
|
||||
function M:seek() end
|
||||
|
||||
function M:spot(job) require("file"):spot(job) end
|
||||
|
||||
return M
|
||||
|
|
@ -43,6 +43,7 @@ impl Default for Loader {
|
|||
("mime.local".to_owned(), preset!("plugins/mime-local").into()),
|
||||
("mime.remote".to_owned(), preset!("plugins/mime-remote").into()),
|
||||
("noop".to_owned(), preset!("plugins/noop").into()),
|
||||
("null".to_owned(), preset!("plugins/null").into()),
|
||||
("pdf".to_owned(), preset!("plugins/pdf").into()),
|
||||
("session".to_owned(), preset!("plugins/session").into()),
|
||||
("svg".to_owned(), preset!("plugins/svg").into()),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue