mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 23:01:05 +00:00
fix: make URLs with invalid UTF-8 serializable (#3023)
This commit is contained in:
parent
0e3cd8545c
commit
9535caad5b
20 changed files with 293 additions and 113 deletions
33
Cargo.lock
generated
33
Cargo.lock
generated
|
|
@ -1357,9 +1357,9 @@ checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
|
|||
|
||||
[[package]]
|
||||
name = "litrs"
|
||||
version = "0.4.1"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5"
|
||||
checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
|
|
@ -2155,9 +2155,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.15"
|
||||
version = "0.5.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e8af0dde094006011e6a740d4879319439489813bd0bcdc7d821beaeeff48ec"
|
||||
checksum = "7251471db004e509f4e75a62cca9435365b5ec7bcdff530d612ac7c87c44a792"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
]
|
||||
|
|
@ -2225,9 +2225,9 @@ checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce"
|
|||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.25"
|
||||
version = "0.1.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f"
|
||||
checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
|
|
@ -2470,12 +2470,12 @@ checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c"
|
|||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.5.10"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
|
||||
checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2684,9 +2684,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.46.1"
|
||||
version = "1.47.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17"
|
||||
checksum = "43864ed400b6043a4757a25c7a64a8efde741aed79a056a2fb348a406701bb35"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
|
|
@ -2699,7 +2699,7 @@ dependencies = [
|
|||
"slab",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -3295,15 +3295,6 @@ dependencies = [
|
|||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ scopeguard = "1.2.0"
|
|||
serde = { version = "1.0.219", features = [ "derive" ] }
|
||||
serde_json = "1.0.141"
|
||||
syntect = { version = "5.2.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] }
|
||||
tokio = { version = "1.46.1", features = [ "full" ] }
|
||||
tokio = { version = "1.47.0", features = [ "full" ] }
|
||||
tokio-stream = "0.1.17"
|
||||
tokio-util = "0.7.15"
|
||||
toml = { version = "0.9.2" }
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
{"words":["Punct","KEYMAP","splitn","crossterm","YAZI","peekable","ratatui","syntect","pbpaste","pbcopy","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","Konsole","Überzug","pkgs","pdftoppm","poppler","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","Upserting","prio","Ghostty","Catmull","Lanczos","cmds","unyank","scrolloff","headsup","unsub","uzers","scopeguard","SPDLOG","globset","filetime","magick","magick","prefetcher","Prework","prefetchers","PREWORKERS","conds","translit","rxvt","Urxvt","realpath","realname","REPARSE","hardlink","hardlinking","nlink","nlink","linemodes","SIGSTOP","sevenzip","rsplitn","replacen","DECSET","DECRQM","repeek","cwds","tcsi","Hyprland","Wayfire","SWAYSOCK","btime","nsec","codegen","gethostname","fchmod","fdfind","Rustc","rustc","ffprobe","vframes","luma","obase","outln","errln","tmtheme","twox","cfgs","fstype","objc","rdev","runloop","exfat","rclone","DECRQSS","DECSCUSR","libvterm","Uninit","lockin","rposition","resvg","foldhash"],"version":"0.2","flagWords":[],"language":"en"}
|
||||
{"version":"0.2","flagWords":[],"words":["Punct","KEYMAP","splitn","crossterm","YAZI","peekable","ratatui","syntect","pbpaste","pbcopy","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","Konsole","Überzug","pkgs","pdftoppm","poppler","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","Upserting","prio","Ghostty","Catmull","Lanczos","cmds","unyank","scrolloff","headsup","unsub","uzers","scopeguard","SPDLOG","globset","filetime","magick","magick","prefetcher","Prework","prefetchers","PREWORKERS","conds","translit","rxvt","Urxvt","realpath","realname","REPARSE","hardlink","hardlinking","nlink","nlink","linemodes","SIGSTOP","sevenzip","rsplitn","replacen","DECSET","DECRQM","repeek","cwds","tcsi","Hyprland","Wayfire","SWAYSOCK","btime","nsec","codegen","gethostname","fchmod","fdfind","Rustc","rustc","ffprobe","vframes","luma","obase","outln","errln","tmtheme","twox","cfgs","fstype","objc","rdev","runloop","exfat","rclone","DECRQSS","DECSCUSR","libvterm","Uninit","lockin","rposition","resvg","foldhash","tilded"],"language":"en"}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
use anyhow::{Context, Result};
|
||||
use yazi_fs::{ok_or_not_found, remove_dir_clean, services::Local};
|
||||
use yazi_macro::outln;
|
||||
use yazi_shared::url::Url;
|
||||
|
||||
use super::Dependency;
|
||||
use crate::shared::{maybe_exists, remove_sealed};
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ use serde::Deserialize;
|
|||
use yazi_codegen::DeserializeOver2;
|
||||
|
||||
use super::{Offset, Origin};
|
||||
use crate::popup::Position;
|
||||
|
||||
#[derive(Deserialize, DeserializeOver2)]
|
||||
pub struct Confirm {
|
||||
|
|
@ -30,4 +31,20 @@ pub struct Confirm {
|
|||
|
||||
impl Confirm {
|
||||
pub const fn border(&self) -> u16 { 2 }
|
||||
|
||||
pub const fn trash_position(&self) -> Position {
|
||||
Position::new(self.trash_origin, self.trash_offset)
|
||||
}
|
||||
|
||||
pub const fn delete_position(&self) -> Position {
|
||||
Position::new(self.delete_origin, self.delete_offset)
|
||||
}
|
||||
|
||||
pub const fn overwrite_position(&self) -> Position {
|
||||
Position::new(self.overwrite_origin, self.overwrite_offset)
|
||||
}
|
||||
|
||||
pub const fn quit_position(&self) -> Position {
|
||||
Position::new(self.quit_origin, self.quit_offset)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use ratatui::{text::{Line, Text}, widgets::{Paragraph, Wrap}};
|
||||
use yazi_shared::url::Url;
|
||||
use yazi_shared::{IntoStringLossy, url::Url};
|
||||
|
||||
use super::{Offset, Origin, Position};
|
||||
use super::{Offset, Position};
|
||||
use crate::YAZI;
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
@ -106,22 +106,22 @@ impl InputCfg {
|
|||
impl ConfirmCfg {
|
||||
fn new(
|
||||
title: String,
|
||||
(origin, offset): (Origin, Offset),
|
||||
position: Position,
|
||||
body: Option<Text<'static>>,
|
||||
list: Option<Text<'static>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
position: Position::new(origin, offset),
|
||||
title: Line::raw(title),
|
||||
body: body.map(|c| Paragraph::new(c).wrap(Wrap { trim: false })).unwrap_or_default(),
|
||||
list: list.map(|l| Paragraph::new(l).wrap(Wrap { trim: false })).unwrap_or_default(),
|
||||
position,
|
||||
title: Line::raw(title),
|
||||
body: body.map(|b| Paragraph::new(b).wrap(Wrap { trim: false })).unwrap_or_default(),
|
||||
list: list.map(|l| Paragraph::new(l).wrap(Wrap { trim: false })).unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn trash(urls: &[yazi_shared::url::Url]) -> Self {
|
||||
Self::new(
|
||||
Self::replace_number(&YAZI.confirm.trash_title, urls.len()),
|
||||
(YAZI.confirm.trash_origin, YAZI.confirm.trash_offset),
|
||||
YAZI.confirm.trash_position(),
|
||||
None,
|
||||
Self::truncate_list(urls.iter(), urls.len(), 100),
|
||||
)
|
||||
|
|
@ -130,7 +130,7 @@ impl ConfirmCfg {
|
|||
pub fn delete(urls: &[yazi_shared::url::Url]) -> Self {
|
||||
Self::new(
|
||||
Self::replace_number(&YAZI.confirm.delete_title, urls.len()),
|
||||
(YAZI.confirm.delete_origin, YAZI.confirm.delete_offset),
|
||||
YAZI.confirm.delete_position(),
|
||||
None,
|
||||
Self::truncate_list(urls.iter(), urls.len(), 100),
|
||||
)
|
||||
|
|
@ -139,16 +139,16 @@ impl ConfirmCfg {
|
|||
pub fn overwrite(url: &Url) -> Self {
|
||||
Self::new(
|
||||
YAZI.confirm.overwrite_title.to_owned(),
|
||||
(YAZI.confirm.overwrite_origin, YAZI.confirm.overwrite_offset),
|
||||
YAZI.confirm.overwrite_position(),
|
||||
Some(Text::raw(&YAZI.confirm.overwrite_body)),
|
||||
Some(url.to_string().into()),
|
||||
Some(url.into_string_lossy().into()),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn quit(len: usize, names: Vec<String>) -> Self {
|
||||
Self::new(
|
||||
Self::replace_number(&YAZI.confirm.quit_title, len),
|
||||
(YAZI.confirm.quit_origin, YAZI.confirm.quit_offset),
|
||||
YAZI.confirm.quit_position(),
|
||||
Some(Text::raw(&YAZI.confirm.quit_body)),
|
||||
Self::truncate_list(names.into_iter(), len, 10),
|
||||
)
|
||||
|
|
@ -159,7 +159,7 @@ impl ConfirmCfg {
|
|||
}
|
||||
|
||||
fn truncate_list(
|
||||
it: impl Iterator<Item = impl Into<String>>,
|
||||
it: impl Iterator<Item = impl IntoStringLossy>,
|
||||
len: usize,
|
||||
max: usize,
|
||||
) -> Option<Text<'static>> {
|
||||
|
|
@ -169,7 +169,7 @@ impl ConfirmCfg {
|
|||
lines.push(format!("... and {} more", len - max));
|
||||
break;
|
||||
}
|
||||
lines.push(s.into());
|
||||
lines.push(s.into_string_lossy());
|
||||
}
|
||||
Some(Text::from_iter(lines))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ pub struct Position {
|
|||
|
||||
impl Position {
|
||||
#[inline]
|
||||
pub fn new(origin: Origin, offset: Offset) -> Self { Self { origin, offset } }
|
||||
pub const fn new(origin: Origin, offset: Offset) -> Self { Self { origin, offset } }
|
||||
|
||||
pub fn rect(&self, WindowSize { columns, rows, .. }: WindowSize) -> Rect {
|
||||
use Origin::*;
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ impl Cha {
|
|||
pub fn from_dummy(_url: &Url, ft: Option<FileType>) -> Self {
|
||||
let mut me = ft.map(Self::from_half_ft).unwrap_or_default();
|
||||
#[cfg(unix)]
|
||||
if yazi_shared::url::Urn::new(_url).is_hidden() {
|
||||
if _url.urn().is_hidden() {
|
||||
me.kind |= ChaKind::HIDDEN;
|
||||
}
|
||||
me
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ use crate::{CWD, services};
|
|||
#[inline]
|
||||
pub fn clean_url(url: &Url) -> Url { Url::from(clean_path(url)) }
|
||||
|
||||
// FIXME: VFS
|
||||
#[inline]
|
||||
pub fn clean_path(path: impl AsRef<Path>) -> PathBuf { _clean_path(path.as_ref()) }
|
||||
|
||||
|
|
@ -30,9 +31,11 @@ fn _clean_path(path: &Path) -> PathBuf {
|
|||
if out.is_empty() { PathBuf::from(".") } else { out.iter().collect() }
|
||||
}
|
||||
|
||||
// FIXME: VFS
|
||||
#[inline]
|
||||
pub fn expand_path(p: impl AsRef<Path>) -> PathBuf { _expand_path(p.as_ref()) }
|
||||
|
||||
// FIXME: keep the original scheme of the Url
|
||||
#[inline]
|
||||
pub fn expand_url<'a>(url: impl Into<Cow<'a, Url>>) -> Cow<'a, Url> {
|
||||
let u: Cow<'a, Url> = url.into();
|
||||
|
|
|
|||
|
|
@ -6,94 +6,170 @@ use crate::services::Local;
|
|||
|
||||
#[inline]
|
||||
pub async fn canonicalize(url: impl AsRef<Url>) -> io::Result<Url> {
|
||||
Local::canonicalize(url.as_ref()).await.map(Into::into)
|
||||
if let Some(path) = url.as_ref().as_path() {
|
||||
Local::canonicalize(path).await.map(Into::into)
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem"))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn create(url: impl AsRef<Url>) -> io::Result<tokio::fs::File> {
|
||||
Local::create(url.as_ref()).await
|
||||
if let Some(path) = url.as_ref().as_path() {
|
||||
Local::create(path).await
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem"))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn create_dir(url: impl AsRef<Url>) -> io::Result<()> {
|
||||
Local::create_dir(url.as_ref()).await
|
||||
if let Some(path) = url.as_ref().as_path() {
|
||||
Local::create_dir(path).await
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem"))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn create_dir_all(url: impl AsRef<Url>) -> io::Result<()> {
|
||||
Local::create_dir_all(url.as_ref()).await
|
||||
if let Some(path) = url.as_ref().as_path() {
|
||||
Local::create_dir_all(path).await
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem"))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn hard_link(original: impl AsRef<Url>, link: impl AsRef<Url>) -> io::Result<()> {
|
||||
Local::hard_link(original.as_ref(), link.as_ref()).await
|
||||
if let (Some(original), Some(link)) = (original.as_ref().as_path(), link.as_ref().as_path()) {
|
||||
Local::hard_link(original, link).await
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem"))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn metadata(url: impl AsRef<Url>) -> io::Result<std::fs::Metadata> {
|
||||
Local::metadata(url.as_ref()).await
|
||||
if let Some(path) = url.as_ref().as_path() {
|
||||
Local::metadata(path).await
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem"))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn open(url: impl AsRef<Url>) -> io::Result<tokio::fs::File> {
|
||||
Local::open(url.as_ref()).await
|
||||
if let Some(path) = url.as_ref().as_path() {
|
||||
Local::open(path).await
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem"))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn read_dir(url: impl AsRef<Url>) -> io::Result<tokio::fs::ReadDir> {
|
||||
Local::read_dir(url.as_ref()).await
|
||||
if let Some(path) = url.as_ref().as_path() {
|
||||
Local::read_dir(path).await
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem"))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn read_dir_sync(url: impl AsRef<Url>) -> io::Result<std::fs::ReadDir> {
|
||||
Local::read_dir_sync(url.as_ref())
|
||||
if let Some(path) = url.as_ref().as_path() {
|
||||
Local::read_dir_sync(path)
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem"))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn read_link(url: impl AsRef<Url>) -> io::Result<Url> {
|
||||
Local::read_link(url.as_ref()).await.map(Into::into)
|
||||
if let Some(path) = url.as_ref().as_path() {
|
||||
Local::read_link(path).await.map(Into::into)
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem"))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn remove_dir(url: impl AsRef<Url>) -> io::Result<()> {
|
||||
Local::remove_dir(url.as_ref()).await
|
||||
if let Some(path) = url.as_ref().as_path() {
|
||||
Local::remove_dir(path).await
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem"))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn remove_dir_all(url: impl AsRef<Url>) -> io::Result<()> {
|
||||
Local::remove_dir_all(url.as_ref()).await
|
||||
if let Some(path) = url.as_ref().as_path() {
|
||||
Local::remove_dir_all(path).await
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem"))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn remove_file(url: impl AsRef<Url>) -> io::Result<()> {
|
||||
Local::remove_file(url.as_ref()).await
|
||||
if let Some(path) = url.as_ref().as_path() {
|
||||
Local::remove_file(path).await
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem"))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn rename(from: impl AsRef<Url>, to: impl AsRef<Url>) -> io::Result<()> {
|
||||
Local::rename(from.as_ref(), to.as_ref()).await
|
||||
if let (Some(from), Some(to)) = (from.as_ref().as_path(), to.as_ref().as_path()) {
|
||||
Local::rename(from, to).await
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem"))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn symlink_dir(original: impl AsRef<Url>, link: impl AsRef<Url>) -> io::Result<()> {
|
||||
Local::symlink_dir(original.as_ref(), link.as_ref()).await
|
||||
if let (Some(original), Some(link)) = (original.as_ref().as_path(), link.as_ref().as_path()) {
|
||||
Local::symlink_dir(original, link).await
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem"))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn symlink_file(original: impl AsRef<Url>, link: impl AsRef<Url>) -> io::Result<()> {
|
||||
Local::symlink_file(original.as_ref(), link.as_ref()).await
|
||||
if let (Some(original), Some(link)) = (original.as_ref().as_path(), link.as_ref().as_path()) {
|
||||
Local::symlink_file(original, link).await
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem"))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn symlink_metadata(url: impl AsRef<Url>) -> io::Result<std::fs::Metadata> {
|
||||
Local::symlink_metadata(url.as_ref()).await
|
||||
if let Some(path) = url.as_ref().as_path() {
|
||||
Local::symlink_metadata(path).await
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem"))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn symlink_metadata_sync(url: impl AsRef<Url>) -> io::Result<std::fs::Metadata> {
|
||||
Local::symlink_metadata_sync(url.as_ref())
|
||||
if let Some(path) = url.as_ref().as_path() {
|
||||
Local::symlink_metadata_sync(path)
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem"))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn write(url: impl AsRef<Url>, contents: impl AsRef<[u8]>) -> io::Result<()> {
|
||||
Local::write(url.as_ref(), contents).await
|
||||
if let Some(path) = url.as_ref().as_path() {
|
||||
Local::write(path, contents).await
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem"))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use mlua::{ExternalError, IntoLua, Lua, Value};
|
||||
use yazi_fs::expand_path;
|
||||
use yazi_fs::expand_url;
|
||||
use yazi_shared::{event::CmdCow, url::Url};
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -12,9 +14,13 @@ pub struct CdOpt {
|
|||
impl From<CmdCow> for CdOpt {
|
||||
fn from(mut c: CmdCow) -> Self {
|
||||
let mut target = c.take_first_url().unwrap_or_default();
|
||||
if target.is_regular() && !c.bool("raw") {
|
||||
target = Url::from(expand_path(target));
|
||||
|
||||
if !c.bool("raw")
|
||||
&& let Cow::Owned(u) = expand_url(&target)
|
||||
{
|
||||
target = u;
|
||||
}
|
||||
|
||||
Self { target, interactive: c.bool("interactive"), source: CdSource::Cd }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use mlua::{ExternalError, IntoLua, Lua, Value};
|
||||
use yazi_fs::expand_path;
|
||||
use yazi_fs::expand_url;
|
||||
use yazi_shared::{event::CmdCow, url::Url};
|
||||
|
||||
use crate::mgr::CdSource;
|
||||
|
|
@ -14,8 +16,11 @@ pub struct RevealOpt {
|
|||
impl From<CmdCow> for RevealOpt {
|
||||
fn from(mut c: CmdCow) -> Self {
|
||||
let mut target = c.take_first_url().unwrap_or_default();
|
||||
if target.is_regular() && !c.bool("raw") {
|
||||
target = Url::from(expand_path(target));
|
||||
|
||||
if !c.bool("raw")
|
||||
&& let Cow::Owned(u) = expand_url(&target)
|
||||
{
|
||||
target = u;
|
||||
}
|
||||
|
||||
Self { target, source: CdSource::Reveal, no_dummy: c.bool("no-dummy") }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use mlua::{ExternalError, IntoLua, Lua, Value};
|
||||
use yazi_boot::BOOT;
|
||||
use yazi_fs::expand_path;
|
||||
use yazi_fs::expand_url;
|
||||
use yazi_shared::{event::CmdCow, url::Url};
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -16,8 +18,10 @@ impl From<CmdCow> for TabCreateOpt {
|
|||
let Some(mut wd) = c.take_first_url() else {
|
||||
return Self { wd: Some(Url::from(&BOOT.cwds[0])) };
|
||||
};
|
||||
if wd.is_regular() && !c.bool("raw") {
|
||||
wd = Url::from(expand_path(wd));
|
||||
if !c.bool("raw")
|
||||
&& let Cow::Owned(u) = expand_url(&wd)
|
||||
{
|
||||
wd = u;
|
||||
}
|
||||
Self { wd: Some(wd) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ impl Scheduler {
|
|||
|
||||
pub fn file_cut(&self, from: Url, mut to: Url, force: bool) {
|
||||
let mut ongoing = self.ongoing.lock();
|
||||
let id = ongoing.add(TaskKind::User, format!("Cut {from} to {to}"));
|
||||
let id = ongoing.add(TaskKind::User, format!("Cut {} to {}", from.display(), to.display()));
|
||||
|
||||
if to.starts_with(&from) && to != from {
|
||||
self.new_and_fail(id, "Cannot cut directory into itself").ok();
|
||||
|
|
@ -108,7 +108,10 @@ impl Scheduler {
|
|||
}
|
||||
|
||||
pub fn file_copy(&self, from: Url, mut to: Url, force: bool, follow: bool) {
|
||||
let id = self.ongoing.lock().add(TaskKind::User, format!("Copy {from} to {to}"));
|
||||
let id = self
|
||||
.ongoing
|
||||
.lock()
|
||||
.add(TaskKind::User, format!("Copy {} to {}", from.display(), to.display()));
|
||||
|
||||
if to.starts_with(&from) && to != from {
|
||||
self.new_and_fail(id, "Cannot copy directory into itself").ok();
|
||||
|
|
@ -125,7 +128,10 @@ impl Scheduler {
|
|||
}
|
||||
|
||||
pub fn file_link(&self, from: Url, mut to: Url, relative: bool, force: bool) {
|
||||
let id = self.ongoing.lock().add(TaskKind::User, format!("Link {from} to {to}"));
|
||||
let id = self
|
||||
.ongoing
|
||||
.lock()
|
||||
.add(TaskKind::User, format!("Link {} to {}", from.display(), to.display()));
|
||||
|
||||
let file = self.file.clone();
|
||||
self.send_micro(id, LOW, async move {
|
||||
|
|
@ -139,7 +145,10 @@ impl Scheduler {
|
|||
}
|
||||
|
||||
pub fn file_hardlink(&self, from: Url, mut to: Url, force: bool, follow: bool) {
|
||||
let id = self.ongoing.lock().add(TaskKind::User, format!("Hardlink {from} to {to}"));
|
||||
let id = self
|
||||
.ongoing
|
||||
.lock()
|
||||
.add(TaskKind::User, format!("Hardlink {} to {}", from.display(), to.display()));
|
||||
|
||||
if to.starts_with(&from) && to != from {
|
||||
self.new_and_fail(id, "Cannot hardlink directory into itself").ok();
|
||||
|
|
@ -157,7 +166,7 @@ impl Scheduler {
|
|||
|
||||
pub fn file_delete(&self, target: Url) {
|
||||
let mut ongoing = self.ongoing.lock();
|
||||
let id = ongoing.add(TaskKind::User, format!("Delete {target}"));
|
||||
let id = ongoing.add(TaskKind::User, format!("Delete {}", target.display()));
|
||||
|
||||
ongoing.hooks.add_async(id, {
|
||||
let target = target.clone();
|
||||
|
|
@ -186,7 +195,7 @@ impl Scheduler {
|
|||
|
||||
pub fn file_trash(&self, target: Url) {
|
||||
let mut ongoing = self.ongoing.lock();
|
||||
let id = ongoing.add(TaskKind::User, format!("Trash {target}"));
|
||||
let id = ongoing.add(TaskKind::User, format!("Trash {}", target.display()));
|
||||
|
||||
ongoing.hooks.add_async(id, {
|
||||
let target = target.clone();
|
||||
|
|
@ -251,7 +260,8 @@ impl Scheduler {
|
|||
let mut ongoing = self.ongoing.lock();
|
||||
|
||||
for target in targets {
|
||||
let id = ongoing.add(TaskKind::Preload, format!("Calculate the size of {target}"));
|
||||
let id =
|
||||
ongoing.add(TaskKind::Preload, format!("Calculate the size of {}", target.display()));
|
||||
let target = target.clone();
|
||||
let throttle = throttle.clone();
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
yazi_macro::mod_pub!(errors event shell translit url);
|
||||
|
||||
yazi_macro::mod_flat!(alias bytes chars condition debounce either env id layer natsort os osstr rand ro_cell source sync_cell terminal throttle time utf8);
|
||||
yazi_macro::mod_flat!(alias bytes chars condition debounce either env id layer natsort os osstr rand ro_cell source string sync_cell terminal throttle time utf8);
|
||||
|
||||
pub fn init() {
|
||||
LOG_LEVEL.replace(<_>::from(std::env::var("YAZI_LOG").unwrap_or_default()));
|
||||
|
|
|
|||
37
yazi-shared/src/string.rs
Normal file
37
yazi-shared/src/string.rs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
use std::{borrow::Cow, ffi::{OsStr, OsString}};
|
||||
|
||||
use crate::url::Url;
|
||||
|
||||
pub trait IntoStringLossy {
|
||||
fn into_string_lossy(self) -> String;
|
||||
}
|
||||
|
||||
impl IntoStringLossy for String {
|
||||
fn into_string_lossy(self) -> String { self }
|
||||
}
|
||||
|
||||
impl IntoStringLossy for &OsStr {
|
||||
fn into_string_lossy(self) -> String { self.to_string_lossy().into_owned() }
|
||||
}
|
||||
|
||||
impl IntoStringLossy for OsString {
|
||||
fn into_string_lossy(self) -> String {
|
||||
match self.to_string_lossy() {
|
||||
Cow::Owned(s) => s,
|
||||
Cow::Borrowed(_) => unsafe { String::from_utf8_unchecked(self.into_encoded_bytes()) },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoStringLossy for Cow<'_, OsStr> {
|
||||
fn into_string_lossy(self) -> String {
|
||||
match self {
|
||||
Cow::Owned(s) => s.into_string_lossy(),
|
||||
Cow::Borrowed(s) => s.into_string_lossy(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoStringLossy for &Url {
|
||||
fn into_string_lossy(self) -> String { self.os_str().into_string_lossy() }
|
||||
}
|
||||
20
yazi-shared/src/url/display.rs
Normal file
20
yazi-shared/src/url/display.rs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
use crate::url::Url;
|
||||
|
||||
pub struct Display<'a> {
|
||||
inner: &'a Url,
|
||||
}
|
||||
|
||||
impl<'a> Display<'a> {
|
||||
#[inline]
|
||||
pub fn new(inner: &'a Url) -> Self { Self { inner } }
|
||||
}
|
||||
|
||||
impl<'a> std::fmt::Display for Display<'a> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Url { loc, scheme } = self.inner;
|
||||
if scheme.is_virtual() {
|
||||
scheme.fmt(f)?;
|
||||
}
|
||||
loc.display().fmt(f)
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +1 @@
|
|||
yazi_macro::mod_flat!(component loc scheme url urn);
|
||||
yazi_macro::mod_flat!(component display loc scheme url urn);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use std::{borrow::Cow, fmt::Display};
|
|||
use anyhow::{Result, bail};
|
||||
use percent_encoding::{CONTROLS, PercentEncode, percent_decode, percent_encode};
|
||||
|
||||
use crate::BytesExt;
|
||||
use crate::{BytesExt, url::Loc};
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
pub enum Scheme {
|
||||
|
|
@ -19,24 +19,29 @@ pub enum Scheme {
|
|||
}
|
||||
|
||||
impl Scheme {
|
||||
pub(super) fn parse(bytes: &[u8]) -> Result<(Self, usize)> {
|
||||
let Some((protocol, rest)) = bytes.split_by_seq(b"://") else {
|
||||
return Ok((Self::Regular, 0));
|
||||
pub(super) fn parse(bytes: &[u8]) -> Result<(Self, usize, bool)> {
|
||||
let Some((mut protocol, rest)) = bytes.split_by_seq(b"://") else {
|
||||
return Ok((Self::Regular, 0, false));
|
||||
};
|
||||
|
||||
let tilde = protocol.ends_with(b"~");
|
||||
if tilde {
|
||||
protocol = &protocol[..protocol.len() - 1];
|
||||
}
|
||||
|
||||
Ok(match protocol {
|
||||
b"regular" => (Self::Regular, 10),
|
||||
b"regular" => (Self::Regular, 10, tilde),
|
||||
b"search" => {
|
||||
let (name, skip) = Self::decode_param(rest)?;
|
||||
(Self::Search(name), 9 + skip)
|
||||
(Self::Search(name), 9 + skip, tilde)
|
||||
}
|
||||
b"archive" => {
|
||||
let (name, skip) = Self::decode_param(rest)?;
|
||||
(Self::Archive(name), 10 + skip)
|
||||
(Self::Archive(name), 10 + skip, tilde)
|
||||
}
|
||||
b"sftp" => {
|
||||
let (name, skip) = Self::decode_param(rest)?;
|
||||
(Self::Sftp(name), 7 + skip)
|
||||
(Self::Sftp(name), 7 + skip, tilde)
|
||||
}
|
||||
_ => bail!("Could not parse protocol from URL: {}", String::from_utf8_lossy(bytes)),
|
||||
})
|
||||
|
|
@ -77,6 +82,17 @@ impl Scheme {
|
|||
|
||||
#[inline]
|
||||
fn encode_param<'a>(s: &'a str) -> PercentEncode<'a> { percent_encode(s.as_bytes(), CONTROLS) }
|
||||
|
||||
pub fn encode_tilded(&self, loc: &Loc) -> String {
|
||||
let loc = percent_encode(loc.as_os_str().as_encoded_bytes(), CONTROLS);
|
||||
match self {
|
||||
Self::Regular => format!("regular~://{loc}"),
|
||||
Self::Search(kw) => format!("search~://{}/{loc}", Self::encode_param(kw)),
|
||||
Self::SearchItem => format!("search-item~://{loc}"),
|
||||
Self::Archive(id) => format!("archive~://{}/{loc}", Self::encode_param(id)),
|
||||
Self::Sftp(id) => format!("sftp~://{}/{loc}", Self::encode_param(id)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Scheme {
|
||||
|
|
@ -84,7 +100,7 @@ impl Display for Scheme {
|
|||
match self {
|
||||
Self::Regular => write!(f, "regular://"),
|
||||
Self::Search(kw) => write!(f, "search://{}/", Self::encode_param(kw)),
|
||||
Self::SearchItem => write!(f, "search_item://"),
|
||||
Self::SearchItem => write!(f, "search-item://"),
|
||||
Self::Archive(id) => write!(f, "archive://{}/", Self::encode_param(id)),
|
||||
Self::Sftp(id) => write!(f, "sftp://{}/", Self::encode_param(id)),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
use std::{borrow::Cow, ffi::OsStr, fmt::{Debug, Display, Formatter}, hash::{BuildHasher, Hash, Hasher}, ops::Deref, path::{Path, PathBuf}};
|
||||
use std::{borrow::Cow, ffi::OsStr, fmt::{Debug, Formatter}, hash::{BuildHasher, Hash, Hasher}, ops::Deref, path::{Path, PathBuf}};
|
||||
|
||||
use percent_encoding::{AsciiSet, CONTROLS, percent_encode};
|
||||
use percent_encoding::percent_decode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::UrnBuf;
|
||||
use crate::{IntoOsStr, url::{Components, Loc, Scheme}};
|
||||
|
||||
const ENCODE_SET: &AsciiSet = &CONTROLS.add(b'#');
|
||||
use crate::{IntoOsStr, url::{Components, Display, Loc, Scheme}};
|
||||
|
||||
#[derive(Clone, Default, Eq, Ord, PartialOrd)]
|
||||
pub struct Url {
|
||||
|
|
@ -53,9 +51,15 @@ impl TryFrom<&[u8]> for Url {
|
|||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
|
||||
let (scheme, skip) = Scheme::parse(bytes)?;
|
||||
let (scheme, skip, tilde) = Scheme::parse(bytes)?;
|
||||
|
||||
Ok(Url { loc: bytes[skip..].into_os_str()?.into(), scheme })
|
||||
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 }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -80,25 +84,6 @@ impl AsRef<Path> for Url {
|
|||
fn as_ref(&self) -> &Path { &self.loc }
|
||||
}
|
||||
|
||||
// FIXME: remove
|
||||
impl Display for Url {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
if matches!(self.scheme, Scheme::Regular | Scheme::SearchItem) {
|
||||
return write!(f, "{}", self.loc.display());
|
||||
}
|
||||
|
||||
let loc = percent_encode(self.loc.as_os_str().as_encoded_bytes(), ENCODE_SET);
|
||||
write!(f, "{}{loc}", self.scheme)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: remove
|
||||
impl From<&Url> for String {
|
||||
fn from(url: &Url) -> Self { url.to_string() }
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Url> for Cow<'a, Url> {
|
||||
fn from(url: &'a Url) -> Self { Cow::Borrowed(url) }
|
||||
}
|
||||
|
|
@ -149,6 +134,12 @@ impl Url {
|
|||
self.scheme.covariant(&other.scheme) && self.loc == other.loc
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn display(&self) -> Display<'_> { Display::new(self) }
|
||||
|
||||
#[inline]
|
||||
pub fn os_str(&self) -> Cow<'_, OsStr> { self.components().os_str() }
|
||||
|
||||
pub fn parent_url(&self) -> Option<Url> {
|
||||
let parent = self.loc.parent()?;
|
||||
let base = self.loc.base();
|
||||
|
|
@ -273,7 +264,12 @@ impl PartialEq for Url {
|
|||
|
||||
impl Serialize for Url {
|
||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
serializer.collect_str(self)
|
||||
let Url { scheme, loc } = self;
|
||||
match (scheme.is_virtual(), loc.to_str()) {
|
||||
(false, Some(s)) => serializer.serialize_str(s),
|
||||
(true, Some(s)) => serializer.serialize_str(&format!("{scheme}{s}")),
|
||||
(_, None) => serializer.serialize_str(&scheme.encode_tilded(loc)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue