mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
feat: allow Url to accept invalid UTF-8 characters (#2889)
This commit is contained in:
parent
c8148d2d75
commit
ec90f26ac3
14 changed files with 222 additions and 162 deletions
|
|
@ -1,6 +1,7 @@
|
||||||
use std::{ops::Deref, path::Path};
|
use std::{ops::Deref, path::Path};
|
||||||
|
|
||||||
use mlua::{AnyUserData, ExternalError, FromLua, Lua, MetaMethod, UserData, UserDataFields, UserDataMethods, UserDataRef, Value};
|
use mlua::{AnyUserData, ExternalError, FromLua, Lua, MetaMethod, UserData, UserDataFields, UserDataMethods, UserDataRef, Value};
|
||||||
|
use yazi_shared::IntoOsStr;
|
||||||
|
|
||||||
use crate::{Urn, cached_field};
|
use crate::{Urn, cached_field};
|
||||||
|
|
||||||
|
|
@ -32,6 +33,14 @@ impl From<Url> for yazi_shared::url::Url {
|
||||||
fn from(value: Url) -> Self { value.inner }
|
fn from(value: Url) -> Self { value.inner }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&[u8]> for Url {
|
||||||
|
type Error = mlua::Error;
|
||||||
|
|
||||||
|
fn try_from(value: &[u8]) -> mlua::Result<Self> {
|
||||||
|
Ok(Self::new(yazi_shared::url::Url::try_from(value)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Url {
|
impl Url {
|
||||||
pub fn new(url: impl Into<yazi_shared::url::Url>) -> Self {
|
pub fn new(url: impl Into<yazi_shared::url::Url>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|
@ -52,7 +61,7 @@ impl Url {
|
||||||
"Url",
|
"Url",
|
||||||
lua.create_function(|_, value: Value| {
|
lua.create_function(|_, value: Value| {
|
||||||
Ok(match value {
|
Ok(match value {
|
||||||
Value::String(s) => Self::new(s.to_str()?.as_ref()),
|
Value::String(s) => Self::try_from(s.as_bytes().as_ref())?,
|
||||||
Value::UserData(ud) => Self::new(ud.borrow::<Self>()?.inner.clone()),
|
Value::UserData(ud) => Self::new(ud.borrow::<Self>()?.inner.clone()),
|
||||||
_ => Err("Expected a string or a Url".into_lua_err())?,
|
_ => Err("Expected a string or a Url".into_lua_err())?,
|
||||||
})
|
})
|
||||||
|
|
@ -89,7 +98,7 @@ impl UserData for Url {
|
||||||
cached_field!(fields, base, |_, me| {
|
cached_field!(fields, base, |_, me| {
|
||||||
Ok(if me.base().as_os_str().is_empty() { None } else { Some(Self::new(me.base())) })
|
Ok(if me.base().as_os_str().is_empty() { None } else { Some(Self::new(me.base())) })
|
||||||
});
|
});
|
||||||
cached_field!(fields, frag, |lua, me| lua.create_string(me.frag()));
|
cached_field!(fields, frag, |lua, me| lua.create_string(me.frag().as_encoded_bytes()));
|
||||||
|
|
||||||
fields.add_field_method_get("is_regular", |_, me| Ok(me.is_regular()));
|
fields.add_field_method_get("is_regular", |_, me| Ok(me.is_regular()));
|
||||||
fields.add_field_method_get("is_search", |_, me| Ok(me.is_search()));
|
fields.add_field_method_get("is_search", |_, me| Ok(me.is_search()));
|
||||||
|
|
@ -130,7 +139,7 @@ impl UserData for Url {
|
||||||
});
|
});
|
||||||
|
|
||||||
methods.add_function_mut("into_search", |_, (ud, frag): (AnyUserData, mlua::String)| {
|
methods.add_function_mut("into_search", |_, (ud, frag): (AnyUserData, mlua::String)| {
|
||||||
Ok(Self::new(ud.take::<Self>()?.inner.into_search(&frag.to_str()?)))
|
Ok(Self::new(ud.take::<Self>()?.inner.into_search(frag.as_bytes().as_ref().into_os_str()?)))
|
||||||
});
|
});
|
||||||
|
|
||||||
methods.add_meta_method(MetaMethod::Eq, |_, me, other: UrlRef| Ok(me.inner == other.inner));
|
methods.add_meta_method(MetaMethod::Eq, |_, me, other: UrlRef| Ok(me.inner == other.inner));
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ impl Mgr {
|
||||||
let updates = opt
|
let updates = opt
|
||||||
.updates
|
.updates
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(url, mime)| (Url::from(url.as_ref()), mime))
|
.flat_map(|(url, mime)| Url::try_from(url.as_ref()).map(|u| (u, mime)))
|
||||||
.filter(|(url, mime)| self.mimetype.by_url(url) != Some(mime))
|
.filter(|(url, mime)| self.mimetype.by_url(url) != Some(mime))
|
||||||
.fold(HashMap::new(), |mut map, (u, m)| {
|
.fold(HashMap::new(), |mut map, (u, m)| {
|
||||||
for u in linked.from_file(&u) {
|
for u in linked.from_file(&u) {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use std::{borrow::Cow, collections::HashMap, path::PathBuf};
|
use std::{borrow::Cow, collections::HashMap, path::PathBuf};
|
||||||
|
|
||||||
use yazi_fs::File;
|
use yazi_fs::File;
|
||||||
use yazi_shared::{MIME_DIR, url::{Url, UrlScheme}};
|
use yazi_shared::{MIME_DIR, url::{Scheme, Url}};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Mimetype(HashMap<PathBuf, String>);
|
pub struct Mimetype(HashMap<PathBuf, String>);
|
||||||
|
|
@ -10,10 +10,10 @@ impl Mimetype {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn by_url(&self, url: &Url) -> Option<&str> {
|
pub fn by_url(&self, url: &Url) -> Option<&str> {
|
||||||
match url.scheme() {
|
match url.scheme() {
|
||||||
UrlScheme::Regular => self.0.get(url.as_path()),
|
Scheme::Regular => self.0.get(url.as_path()),
|
||||||
UrlScheme::Search => None,
|
Scheme::Search => None,
|
||||||
UrlScheme::SearchItem => self.0.get(url.as_path()),
|
Scheme::SearchItem => self.0.get(url.as_path()),
|
||||||
UrlScheme::Archive => None,
|
Scheme::Archive => None,
|
||||||
}
|
}
|
||||||
.map(|s| s.as_str())
|
.map(|s| s.as_str())
|
||||||
}
|
}
|
||||||
|
|
@ -36,10 +36,10 @@ impl Mimetype {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn contains(&self, url: &Url) -> bool {
|
pub fn contains(&self, url: &Url) -> bool {
|
||||||
match url.scheme() {
|
match url.scheme() {
|
||||||
UrlScheme::Regular => self.0.contains_key(url.as_path()),
|
Scheme::Regular => self.0.contains_key(url.as_path()),
|
||||||
UrlScheme::Search => false,
|
Scheme::Search => false,
|
||||||
UrlScheme::SearchItem => self.0.contains_key(url.as_path()),
|
Scheme::SearchItem => self.0.contains_key(url.as_path()),
|
||||||
UrlScheme::Archive => false,
|
Scheme::Archive => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -47,10 +47,10 @@ impl Mimetype {
|
||||||
self.0.extend(iter.into_iter().filter_map(|(u, s)| {
|
self.0.extend(iter.into_iter().filter_map(|(u, s)| {
|
||||||
Some((
|
Some((
|
||||||
match u.scheme() {
|
match u.scheme() {
|
||||||
UrlScheme::Regular => u.into_path(),
|
Scheme::Regular => u.into_path(),
|
||||||
UrlScheme::Search => None?,
|
Scheme::Search => None?,
|
||||||
UrlScheme::SearchItem => u.into_path(),
|
Scheme::SearchItem => u.into_path(),
|
||||||
UrlScheme::Archive => None?,
|
Scheme::Archive => None?,
|
||||||
},
|
},
|
||||||
s,
|
s,
|
||||||
))
|
))
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ impl Tab {
|
||||||
handle.abort();
|
handle.abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
let cwd = self.cwd().to_search(&opt.subject);
|
let cwd = self.cwd().to_search(opt.subject.as_ref());
|
||||||
let hidden = self.pref.show_hidden;
|
let hidden = self.pref.show_hidden;
|
||||||
|
|
||||||
self.search = Some(tokio::spawn(async move {
|
self.search = Some(tokio::spawn(async move {
|
||||||
|
|
|
||||||
|
|
@ -118,12 +118,14 @@ impl Selected {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
fn url(s: &str) -> Url { Url::try_from(s).unwrap() }
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_insert_non_conflicting() {
|
fn test_insert_non_conflicting() {
|
||||||
let mut s = Selected::default();
|
let mut s = Selected::default();
|
||||||
|
|
||||||
assert!(s.add(&Url::from("/a/b")));
|
assert!(s.add(&url("/a/b")));
|
||||||
assert!(s.add(&Url::from("/c/d")));
|
assert!(s.add(&url("/c/d")));
|
||||||
assert_eq!(s.inner.len(), 2);
|
assert_eq!(s.inner.len(), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -131,27 +133,27 @@ mod tests {
|
||||||
fn test_insert_conflicting_parent() {
|
fn test_insert_conflicting_parent() {
|
||||||
let mut s = Selected::default();
|
let mut s = Selected::default();
|
||||||
|
|
||||||
assert!(s.add(&Url::from("/a")));
|
assert!(s.add(&url("/a")));
|
||||||
assert!(!s.add(&Url::from("/a/b")));
|
assert!(!s.add(&url("/a/b")));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_insert_conflicting_child() {
|
fn test_insert_conflicting_child() {
|
||||||
let mut s = Selected::default();
|
let mut s = Selected::default();
|
||||||
|
|
||||||
assert!(s.add(&Url::from("/a/b/c")));
|
assert!(s.add(&url("/a/b/c")));
|
||||||
assert!(!s.add(&Url::from("/a/b")));
|
assert!(!s.add(&url("/a/b")));
|
||||||
assert!(s.add(&Url::from("/a/b/d")));
|
assert!(s.add(&url("/a/b/d")));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_remove() {
|
fn test_remove() {
|
||||||
let mut s = Selected::default();
|
let mut s = Selected::default();
|
||||||
|
|
||||||
assert!(s.add(&Url::from("/a/b")));
|
assert!(s.add(&url("/a/b")));
|
||||||
assert!(!s.remove(&Url::from("/a/c")));
|
assert!(!s.remove(&url("/a/c")));
|
||||||
assert!(s.remove(&Url::from("/a/b")));
|
assert!(s.remove(&url("/a/b")));
|
||||||
assert!(!s.remove(&Url::from("/a/b")));
|
assert!(!s.remove(&url("/a/b")));
|
||||||
assert!(s.inner.is_empty());
|
assert!(s.inner.is_empty());
|
||||||
assert!(s.parents.is_empty());
|
assert!(s.parents.is_empty());
|
||||||
}
|
}
|
||||||
|
|
@ -162,11 +164,7 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
3,
|
3,
|
||||||
s.add_same(&[
|
s.add_same(&[&url("/parent/child1"), &url("/parent/child2"), &url("/parent/child3")])
|
||||||
&Url::from("/parent/child1"),
|
|
||||||
&Url::from("/parent/child2"),
|
|
||||||
&Url::from("/parent/child3")
|
|
||||||
])
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -174,16 +172,16 @@ mod tests {
|
||||||
fn insert_many_with_existing_parent_fails() {
|
fn insert_many_with_existing_parent_fails() {
|
||||||
let mut s = Selected::default();
|
let mut s = Selected::default();
|
||||||
|
|
||||||
s.add(&Url::from("/parent"));
|
s.add(&url("/parent"));
|
||||||
assert_eq!(0, s.add_same(&[&Url::from("/parent/child1"), &Url::from("/parent/child2")]));
|
assert_eq!(0, s.add_same(&[&url("/parent/child1"), &url("/parent/child2")]));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn insert_many_with_existing_child_fails() {
|
fn insert_many_with_existing_child_fails() {
|
||||||
let mut s = Selected::default();
|
let mut s = Selected::default();
|
||||||
|
|
||||||
s.add(&Url::from("/parent/child1"));
|
s.add(&url("/parent/child1"));
|
||||||
assert_eq!(2, s.add_same(&[&Url::from("/parent/child1"), &Url::from("/parent/child2")]));
|
assert_eq!(2, s.add_same(&[&url("/parent/child1"), &url("/parent/child2")]));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -197,51 +195,48 @@ mod tests {
|
||||||
fn insert_many_with_parent_as_child_of_another_url() {
|
fn insert_many_with_parent_as_child_of_another_url() {
|
||||||
let mut s = Selected::default();
|
let mut s = Selected::default();
|
||||||
|
|
||||||
s.add(&Url::from("/parent/child"));
|
s.add(&url("/parent/child"));
|
||||||
assert_eq!(
|
assert_eq!(0, s.add_same(&[&url("/parent/child/child1"), &url("/parent/child/child2")]));
|
||||||
0,
|
|
||||||
s.add_same(&[&Url::from("/parent/child/child1"), &Url::from("/parent/child/child2")])
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn insert_many_with_direct_parent_fails() {
|
fn insert_many_with_direct_parent_fails() {
|
||||||
let mut s = Selected::default();
|
let mut s = Selected::default();
|
||||||
|
|
||||||
s.add(&Url::from("/a"));
|
s.add(&url("/a"));
|
||||||
assert_eq!(0, s.add_same(&[&Url::from("/a/b")]));
|
assert_eq!(0, s.add_same(&[&url("/a/b")]));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn insert_many_with_nested_child_fails() {
|
fn insert_many_with_nested_child_fails() {
|
||||||
let mut s = Selected::default();
|
let mut s = Selected::default();
|
||||||
|
|
||||||
s.add(&Url::from("/a/b"));
|
s.add(&url("/a/b"));
|
||||||
assert_eq!(0, s.add_same(&[&Url::from("/a")]));
|
assert_eq!(0, s.add_same(&[&url("/a")]));
|
||||||
assert_eq!(1, s.add_same(&[&Url::from("/b"), &Url::from("/a")]));
|
assert_eq!(1, s.add_same(&[&url("/b"), &url("/a")]));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn insert_many_sibling_directories_success() {
|
fn insert_many_sibling_directories_success() {
|
||||||
let mut s = Selected::default();
|
let mut s = Selected::default();
|
||||||
|
|
||||||
assert_eq!(2, s.add_same(&[&Url::from("/a/b"), &Url::from("/a/c")]));
|
assert_eq!(2, s.add_same(&[&url("/a/b"), &url("/a/c")]));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn insert_many_with_grandchild_fails() {
|
fn insert_many_with_grandchild_fails() {
|
||||||
let mut s = Selected::default();
|
let mut s = Selected::default();
|
||||||
|
|
||||||
s.add(&Url::from("/a/b"));
|
s.add(&url("/a/b"));
|
||||||
assert_eq!(0, s.add_same(&[&Url::from("/a/b/c")]));
|
assert_eq!(0, s.add_same(&[&url("/a/b/c")]));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_insert_many_with_remove() {
|
fn test_insert_many_with_remove() {
|
||||||
let mut s = Selected::default();
|
let mut s = Selected::default();
|
||||||
|
|
||||||
let child1 = Url::from("/parent/child1");
|
let child1 = url("/parent/child1");
|
||||||
let child2 = Url::from("/parent/child2");
|
let child2 = url("/parent/child2");
|
||||||
let child3 = Url::from("/parent/child3");
|
let child3 = url("/parent/child3");
|
||||||
assert_eq!(3, s.add_same(&[&child1, &child2, &child3]));
|
assert_eq!(3, s.add_same(&[&child1, &child2, &child3]));
|
||||||
|
|
||||||
assert!(s.remove(&child1));
|
assert!(s.remove(&child1));
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use std::path::{MAIN_SEPARATOR_STR, Path};
|
use std::path::MAIN_SEPARATOR_STR;
|
||||||
|
|
||||||
use ratatui::{buffer::Buffer, layout::Rect, widgets::{Block, BorderType, List, ListItem, Widget}};
|
use ratatui::{buffer::Buffer, layout::Rect, widgets::{Block, BorderType, List, ListItem, Widget}};
|
||||||
use yazi_adapter::Dimension;
|
use yazi_adapter::Dimension;
|
||||||
|
|
@ -26,8 +26,7 @@ impl Widget for Cmp<'_> {
|
||||||
let icon = if x.is_dir { &THEME.cmp.icon_folder } else { &THEME.cmp.icon_file };
|
let icon = if x.is_dir { &THEME.cmp.icon_folder } else { &THEME.cmp.icon_file };
|
||||||
let slash = if x.is_dir { MAIN_SEPARATOR_STR } else { "" };
|
let slash = if x.is_dir { MAIN_SEPARATOR_STR } else { "" };
|
||||||
|
|
||||||
// FIXME: Bump Rust to 1.87.0 and use `OsStr::display()` instead
|
let mut item = ListItem::new(format!(" {icon} {}{slash}", x.name.display()));
|
||||||
let mut item = ListItem::new(format!(" {icon} {}{slash}", Path::new(&x.name).display()));
|
|
||||||
if i == self.cx.cmp.rel_cursor() {
|
if i == self.cx.cmp.rel_cursor() {
|
||||||
item = item.style(THEME.cmp.active);
|
item = item.style(THEME.cmp.active);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ impl TabProxy {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reveal(target: &Url) {
|
pub fn reveal(target: &Url) {
|
||||||
emit!(Call(Cmd::args("mgr:reveal", [target]).with("no-dummy", true)));
|
emit!(Call(Cmd::args("mgr:reveal", [target]).with("raw", true).with("no-dummy", true)));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn arrow(step: impl Into<Cow<'static, str>>) {
|
pub fn arrow(step: impl Into<Cow<'static, str>>) {
|
||||||
|
|
|
||||||
11
yazi-shared/src/bytes.rs
Normal file
11
yazi-shared/src/bytes.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
pub trait BytesExt {
|
||||||
|
fn split_by_seq(&self, sep: &[u8]) -> Option<(&[u8], &[u8])>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BytesExt for [u8] {
|
||||||
|
fn split_by_seq(&self, sep: &[u8]) -> Option<(&[u8], &[u8])> {
|
||||||
|
let idx = memchr::memmem::find(self, sep)?;
|
||||||
|
let (left, right) = self.split_at(idx);
|
||||||
|
Some((left, &right[sep.len()..]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -56,7 +56,7 @@ impl Data {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn into_url(self) -> Option<Url> {
|
pub fn into_url(self) -> Option<Url> {
|
||||||
match self {
|
match self {
|
||||||
Data::String(s) => Some(Url::from(s.as_ref())),
|
Data::String(s) => Url::try_from(s.as_ref()).ok(),
|
||||||
Data::Url(u) => Some(u),
|
Data::Url(u) => Some(u),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
|
|
@ -79,7 +79,7 @@ impl Data {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn to_url(&self) -> Option<Url> {
|
pub fn to_url(&self) -> Option<Url> {
|
||||||
match self {
|
match self {
|
||||||
Self::String(s) => Some(Url::from(s.as_ref())),
|
Self::String(s) => Url::try_from(s.as_ref()).ok(),
|
||||||
Self::Url(u) => Some(u.clone()),
|
Self::Url(u) => Some(u.clone()),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
yazi_macro::mod_pub!(errors event shell translit url);
|
yazi_macro::mod_pub!(errors event shell translit url);
|
||||||
|
|
||||||
yazi_macro::mod_flat!(chars condition debounce either env id layer natsort number os rand ro_cell sync_cell terminal throttle time utf8);
|
yazi_macro::mod_flat!(bytes chars condition debounce either env id layer natsort number os osstr rand ro_cell sync_cell terminal throttle time utf8);
|
||||||
|
|
||||||
pub fn init() {
|
pub fn init() {
|
||||||
LOG_LEVEL.replace(<_>::from(std::env::var("YAZI_LOG").unwrap_or_default()));
|
LOG_LEVEL.replace(<_>::from(std::env::var("YAZI_LOG").unwrap_or_default()));
|
||||||
|
|
|
||||||
35
yazi-shared/src/osstr.rs
Normal file
35
yazi-shared/src/osstr.rs
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
use std::{borrow::Cow, ffi::{OsStr, OsString}};
|
||||||
|
|
||||||
|
pub trait IntoOsStr<'a> {
|
||||||
|
type Error;
|
||||||
|
|
||||||
|
fn into_os_str(self) -> Result<Cow<'a, OsStr>, Self::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoOsStr<'a> for Cow<'a, [u8]> {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn into_os_str(self) -> Result<Cow<'a, OsStr>, Self::Error> {
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
use std::os::unix::ffi::{OsStrExt, OsStringExt};
|
||||||
|
Ok(match self {
|
||||||
|
Cow::Borrowed(b) => Cow::Borrowed(OsStr::from_bytes(b)),
|
||||||
|
Cow::Owned(b) => Cow::Owned(OsString::from_vec(b)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
Ok(match self {
|
||||||
|
Cow::Borrowed(b) => Cow::Borrowed(OsStr::new(str::from_utf8(b)?)),
|
||||||
|
Cow::Owned(b) => Cow::Owned(OsString::from(String::from_utf8(b)?)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoOsStr<'a> for &'a [u8] {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn into_os_str(self) -> Result<Cow<'a, OsStr>, Self::Error> { Cow::Borrowed(self).into_os_str() }
|
||||||
|
}
|
||||||
|
|
@ -1 +1 @@
|
||||||
yazi_macro::mod_flat!(loc url urn);
|
yazi_macro::mod_flat!(loc scheme url urn);
|
||||||
|
|
|
||||||
36
yazi-shared/src/url/scheme.rs
Normal file
36
yazi-shared/src/url/scheme.rs
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
use anyhow::bail;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||||
|
pub enum Scheme {
|
||||||
|
#[default]
|
||||||
|
Regular,
|
||||||
|
Search,
|
||||||
|
SearchItem,
|
||||||
|
Archive,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&[u8]> for Scheme {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
|
||||||
|
Ok(match value {
|
||||||
|
b"regular" => Scheme::Regular,
|
||||||
|
b"search" => Scheme::Search,
|
||||||
|
b"archive" => Scheme::Archive,
|
||||||
|
_ => bail!("Unknown URL scheme: {}", String::from_utf8_lossy(value)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Scheme {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Scheme::Regular => write!(f, "regular"),
|
||||||
|
Scheme::Search => write!(f, "search"),
|
||||||
|
Scheme::SearchItem => write!(f, "search_item"),
|
||||||
|
Scheme::Archive => write!(f, "archive"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,27 +1,18 @@
|
||||||
use std::{ffi::OsStr, fmt::{Debug, Display, Formatter}, hash::{BuildHasher, Hash, Hasher}, ops::Deref, path::{Path, PathBuf}};
|
use std::{borrow::Cow, ffi::{OsStr, OsString}, fmt::{Debug, Display, Formatter}, hash::{BuildHasher, Hash, Hasher}, ops::Deref, path::{Path, PathBuf}};
|
||||||
|
|
||||||
use percent_encoding::{AsciiSet, CONTROLS, percent_decode_str, percent_encode};
|
use percent_encoding::{AsciiSet, CONTROLS, percent_decode, percent_encode};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::UrnBuf;
|
use super::UrnBuf;
|
||||||
use crate::url::Loc;
|
use crate::{BytesExt, IntoOsStr, url::{Loc, Scheme}};
|
||||||
|
|
||||||
const ENCODE_SET: &AsciiSet = &CONTROLS.add(b'#');
|
const ENCODE_SET: &AsciiSet = &CONTROLS.add(b'#');
|
||||||
|
|
||||||
#[derive(Clone, Default, Eq, Ord, PartialOrd)]
|
#[derive(Clone, Default, Eq, Ord, PartialOrd)]
|
||||||
pub struct Url {
|
pub struct Url {
|
||||||
loc: Loc,
|
loc: Loc,
|
||||||
scheme: UrlScheme,
|
scheme: Scheme,
|
||||||
frag: String,
|
frag: OsString,
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
|
||||||
pub enum UrlScheme {
|
|
||||||
#[default]
|
|
||||||
Regular,
|
|
||||||
Search,
|
|
||||||
SearchItem,
|
|
||||||
Archive,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for Url {
|
impl Deref for Url {
|
||||||
|
|
@ -33,10 +24,10 @@ impl Deref for Url {
|
||||||
impl Debug for Url {
|
impl Debug for Url {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
match self.scheme {
|
match self.scheme {
|
||||||
UrlScheme::Regular => write!(f, "Regular({:?})", self.loc),
|
Scheme::Regular => write!(f, "Regular({:?})", self.loc),
|
||||||
UrlScheme::Search => write!(f, "Search({:?}, {})", self.loc, self.frag),
|
Scheme::Search => write!(f, "Search({:?}, {})", self.loc, self.frag.display()),
|
||||||
UrlScheme::SearchItem => write!(f, "SearchItem({:?})", self.loc),
|
Scheme::SearchItem => write!(f, "SearchItem({:?})", self.loc),
|
||||||
UrlScheme::Archive => write!(f, "Archive({:?})", self.loc),
|
Scheme::Archive => write!(f, "Archive({:?})", self.loc),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -57,44 +48,45 @@ impl From<&Path> for Url {
|
||||||
fn from(path: &Path) -> Self { path.to_owned().into() }
|
fn from(path: &Path) -> Self { path.to_owned().into() }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&str> for Url {
|
impl TryFrom<&[u8]> for Url {
|
||||||
fn from(mut path: &str) -> Self {
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
|
||||||
let mut url = Url::default();
|
let mut url = Url::default();
|
||||||
match path.split_once("://").map(|(a, b)| (UrlScheme::from(a), b)) {
|
let Some((scheme, rest)) = value.split_by_seq(b"://") else {
|
||||||
None => {
|
url.loc = Loc::new(value.into_os_str()?.into_owned().into());
|
||||||
url.loc = Loc::new(PathBuf::from(path));
|
return Ok(url);
|
||||||
return url;
|
};
|
||||||
}
|
|
||||||
Some((UrlScheme::Regular, b)) => {
|
url.scheme = Scheme::try_from(scheme)?;
|
||||||
url.loc = Loc::new(PathBuf::from(b));
|
if url.scheme == Scheme::Regular {
|
||||||
return url;
|
url.loc = Loc::new(rest.into_os_str()?.into_owned().into());
|
||||||
}
|
return Ok(url);
|
||||||
Some((a, b)) => {
|
|
||||||
url.scheme = a;
|
|
||||||
path = b;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
match path.split_once('#') {
|
|
||||||
None => {
|
let (loc, frag) = match rest.split_by_seq(b"#") {
|
||||||
// FIXME: use `Loc::from(base, path)` instead
|
None => (rest, OsString::new()),
|
||||||
url.loc = Loc::new(percent_decode_str(path).decode_utf8_lossy().into_owned().into());
|
Some((a, b)) => (a, b.into_os_str()?.into_owned()),
|
||||||
}
|
};
|
||||||
Some((a, b)) => {
|
|
||||||
// FIXME: use `Loc::from(base, path)` instead
|
// FIXME: use `Loc::from(base, path)` instead
|
||||||
url.loc = Loc::new(percent_decode_str(a).decode_utf8_lossy().into_owned().into());
|
url.loc = Loc::new(Cow::from(percent_decode(loc)).into_os_str()?.into_owned().into());
|
||||||
url.frag = b.to_string();
|
url.frag = frag;
|
||||||
}
|
|
||||||
}
|
Ok(url)
|
||||||
url
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<String> for Url {
|
impl TryFrom<&str> for Url {
|
||||||
fn from(path: String) -> Self { path.as_str().into() }
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn try_from(value: &str) -> Result<Self, Self::Error> { value.as_bytes().try_into() }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&String> for Url {
|
impl TryFrom<String> for Url {
|
||||||
fn from(path: &String) -> Self { path.as_str().into() }
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn try_from(value: String) -> Result<Self, Self::Error> { value.as_bytes().try_into() }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsRef<Url> for Url {
|
impl AsRef<Url> for Url {
|
||||||
|
|
@ -111,20 +103,15 @@ impl AsRef<OsStr> for Url {
|
||||||
|
|
||||||
impl Display for Url {
|
impl Display for Url {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
if matches!(self.scheme, UrlScheme::Regular | UrlScheme::SearchItem) {
|
if matches!(self.scheme, Scheme::Regular | Scheme::SearchItem) {
|
||||||
return write!(f, "{}", self.loc.display());
|
return write!(f, "{}", self.loc.display());
|
||||||
}
|
}
|
||||||
|
|
||||||
let scheme = match self.scheme {
|
let loc = percent_encode(self.loc.as_os_str().as_encoded_bytes(), ENCODE_SET);
|
||||||
UrlScheme::Regular | UrlScheme::SearchItem => unreachable!(),
|
write!(f, "{}://{loc}", self.scheme)?;
|
||||||
UrlScheme::Search => "search://",
|
|
||||||
UrlScheme::Archive => "archive://",
|
|
||||||
};
|
|
||||||
let path = percent_encode(self.loc.as_os_str().as_encoded_bytes(), ENCODE_SET);
|
|
||||||
|
|
||||||
write!(f, "{scheme}{path}")?;
|
|
||||||
if !self.frag.is_empty() {
|
if !self.frag.is_empty() {
|
||||||
write!(f, "#{}", self.frag)?;
|
write!(f, "#{}", percent_encode(self.frag.as_encoded_bytes(), ENCODE_SET))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -139,16 +126,16 @@ impl Url {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn join(&self, path: impl AsRef<Path>) -> Self {
|
pub fn join(&self, path: impl AsRef<Path>) -> Self {
|
||||||
match self.scheme {
|
match self.scheme {
|
||||||
UrlScheme::Regular => Self::from(self.loc.join(path)),
|
Scheme::Regular => Self::from(self.loc.join(path)),
|
||||||
UrlScheme::Search => {
|
Scheme::Search => {
|
||||||
let loc = Loc::from(&self.loc, self.loc.join(path));
|
let loc = Loc::from(&self.loc, self.loc.join(path));
|
||||||
Self::from(loc).into_search_item()
|
Self::from(loc).into_search_item()
|
||||||
}
|
}
|
||||||
UrlScheme::SearchItem => {
|
Scheme::SearchItem => {
|
||||||
let loc = Loc::from(self.loc.base(), self.loc.join(path));
|
let loc = Loc::from(self.loc.base(), self.loc.join(path));
|
||||||
Self::from(loc).into_search_item()
|
Self::from(loc).into_search_item()
|
||||||
}
|
}
|
||||||
UrlScheme::Archive => Self::from(self.loc.join(path)).into_archive(),
|
Scheme::Archive => Self::from(self.loc.join(path)).into_archive(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -156,15 +143,15 @@ impl Url {
|
||||||
pub fn parent_url(&self) -> Option<Url> {
|
pub fn parent_url(&self) -> Option<Url> {
|
||||||
let p = self.loc.parent()?;
|
let p = self.loc.parent()?;
|
||||||
Some(match self.scheme {
|
Some(match self.scheme {
|
||||||
UrlScheme::Regular | UrlScheme::Search => Self::from(p),
|
Scheme::Regular | Scheme::Search => Self::from(p),
|
||||||
UrlScheme::SearchItem => {
|
Scheme::SearchItem => {
|
||||||
if p == self.loc.base() {
|
if p == self.loc.base() {
|
||||||
Self::from(p).into_search("")
|
Self::from(p).into_search("")
|
||||||
} else {
|
} else {
|
||||||
Self::from(p).into_search_item()
|
Self::from(p).into_search_item()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
UrlScheme::Archive => Self::from(p),
|
Scheme::Archive => Self::from(p),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -184,58 +171,56 @@ impl Url {
|
||||||
impl Url {
|
impl Url {
|
||||||
// --- Regular
|
// --- Regular
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_regular(&self) -> bool { self.scheme == UrlScheme::Regular }
|
pub fn is_regular(&self) -> bool { self.scheme == Scheme::Regular }
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn to_regular(&self) -> Self {
|
pub fn to_regular(&self) -> Self { Self { loc: self.loc.clone(), ..Default::default() } }
|
||||||
Self { loc: self.loc.clone(), scheme: UrlScheme::Regular, frag: String::new() }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn into_regular(mut self) -> Self {
|
pub fn into_regular(mut self) -> Self {
|
||||||
self.scheme = UrlScheme::Regular;
|
self.scheme = Scheme::Regular;
|
||||||
self.frag = String::new();
|
self.frag = OsString::new();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Search
|
// --- Search
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_search(&self) -> bool { self.scheme == UrlScheme::Search }
|
pub fn is_search(&self) -> bool { self.scheme == Scheme::Search }
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn to_search(&self, frag: &str) -> Self {
|
pub fn to_search(&self, frag: impl AsRef<OsStr>) -> Self {
|
||||||
Self { loc: self.loc.clone(), scheme: UrlScheme::Search, frag: frag.to_owned() }
|
Self { loc: self.loc.clone(), scheme: Scheme::Search, frag: frag.as_ref().to_owned() }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn into_search(mut self, frag: &str) -> Self {
|
pub fn into_search(mut self, frag: impl AsRef<OsStr>) -> Self {
|
||||||
self.scheme = UrlScheme::Search;
|
self.scheme = Scheme::Search;
|
||||||
self.frag = frag.to_owned();
|
self.frag = frag.as_ref().to_owned();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_search_item(&self) -> bool { self.scheme == UrlScheme::SearchItem }
|
pub fn is_search_item(&self) -> bool { self.scheme == Scheme::SearchItem }
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn into_search_item(mut self) -> Self {
|
pub fn into_search_item(mut self) -> Self {
|
||||||
self.scheme = UrlScheme::SearchItem;
|
self.scheme = Scheme::SearchItem;
|
||||||
self.frag = String::new();
|
self.frag = OsString::new();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_archive(&self) -> bool { self.scheme == UrlScheme::Archive }
|
pub fn is_archive(&self) -> bool { self.scheme == Scheme::Archive }
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn to_archive(&self) -> Self {
|
pub fn to_archive(&self) -> Self {
|
||||||
Self { loc: self.loc.clone(), scheme: UrlScheme::Archive, frag: String::new() }
|
Self { loc: self.loc.clone(), scheme: Scheme::Archive, ..Default::default() }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn into_archive(mut self) -> Self {
|
pub fn into_archive(mut self) -> Self {
|
||||||
self.scheme = UrlScheme::Archive;
|
self.scheme = Scheme::Archive;
|
||||||
self.frag = String::new();
|
self.frag = OsString::new();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -251,21 +236,11 @@ impl Url {
|
||||||
|
|
||||||
// --- Scheme
|
// --- Scheme
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn scheme(&self) -> UrlScheme { self.scheme }
|
pub fn scheme(&self) -> Scheme { self.scheme }
|
||||||
|
|
||||||
// --- Frag
|
// --- Frag
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn frag(&self) -> &str { &self.frag }
|
pub fn frag(&self) -> &OsStr { &self.frag }
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&str> for UrlScheme {
|
|
||||||
fn from(value: &str) -> Self {
|
|
||||||
match value {
|
|
||||||
"search" => UrlScheme::Search,
|
|
||||||
"archive" => UrlScheme::Archive,
|
|
||||||
_ => UrlScheme::Regular,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hash for Url {
|
impl Hash for Url {
|
||||||
|
|
@ -273,8 +248,8 @@ impl Hash for Url {
|
||||||
self.loc.hash(state);
|
self.loc.hash(state);
|
||||||
|
|
||||||
match self.scheme {
|
match self.scheme {
|
||||||
UrlScheme::Regular | UrlScheme::SearchItem => {}
|
Scheme::Regular | Scheme::SearchItem => {}
|
||||||
UrlScheme::Search | UrlScheme::Archive => {
|
Scheme::Search | Scheme::Archive => {
|
||||||
self.scheme.hash(state);
|
self.scheme.hash(state);
|
||||||
self.frag.hash(state);
|
self.frag.hash(state);
|
||||||
}
|
}
|
||||||
|
|
@ -285,7 +260,7 @@ impl Hash for Url {
|
||||||
impl PartialEq for Url {
|
impl PartialEq for Url {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
match (self.scheme, other.scheme) {
|
match (self.scheme, other.scheme) {
|
||||||
(UrlScheme::Regular | UrlScheme::SearchItem, UrlScheme::Regular | UrlScheme::SearchItem) => {
|
(Scheme::Regular | Scheme::SearchItem, Scheme::Regular | Scheme::SearchItem) => {
|
||||||
self.loc == other.loc
|
self.loc == other.loc
|
||||||
}
|
}
|
||||||
_ => self.loc == other.loc && self.scheme == other.scheme,
|
_ => self.loc == other.loc && self.scheme == other.scheme,
|
||||||
|
|
@ -305,6 +280,6 @@ impl<'de> Deserialize<'de> for Url {
|
||||||
D: serde::Deserializer<'de>,
|
D: serde::Deserializer<'de>,
|
||||||
{
|
{
|
||||||
let s = String::deserialize(deserializer)?;
|
let s = String::deserialize(deserializer)?;
|
||||||
Ok(Url::from(s))
|
Url::try_from(s).map_err(serde::de::Error::custom)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue