mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 23:01:05 +00:00
feat: URL covariance for the mimetype agency (#3030)
This commit is contained in:
parent
9adc00c3a9
commit
da97e5a8b4
15 changed files with 198 additions and 127 deletions
35
Cargo.lock
generated
35
Cargo.lock
generated
|
|
@ -391,9 +391,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.41"
|
||||
version = "4.5.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9"
|
||||
checksum = "ed87a9d530bb41a67537289bafcac159cb3ee28460e0a4571123d2a778a6a882"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
|
|
@ -401,9 +401,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.41"
|
||||
version = "4.5.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d"
|
||||
checksum = "64f4f3f3c77c94aff3c7e9aac9a2ca1974a5adf392a8bb751e827d6d127ab966"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
|
|
@ -1335,9 +1335,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.6"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4488594b9328dee448adb906d8b126d9b7deb7cf5c22161ee591610bb1be83c0"
|
||||
checksum = "360e552c93fa0e8152ab463bc4c4837fce76a225df11dfaeea66c313de5e61f7"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"libc",
|
||||
|
|
@ -2155,9 +2155,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.16"
|
||||
version = "0.5.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7251471db004e509f4e75a62cca9435365b5ec7bcdff530d612ac7c87c44a792"
|
||||
checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
]
|
||||
|
|
@ -2751,9 +2751,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.9.2"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed0aee96c12fa71097902e0bb061a5e1ebd766a6636bb605ba401c45c1650eac"
|
||||
checksum = "41ae868b5a0f67631c14589f7e250c1ea2c574ee5ba21c6c8dd4b1485705a5a1"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
|
|
@ -3310,7 +3310,7 @@ version = "0.60.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
|
||||
dependencies = [
|
||||
"windows-targets 0.53.2",
|
||||
"windows-targets 0.53.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -3331,10 +3331,11 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.53.2"
|
||||
version = "0.53.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef"
|
||||
checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows_aarch64_gnullvm 0.53.0",
|
||||
"windows_aarch64_msvc 0.53.0",
|
||||
"windows_i686_gnu 0.53.0",
|
||||
|
|
@ -3578,7 +3579,7 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"toml 0.9.2",
|
||||
"toml 0.9.4",
|
||||
"twox-hash",
|
||||
"vergen-gitcl",
|
||||
"yazi-boot",
|
||||
|
|
@ -3609,7 +3610,7 @@ dependencies = [
|
|||
"ratatui",
|
||||
"regex",
|
||||
"serde",
|
||||
"toml 0.9.2",
|
||||
"toml 0.9.4",
|
||||
"tracing",
|
||||
"yazi-codegen",
|
||||
"yazi-fs",
|
||||
|
|
@ -3946,9 +3947,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "zune-jpeg"
|
||||
version = "0.4.19"
|
||||
version = "0.4.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c9e525af0a6a658e031e95f14b7f889976b74a11ba0eca5a5fc9ac8a1c43a6a"
|
||||
checksum = "fc1f7e205ce79eb2da3cd71c5f55f3589785cb7c79f6a03d1c8d1491bda5d089"
|
||||
dependencies = [
|
||||
"zune-core",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ ansi-to-tui = "7.0.0"
|
|||
anyhow = "1.0.98"
|
||||
base64 = "0.22.1"
|
||||
bitflags = { version = "2.9.1", features = [ "serde" ] }
|
||||
clap = { version = "4.5.41", features = [ "derive" ] }
|
||||
clap = { version = "4.5.42", features = [ "derive" ] }
|
||||
core-foundation-sys = "0.8.7"
|
||||
crossterm = { version = "0.29.0", features = [ "event-stream" ] }
|
||||
dirs = "6.0.0"
|
||||
|
|
@ -48,7 +48,7 @@ syntect = { version = "5.2.0", default-features = false, features =
|
|||
tokio = { version = "1.47.0", features = [ "full" ] }
|
||||
tokio-stream = "0.1.17"
|
||||
tokio-util = "0.7.15"
|
||||
toml = { version = "0.9.2" }
|
||||
toml = { version = "0.9.4" }
|
||||
tracing = { version = "0.1.41", features = [ "max_level_debug", "release_max_level_debug" ] }
|
||||
twox-hash = { version = "2.1.1", default-features = false, features = [ "std", "random", "xxhash3_128" ] }
|
||||
unicode-width = { version = "0.2.0", default-features = false }
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::{mem, ops::ControlFlow};
|
||||
use std::{ffi::OsStr, mem, ops::ControlFlow};
|
||||
|
||||
use anyhow::Result;
|
||||
use yazi_macro::{render, succ};
|
||||
|
|
@ -30,7 +30,7 @@ impl Actor for Show {
|
|||
};
|
||||
|
||||
cmp.ticket = opt.ticket;
|
||||
cmp.cands = Self::match_candidates(&opt.word, cache);
|
||||
cmp.cands = Self::match_candidates(opt.word.as_os_str(), cache);
|
||||
if cmp.cands.is_empty() {
|
||||
succ!(render!(mem::replace(&mut cmp.visible, false)));
|
||||
}
|
||||
|
|
@ -43,8 +43,8 @@ impl Actor for Show {
|
|||
}
|
||||
|
||||
impl Show {
|
||||
fn match_candidates(word: &str, cache: &[CmpItem]) -> Vec<CmpItem> {
|
||||
let smart = !word.bytes().any(|c| c.is_ascii_uppercase());
|
||||
fn match_candidates(word: &OsStr, cache: &[CmpItem]) -> Vec<CmpItem> {
|
||||
let smart = !word.as_encoded_bytes().iter().any(|c| c.is_ascii_uppercase());
|
||||
|
||||
let flow = cache.iter().try_fold((Vec::new(), Vec::new()), |(mut exact, mut fuzzy), item| {
|
||||
if osstr_starts_with(&item.name, word, smart) {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
use std::{ffi::OsString, mem, path::{MAIN_SEPARATOR_STR, Path, PathBuf}};
|
||||
use std::{ffi::OsString, mem, path::MAIN_SEPARATOR_STR};
|
||||
|
||||
use anyhow::Result;
|
||||
use yazi_fs::{CWD, expand_path, services::Local};
|
||||
use yazi_fs::{CWD, expand_url, services::Local};
|
||||
use yazi_macro::{act, render, succ};
|
||||
use yazi_parser::cmp::{CmpItem, ShowOpt, TriggerOpt};
|
||||
use yazi_proxy::CmpProxy;
|
||||
use yazi_shared::{event::Data, natsort};
|
||||
use yazi_shared::{OsStrSplit, event::Data, natsort, url::{Url, UrnBuf}};
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
|
|
@ -25,13 +25,13 @@ impl Actor for Trigger {
|
|||
cmp.ticket = t;
|
||||
}
|
||||
|
||||
let Some((parent, word)) = Self::split_path(&opt.word) else {
|
||||
let Some((parent, word)) = Self::split_url(&opt.word) else {
|
||||
return act!(cmp:close, cx, false);
|
||||
};
|
||||
|
||||
if cmp.caches.contains_key(&parent) {
|
||||
let ticket = cmp.ticket;
|
||||
return act!(cmp:show, cx, ShowOpt { cache_name: parent, word: word.into(), ticket, ..Default::default() });
|
||||
return act!(cmp:show, cx, ShowOpt { cache_name: parent, word, ticket, ..Default::default() });
|
||||
}
|
||||
|
||||
let ticket = cmp.ticket;
|
||||
|
|
@ -42,7 +42,7 @@ impl Actor for Trigger {
|
|||
|
||||
// "/" is both a directory separator and the root directory per se
|
||||
// As there's no parent directory for the FS root, it is a special case
|
||||
if parent == Path::new("/") {
|
||||
if parent.loc.as_os_str() == "/" {
|
||||
cache.push(CmpItem { name: OsString::new(), is_dir: true });
|
||||
}
|
||||
|
||||
|
|
@ -56,7 +56,7 @@ impl Actor for Trigger {
|
|||
cache.sort_unstable_by(|a, b| {
|
||||
natsort(a.name.as_encoded_bytes(), b.name.as_encoded_bytes(), false)
|
||||
});
|
||||
CmpProxy::show(ShowOpt { cache, cache_name: parent, word: word.into(), ticket });
|
||||
CmpProxy::show(ShowOpt { cache, cache_name: parent, word, ticket });
|
||||
}
|
||||
|
||||
Ok::<_, anyhow::Error>(())
|
||||
|
|
@ -67,34 +67,36 @@ impl Actor for Trigger {
|
|||
}
|
||||
|
||||
impl Trigger {
|
||||
fn split_path(s: &str) -> Option<(PathBuf, String)> {
|
||||
if s == "~" {
|
||||
fn split_url(s: &str) -> Option<(Url, UrnBuf)> {
|
||||
let (scheme, path) = Url::parse(s.as_bytes()).ok()?;
|
||||
|
||||
if !scheme.is_virtual() && path.as_os_str() == "~" {
|
||||
return None; // We don't autocomplete a `~`, but `~/`
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
const SEP: [char; 2] = ['/', '\\'];
|
||||
const SEP: &[char] = &['/', '\\'];
|
||||
#[cfg(not(windows))]
|
||||
const SEP: char = std::path::MAIN_SEPARATOR;
|
||||
|
||||
Some(match s.rsplit_once(SEP) {
|
||||
Some(("", c)) => (PathBuf::from(MAIN_SEPARATOR_STR), c.to_owned()),
|
||||
Some((p, c)) => (expand_path(p), c.to_owned()),
|
||||
None => (CWD.load().to_path_buf(), s.to_owned()),
|
||||
Some(match path.as_os_str().rsplit_once(SEP) {
|
||||
Some((p, c)) if p.is_empty() => (Url { loc: MAIN_SEPARATOR_STR.into(), scheme }, c.into()),
|
||||
Some((p, c)) => (expand_url(Url { loc: p.into(), scheme }).into_owned(), c.into()),
|
||||
None => (CWD.load().as_ref().clone(), path.into()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::Path;
|
||||
use yazi_shared::url::Urn;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn compare(s: &str, parent: &str, child: &str) {
|
||||
let (p, c) = Trigger::split_path(s).unwrap();
|
||||
let p = p.strip_prefix(yazi_fs::CWD.load().as_ref()).unwrap_or(&p);
|
||||
assert_eq!((p, c.as_str()), (Path::new(parent), child));
|
||||
let (p, c) = Trigger::split_url(s).unwrap();
|
||||
let p = p.strip_prefix(yazi_fs::CWD.load().as_ref()).unwrap_or(p);
|
||||
assert_eq!((p, c.as_urn()), (parent.try_into().unwrap(), Urn::new(child)));
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ impl Actor for Follow {
|
|||
if link_to.is_absolute() {
|
||||
act!(mgr:reveal, cx, link_to.to_owned())
|
||||
} else if let Some(p) = file.url.parent_url() {
|
||||
act!(mgr:reveal, cx, clean_url(&p.join(link_to)))
|
||||
act!(mgr:reveal, cx, clean_url(p.join(link_to)).into_owned())
|
||||
} else {
|
||||
succ!()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
use std::{collections::HashMap, path::PathBuf};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use yazi_parser::cmp::CmpItem;
|
||||
use yazi_shared::Id;
|
||||
use yazi_shared::{Id, url::Url};
|
||||
use yazi_widgets::Scrollable;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Cmp {
|
||||
pub caches: HashMap<PathBuf, Vec<CmpItem>>,
|
||||
pub caches: HashMap<Url, Vec<CmpItem>>,
|
||||
pub cands: Vec<CmpItem>,
|
||||
pub offset: usize,
|
||||
pub cursor: usize,
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
use std::{borrow::Cow, collections::HashMap};
|
||||
|
||||
use yazi_fs::File;
|
||||
use yazi_shared::{MIME_DIR, SStr, url::Url};
|
||||
use yazi_shared::{MIME_DIR, SStr, url::{CovUrl, Url}};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Mimetype(HashMap<Url, String>);
|
||||
pub struct Mimetype(HashMap<CovUrl, String>);
|
||||
|
||||
impl Mimetype {
|
||||
#[inline]
|
||||
pub fn by_url(&self, url: &Url) -> Option<&str> { self.0.get(url).map(|s| s.as_str()) }
|
||||
pub fn by_url(&self, url: &Url) -> Option<&str> {
|
||||
self.0.get(CovUrl::new(url)).map(|s| s.as_str())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn by_url_owned(&self, url: &Url) -> Option<SStr> {
|
||||
|
|
@ -26,8 +28,10 @@ impl Mimetype {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub fn contains(&self, url: &Url) -> bool { self.0.contains_key(url) }
|
||||
pub fn contains(&self, url: &Url) -> bool { self.0.contains_key(CovUrl::new(url)) }
|
||||
|
||||
#[inline]
|
||||
pub fn extend(&mut self, iter: impl IntoIterator<Item = (Url, String)>) { self.0.extend(iter) }
|
||||
pub fn extend(&mut self, iter: impl IntoIterator<Item = (Url, String)>) {
|
||||
self.0.extend(iter.into_iter().map(|(u, m)| (CovUrl(u), m)));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -333,7 +333,7 @@ pub fn max_common_root(urls: &[Url]) -> usize {
|
|||
if urls.is_empty() {
|
||||
return 0;
|
||||
} else if urls.len() == 1 {
|
||||
return urls[0].components().count().saturating_sub(1);
|
||||
return urls[0].components().count() - 1;
|
||||
}
|
||||
|
||||
let mut it = urls.iter().map(|u| u.parent_url());
|
||||
|
|
@ -341,13 +341,13 @@ pub fn max_common_root(urls: &[Url]) -> usize {
|
|||
return 0; // The first URL has no parent
|
||||
};
|
||||
|
||||
let mut min = first.components().count();
|
||||
let mut common = first.components().count();
|
||||
for parent in it {
|
||||
let Some(parent) = parent else {
|
||||
return 0; // One of the URLs has no parent
|
||||
};
|
||||
|
||||
min = first
|
||||
common = first
|
||||
.components()
|
||||
.zip(parent.components())
|
||||
.take_while(|(a, b)| match (a, b) {
|
||||
|
|
@ -355,10 +355,14 @@ pub fn max_common_root(urls: &[Url]) -> usize {
|
|||
(a, b) => a == b,
|
||||
})
|
||||
.count()
|
||||
.min(min);
|
||||
.min(common);
|
||||
|
||||
if common == 0 {
|
||||
break; // No common root found
|
||||
}
|
||||
}
|
||||
|
||||
min
|
||||
common
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
|
|
|
|||
|
|
@ -5,14 +5,18 @@ use yazi_shared::url::{Loc, Url};
|
|||
|
||||
use crate::{CWD, services};
|
||||
|
||||
#[inline]
|
||||
pub fn clean_url(url: &Url) -> Url { Url::from(clean_path(url)) }
|
||||
pub fn clean_url<'a>(url: impl Into<Cow<'a, Url>>) -> Cow<'a, Url> {
|
||||
let url = url.into();
|
||||
let path = clean_path(&url.loc);
|
||||
|
||||
// FIXME: VFS
|
||||
#[inline]
|
||||
pub fn clean_path(path: impl AsRef<Path>) -> PathBuf { _clean_path(path.as_ref()) }
|
||||
if path.as_os_str() == url.loc.as_os_str() {
|
||||
url
|
||||
} else {
|
||||
url.with(Loc::with(&clean_path(url.loc.base()), path)).into()
|
||||
}
|
||||
}
|
||||
|
||||
fn _clean_path(path: &Path) -> PathBuf {
|
||||
fn clean_path(path: &Path) -> PathBuf {
|
||||
use std::path::Component::*;
|
||||
|
||||
let mut out = vec![];
|
||||
|
|
@ -33,17 +37,20 @@ fn _clean_path(path: &Path) -> PathBuf {
|
|||
|
||||
// FIXME: VFS
|
||||
#[inline]
|
||||
pub fn expand_path(p: impl AsRef<Path>) -> PathBuf { _expand_path(p.as_ref()) }
|
||||
pub fn expand_path(p: impl AsRef<Path>) -> PathBuf {
|
||||
expand_url(Url::from(p.as_ref())).into_owned().loc.into_path()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn expand_url<'a>(url: impl Into<Cow<'a, Url>>) -> Cow<'a, Url> {
|
||||
let u: Cow<'a, Url> = url.into();
|
||||
let Some(p) = u.as_path() else { return u };
|
||||
|
||||
Url { loc: Loc::with(u.loc.base(), _expand_path(p)), scheme: u.scheme.clone() }.into()
|
||||
let cow: Cow<'a, Url> = url.into();
|
||||
match _expand_url(&cow) {
|
||||
Cow::Borrowed(_) => cow,
|
||||
Cow::Owned(url) => url.into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn _expand_path(p: &Path) -> PathBuf {
|
||||
fn _expand_url(url: &Url) -> Cow<'_, Url> {
|
||||
// ${HOME} or $HOME
|
||||
#[cfg(unix)]
|
||||
let re = regex::bytes::Regex::new(r"\$(?:\{([^}]+)\}|([a-zA-Z\d_]+))").unwrap();
|
||||
|
|
@ -52,7 +59,16 @@ fn _expand_path(p: &Path) -> PathBuf {
|
|||
#[cfg(windows)]
|
||||
let re = regex::bytes::Regex::new(r"%([^%]+)%").unwrap();
|
||||
|
||||
let b = re.replace_all(p.as_os_str().as_encoded_bytes(), |caps: ®ex::bytes::Captures| {
|
||||
let b = url.loc.as_os_str().as_encoded_bytes();
|
||||
let local = !url.scheme.is_virtual();
|
||||
|
||||
// Windows paths that only have a drive letter but no root, e.g. "D:"
|
||||
#[cfg(windows)]
|
||||
if local && b.len() == 2 && b[1] == b':' && b[0].is_ascii_alphabetic() {
|
||||
return url.with(format!(r"{}:\", b[0].to_ascii_uppercase() as char)).into();
|
||||
}
|
||||
|
||||
let b = re.replace_all(b, |caps: ®ex::bytes::Captures| {
|
||||
let name = caps.get(2).or_else(|| caps.get(1)).unwrap();
|
||||
str::from_utf8(name.as_bytes())
|
||||
.ok()
|
||||
|
|
@ -60,21 +76,19 @@ fn _expand_path(p: &Path) -> PathBuf {
|
|||
.map_or_else(|| caps.get(0).unwrap().as_bytes().to_owned(), |s| s.into_encoded_bytes())
|
||||
});
|
||||
|
||||
// Windows paths that only have a drive letter but no root, e.g. "D:"
|
||||
#[cfg(windows)]
|
||||
if b.len() == 2 {
|
||||
if b[1] == b':' && b[0].is_ascii_alphabetic() {
|
||||
return PathBuf::from(format!("{}:\\", b[0].to_ascii_uppercase() as char));
|
||||
let path: Cow<_> = unsafe {
|
||||
match b {
|
||||
Cow::Borrowed(b) => Path::new(OsStr::from_encoded_bytes_unchecked(b)).into(),
|
||||
Cow::Owned(b) => PathBuf::from(OsString::from_encoded_bytes_unchecked(b)).into(),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let p = unsafe { Path::new(OsStr::from_encoded_bytes_unchecked(b.as_ref())) };
|
||||
if let Ok(rest) = p.strip_prefix("~") {
|
||||
clean_path(dirs::home_dir().unwrap_or_default().join(rest))
|
||||
} else if p.is_absolute() {
|
||||
clean_path(p)
|
||||
if let Some(rest) = path.strip_prefix("~").ok().filter(|_| local) {
|
||||
url.with(clean_path(&dirs::home_dir().unwrap_or_default().join(rest))).into()
|
||||
} else if path.is_absolute() {
|
||||
url.with(clean_path(&path)).into()
|
||||
} else {
|
||||
clean_path(CWD.load().join(p))
|
||||
clean_url(CWD.load().join(path))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -150,7 +164,7 @@ pub fn url_relative_to<'a>(from: &Url, to: &'a Url) -> Result<Cow<'a, Url>> {
|
|||
}
|
||||
|
||||
if from.covariant(to) {
|
||||
return Ok(Url { loc: Path::new(".").into(), scheme: to.scheme.clone() }.into());
|
||||
return Ok(to.with(Path::new(".")).into());
|
||||
}
|
||||
|
||||
let (mut f_it, mut t_it) = (from.components(), to.components());
|
||||
|
|
@ -172,7 +186,7 @@ pub fn url_relative_to<'a>(from: &Url, to: &'a Url) -> Result<Cow<'a, Url>> {
|
|||
let rest = t_head.into_iter().chain(t_it);
|
||||
|
||||
let buf: PathBuf = dots.chain(rest).collect();
|
||||
Ok(Url { loc: buf.into(), scheme: to.scheme.clone() }.into())
|
||||
Ok(to.with(buf).into())
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
use std::{ffi::OsString, path::{MAIN_SEPARATOR_STR, PathBuf}};
|
||||
use std::{ffi::OsString, path::MAIN_SEPARATOR_STR};
|
||||
|
||||
use yazi_shared::{Id, SStr, event::CmdCow};
|
||||
use anyhow::bail;
|
||||
use yazi_shared::{Id, event::CmdCow, url::{Url, UrnBuf}};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ShowOpt {
|
||||
pub cache: Vec<CmpItem>,
|
||||
pub cache_name: PathBuf,
|
||||
pub word: SStr,
|
||||
pub cache_name: Url,
|
||||
pub word: UrnBuf,
|
||||
pub ticket: Id,
|
||||
}
|
||||
|
||||
|
|
@ -15,15 +16,10 @@ impl TryFrom<CmdCow> for ShowOpt {
|
|||
|
||||
fn try_from(mut c: CmdCow) -> Result<Self, Self::Error> {
|
||||
if let Some(opt) = c.take_any2("opt") {
|
||||
return opt;
|
||||
opt
|
||||
} else {
|
||||
bail!("missing 'opt' argument");
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
cache: c.take_any("cache").unwrap_or_default(),
|
||||
cache_name: c.take_any("cache-name").unwrap_or_default(),
|
||||
word: c.take_str("word").unwrap_or_default(),
|
||||
ticket: c.id("ticket").unwrap_or_default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -58,3 +58,38 @@ where
|
|||
result
|
||||
}
|
||||
}
|
||||
|
||||
// --- OsStrSplit
|
||||
pub trait OsStrSplit {
|
||||
fn rsplit_once<P: Pattern>(&self, predicate: P) -> Option<(&Self, &Self)>;
|
||||
}
|
||||
|
||||
impl OsStrSplit for OsStr {
|
||||
fn rsplit_once<P: Pattern>(&self, pat: P) -> Option<(&Self, &Self)> {
|
||||
let bytes = self.as_encoded_bytes();
|
||||
for (i, &byte) in bytes.iter().enumerate().rev() {
|
||||
if !pat.predicate(byte) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let (a, b) = bytes.split_at(i);
|
||||
// SAFETY: These substrings were separated by a UTF-8 string.
|
||||
return Some(unsafe {
|
||||
(OsStr::from_encoded_bytes_unchecked(a), OsStr::from_encoded_bytes_unchecked(&b[1..]))
|
||||
});
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Pattern {
|
||||
fn predicate(&self, byte: u8) -> bool;
|
||||
}
|
||||
|
||||
impl Pattern for char {
|
||||
fn predicate(&self, byte: u8) -> bool { *self == byte as char }
|
||||
}
|
||||
|
||||
impl Pattern for &[char] {
|
||||
fn predicate(&self, byte: u8) -> bool { self.contains(&(byte as char)) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,16 +47,12 @@ impl Debug for Loc {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<&Path> for Loc {
|
||||
fn from(value: &Path) -> Self { Self::from(value.to_path_buf()) }
|
||||
impl From<OsString> for Loc {
|
||||
fn from(value: OsString) -> Self { Self::from(PathBuf::from(value)) }
|
||||
}
|
||||
|
||||
impl From<Cow<'_, Path>> for Loc {
|
||||
fn from(value: Cow<'_, Path>) -> Self { Self::from(value.into_owned()) }
|
||||
}
|
||||
|
||||
impl From<Cow<'_, OsStr>> for Loc {
|
||||
fn from(value: Cow<'_, OsStr>) -> Self { Self::from(PathBuf::from(value.into_owned())) }
|
||||
impl From<String> for Loc {
|
||||
fn from(value: String) -> Self { Self::from(PathBuf::from(value)) }
|
||||
}
|
||||
|
||||
impl From<PathBuf> for Loc {
|
||||
|
|
@ -81,6 +77,14 @@ impl From<PathBuf> for Loc {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Cow<'_, Path>> for Loc {
|
||||
fn from(value: Cow<'_, Path>) -> Self { Self::from(value.into_owned()) }
|
||||
}
|
||||
|
||||
impl<T: ?Sized + AsRef<OsStr>> From<&T> for Loc {
|
||||
fn from(value: &T) -> Self { Self::from(value.as_ref().to_os_string()) }
|
||||
}
|
||||
|
||||
impl Loc {
|
||||
pub fn with(base: &Path, path: PathBuf) -> Self {
|
||||
let mut loc = Self::from(path);
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ impl Scheme {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn is_virtual(&self) -> bool {
|
||||
pub fn is_virtual(&self) -> bool {
|
||||
match self {
|
||||
Self::Regular | Self::Search(_) | Self::SearchItem => false,
|
||||
Self::Archive(_) | Self::Sftp(_) => true,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use std::{borrow::Cow, ffi::OsStr, fmt::{Debug, Formatter}, hash::BuildHasher, ops::Deref, path::{Path, PathBuf}};
|
||||
|
||||
use anyhow::Result;
|
||||
use percent_encoding::percent_decode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
|
@ -38,15 +39,8 @@ impl TryFrom<&[u8]> for Url {
|
|||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
|
||||
let (scheme, skip, tilde) = Scheme::parse(bytes)?;
|
||||
|
||||
let rest = &bytes[skip + tilde as usize..];
|
||||
|
||||
Ok(if tilde {
|
||||
Self { loc: Cow::from(percent_decode(rest)).into_os_str()?.into(), scheme }
|
||||
} else {
|
||||
Self { loc: rest.into_os_str()?.into(), scheme }
|
||||
})
|
||||
let (scheme, path) = Self::parse(bytes)?;
|
||||
Ok(Self { loc: path.into(), scheme })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -84,15 +78,22 @@ impl From<Cow<'_, Url>> for Url {
|
|||
}
|
||||
|
||||
impl Url {
|
||||
#[inline]
|
||||
pub fn with(&self, loc: impl Into<Loc>) -> Self {
|
||||
let loc: Loc = loc.into();
|
||||
// FIXME: simplify this
|
||||
Self { loc: Loc::with(self.loc.base(), loc.into_path()), scheme: self.scheme.clone() }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn base(&self) -> Url {
|
||||
let loc: Loc = self.loc.base().into();
|
||||
match &self.scheme {
|
||||
Scheme::Regular => Self { loc, scheme: Scheme::Regular },
|
||||
Scheme::Search(_) => Self { loc, scheme: self.scheme.clone() },
|
||||
Scheme::Search(_) => self.with(loc),
|
||||
Scheme::SearchItem => Self { loc, scheme: Scheme::Search(String::new()) },
|
||||
Scheme::Archive(_) => Self { loc, scheme: self.scheme.clone() },
|
||||
Scheme::Sftp(_) => Self { loc, scheme: self.scheme.clone() },
|
||||
Scheme::Archive(_) => self.with(loc),
|
||||
Scheme::Sftp(_) => self.with(loc),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -105,14 +106,11 @@ impl Url {
|
|||
Scheme::SearchItem => {
|
||||
Self { loc: Loc::with(self.loc.base(), self.loc.join(path)), scheme: Scheme::SearchItem }
|
||||
}
|
||||
Scheme::Archive(_) => {
|
||||
Self { loc: self.loc.join(path).into(), scheme: self.scheme.clone() }
|
||||
}
|
||||
Scheme::Sftp(_) => Self { loc: self.loc.join(path).into(), scheme: self.scheme.clone() },
|
||||
Scheme::Archive(_) => self.with(self.loc.join(path)),
|
||||
Scheme::Sftp(_) => self.with(self.loc.join(path)),
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: check usages
|
||||
#[inline]
|
||||
pub fn components(&self) -> Components<'_> { Components::new(self) }
|
||||
|
||||
|
|
@ -140,8 +138,8 @@ impl Url {
|
|||
Scheme::SearchItem => {
|
||||
Self { loc: Loc::with(base, parent.to_owned()), scheme: Scheme::SearchItem }
|
||||
}
|
||||
Scheme::Archive(_) => Self { loc: parent.into(), scheme: self.scheme.clone() },
|
||||
Scheme::Sftp(_) => Self { loc: parent.into(), scheme: self.scheme.clone() },
|
||||
Scheme::Archive(_) => self.with(parent),
|
||||
Scheme::Sftp(_) => self.with(parent),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -151,10 +149,7 @@ impl Url {
|
|||
return None;
|
||||
}
|
||||
|
||||
Some(Self {
|
||||
loc: self.loc.strip_prefix(&base.loc).ok()?.into(),
|
||||
scheme: self.scheme.clone(),
|
||||
})
|
||||
Some(self.with(self.loc.strip_prefix(&base.loc).ok()?))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
@ -176,6 +171,19 @@ impl Url {
|
|||
debug_assert!(self.is_regular());
|
||||
self.loc.rebase(parent).into()
|
||||
}
|
||||
|
||||
pub fn parse(bytes: &[u8]) -> Result<(Scheme, Cow<'_, Path>)> {
|
||||
let (scheme, skip, tilde) = Scheme::parse(bytes)?;
|
||||
|
||||
let rest = &bytes[skip + tilde as usize..];
|
||||
let rest =
|
||||
if tilde { Cow::from(percent_decode(rest)).into_os_str()? } else { rest.into_os_str()? };
|
||||
|
||||
Ok((scheme, match rest {
|
||||
Cow::Borrowed(s) => Path::new(s).into(),
|
||||
Cow::Owned(s) => PathBuf::from(s).into(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl Url {
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ impl PartialEq<Cow<'_, OsStr>> for &Urn {
|
|||
}
|
||||
|
||||
// --- UrnBuf
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize)]
|
||||
#[derive(Clone, Debug, Default, Eq, Hash, PartialEq, Serialize)]
|
||||
pub struct UrnBuf(PathBuf);
|
||||
|
||||
impl Borrow<Urn> for UrnBuf {
|
||||
|
|
@ -81,4 +81,7 @@ impl<T: Into<PathBuf>> From<T> for UrnBuf {
|
|||
impl UrnBuf {
|
||||
#[inline]
|
||||
pub fn as_urn(&self) -> &Urn { self.borrow() }
|
||||
|
||||
#[inline]
|
||||
pub fn as_os_str(&self) -> &OsStr { self.0.as_os_str() }
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue