feat: new mimetype fetcher dedicated to remote files (#3268)

This commit is contained in:
三咲雅 misaki masa 2025-10-21 15:14:08 +08:00 committed by GitHub
parent 4e32044450
commit 4b08e8d899
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 320 additions and 168 deletions

View file

@ -5,7 +5,7 @@ use yazi_fs::{CWD, path::expand_url, provider::{DirReader, FileHolder}};
use yazi_macro::{act, render, succ};
use yazi_parser::cmp::{CmpItem, ShowOpt, TriggerOpt};
use yazi_proxy::CmpProxy;
use yazi_shared::{OsStrSplit, data::Data, natsort, url::{UrlBuf, UrlCow, UrnBuf}};
use yazi_shared::{OsStrSplit, data::Data, natsort, scheme::SchemeLike, url::{UrlBuf, UrlCow, UrnBuf}};
use yazi_vfs::provider;
use crate::{Actor, Ctx};
@ -70,7 +70,7 @@ impl Trigger {
fn split_url(s: &str) -> Option<(UrlBuf, UrnBuf)> {
let (scheme, path, ..) = UrlCow::parse(s.as_bytes()).ok()?;
if !scheme.is_virtual() && path.as_os_str() == "~" {
if scheme.is_local() && path.as_os_str() == "~" {
return None; // We don't autocomplete a `~`, but `~/`
}

View file

@ -4,7 +4,7 @@ use anyhow::Result;
use futures::{StreamExt, stream::FuturesUnordered};
use hashbrown::HashSet;
use tokio::sync::oneshot;
use yazi_fs::{File, FsUrl, provider::{Provider, local::Local}};
use yazi_fs::{File, FsScheme, provider::{Provider, local::Local}};
use yazi_macro::succ;
use yazi_parser::mgr::{DownloadOpt, OpenOpt};
use yazi_proxy::MgrProxy;
@ -75,7 +75,7 @@ impl Actor for Download {
impl Download {
async fn prepare(urls: &[UrlCow<'_>]) {
let roots: HashSet<_> = urls.iter().filter_map(|u| u.cache_root()).collect();
let roots: HashSet<_> = urls.iter().filter_map(|u| u.scheme().cache()).collect();
for mut root in roots {
root.push("%lock");
Local.create_dir_all(root).await.ok();

View file

@ -1,7 +1,7 @@
use std::{ops::Deref, time::{Duration, SystemTime}};
use mlua::{ExternalError, FromLua, IntoLua, Lua, Table, UserData, UserDataFields, UserDataMethods};
use yazi_fs::cha::{ChaKind, ChaMode};
use yazi_fs::{FsHash128, cha::{ChaKind, ChaMode}};
#[derive(Clone, Copy, FromLua)]
pub struct Cha(pub yazi_fs::cha::Cha);
@ -76,6 +76,13 @@ impl UserData for Cha {
}
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_method("hash", |_, me, long: Option<bool>| {
Ok(if long.unwrap_or(false) {
format!("{:x}", me.hash_u128())
} else {
Err("Short hash not supported".into_lua_err())?
})
});
methods.add_method("perm", |lua, _me, ()| {
Ok(
#[cfg(unix)]

View file

@ -1,13 +1,16 @@
use std::ops::Deref;
use mlua::{UserData, UserDataFields, Value};
use yazi_fs::FsScheme;
use yazi_shared::scheme::SchemeLike;
use crate::cached_field;
use crate::{Url, cached_field};
pub struct Scheme {
inner: yazi_shared::scheme::Scheme,
v_kind: Option<Value>,
v_kind: Option<Value>,
v_cache: Option<Value>,
}
impl Deref for Scheme {
@ -18,13 +21,14 @@ impl Deref for Scheme {
impl Scheme {
pub fn new(scheme: &yazi_shared::scheme::Scheme) -> Self {
Self { inner: scheme.clone(), v_kind: None }
Self { inner: scheme.clone(), v_kind: None, v_cache: None }
}
}
impl UserData for Scheme {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
cached_field!(fields, kind, |_, me| Ok(me.kind()));
cached_field!(fields, cache, |_, me| Ok(me.cache().map(Url::new)));
fields.add_field_method_get("is_virtual", |_, me| Ok(me.is_virtual()));
}

View file

@ -1,7 +1,8 @@
use std::ops::Deref;
use mlua::{AnyUserData, ExternalError, FromLua, Lua, MetaMethod, UserData, UserDataFields, UserDataMethods, UserDataRef, Value};
use yazi_shared::{IntoOsStr, url::{AsUrl, UrlCow, UrlLike}};
use yazi_fs::{FsHash64, FsHash128};
use yazi_shared::{IntoOsStr, scheme::SchemeLike, url::{AsUrl, UrlCow, UrlLike}};
use crate::{Scheme, Urn, cached_field, deprecate};
@ -128,6 +129,13 @@ impl UserData for Url {
}
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_method("hash", |_, me, long: Option<bool>| {
Ok(if long.unwrap_or(false) {
format!("{:x}", me.hash_u128())
} else {
format!("{:x}", me.hash_u64())
})
});
methods.add_method("join", |_, me, other: Value| {
Ok(Self::new(match other {
Value::String(s) => me.join(s.as_bytes().into_os_str()?),

View file

@ -232,7 +232,7 @@ rules = [
# Document
{ mime = "application/{pdf,doc,rtf}", fg = "cyan" },
# Virtual file system
{ mime = "vfs/*", fg = "gray" },
{ mime = "vfs/{absent,stale}", fg = "gray" },
# Special file
{ url = "*", is = "orphan", bg = "red" },
{ url = "*", is = "exec" , fg = "green" },

View file

@ -232,7 +232,7 @@ rules = [
# Document
{ mime = "application/{pdf,doc,rtf}", fg = "cyan" },
# Virtual file system
{ mime = "vfs/*", fg = "darkgray" },
{ mime = "vfs/{absent,stale}", fg = "darkgray" },
# Special file
{ url = "*", is = "orphan", bg = "red" },
{ url = "*", is = "exec" , fg = "green" },

View file

@ -81,7 +81,7 @@ rules = [
# Empty file
{ mime = "inode/empty", use = [ "edit", "reveal" ] },
# Virtual file system
{ mime = "vfs/*", use = "download" },
{ mime = "vfs/{absent,stale}", use = "download" },
# Fallback
{ url = "*", use = [ "open", "reveal" ] },
]
@ -97,8 +97,9 @@ suppress_preload = false
[plugin]
fetchers = [
# Mimetype
{ id = "mime", url = "*/", run = "mime.dir", prio = "high" },
{ id = "mime", url = "*", run = "mime.file", prio = "high" },
{ id = "mime", url = "*/", run = "mime.dir", prio = "high" },
{ id = "mime", url = "local://*", run = "mime.local", prio = "high" },
{ id = "mime", url = "remote://*", run = "mime.remote", prio = "high" },
]
spotters = [
{ url = "*/", run = "folder" },

View file

@ -1,11 +1,11 @@
use std::str::FromStr;
use std::{fmt::Debug, str::FromStr};
use anyhow::Result;
use anyhow::{Result, bail};
use globset::GlobBuilder;
use serde::Deserialize;
use yazi_shared::{scheme::{SchemeCow, SchemeRef}, url::AsUrl};
use yazi_shared::{scheme::SchemeRef, url::AsUrl};
#[derive(Debug, Deserialize)]
#[derive(Deserialize)]
#[serde(try_from = "String")]
pub struct Pattern {
inner: globset::GlobMatcher,
@ -16,16 +16,27 @@ pub struct Pattern {
sep_lit: bool,
}
impl Debug for Pattern {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Pattern")
.field("regex", &self.inner.glob().regex())
.field("scheme", &self.scheme)
.field("is_dir", &self.is_dir)
.field("is_star", &self.is_star)
.finish()
}
}
impl Pattern {
pub fn match_url(&self, url: impl AsUrl, is_dir: bool) -> bool {
let url = url.as_url();
if is_dir != self.is_dir {
return false;
} else if self.is_star {
return true;
} else if !self.scheme.matches(url.scheme) {
return false;
} else if self.is_star {
return true;
}
#[cfg(unix)]
@ -95,26 +106,59 @@ impl TryFrom<String> for Pattern {
}
// --- Scheme
#[derive(Debug)]
struct PatternScheme(Option<&'static str>);
#[derive(Clone, Copy, Debug)]
enum PatternScheme {
Any,
Local,
Remote,
Virtual,
Regular,
Search,
Archive,
Sftp,
}
impl PatternScheme {
fn parse(s: &str) -> Result<(Self, usize)> {
let mut me = Self(None);
let Some((protocol, _)) = s.split_once("://") else {
return Ok((me, 0));
return Ok((Self::Any, 0));
};
if protocol != "*" {
me.0 = Some(SchemeCow::parse_kind(protocol.as_bytes())?);
}
let scheme = match protocol {
"*" => Self::Any,
"local" => Self::Local,
"remote" => Self::Remote,
"virtual" => Self::Virtual,
Ok((me, protocol.len() + 3))
"regular" => Self::Regular,
"search" => Self::Search,
"archive" => Self::Archive,
"sftp" => Self::Sftp,
"" => bail!("Invalid URL pattern: protocol is empty"),
_ => bail!("Unknown protocol in URL pattern: {protocol}"),
};
Ok((scheme, protocol.len() + 3))
}
#[inline]
fn matches<'a>(&self, scheme: impl Into<SchemeRef<'a>>) -> bool {
self.0.is_none_or(|s| s == scheme.into().kind())
fn matches(self, scheme: SchemeRef) -> bool {
use SchemeRef as S;
match (self, scheme) {
(Self::Any, _) => true,
(Self::Local, s) => s.is_local(),
(Self::Remote, s) => s.is_remote(),
(Self::Virtual, s) => s.is_virtual(),
(Self::Regular, S::Regular) => true,
(Self::Search, S::Search(_)) => true,
(Self::Archive, S::Archive(_)) => true,
(Self::Sftp, S::Sftp(_)) => true,
_ => false,
}
}
}

View file

@ -30,9 +30,8 @@ impl FsHash128 for Cha {
self.mode.bits().hash(&mut h);
self.len.hash(&mut h);
self.mtime_dur().ok().map(|d| d.as_nanos()).hash(&mut h);
self.btime_dur().ok().map(|d| d.as_nanos()).hash(&mut h);
self.ctime_dur().ok().map(|d| d.as_nanos()).hash(&mut h);
self.mtime_dur().ok().map(|d| d.as_nanos()).hash(&mut h);
h.finish_128()
}

View file

@ -2,7 +2,7 @@
yazi_macro::mod_pub!(cha error mounts path provider);
yazi_macro::mod_flat!(cwd file files filter fns hash op sorter sorting splatter stage url xdg);
yazi_macro::mod_flat!(cwd file files filter fns hash op scheme sorter sorting splatter stage url xdg);
pub fn init() {
CWD.init(<_>::default());

View file

@ -34,10 +34,10 @@ impl FileBuilder for Gate {
}
async fn new(scheme: SchemeRef<'_>) -> io::Result<Self> {
if scheme.is_virtual() {
Err(io::Error::new(io::ErrorKind::InvalidInput, "Not a local filesystem"))?
} else {
if scheme.is_local() {
Ok(Self::default())
} else {
Err(io::Error::new(io::ErrorKind::InvalidInput, "Not a local filesystem"))?
}
}

View file

@ -17,10 +17,10 @@ impl Provider for Local {
U: AsUrl,
{
let url = url.as_url();
if url.scheme.is_virtual() {
Err(io::Error::new(io::ErrorKind::InvalidInput, "Not a local URL"))
} else {
if url.scheme.is_local() {
Ok(absolute_url(url))
} else {
Err(io::Error::new(io::ErrorKind::InvalidInput, "Not a local URL"))
}
}

27
yazi-fs/src/scheme.rs Normal file
View file

@ -0,0 +1,27 @@
use std::path::PathBuf;
use yazi_shared::scheme::{AsScheme, Scheme, SchemeRef};
use crate::Xdg;
pub trait FsScheme {
fn cache(&self) -> Option<PathBuf>;
}
impl FsScheme for SchemeRef<'_> {
fn cache(&self) -> Option<PathBuf> {
match self {
SchemeRef::Regular | SchemeRef::Search(_) => None,
SchemeRef::Archive(name) => {
Some(Xdg::cache_dir().join(format!("archive-{}", yazi_shared::url::Encode::domain(name))))
}
SchemeRef::Sftp(name) => {
Some(Xdg::cache_dir().join(format!("sftp-{}", yazi_shared::url::Encode::domain(name))))
}
}
}
}
impl FsScheme for Scheme {
fn cache(&self) -> Option<PathBuf> { self.as_scheme().cache() }
}

View file

@ -1,16 +1,14 @@
use std::{borrow::Cow, ffi::OsStr, path::{Path, PathBuf}};
use yazi_shared::{loc::Loc, scheme::SchemeRef, url::{AsUrl, Url, UrlBuf, UrlCow}};
use yazi_shared::{loc::Loc, url::{AsUrl, Url, UrlBuf, UrlCow}};
use crate::{FsHash128, Xdg, path::PercentEncoding};
use crate::{FsHash128, FsScheme, path::PercentEncoding};
pub trait FsUrl<'a> {
fn cache(&self) -> Option<PathBuf>;
fn cache_lock(&self) -> Option<PathBuf>;
fn cache_root(&self) -> Option<PathBuf>;
fn unified_path(self) -> Cow<'a, Path>;
fn unified_path_str(self) -> Cow<'a, OsStr>
@ -37,29 +35,17 @@ impl<'a> FsUrl<'a> for Url<'a> {
path
}
self.cache_root().map(|root| with_loc(self.loc, root))
self.scheme.cache().map(|root| with_loc(self.loc, root))
}
fn cache_lock(&self) -> Option<PathBuf> {
self.cache_root().map(|mut root| {
self.scheme.cache().map(|mut root| {
root.push("%lock");
root.push(format!("{:x}", self.hash_u128()));
root
})
}
fn cache_root(&self) -> Option<PathBuf> {
match self.scheme {
SchemeRef::Regular | SchemeRef::Search(_) => None,
SchemeRef::Archive(name) => {
Some(Xdg::cache_dir().join(format!("archive-{}", yazi_shared::url::Encode::domain(name))))
}
SchemeRef::Sftp(name) => {
Some(Xdg::cache_dir().join(format!("sftp-{}", yazi_shared::url::Encode::domain(name))))
}
}
}
fn unified_path(self) -> Cow<'a, Path> {
self.cache().map(Cow::Owned).unwrap_or_else(|| Cow::Borrowed(self.loc.as_path()))
}
@ -70,8 +56,6 @@ impl FsUrl<'_> for UrlBuf {
fn cache_lock(&self) -> Option<PathBuf> { self.as_url().cache_lock() }
fn cache_root(&self) -> Option<PathBuf> { self.as_url().cache_root() }
fn unified_path(self) -> Cow<'static, Path> {
self.cache().unwrap_or_else(|| self.loc.into_path()).into()
}
@ -82,8 +66,6 @@ impl<'a> FsUrl<'a> for UrlCow<'a> {
fn cache_lock(&self) -> Option<PathBuf> { self.as_url().cache_lock() }
fn cache_root(&self) -> Option<PathBuf> { self.as_url().cache_root() }
fn unified_path(self) -> Cow<'a, Path> {
match (self.cache(), self) {
(None, UrlCow::Borrowed { loc, .. }) => loc.as_path().into(),

View file

@ -40,9 +40,12 @@ function M:spot_base(job)
for i, v in ipairs(fetchers) do
fetchers[i] = v.cmd
end
fetchers = #fetchers ~= 0 and fetchers or { "-" }
for i, v in ipairs(preloaders) do
preloaders[i] = v.cmd
end
preloaders = #preloaders ~= 0 and preloaders or { "-" }
return {
ui.Row({ "Base" }):style(ui.Style():fg("green")),
@ -54,8 +57,8 @@ function M:spot_base(job)
ui.Row({ "Plugins" }):style(ui.Style():fg("green")),
ui.Row { " Spotter:", spotter and spotter.cmd or "-" },
ui.Row { " Previewer:", previewer and previewer.cmd or "-" },
ui.Row { " Fetchers:", #fetchers ~= 0 and fetchers or "-" },
ui.Row { " Preloaders:", #preloaders ~= 0 and preloaders or "-" },
ui.Row({ " Fetchers:", fetchers }):height(#fetchers),
ui.Row({ " Preloaders:", preloaders }):height(#preloaders),
}
end

View file

@ -15,20 +15,11 @@ local function match_mimetype(line)
end
end
local function miss_cache(cache, line)
if line:match("^cannot open `.+' %(No such file or directory%)%s+$") then
return true
else
local _, err = fs.cha(Url(cache))
return err and err.code == 2
end
end
function M:fetch(job)
local paths, origins = {}, {}
local urls, paths = {}, {}
for i, file in ipairs(job.files) do
if file.cache then
paths[i], origins[i] = tostring(file.cache), tostring(file.url)
urls[i], paths[i] = tostring(file.url), tostring(file.cache)
else
paths[i] = tostring(file.url)
end
@ -63,14 +54,9 @@ function M:fetch(job)
match, ignore = match_mimetype(line)
if match then
updates[origins[i] or paths[i]], state[i], i = match, true, i + 1
updates[urls[i] or paths[i]], state[i], i = match, true, i + 1
flush(false)
elseif ignore then
goto continue
elseif origins[i] and miss_cache(paths[i], line) then
updates[origins[i]], state[i], i = "vfs/todo", true, i + 1
flush(false)
else
elseif not ignore then
state[i], i = false, i + 1
end
::continue::

View file

@ -0,0 +1,59 @@
local M = {}
local function stale_cache(file)
local url = file.url
local lock = url.scheme.cache:join(string.format("%%lock/%s", url:hash(true)))
local f = io.open(tostring(lock), "r")
if not f then
return true
end
local hash = f:read(32)
f:close()
return hash ~= file.cha:hash(true)
end
function M:fetch(job)
local updates, unknown, state = {}, {}, {}
for i, file in ipairs(job.files) do
if not file.cache then
unknown[#unknown + 1] = file
elseif not fs.cha(file.cache) then
updates[file.url], state[i] = "vfs/absent", true
elseif stale_cache(file) then
updates[file.url], state[i] = "vfs/stale", true
else
unknown[#unknown + 1] = file
end
end
if next(updates) then
ya.emit("update_mimes", { updates = updates })
end
if #unknown == 0 then
return state
else
return self.fallback_local(job, unknown, state)
end
end
function M.fallback_local(job, unknown, state)
local indices = {}
for i, f in ipairs(job.files) do
indices[f:hash()] = i
end
local result = require("mime.local"):fetch(ya.dict_merge(job, { files = unknown }))
for i, f in ipairs(unknown) do
if type(result) == "table" then
state[indices[f:hash()]] = result[i]
else
state[indices[f:hash()]] = result
end
end
return state
end
return M

View file

@ -1,12 +1,12 @@
local function fetch(_, job)
ya.notify {
title = "Deprecated plugin",
content = "The `mime` fetcher is deprecated, use `mime.file` instead in your `yazi.toml`\n\nSee https://github.com/sxyazi/yazi/pull/3222 for more details.",
content = "The `mime` fetcher is deprecated, use `mime.local` instead in your `yazi.toml`\n\nSee https://github.com/sxyazi/yazi/pull/3222 for more details.",
timeout = 15,
level = "warn",
}
return require("mime.file"):fetch(job)
return require("mime.local"):fetch(job)
end
return { fetch = fetch }

View file

@ -40,7 +40,8 @@ impl Default for Loader {
("magick".to_owned(), preset!("plugins/magick").into()),
("mime".to_owned(), preset!("plugins/mime").into()), // TODO: remove this
("mime.dir".to_owned(), preset!("plugins/mime-dir").into()),
("mime.file".to_owned(), preset!("plugins/mime-file").into()),
("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()),
("pdf".to_owned(), preset!("plugins/pdf").into()),
("session".to_owned(), preset!("plugins/session").into()),

View file

@ -6,7 +6,7 @@ use tracing::warn;
use yazi_config::YAZI;
use yazi_fs::{Cwd, FsHash128, FsUrl, cha::Cha, ok_or_not_found, path::{path_relative_to, skip_url}, provider::{DirReader, FileHolder, Provider, local::Local}};
use yazi_macro::ok_or_not_found;
use yazi_shared::{timestamp_us, url::{AsUrl, UrlBuf, UrlCow, UrlLike}};
use yazi_shared::{scheme::SchemeLike, timestamp_us, url::{AsUrl, UrlBuf, UrlCow, UrlLike}};
use yazi_vfs::{VfsCha, copy_with_progress, maybe_exists, provider::{self, DirEntry}, unique_name};
use super::{FileInDelete, FileInHardlink, FileInLink, FileInPaste, FileInTrash};

View file

@ -8,7 +8,7 @@ use yazi_config::{YAZI, plugin::{Fetcher, Preloader}};
use yazi_dds::Pump;
use yazi_parser::{app::PluginOpt, tasks::ProcessOpenOpt};
use yazi_proxy::TasksProxy;
use yazi_shared::{Id, Throttle, url::{UrlBuf, UrlLike}};
use yazi_shared::{Id, Throttle, scheme::SchemeLike, url::{UrlBuf, UrlLike}};
use yazi_vfs::{must_be_dir, provider, unique_name};
use super::{Ongoing, TaskOp};
@ -215,7 +215,7 @@ impl Scheduler {
ongoing.hooks.add_sync(id, move |canceled| _ = tx.send(canceled));
}
if !url.scheme.is_virtual() {
if !url.scheme.is_remote() {
return self.ops.out(id, FileOutDownload::Fail("Cannot download non-remote file".to_owned()));
};
@ -229,7 +229,7 @@ impl Scheduler {
let mut ongoing = self.ongoing.lock();
let id = ongoing.add::<FileProgUpload>(format!("Upload {}", url.display()));
if !url.scheme.is_virtual() {
if !url.scheme.is_remote() {
return self.ops.out(id, FileOutUpload::Fail("Cannot upload non-remote file".to_owned()));
};

View file

@ -3,7 +3,7 @@ use std::{borrow::Cow, ops::Not, path::Path};
use anyhow::{Result, bail, ensure};
use percent_encoding::percent_decode;
use crate::{BytesExt, pool::InternStr, scheme::{Scheme, SchemeRef}};
use crate::{BytesExt, pool::InternStr, scheme::{AsScheme, Scheme, SchemeRef}};
#[derive(Clone, Debug)]
pub enum SchemeCow<'a> {
@ -15,18 +15,21 @@ impl Default for SchemeCow<'_> {
fn default() -> Self { Self::Borrowed(SchemeRef::Regular) }
}
impl From<Scheme> for SchemeCow<'_> {
fn from(value: Scheme) -> Self { Self::Owned(value) }
}
impl<'a> From<&'a Scheme> for SchemeCow<'a> {
fn from(value: &'a Scheme) -> Self { Self::Borrowed(value.as_ref()) }
}
impl<'a> From<SchemeRef<'a>> for SchemeCow<'a> {
fn from(value: SchemeRef<'a>) -> Self { Self::Borrowed(value) }
}
impl<'a, T> From<&'a T> for SchemeCow<'a>
where
T: AsScheme + ?Sized,
{
fn from(value: &'a T) -> Self { Self::Borrowed(value.as_scheme()) }
}
impl From<Scheme> for SchemeCow<'_> {
fn from(value: Scheme) -> Self { Self::Owned(value) }
}
impl From<SchemeCow<'_>> for Scheme {
fn from(value: SchemeCow<'_>) -> Self {
match value {
@ -58,14 +61,6 @@ impl<'a> SchemeCow<'a> {
}
}
#[inline]
pub fn as_ref(&self) -> SchemeRef<'_> {
match self {
Self::Borrowed(s) => *s,
Self::Owned(s) => s.into(),
}
}
pub(crate) fn parse(
bytes: &'a [u8],
skip: &mut usize,
@ -103,17 +98,6 @@ impl<'a> SchemeCow<'a> {
Ok((scheme, tilde, uri, urn))
}
#[inline]
pub fn parse_kind(bytes: &[u8]) -> Result<&'static str> {
match bytes {
b"regular" => Ok("regular"),
b"search" => Ok("search"),
b"archive" => Ok("archive"),
b"sftp" => Ok("sftp"),
_ => bail!("Could not parse protocol from URL: {}", String::from_utf8_lossy(bytes)),
}
}
fn decode_param(
bytes: &'a [u8],
skip: &mut usize,
@ -154,7 +138,7 @@ impl<'a> SchemeCow<'a> {
urn: Option<usize>,
path: &Path,
) -> Result<Option<(usize, usize)>> {
Ok(match self.as_ref() {
Ok(match self.as_scheme() {
SchemeRef::Regular => {
ensure!(uri.is_none() && urn.is_none(), "Regular scheme cannot have ports");
None
@ -175,9 +159,6 @@ impl<'a> SchemeCow<'a> {
}
impl SchemeCow<'_> {
#[inline]
pub fn is_virtual(&self) -> bool { self.as_ref().is_virtual() }
#[inline]
pub fn into_owned(self) -> Scheme { self.into() }
}

View file

@ -1 +1 @@
yazi_macro::mod_flat!(cow r#ref scheme);
yazi_macro::mod_flat!(cow r#ref scheme traits);

View file

@ -1,4 +1,4 @@
use crate::{pool::InternStr, scheme::Scheme};
use crate::{pool::InternStr, scheme::{AsScheme, Scheme}};
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
pub enum SchemeRef<'a> {
@ -12,17 +12,6 @@ pub enum SchemeRef<'a> {
Sftp(&'a str),
}
impl<'a> From<&'a Scheme> for SchemeRef<'a> {
fn from(value: &'a Scheme) -> Self {
match value {
Scheme::Regular => Self::Regular,
Scheme::Search(d) => Self::Search(d),
Scheme::Archive(d) => Self::Archive(d),
Scheme::Sftp(d) => Self::Sftp(d),
}
}
}
impl From<SchemeRef<'_>> for Scheme {
fn from(value: SchemeRef) -> Self {
match value {
@ -54,13 +43,29 @@ impl<'a> SchemeRef<'a> {
}
#[inline]
pub fn covariant(self, other: impl Into<Self>) -> bool {
let other = other.into();
pub fn covariant(self, other: impl AsScheme) -> bool {
let other = other.as_scheme();
if self.is_virtual() || other.is_virtual() { self == other } else { true }
}
#[inline]
pub fn is_virtual(&self) -> bool {
pub fn is_local(self) -> bool {
match self {
Self::Regular | Self::Search(_) => true,
Self::Archive(_) | Self::Sftp(_) => false,
}
}
#[inline]
pub fn is_remote(self) -> bool {
match self {
Self::Regular | Self::Search(_) | Self::Archive(_) => false,
Self::Sftp(_) => true,
}
}
#[inline]
pub fn is_virtual(self) -> bool {
match self {
Self::Regular | Self::Search(_) => false,
Self::Archive(_) | Self::Sftp(_) => true,

View file

@ -1,6 +1,6 @@
use std::hash::{Hash, Hasher};
use crate::{pool::Symbol, scheme::SchemeRef};
use crate::{pool::Symbol, scheme::{AsScheme, SchemeRef}};
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub enum Scheme {
@ -15,26 +15,9 @@ pub enum Scheme {
}
impl Hash for Scheme {
fn hash<H: Hasher>(&self, state: &mut H) { self.as_ref().hash(state); }
fn hash<H: Hasher>(&self, state: &mut H) { self.as_scheme().hash(state); }
}
impl PartialEq<SchemeRef<'_>> for Scheme {
fn eq(&self, other: &SchemeRef<'_>) -> bool { self.as_ref() == *other }
}
impl Scheme {
#[inline]
pub fn as_ref(&self) -> SchemeRef<'_> { self.into() }
#[inline]
pub fn kind(&self) -> &'static str { self.as_ref().kind() }
#[inline]
pub fn domain(&self) -> Option<&str> { self.as_ref().domain() }
#[inline]
pub fn covariant(&self, other: &Self) -> bool { self.as_ref().covariant(other) }
#[inline]
pub fn is_virtual(&self) -> bool { self.as_ref().is_virtual() }
fn eq(&self, other: &SchemeRef<'_>) -> bool { self.as_scheme() == *other }
}

View file

@ -0,0 +1,63 @@
use crate::scheme::{Scheme, SchemeCow, SchemeRef};
pub trait AsScheme {
fn as_scheme(&self) -> SchemeRef<'_>;
}
impl AsScheme for SchemeRef<'_> {
#[inline]
fn as_scheme(&self) -> SchemeRef<'_> { *self }
}
impl AsScheme for Scheme {
#[inline]
fn as_scheme(&self) -> SchemeRef<'_> {
match self {
Scheme::Regular => SchemeRef::Regular,
Scheme::Search(d) => SchemeRef::Search(d),
Scheme::Archive(d) => SchemeRef::Archive(d),
Scheme::Sftp(d) => SchemeRef::Sftp(d),
}
}
}
impl AsScheme for &Scheme {
#[inline]
fn as_scheme(&self) -> SchemeRef<'_> { (**self).as_scheme() }
}
impl AsScheme for SchemeCow<'_> {
#[inline]
fn as_scheme(&self) -> SchemeRef<'_> {
match self {
SchemeCow::Borrowed(s) => *s,
SchemeCow::Owned(s) => s.as_scheme(),
}
}
}
impl AsScheme for &SchemeCow<'_> {
#[inline]
fn as_scheme(&self) -> SchemeRef<'_> { (**self).as_scheme() }
}
// --- SchemeLike
pub trait SchemeLike
where
Self: AsScheme + Sized,
{
fn kind(&self) -> &'static str { self.as_scheme().kind() }
fn domain(&self) -> Option<&str> { self.as_scheme().domain() }
fn covariant(&self, other: impl AsScheme) -> bool { self.as_scheme().covariant(other) }
fn is_local(&self) -> bool { self.as_scheme().is_local() }
fn is_remote(&self) -> bool { self.as_scheme().is_remote() }
fn is_virtual(&self) -> bool { self.as_scheme().is_virtual() }
}
impl SchemeLike for Scheme {}
impl SchemeLike for SchemeCow<'_> {}

View file

@ -3,7 +3,7 @@ use std::{borrow::Cow, ffi::OsStr, fmt::{Debug, Formatter}, path::{Path, PathBuf
use anyhow::Result;
use serde::{Deserialize, Serialize};
use crate::{loc::LocBuf, pool::Pool, scheme::Scheme, url::{AsUrl, Encode, EncodeTilded, Url, UrlCow}};
use crate::{loc::LocBuf, pool::Pool, scheme::{Scheme, SchemeLike}, url::{AsUrl, Encode, EncodeTilded, Url, UrlCow}};
#[derive(Clone, Default, Eq, Hash, PartialEq)]
pub struct UrlBuf {
@ -91,7 +91,7 @@ impl UrlBuf {
#[inline]
pub fn into_path(self) -> Option<PathBuf> {
Some(self.loc.into_path()).filter(|_| !self.scheme.is_virtual())
Some(self.loc.into_path()).filter(|_| self.scheme.is_local())
}
#[inline]

View file

@ -77,7 +77,7 @@ impl<'a> From<Url<'a>> for Components<'a> {
impl<'a> Components<'a> {
pub fn os_str(&self) -> Cow<'a, OsStr> {
let path = self.inner.as_path();
if !self.url.scheme.is_virtual() || self.scheme_yielded {
if self.url.scheme.is_local() || self.scheme_yielded {
return path.as_os_str().into();
}

View file

@ -3,7 +3,7 @@ use std::{borrow::Cow, path::{Path, PathBuf}};
use anyhow::Result;
use percent_encoding::percent_decode;
use crate::{IntoOsStr, loc::{Loc, LocBuf}, scheme::{SchemeCow, SchemeRef}, url::{AsUrl, Url, UrlBuf}};
use crate::{IntoOsStr, loc::{Loc, LocBuf}, scheme::{AsScheme, SchemeCow, SchemeRef}, url::{AsUrl, Url, UrlBuf}};
#[derive(Clone, Debug)]
pub enum UrlCow<'a> {
@ -109,8 +109,8 @@ impl<'a> UrlCow<'a> {
#[inline]
pub fn scheme(&self) -> SchemeRef<'_> {
match self {
UrlCow::Borrowed { scheme, .. } => scheme.as_ref(),
UrlCow::Owned { scheme, .. } => scheme.as_ref(),
UrlCow::Borrowed { scheme, .. } => scheme.as_scheme(),
UrlCow::Owned { scheme, .. } => scheme.as_scheme(),
}
}

View file

@ -1,6 +1,6 @@
use std::{borrow::Cow, ffi::OsStr, path::{Path, PathBuf}};
use crate::{scheme::SchemeRef, url::{Components, Display, Uri, Url, UrlBuf, UrlCow, Urn}};
use crate::{scheme::{AsScheme, SchemeRef}, url::{Components, Display, Uri, Url, UrlBuf, UrlCow, Urn}};
// --- AsUrl
pub trait AsUrl {
@ -34,7 +34,7 @@ impl AsUrl for Url<'_> {
impl AsUrl for UrlBuf {
#[inline]
fn as_url(&self) -> Url<'_> { Url { loc: self.loc.as_loc(), scheme: self.scheme.as_ref() } }
fn as_url(&self) -> Url<'_> { Url { loc: self.loc.as_loc(), scheme: self.scheme.as_scheme() } }
}
impl AsUrl for &UrlBuf {
@ -51,8 +51,8 @@ impl AsUrl for UrlCow<'_> {
#[inline]
fn as_url(&self) -> Url<'_> {
match self {
UrlCow::Borrowed { loc, scheme } => Url { loc: *loc, scheme: scheme.as_ref() },
UrlCow::Owned { loc, scheme } => Url { loc: loc.as_loc(), scheme: scheme.as_ref() },
UrlCow::Borrowed { loc, scheme } => Url { loc: *loc, scheme: scheme.as_scheme() },
UrlCow::Owned { loc, scheme } => Url { loc: loc.as_loc(), scheme: scheme.as_scheme() },
}
}
}
@ -122,6 +122,5 @@ where
fn urn(&self) -> &Urn { self.as_url().urn() }
}
impl UrlLike for Url<'_> {}
impl UrlLike for UrlBuf {}
impl UrlLike for UrlCow<'_> {}

View file

@ -200,7 +200,7 @@ impl<'a> Url<'a> {
#[inline]
pub fn as_path(self) -> Option<&'a Path> {
Some(self.loc.as_path()).filter(|_| !self.scheme.is_virtual())
Some(self.loc.as_path()).filter(|_| self.scheme.is_local())
}
#[inline]