mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 00:31:04 +00:00
279 lines
6.4 KiB
Rust
279 lines
6.4 KiB
Rust
use std::{ffi::OsStr, fmt::{Debug, Display, Formatter}, ops::Deref, path::{Path, PathBuf}};
|
|
|
|
use percent_encoding::{percent_decode_str, percent_encode, AsciiSet, CONTROLS};
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use super::{Loc, UrnBuf};
|
|
|
|
const ENCODE_SET: &AsciiSet = &CONTROLS.add(b'#');
|
|
|
|
#[derive(Clone, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
|
pub struct Url {
|
|
loc: Loc,
|
|
scheme: UrlScheme,
|
|
frag: String,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
|
pub enum UrlScheme {
|
|
#[default]
|
|
Regular,
|
|
Search,
|
|
SearchItem,
|
|
Archive,
|
|
}
|
|
|
|
impl Deref for Url {
|
|
type Target = Loc;
|
|
|
|
fn deref(&self) -> &Self::Target { &self.loc }
|
|
}
|
|
|
|
impl Debug for Url {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
match self.scheme {
|
|
UrlScheme::Regular => write!(f, "Regular({:?})", self.loc),
|
|
UrlScheme::Search => write!(f, "Search({:?}, {})", self.loc, self.frag),
|
|
UrlScheme::SearchItem => write!(f, "SearchItem({:?})", self.loc),
|
|
UrlScheme::Archive => write!(f, "Archive({:?})", self.loc),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<Loc> for Url {
|
|
fn from(loc: Loc) -> Self { Self { loc, ..Default::default() } }
|
|
}
|
|
|
|
impl From<PathBuf> for Url {
|
|
fn from(path: PathBuf) -> Self { Loc::new(path).into() }
|
|
}
|
|
|
|
impl From<&PathBuf> for Url {
|
|
fn from(path: &PathBuf) -> Self { path.to_owned().into() }
|
|
}
|
|
|
|
impl From<&Path> for Url {
|
|
fn from(path: &Path) -> Self { path.to_owned().into() }
|
|
}
|
|
|
|
impl From<&str> for Url {
|
|
fn from(mut path: &str) -> Self {
|
|
let mut url = Url::default();
|
|
match path.split_once("://").map(|(a, b)| (UrlScheme::from(a), b)) {
|
|
None => {
|
|
url.loc = Loc::new(PathBuf::from(path));
|
|
return url;
|
|
}
|
|
Some((UrlScheme::Regular, b)) => {
|
|
url.loc = Loc::new(PathBuf::from(b));
|
|
return url;
|
|
}
|
|
Some((a, b)) => {
|
|
url.scheme = a;
|
|
path = b;
|
|
}
|
|
}
|
|
match path.split_once('#') {
|
|
None => {
|
|
// FIXME: use `Loc::from(base, path)` instead
|
|
url.loc = Loc::new(percent_decode_str(path).decode_utf8_lossy().into_owned().into());
|
|
}
|
|
Some((a, b)) => {
|
|
// FIXME: use `Loc::from(base, path)` instead
|
|
url.loc = Loc::new(percent_decode_str(a).decode_utf8_lossy().into_owned().into());
|
|
url.frag = b.to_string();
|
|
}
|
|
}
|
|
url
|
|
}
|
|
}
|
|
|
|
impl From<String> for Url {
|
|
fn from(path: String) -> Self { path.as_str().into() }
|
|
}
|
|
|
|
impl From<&String> for Url {
|
|
fn from(path: &String) -> Self { path.as_str().into() }
|
|
}
|
|
|
|
impl AsRef<Url> for Url {
|
|
fn as_ref(&self) -> &Url { self }
|
|
}
|
|
|
|
impl AsRef<Path> for Url {
|
|
fn as_ref(&self) -> &Path { &self.loc }
|
|
}
|
|
|
|
impl AsRef<OsStr> for Url {
|
|
fn as_ref(&self) -> &OsStr { self.loc.as_os_str() }
|
|
}
|
|
|
|
impl Display for Url {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
if matches!(self.scheme, UrlScheme::Regular | UrlScheme::SearchItem) {
|
|
return f.write_str(&self.loc.to_string_lossy());
|
|
}
|
|
|
|
let scheme = match self.scheme {
|
|
UrlScheme::Regular | UrlScheme::SearchItem => unreachable!(),
|
|
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() {
|
|
write!(f, "#{}", self.frag)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl From<&Url> for String {
|
|
fn from(url: &Url) -> Self { url.to_string() }
|
|
}
|
|
|
|
impl Url {
|
|
#[inline]
|
|
pub fn join(&self, path: impl AsRef<Path>) -> Self {
|
|
match self.scheme {
|
|
UrlScheme::Regular => Self::from(self.loc.join(path)),
|
|
UrlScheme::Search => {
|
|
let loc = Loc::from(&self.loc, self.loc.join(path));
|
|
Self::from(loc).into_search_item()
|
|
}
|
|
UrlScheme::SearchItem => {
|
|
let loc = Loc::from(self.loc.base(), self.loc.join(path));
|
|
Self::from(loc).into_search_item()
|
|
}
|
|
UrlScheme::Archive => Self::from(self.loc.join(path)).into_archive(),
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub fn parent_url(&self) -> Option<Url> {
|
|
let p = self.loc.parent()?;
|
|
Some(match self.scheme {
|
|
UrlScheme::Regular | UrlScheme::Search => Self::from(p),
|
|
UrlScheme::SearchItem => {
|
|
if p == self.loc.base() {
|
|
Self::from(p).into_search("")
|
|
} else {
|
|
Self::from(p).into_search_item()
|
|
}
|
|
}
|
|
UrlScheme::Archive => Self::from(p),
|
|
})
|
|
}
|
|
|
|
#[inline]
|
|
pub fn pair(&self) -> Option<(Self, UrnBuf)> { Some((self.parent_url()?, self.loc.urn_owned())) }
|
|
|
|
#[cfg(unix)]
|
|
#[inline]
|
|
pub fn is_hidden(&self) -> bool { self.loc.urn().is_hidden() }
|
|
|
|
#[inline]
|
|
pub fn rebase(&self, parent: &Path) -> Self {
|
|
debug_assert!(self.is_regular());
|
|
self.loc.rebase(parent).into()
|
|
}
|
|
}
|
|
|
|
impl Url {
|
|
// --- Regular
|
|
#[inline]
|
|
pub fn is_regular(&self) -> bool { self.scheme == UrlScheme::Regular }
|
|
|
|
#[inline]
|
|
pub fn to_regular(&self) -> Self {
|
|
Self { loc: self.loc.clone(), scheme: UrlScheme::Regular, frag: String::new() }
|
|
}
|
|
|
|
#[inline]
|
|
pub fn into_regular(mut self) -> Self {
|
|
self.scheme = UrlScheme::Regular;
|
|
self.frag = String::new();
|
|
self
|
|
}
|
|
|
|
// --- Search
|
|
#[inline]
|
|
pub fn is_search(&self) -> bool { self.scheme == UrlScheme::Search }
|
|
|
|
#[inline]
|
|
pub fn to_search(&self, frag: &str) -> Self {
|
|
Self { loc: self.loc.clone(), scheme: UrlScheme::Search, frag: frag.to_owned() }
|
|
}
|
|
|
|
#[inline]
|
|
pub fn into_search(mut self, frag: &str) -> Self {
|
|
self.scheme = UrlScheme::Search;
|
|
self.frag = frag.to_owned();
|
|
self
|
|
}
|
|
|
|
#[inline]
|
|
pub fn is_search_item(&self) -> bool { self.scheme == UrlScheme::SearchItem }
|
|
|
|
#[inline]
|
|
pub fn into_search_item(mut self) -> Self {
|
|
self.scheme = UrlScheme::SearchItem;
|
|
self.frag = String::new();
|
|
self
|
|
}
|
|
|
|
#[inline]
|
|
pub fn is_archive(&self) -> bool { self.scheme == UrlScheme::Archive }
|
|
|
|
#[inline]
|
|
pub fn to_archive(&self) -> Self {
|
|
Self { loc: self.loc.clone(), scheme: UrlScheme::Archive, frag: String::new() }
|
|
}
|
|
|
|
#[inline]
|
|
pub fn into_archive(mut self) -> Self {
|
|
self.scheme = UrlScheme::Archive;
|
|
self.frag = String::new();
|
|
self
|
|
}
|
|
|
|
// --- Path
|
|
#[inline]
|
|
pub fn into_path(self) -> PathBuf { self.loc.into_path() }
|
|
|
|
// --- Scheme
|
|
#[inline]
|
|
pub fn scheme(&self) -> UrlScheme { self.scheme }
|
|
|
|
// --- Frag
|
|
#[inline]
|
|
pub fn frag(&self) -> &str { &self.frag }
|
|
}
|
|
|
|
impl From<&str> for UrlScheme {
|
|
fn from(value: &str) -> Self {
|
|
match value {
|
|
"search" => UrlScheme::Search,
|
|
"archive" => UrlScheme::Archive,
|
|
_ => UrlScheme::Regular,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Serialize for Url {
|
|
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
|
serializer.collect_str(self)
|
|
}
|
|
}
|
|
|
|
impl<'de> Deserialize<'de> for Url {
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
where
|
|
D: serde::Deserializer<'de>,
|
|
{
|
|
let s = String::deserialize(deserializer)?;
|
|
Ok(Url::from(s))
|
|
}
|
|
}
|