mirror of
https://github.com/sxyazi/yazi.git
synced 2026-05-13 08:16:40 +00:00
feat: introduce the concept of Uri (#3049)
This commit is contained in:
parent
a9bfeeeaad
commit
1bfcbc1664
35 changed files with 356 additions and 281 deletions
4
.github/workflows/draft.yml
vendored
4
.github/workflows/draft.yml
vendored
|
|
@ -184,14 +184,14 @@ jobs:
|
|||
env:
|
||||
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_TOKEN }}
|
||||
run: |
|
||||
parallel 'snapcraft push -v --release latest/candidate {}' ::: yazi-*.snap || true
|
||||
parallel 'snapcraft upload -v --release latest/candidate {}' ::: yazi-*.snap || true
|
||||
|
||||
- name: Push snap to edge channel
|
||||
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
||||
env:
|
||||
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_TOKEN }}
|
||||
run: |
|
||||
parallel 'snapcraft push -v --release latest/edge {}' ::: yazi-*.snap || true
|
||||
parallel 'snapcraft upload -v --release latest/edge {}' ::: yazi-*.snap || true
|
||||
|
||||
draft:
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
|
|
|
|||
64
Cargo.lock
generated
64
Cargo.lock
generated
|
|
@ -121,9 +121,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.98"
|
||||
version = "1.0.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
|
||||
checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100"
|
||||
|
||||
[[package]]
|
||||
name = "approx"
|
||||
|
|
@ -319,9 +319,9 @@ checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06"
|
|||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.23.1"
|
||||
version = "1.23.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422"
|
||||
checksum = "3995eaeebcdf32f91f980d360f78732ddc061097ab4e39991ae7a6ace9194677"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder-lite"
|
||||
|
|
@ -352,9 +352,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.31"
|
||||
version = "1.2.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3a42d84bb6b69d3a8b3eaacf0d88f179e1929695e1ad012b6cf64d9caaa5fd2"
|
||||
checksum = "2352e5597e9c544d5e6d9c95190d5d27738ade584fa8db0a16e130e5c2b5296e"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"libc",
|
||||
|
|
@ -391,9 +391,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.42"
|
||||
version = "4.5.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed87a9d530bb41a67537289bafcac159cb3ee28460e0a4571123d2a778a6a882"
|
||||
checksum = "1c1f056bae57e3e54c3375c41ff79619ddd13460a17d7438712bd0d83fda4ff8"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
|
|
@ -401,9 +401,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.42"
|
||||
version = "4.5.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64f4f3f3c77c94aff3c7e9aac9a2ca1974a5adf392a8bb751e827d6d127ab966"
|
||||
checksum = "b3e7f4214277f3c7aa526a59dd3fbe306a370daee1f8b7b8c987069cd8e888a8"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
|
|
@ -413,9 +413,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_complete"
|
||||
version = "4.5.55"
|
||||
version = "4.5.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5abde44486daf70c5be8b8f8f1b66c49f86236edf6fa2abadb4d961c4c6229a"
|
||||
checksum = "4d9501bd3f5f09f7bbee01da9a511073ed30a80cd7a509f1214bb74eadea71ad"
|
||||
dependencies = [
|
||||
"clap",
|
||||
]
|
||||
|
|
@ -1072,9 +1072,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.4"
|
||||
version = "0.15.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
|
||||
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||
dependencies = [
|
||||
"allocator-api2",
|
||||
"equivalent",
|
||||
|
|
@ -1319,9 +1319,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
|
|||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.174"
|
||||
version = "0.2.175"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
|
||||
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
|
||||
|
||||
[[package]]
|
||||
name = "libfuzzer-sys"
|
||||
|
|
@ -1486,9 +1486,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "mlua"
|
||||
version = "0.11.1"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de25fc513588ac1273aa8c6dc0fffee6d32c12f38dc75f5cdc74547121a107ef"
|
||||
checksum = "ab2fea92b2adabd51808311b101551d6e3f8602b65e9fae51f7ad5b3d500f4cd"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bstr",
|
||||
|
|
@ -1507,9 +1507,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "mlua-sys"
|
||||
version = "0.8.2"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bcdf7c9e260ca82aaa32ac11148941952b856bb8c69aa5a9e65962f21fcb8637"
|
||||
checksum = "3d4dc9cfc5a7698899802e97480617d9726f7da78c910db989d4d0fd4991d900"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
|
|
@ -1928,9 +1928,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.95"
|
||||
version = "1.0.97"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
|
||||
checksum = "d61789d7719defeb74ea5fe81f2fdfdbd28a803847077cecce2ff14e1472f6f1"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
|
@ -2170,7 +2170,7 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
|
|||
dependencies = [
|
||||
"getrandom 0.2.16",
|
||||
"libredox",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.14",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2272,9 +2272,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.21"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
|
||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
|
|
@ -2452,9 +2452,9 @@ checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
|
|||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.10"
|
||||
version = "0.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d"
|
||||
checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
|
|
@ -2591,11 +2591,11 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.12"
|
||||
version = "2.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
|
||||
checksum = "0b0949c3a6c842cbde3f1686d6eea5a010516deb7085f79db747562d4102f41e"
|
||||
dependencies = [
|
||||
"thiserror-impl 2.0.12",
|
||||
"thiserror-impl 2.0.14",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2611,9 +2611,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.12"
|
||||
version = "2.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
|
||||
checksum = "cc5b44b4ab9c2fdd0e0512e6bece8388e214c0749f5862b114cc5b7a25daf227"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
|
|||
|
|
@ -21,10 +21,10 @@ lto = false
|
|||
|
||||
[workspace.dependencies]
|
||||
ansi-to-tui = "7.0.0"
|
||||
anyhow = "1.0.98"
|
||||
anyhow = "1.0.99"
|
||||
base64 = "0.22.1"
|
||||
bitflags = { version = "2.9.1", features = [ "serde" ] }
|
||||
clap = { version = "4.5.42", features = [ "derive" ] }
|
||||
clap = { version = "4.5.44", features = [ "derive" ] }
|
||||
core-foundation-sys = "0.8.7"
|
||||
crossterm = { version = "0.29.0", features = [ "event-stream" ] }
|
||||
dirs = "6.0.0"
|
||||
|
|
@ -32,9 +32,9 @@ foldhash = "0.1.5"
|
|||
futures = "0.3.31"
|
||||
globset = "0.4.16"
|
||||
indexmap = { version = "2.10.0", features = [ "serde" ] }
|
||||
libc = "0.2.174"
|
||||
libc = "0.2.175"
|
||||
lru = "0.16.0"
|
||||
mlua = { version = "0.11.1", features = [ "anyhow", "async", "error-send", "lua54", "macros", "serde" ] }
|
||||
mlua = { version = "0.11.2", features = [ "anyhow", "async", "error-send", "lua54", "macros", "serde" ] }
|
||||
objc = "0.2.7"
|
||||
ordered-float = { version = "5.0.0", features = [ "serde" ] }
|
||||
parking_lot = "0.12.4"
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
{"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"}
|
||||
{"language":"en","flagWords":[],"version":"0.2","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","futs"]}
|
||||
|
|
@ -123,8 +123,10 @@ parts:
|
|||
# expected binary without much overhead, and works across platforms.
|
||||
magick:
|
||||
plugin: autotools
|
||||
source: https://imagemagick.org/archive/ImageMagick.tar.gz
|
||||
source-type: tar
|
||||
source: https://github.com/ImageMagick/ImageMagick.git
|
||||
source-type: git
|
||||
source-tag: 7.1.2-0
|
||||
source-depth: 1
|
||||
stage-packages:
|
||||
- libgomp1
|
||||
autotools-configure-parameters:
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ impl Trigger {
|
|||
|
||||
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()),
|
||||
Some((p, c)) => (expand_url(Url { loc: p.into(), scheme }), c.into()),
|
||||
None => (CWD.load().as_ref().clone(), path.into()),
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ impl Cd {
|
|||
Ok(s) => {
|
||||
let Ok(url) = Url::try_from(s).map(expand_url) else { return };
|
||||
|
||||
let Ok(file) = File::new(url.as_ref().clone()).await else { return };
|
||||
let Ok(file) = File::new(url.clone()).await else { return };
|
||||
if file.is_dir() {
|
||||
return MgrProxy::cd(&url);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ impl Actor for OpenWith {
|
|||
succ!(cx.tasks.process_from_opener(
|
||||
opt.cwd,
|
||||
opt.opener,
|
||||
opt.targets.into_iter().map(|u| u.into_path().into_os_string()).collect(),
|
||||
opt.targets.into_iter().map(|u| u.into_path2().into_os_string()).collect(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ yazi-shared = { path = "../yazi-shared", version = "25.6.11" }
|
|||
|
||||
# External dependencies
|
||||
clap = { workspace = true }
|
||||
clap_complete = "4.5.55"
|
||||
clap_complete = "4.5.57"
|
||||
clap_complete_fig = "4.5.2"
|
||||
clap_complete_nushell = "4.5.8"
|
||||
vergen-gitcl = { version = "1.0.8", features = [ "build", "rustc" ] }
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::{borrow::Cow, collections::HashSet, path::PathBuf};
|
||||
use std::{collections::HashSet, path::PathBuf};
|
||||
|
||||
use futures::executor::block_on;
|
||||
use serde::Serialize;
|
||||
|
|
@ -25,15 +25,15 @@ impl Boot {
|
|||
return (vec![CWD.load().as_ref().clone()], vec![UrnBuf::default()]);
|
||||
}
|
||||
|
||||
async fn go<'a>(entry: Cow<'a, Url>) -> (Url, UrnBuf) {
|
||||
async fn go<'a>(entry: Url) -> (Url, UrnBuf) {
|
||||
let Some((parent, child)) = entry.pair() else {
|
||||
return (entry.into_owned(), UrnBuf::default());
|
||||
return (entry, UrnBuf::default());
|
||||
};
|
||||
|
||||
if provider::metadata(&entry).await.is_ok_and(|m| m.is_file()) {
|
||||
(parent, child)
|
||||
} else {
|
||||
(entry.into_owned(), UrnBuf::default())
|
||||
(entry, UrnBuf::default())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ yazi-shared = { path = "../yazi-shared", version = "25.6.11" }
|
|||
# External build dependencies
|
||||
anyhow = { workspace = true }
|
||||
clap = { workspace = true }
|
||||
clap_complete = "4.5.55"
|
||||
clap_complete = "4.5.57"
|
||||
clap_complete_fig = "4.5.2"
|
||||
clap_complete_nushell = "4.5.8"
|
||||
serde_json = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ use std::{borrow::Cow, path::PathBuf};
|
|||
use anyhow::{Context, Result, bail};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use yazi_codegen::DeserializeOver2;
|
||||
use yazi_fs::{Xdg, path::expand_path};
|
||||
use yazi_shared::{SStr, timestamp_us};
|
||||
use yazi_fs::{Xdg, path::expand_url};
|
||||
use yazi_shared::{SStr, timestamp_us, url::Url};
|
||||
|
||||
use super::PreviewWrap;
|
||||
|
||||
|
|
@ -53,8 +53,10 @@ impl Preview {
|
|||
|
||||
self.cache_dir = if self.cache_dir.as_os_str().is_empty() {
|
||||
Xdg::cache_dir()
|
||||
} else if let Some(p) = expand_url(Url::from(&self.cache_dir)).into_path() {
|
||||
p
|
||||
} else {
|
||||
expand_path(&self.cache_dir)
|
||||
bail!("[preview].cache_dir must be a path within local filesystem.");
|
||||
};
|
||||
|
||||
std::fs::create_dir_all(&self.cache_dir).context("Failed to create cache directory")?;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::{Context, Result, bail};
|
||||
use anyhow::{Context, Result, anyhow, bail};
|
||||
use serde::Deserialize;
|
||||
use yazi_codegen::{DeserializeOver1, DeserializeOver2};
|
||||
use yazi_fs::{Xdg, ok_or_not_found, path::expand_path};
|
||||
use yazi_fs::{Xdg, ok_or_not_found, path::expand_url};
|
||||
use yazi_shared::url::Url;
|
||||
|
||||
use super::{Filetype, Flavor, Icon};
|
||||
use crate::Style;
|
||||
|
|
@ -219,8 +220,11 @@ impl Theme {
|
|||
|
||||
self.icon = self.icon.reshape()?;
|
||||
|
||||
self.mgr.syntect_theme =
|
||||
self.flavor.syntect_path(light).unwrap_or_else(|| expand_path(&self.mgr.syntect_theme));
|
||||
self.mgr.syntect_theme = self
|
||||
.flavor
|
||||
.syntect_path(light)
|
||||
.or_else(|| expand_url(Url::from(&self.mgr.syntect_theme)).into_path())
|
||||
.ok_or(anyhow!("[mgr].syntect_theme must be a path within local filesystem"))?;
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,12 +77,6 @@ impl Watcher {
|
|||
|
||||
// TODO: performance improvement
|
||||
pub fn trigger_dirs(&self, folders: &[&Folder]) {
|
||||
let todo: Vec<_> =
|
||||
folders.iter().filter(|&f| f.url.is_regular()).map(|&f| (f.url.to_owned(), f.cha)).collect();
|
||||
if todo.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
async fn go(cwd: Url, cha: Cha) {
|
||||
let Some(cha) = Files::assert_stale(&cwd, cha).await else { return };
|
||||
|
||||
|
|
@ -92,9 +86,15 @@ impl Watcher {
|
|||
}
|
||||
}
|
||||
|
||||
tokio::spawn(async move {
|
||||
futures::future::join_all(todo.into_iter().map(|(cwd, cha)| go(cwd, cha))).await;
|
||||
});
|
||||
let futs: Vec<_> = folders
|
||||
.iter()
|
||||
.filter(|&f| f.url.scheme.is_builtin())
|
||||
.map(|&f| go(f.url.to_owned(), f.cha))
|
||||
.collect();
|
||||
|
||||
if !futs.is_empty() {
|
||||
tokio::spawn(futures::future::join_all(futs));
|
||||
}
|
||||
}
|
||||
|
||||
async fn fan_in(
|
||||
|
|
|
|||
|
|
@ -38,10 +38,10 @@ impl Preview {
|
|||
|
||||
pub fn go_folder(&mut self, file: File, dir: Option<Cha>, force: bool) {
|
||||
let same = self.same_file(&file, MIME_DIR);
|
||||
let (wd, cha) = (file.url_owned(), file.cha);
|
||||
let (wd, cha, builtin) = (file.url_owned(), file.cha, file.url.scheme.is_builtin());
|
||||
|
||||
self.go(file, Cow::Borrowed(MIME_DIR), force);
|
||||
if same {
|
||||
if same || !builtin {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ impl Tasks {
|
|||
self.process_from_opener(
|
||||
cwd.clone(),
|
||||
Cow::Borrowed(opener),
|
||||
args.into_iter().map(|u| u.into_path().into_os_string()).collect(),
|
||||
args.into_iter().map(|u| u.into_path2().into_os_string()).collect(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use std::{ffi::OsStr, fs::{FileType, Metadata}, hash::{BuildHasher, Hash, Hasher}, ops::Deref};
|
||||
|
||||
use anyhow::Result;
|
||||
use yazi_shared::url::{Url, Urn, UrnBuf};
|
||||
use yazi_shared::url::{Uri, Url, Urn, UrnBuf};
|
||||
|
||||
use crate::{cha::Cha, provider};
|
||||
|
||||
|
|
@ -56,7 +56,7 @@ impl File {
|
|||
pub fn url_owned(&self) -> Url { self.url.to_owned() }
|
||||
|
||||
#[inline]
|
||||
pub fn uri(&self) -> &Urn { self.url.uri() }
|
||||
pub fn uri(&self) -> &Uri { self.url.uri() }
|
||||
|
||||
#[inline]
|
||||
pub fn urn(&self) -> &Urn { self.url.urn() }
|
||||
|
|
|
|||
|
|
@ -2,22 +2,10 @@ use std::{borrow::Cow, ffi::{OsStr, OsString}, path::{Path, PathBuf}};
|
|||
|
||||
use yazi_shared::url::{Loc, Url};
|
||||
|
||||
use crate::CWD;
|
||||
use crate::{CWD, path::clean_url};
|
||||
|
||||
#[inline]
|
||||
pub fn expand_url<'a>(url: impl Into<Cow<'a, Url>>) -> Cow<'a, Url> {
|
||||
let cow = url.into();
|
||||
match expand_url_impl(&cow) {
|
||||
Cow::Borrowed(_) => cow,
|
||||
Cow::Owned(url) => url.into(),
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: VFS
|
||||
#[inline]
|
||||
pub fn expand_path(p: impl AsRef<Path>) -> PathBuf {
|
||||
expand_url(Url::from(p.as_ref())).into_owned().loc.into_path()
|
||||
}
|
||||
pub fn expand_url<'a>(url: impl AsRef<Url>) -> Url { clean_url(expand_url_impl(url.as_ref())) }
|
||||
|
||||
fn expand_url_impl(url: &Url) -> Cow<'_, Url> {
|
||||
let (o_base, o_rest, o_urn) = url.loc.triple();
|
||||
|
|
|
|||
|
|
@ -46,11 +46,9 @@ async fn _unique_name(mut url: Url, append: bool) -> io::Result<Url> {
|
|||
|
||||
if append {
|
||||
name.push(&dot_ext);
|
||||
name.push("_");
|
||||
name.push(i.to_string());
|
||||
name.push(format!("_{i}"));
|
||||
} else {
|
||||
name.push("_");
|
||||
name.push(i.to_string());
|
||||
name.push(format!("_{i}"));
|
||||
name.push(&dot_ext);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
use std::{env, path::PathBuf};
|
||||
|
||||
use crate::path::expand_path;
|
||||
|
||||
pub struct Xdg;
|
||||
|
||||
impl Xdg {
|
||||
pub fn config_dir() -> PathBuf {
|
||||
if let Some(p) = env::var_os("YAZI_CONFIG_HOME").map(expand_path).filter(|p| p.is_absolute()) {
|
||||
if let Some(p) = env::var_os("YAZI_CONFIG_HOME").map(PathBuf::from)
|
||||
&& p.is_absolute()
|
||||
{
|
||||
return p;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
|
||||
use yazi_fs::path::expand_url;
|
||||
use yazi_shared::{event::CmdCow, url::Url};
|
||||
|
|
@ -15,10 +13,8 @@ impl From<CmdCow> for CdOpt {
|
|||
fn from(mut c: CmdCow) -> Self {
|
||||
let mut target = c.take_first_url().unwrap_or_default();
|
||||
|
||||
if !c.bool("raw")
|
||||
&& let Cow::Owned(u) = expand_url(&target)
|
||||
{
|
||||
target = u;
|
||||
if !c.bool("raw") {
|
||||
target = expand_url(&target);
|
||||
}
|
||||
|
||||
Self { target, interactive: c.bool("interactive"), source: CdSource::Cd }
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
|
||||
use yazi_fs::path::expand_url;
|
||||
use yazi_shared::{event::CmdCow, url::Url};
|
||||
|
|
@ -17,10 +15,8 @@ impl From<CmdCow> for RevealOpt {
|
|||
fn from(mut c: CmdCow) -> Self {
|
||||
let mut target = c.take_first_url().unwrap_or_default();
|
||||
|
||||
if !c.bool("raw")
|
||||
&& let Cow::Owned(u) = expand_url(&target)
|
||||
{
|
||||
target = u;
|
||||
if !c.bool("raw") {
|
||||
target = expand_url(&target);
|
||||
}
|
||||
|
||||
Self { target, source: CdSource::Reveal, no_dummy: c.bool("no-dummy") }
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
|
||||
use yazi_boot::BOOT;
|
||||
use yazi_fs::path::expand_url;
|
||||
|
|
@ -18,10 +16,8 @@ impl From<CmdCow> for TabCreateOpt {
|
|||
let Some(mut wd) = c.take_first_url() else {
|
||||
return Self { wd: Some(BOOT.cwds[0].clone()) };
|
||||
};
|
||||
if !c.bool("raw")
|
||||
&& let Cow::Owned(u) = expand_url(&wd)
|
||||
{
|
||||
wd = u;
|
||||
if !c.bool("raw") {
|
||||
wd = expand_url(&wd);
|
||||
}
|
||||
Self { wd: Some(wd) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ local M = {}
|
|||
|
||||
function M:peek(job)
|
||||
local folder = cx.active.preview.folder
|
||||
if not folder or folder.cwd ~= job.file.url then
|
||||
if not folder then
|
||||
return ya.preview_widget(job, ui.Line("Loading..."):area(job.area):align(ui.Align.CENTER))
|
||||
elseif folder.cwd ~= job.file.url then
|
||||
return
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::{borrow::Cow, str::FromStr};
|
||||
use std::str::FromStr;
|
||||
|
||||
use mlua::{ExternalError, Function, IntoLua, IntoLuaMulti, Lua, Table, Value};
|
||||
use yazi_binding::{Cha, Composer, ComposerGet, ComposerSet, Error, File, Url, UrlRef};
|
||||
|
|
@ -154,16 +154,13 @@ fn calc_size(lua: &Lua) -> mlua::Result<Function> {
|
|||
}
|
||||
|
||||
fn expand_url(lua: &Lua) -> mlua::Result<Function> {
|
||||
lua.create_function(|lua, value: Value| {
|
||||
lua.create_function(|_, value: Value| {
|
||||
use yazi_fs::path::expand_url;
|
||||
match &value {
|
||||
Value::String(s) => Url::new(expand_url(Url::try_from(s.as_bytes().as_ref())?)).into_lua(lua),
|
||||
Value::UserData(ud) => match expand_url(&*ud.borrow::<yazi_binding::Url>()?) {
|
||||
Cow::Borrowed(_) => Ok(value),
|
||||
Cow::Owned(u) => Url::new(u).into_lua(lua),
|
||||
},
|
||||
_ => Err("must be a string or a Url".into_lua_err()),
|
||||
}
|
||||
Ok(Url::new(match value {
|
||||
Value::String(s) => expand_url(Url::try_from(s.as_bytes().as_ref())?),
|
||||
Value::UserData(ud) => expand_url(&*ud.borrow::<yazi_binding::Url>()?),
|
||||
_ => Err("must be a string or a Url".into_lua_err())?,
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,38 +5,32 @@ use yazi_shared::Id;
|
|||
|
||||
#[derive(Default)]
|
||||
pub(super) struct Hooks {
|
||||
inner: HashMap<Id, Hook>,
|
||||
inner: HashMap<Id, Box<dyn Hook>>,
|
||||
}
|
||||
|
||||
impl Hooks {
|
||||
#[allow(dead_code)]
|
||||
pub(super) fn add_sync<F>(&mut self, id: Id, f: F)
|
||||
#[inline]
|
||||
pub(super) fn add_async<F, Fut>(&mut self, id: Id, f: F)
|
||||
where
|
||||
F: FnOnce(bool) + Send + Sync + 'static,
|
||||
F: FnOnce(bool) -> Fut + Send + 'static,
|
||||
Fut: Future<Output = ()> + Send + 'static,
|
||||
{
|
||||
self.inner.insert(id, Hook::Sync(Box::new(f)));
|
||||
self.inner.insert(id, Box::new(f));
|
||||
}
|
||||
|
||||
pub(super) fn add_async<F>(&mut self, id: Id, f: F)
|
||||
where
|
||||
F: FnOnce(bool) -> BoxFuture<'static, ()> + Send + Sync + 'static,
|
||||
{
|
||||
self.inner.insert(id, Hook::Async(Box::new(f)));
|
||||
}
|
||||
|
||||
pub(super) fn run_or_pop(&mut self, id: Id, cancel: bool) -> Option<BoxFuture<'static, ()>> {
|
||||
match self.inner.remove(&id)? {
|
||||
Hook::Sync(f) => {
|
||||
f(cancel);
|
||||
None
|
||||
}
|
||||
Hook::Async(f) => Some(f(cancel)),
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub(super) fn pop(&mut self, id: Id) -> Option<Box<dyn Hook>> { self.inner.remove(&id) }
|
||||
}
|
||||
|
||||
// --- Hook
|
||||
pub(super) enum Hook {
|
||||
Sync(Box<dyn FnOnce(bool) + Send>),
|
||||
Async(Box<dyn (FnOnce(bool) -> BoxFuture<'static, ()>) + Send>),
|
||||
pub trait Hook: Send {
|
||||
fn call(self: Box<Self>, cancel: bool) -> BoxFuture<'static, ()>;
|
||||
}
|
||||
|
||||
impl<F, Fut> Hook for F
|
||||
where
|
||||
F: FnOnce(bool) -> Fut + Send,
|
||||
Fut: Future<Output = ()> + Send + 'static,
|
||||
{
|
||||
fn call(self: Box<Self>, cancel: bool) -> BoxFuture<'static, ()> { Box::pin((*self)(cancel)) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use futures::future::BoxFuture;
|
||||
use yazi_config::YAZI;
|
||||
use yazi_parser::app::TasksProgress;
|
||||
use yazi_shared::{Id, Ids};
|
||||
|
||||
use super::{Task, TaskStage};
|
||||
use crate::{Hooks, TaskKind};
|
||||
use crate::{Hook, Hooks, TaskKind};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Ongoing {
|
||||
|
|
@ -56,7 +55,7 @@ impl Ongoing {
|
|||
#[inline]
|
||||
pub fn is_empty(&self) -> bool { self.len() == 0 }
|
||||
|
||||
pub fn try_remove(&mut self, id: Id, stage: TaskStage) -> Option<BoxFuture<'static, ()>> {
|
||||
pub fn try_remove(&mut self, id: Id, stage: TaskStage) -> Option<Box<dyn Hook>> {
|
||||
if let Some(task) = self.get_mut(id) {
|
||||
if stage > task.stage {
|
||||
task.stage = stage;
|
||||
|
|
@ -68,8 +67,8 @@ impl Ongoing {
|
|||
if task.succ < task.total {
|
||||
return None;
|
||||
}
|
||||
if let Some(fut) = self.hooks.run_or_pop(id, false) {
|
||||
return Some(fut);
|
||||
if let Some(hook) = self.hooks.pop(id) {
|
||||
return Some(hook);
|
||||
}
|
||||
}
|
||||
TaskStage::Hooked => {}
|
||||
|
|
|
|||
|
|
@ -59,8 +59,8 @@ impl Scheduler {
|
|||
pub fn cancel(&self, id: Id) -> bool {
|
||||
let mut ongoing = self.ongoing.lock();
|
||||
|
||||
if let Some(fut) = ongoing.hooks.run_or_pop(id, true) {
|
||||
self.micro.try_send(fut, HIGH).ok();
|
||||
if let Some(hook) = ongoing.hooks.pop(id) {
|
||||
self.micro.try_send(hook.call(true), HIGH).ok();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -86,16 +86,13 @@ impl Scheduler {
|
|||
let ongoing = self.ongoing.clone();
|
||||
let (from, to) = (from.clone(), to.clone());
|
||||
|
||||
move |canceled: bool| {
|
||||
async move {
|
||||
move |canceled: bool| async move {
|
||||
if !canceled {
|
||||
remove_dir_clean(&from).await;
|
||||
Pump::push_move(from, to);
|
||||
}
|
||||
ongoing.lock().try_remove(id, TaskStage::Hooked);
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
});
|
||||
|
||||
let file = self.file.clone();
|
||||
|
|
@ -172,8 +169,7 @@ impl Scheduler {
|
|||
let target = target.clone();
|
||||
let ongoing = self.ongoing.clone();
|
||||
|
||||
move |canceled: bool| {
|
||||
async move {
|
||||
move |canceled: bool| async move {
|
||||
if !canceled {
|
||||
provider::remove_dir_all(&target).await.ok();
|
||||
MgrProxy::update_tasks(&target);
|
||||
|
|
@ -181,8 +177,6 @@ impl Scheduler {
|
|||
}
|
||||
ongoing.lock().try_remove(id, TaskStage::Hooked);
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
});
|
||||
|
||||
let file = self.file.clone();
|
||||
|
|
@ -201,16 +195,13 @@ impl Scheduler {
|
|||
let target = target.clone();
|
||||
let ongoing = self.ongoing.clone();
|
||||
|
||||
move |canceled: bool| {
|
||||
async move {
|
||||
move |canceled: bool| async move {
|
||||
if !canceled {
|
||||
MgrProxy::update_tasks(&target);
|
||||
Pump::push_trash(target);
|
||||
}
|
||||
ongoing.lock().try_remove(id, TaskStage::Hooked);
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
});
|
||||
|
||||
let file = self.file.clone();
|
||||
|
|
@ -288,8 +279,7 @@ impl Scheduler {
|
|||
let id = ongoing.add(TaskKind::User, name);
|
||||
ongoing.hooks.add_async(id, {
|
||||
let ongoing = self.ongoing.clone();
|
||||
move |canceled: bool| {
|
||||
async move {
|
||||
move |canceled: bool| async move {
|
||||
if canceled {
|
||||
cancel_tx.send(()).await.ok();
|
||||
cancel_tx.closed().await;
|
||||
|
|
@ -299,8 +289,6 @@ impl Scheduler {
|
|||
}
|
||||
ongoing.lock().try_remove(id, TaskStage::Hooked);
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
});
|
||||
|
||||
let cmd = OsString::from(&opener.run);
|
||||
|
|
@ -388,14 +376,14 @@ impl Scheduler {
|
|||
task.processed += processed;
|
||||
}
|
||||
if succ > 0
|
||||
&& let Some(fut) = ongoing.try_remove(id, TaskStage::Pending)
|
||||
&& let Some(hook) = ongoing.try_remove(id, TaskStage::Pending)
|
||||
{
|
||||
micro.try_send(fut, LOW).ok();
|
||||
micro.try_send(hook.call(false), LOW).ok();
|
||||
}
|
||||
}
|
||||
TaskProg::Succ(id) => {
|
||||
if let Some(fut) = ongoing.lock().try_remove(id, TaskStage::Dispatched) {
|
||||
micro.try_send(fut, LOW).ok();
|
||||
if let Some(hook) = ongoing.lock().try_remove(id, TaskStage::Dispatched) {
|
||||
micro.try_send(hook.call(false), LOW).ok();
|
||||
}
|
||||
}
|
||||
TaskProg::Fail(id, reason) => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::fmt::{self, Display};
|
||||
use std::{fmt::{self, Display}, ops::Not};
|
||||
|
||||
use percent_encoding::{AsciiSet, CONTROLS, PercentEncode, percent_encode};
|
||||
|
||||
|
|
@ -23,23 +23,36 @@ impl<'a> Encode<'a> {
|
|||
percent_encode(s.as_bytes(), SET)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn urn(loc: &'a Loc) -> impl Display {
|
||||
struct D(usize, usize);
|
||||
fn urn(&self) -> impl Display {
|
||||
struct D<'a>(&'a Encode<'a>);
|
||||
|
||||
impl Display for D {
|
||||
impl Display for D<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let (uri, urn) = (self.0, self.1);
|
||||
match (uri != 0, urn != 0) {
|
||||
macro_rules! w {
|
||||
($default_uri:expr, $default_urn:expr) => {{
|
||||
let uri = self.0.loc.uri().count();
|
||||
let urn = self.0.loc.urn().count();
|
||||
match (uri != $default_uri, urn != $default_urn) {
|
||||
(true, true) => write!(f, ":{uri}:{urn}"),
|
||||
(true, false) => write!(f, ":{uri}"),
|
||||
(false, true) => write!(f, "::{urn}"),
|
||||
(false, false) => Ok(()),
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
match self.0.scheme {
|
||||
Scheme::Regular => Ok(()),
|
||||
Scheme::Search(_) | Scheme::Archive(_) => w!(0, 0),
|
||||
Scheme::Sftp(_) => w!(
|
||||
self.0.loc.as_os_str().is_empty().not() as usize,
|
||||
self.0.loc.file_name().is_some() as usize
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
D(loc.uri().count(), loc.urn().count())
|
||||
D(self)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -47,9 +60,9 @@ impl Display for Encode<'_> {
|
|||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.scheme {
|
||||
Scheme::Regular => write!(f, "regular://"),
|
||||
Scheme::Search(d) => write!(f, "search://{}{}/", Self::domain(d), Self::urn(self.loc)),
|
||||
Scheme::Archive(d) => write!(f, "archive://{}{}/", Self::domain(d), Self::urn(self.loc)),
|
||||
Scheme::Sftp(d) => write!(f, "sftp://{}{}/", Self::domain(d), Self::urn(self.loc)),
|
||||
Scheme::Search(d) => write!(f, "search://{}{}/", Self::domain(d), self.urn()),
|
||||
Scheme::Archive(d) => write!(f, "archive://{}{}/", Self::domain(d), self.urn()),
|
||||
Scheme::Sftp(d) => write!(f, "sftp://{}{}/", Self::domain(d), self.urn()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -64,6 +77,10 @@ impl<'a> From<&'a Url> for EncodeTilded<'a> {
|
|||
fn from(url: &'a Url) -> Self { Self { loc: &url.loc, scheme: &url.scheme } }
|
||||
}
|
||||
|
||||
impl<'a> From<&'a EncodeTilded<'a>> for Encode<'a> {
|
||||
fn from(value: &'a EncodeTilded<'a>) -> Self { Self::new(value.loc, value.scheme) }
|
||||
}
|
||||
|
||||
impl Display for EncodeTilded<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
use Encode as E;
|
||||
|
|
@ -71,9 +88,9 @@ impl Display for EncodeTilded<'_> {
|
|||
let loc = percent_encode(self.loc.as_os_str().as_encoded_bytes(), CONTROLS);
|
||||
match self.scheme {
|
||||
Scheme::Regular => write!(f, "regular~://{loc}"),
|
||||
Scheme::Search(d) => write!(f, "search~://{}{}/{loc}", E::domain(d), E::urn(self.loc)),
|
||||
Scheme::Archive(d) => write!(f, "archive~://{}{}/{loc}", E::domain(d), E::urn(self.loc)),
|
||||
Scheme::Sftp(d) => write!(f, "sftp~://{}{}/{loc}", E::domain(d), E::urn(self.loc)),
|
||||
Scheme::Search(d) => write!(f, "search~://{}{}/{loc}", E::domain(d), E::urn(&self.into())),
|
||||
Scheme::Archive(d) => write!(f, "archive~://{}{}/{loc}", E::domain(d), E::urn(&self.into())),
|
||||
Scheme::Sftp(d) => write!(f, "sftp~://{}{}/{loc}", E::domain(d), E::urn(&self.into())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use std::{borrow::Cow, cmp, ffi::{OsStr, OsString}, fmt::{self, Debug, Formatter}, hash::{Hash, Hasher}, ops::Deref, path::{Path, PathBuf}};
|
||||
use std::{borrow::Cow, cmp, ffi::{OsStr, OsString}, fmt::{self, Debug, Formatter}, hash::{Hash, Hasher}, mem, ops::Deref, path::{Path, PathBuf}};
|
||||
|
||||
use anyhow::{Result, bail};
|
||||
|
||||
use crate::url::{Urn, UrnBuf};
|
||||
use crate::url::{Uri, Urn, UrnBuf};
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Loc {
|
||||
|
|
@ -143,8 +143,8 @@ impl Loc {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub fn uri(&self) -> &Urn {
|
||||
Urn::new(unsafe {
|
||||
pub fn uri(&self) -> &Uri {
|
||||
Uri::new(unsafe {
|
||||
OsStr::from_encoded_bytes_unchecked(
|
||||
self.bytes().get_unchecked(self.bytes().len() - self.uri..),
|
||||
)
|
||||
|
|
@ -167,19 +167,28 @@ impl Loc {
|
|||
pub fn name(&self) -> &OsStr { self.inner.file_name().unwrap_or(OsStr::new("")) }
|
||||
|
||||
pub fn set_name(&mut self, name: impl AsRef<OsStr>) {
|
||||
let (old, new) = (self.name(), name.as_ref());
|
||||
if old == new {
|
||||
let old = self.bytes().len();
|
||||
self.mutate(|path| path.set_file_name(name));
|
||||
|
||||
let new = self.bytes().len();
|
||||
if new == old {
|
||||
return;
|
||||
}
|
||||
|
||||
if old.len() > new.len() {
|
||||
let n = old.len() - new.len();
|
||||
(self.uri, self.urn) = (self.uri - n, self.urn - n);
|
||||
if self.uri != 0 {
|
||||
if new > old {
|
||||
self.uri += new - old;
|
||||
} else {
|
||||
let n = new.len() - old.len();
|
||||
(self.uri, self.urn) = (self.uri + n, self.urn + n);
|
||||
self.uri -= old - new;
|
||||
}
|
||||
}
|
||||
if self.urn != 0 {
|
||||
if new > old {
|
||||
self.urn += new - old;
|
||||
} else {
|
||||
self.urn -= old - new;
|
||||
}
|
||||
}
|
||||
self.inner.set_file_name(new);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
@ -240,11 +249,19 @@ impl Loc {
|
|||
|
||||
#[inline]
|
||||
fn bytes(&self) -> &[u8] { self.inner.as_os_str().as_encoded_bytes() }
|
||||
|
||||
#[inline]
|
||||
fn mutate<F: FnOnce(&mut PathBuf)>(&mut self, f: F) {
|
||||
let mut inner = mem::take(&mut self.inner);
|
||||
f(&mut inner);
|
||||
self.inner = Self::from(inner).inner;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::url::Url;
|
||||
|
||||
#[test]
|
||||
fn test_new() {
|
||||
|
|
@ -318,22 +335,34 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_set_name() -> Result<()> {
|
||||
const S: char = std::path::MAIN_SEPARATOR;
|
||||
let cases = [
|
||||
// Regular
|
||||
("/", "a", "regular:///a"),
|
||||
("/a/b", "c", "regular:///a/c"),
|
||||
// Archive
|
||||
("archive:////", "a.zip", "archive:////a.zip"),
|
||||
("archive:////a.zip/b", "c", "archive:////a.zip/c"),
|
||||
("archive://:2//a.zip/b", "c", "archive://:2//a.zip/c"),
|
||||
("archive://:2:1//a.zip/b", "c", "archive://:2:1//a.zip/c"),
|
||||
// Empty
|
||||
("/a", "", "regular:///"),
|
||||
("archive:////a.zip", "", "archive:////"),
|
||||
("archive:////a.zip/b", "", "archive:////a.zip"),
|
||||
("archive://:1:1//a.zip", "", "archive:////"),
|
||||
("archive://:2//a.zip/b", "", "archive://:1//a.zip"),
|
||||
("archive://:2:2//a.zip/b", "", "archive://:1:1//a.zip"),
|
||||
];
|
||||
|
||||
let mut loc = Loc::with("/root/code/foo/".into(), 2, 1)?;
|
||||
assert_eq!(loc.uri().as_os_str(), OsStr::new("code/foo"));
|
||||
assert_eq!(loc.name(), OsStr::new("foo"));
|
||||
assert_eq!(loc.base().as_os_str(), OsStr::new("/root/"));
|
||||
for (input, name, expected) in cases {
|
||||
let mut a: Url = input.parse()?;
|
||||
let b: Url = expected.parse()?;
|
||||
a.set_name(name);
|
||||
assert_eq!(
|
||||
(a.name(), format!("{a:?}").replace(r"\", "/")),
|
||||
(b.name(), expected.replace(r"\", "/"))
|
||||
);
|
||||
}
|
||||
|
||||
loc.set_name("bar.txt");
|
||||
assert_eq!(loc.uri().as_os_str(), OsString::from(format!("code{S}bar.txt")));
|
||||
assert_eq!(loc.name(), OsStr::new("bar.txt"));
|
||||
assert_eq!(loc.base().as_os_str(), OsStr::new("/root/"));
|
||||
|
||||
loc.set_name("baz");
|
||||
assert_eq!(loc.uri().as_os_str(), OsString::from(format!("code{S}baz")));
|
||||
assert_eq!(loc.name(), OsStr::new("baz"));
|
||||
assert_eq!(loc.base().as_os_str(), OsStr::new("/root/"));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
yazi_macro::mod_flat!(component cov display encode loc scheme url urn);
|
||||
yazi_macro::mod_flat!(component cov display encode loc scheme uri url urn);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use std::borrow::Cow;
|
||||
use std::{borrow::Cow, ops::Not, path::Path};
|
||||
|
||||
use anyhow::{Result, bail};
|
||||
use anyhow::{Result, bail, ensure};
|
||||
use percent_encoding::percent_decode;
|
||||
|
||||
use crate::BytesExt;
|
||||
|
|
@ -39,9 +39,9 @@ impl Scheme {
|
|||
pub(super) fn parse(
|
||||
bytes: &[u8],
|
||||
skip: &mut usize,
|
||||
) -> Result<(Self, bool, Option<(usize, usize)>)> {
|
||||
) -> Result<(Self, bool, Option<usize>, Option<usize>)> {
|
||||
let Some((mut protocol, rest)) = bytes.split_by_seq(b"://") else {
|
||||
return Ok((Self::Regular, false, None));
|
||||
return Ok((Self::Regular, false, None, None));
|
||||
};
|
||||
|
||||
// Advance to the beginning of the path
|
||||
|
|
@ -53,24 +53,24 @@ impl Scheme {
|
|||
protocol = &protocol[..protocol.len() - 1];
|
||||
}
|
||||
|
||||
let (scheme, port) = match protocol {
|
||||
b"regular" => (Self::Regular, None),
|
||||
let (scheme, uri, urn) = match protocol {
|
||||
b"regular" => (Self::Regular, None, None),
|
||||
b"search" => {
|
||||
let (domain, uri, urn) = Self::decode_param(rest, skip)?;
|
||||
(Self::Search(domain), Some((uri, urn)))
|
||||
(Self::Search(domain), uri, urn)
|
||||
}
|
||||
b"archive" => {
|
||||
let (domain, uri, urn) = Self::decode_param(rest, skip)?;
|
||||
(Self::Archive(domain), Some((uri, urn)))
|
||||
(Self::Archive(domain), uri, urn)
|
||||
}
|
||||
b"sftp" => {
|
||||
let (domain, uri, urn) = Self::decode_param(rest, skip)?;
|
||||
(Self::Sftp(domain), Some((uri, urn)))
|
||||
(Self::Sftp(domain), uri, urn)
|
||||
}
|
||||
_ => bail!("Could not parse protocol from URL: {}", String::from_utf8_lossy(bytes)),
|
||||
};
|
||||
|
||||
Ok((scheme, tilde, port))
|
||||
Ok((scheme, tilde, uri, urn))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
@ -89,6 +89,14 @@ impl Scheme {
|
|||
if self.is_virtual() || other.is_virtual() { self == other } else { true }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_builtin(&self) -> bool {
|
||||
match self {
|
||||
Self::Regular | Self::Search(_) | Self::Sftp(_) => true,
|
||||
Self::Archive(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_virtual(&self) -> bool {
|
||||
match self {
|
||||
|
|
@ -97,12 +105,15 @@ impl Scheme {
|
|||
}
|
||||
}
|
||||
|
||||
fn decode_param(bytes: &[u8], skip: &mut usize) -> Result<(String, usize, usize)> {
|
||||
fn decode_param(
|
||||
bytes: &[u8],
|
||||
skip: &mut usize,
|
||||
) -> Result<(String, Option<usize>, Option<usize>)> {
|
||||
let mut len = bytes.iter().copied().take_while(|&b| b != b'/').count();
|
||||
let slash = bytes.get(len).is_some_and(|&b| b == b'/');
|
||||
*skip += len + slash as usize;
|
||||
|
||||
let (uri, urn) = Self::decode_port(&bytes[..len], &mut len)?;
|
||||
let (uri, urn) = Self::decode_ports(&bytes[..len], &mut len)?;
|
||||
let domain = match Cow::from(percent_decode(&bytes[..len])) {
|
||||
Cow::Borrowed(b) => str::from_utf8(b)?.to_owned(),
|
||||
Cow::Owned(b) => String::from_utf8(b)?,
|
||||
|
|
@ -111,19 +122,47 @@ impl Scheme {
|
|||
Ok((domain, uri, urn))
|
||||
}
|
||||
|
||||
fn decode_port(bytes: &[u8], skip: &mut usize) -> anyhow::Result<(usize, usize)> {
|
||||
let Some(a_idx) = bytes.iter().rposition(|&b| b == b':') else { return Ok((0, 0)) };
|
||||
fn decode_ports(bytes: &[u8], skip: &mut usize) -> Result<(Option<usize>, Option<usize>)> {
|
||||
let Some(a_idx) = bytes.iter().rposition(|&b| b == b':') else { return Ok((None, None)) };
|
||||
let a_len = bytes.len() - a_idx;
|
||||
*skip -= a_len;
|
||||
let a = if a_len == 1 { 0 } else { str::from_utf8(&bytes[a_idx + 1..])?.parse()? };
|
||||
let a = if a_len == 1 { None } else { Some(str::from_utf8(&bytes[a_idx + 1..])?.parse()?) };
|
||||
|
||||
let Some(b_idx) = bytes[..a_idx].iter().rposition(|&b| b == b':') else { return Ok((a, 0)) };
|
||||
let Some(b_idx) = bytes[..a_idx].iter().rposition(|&b| b == b':') else {
|
||||
return Ok((a, None));
|
||||
};
|
||||
let b_len = bytes[..a_idx].len() - b_idx;
|
||||
*skip -= b_len;
|
||||
let b = if b_len == 1 { 0 } else { str::from_utf8(&bytes[b_idx + 1..a_idx])?.parse()? };
|
||||
let b =
|
||||
if b_len == 1 { None } else { Some(str::from_utf8(&bytes[b_idx + 1..a_idx])?.parse()?) };
|
||||
|
||||
Ok((b, a))
|
||||
}
|
||||
|
||||
pub(super) fn normalize_ports(
|
||||
&self,
|
||||
uri: Option<usize>,
|
||||
urn: Option<usize>,
|
||||
path: &Path,
|
||||
) -> Result<Option<(usize, usize)>> {
|
||||
Ok(match self {
|
||||
Scheme::Regular => {
|
||||
ensure!(uri.is_none() && urn.is_none(), "Regular scheme cannot have ports");
|
||||
None
|
||||
}
|
||||
Scheme::Search(_) => {
|
||||
let (uri, urn) = (uri.unwrap_or(0), urn.unwrap_or(0));
|
||||
ensure!(uri == urn, "Search scheme requires URI and URN to be equal");
|
||||
Some((uri, urn))
|
||||
}
|
||||
Scheme::Archive(_) => Some((uri.unwrap_or(0), urn.unwrap_or(0))),
|
||||
Scheme::Sftp(_) => {
|
||||
let uri = uri.unwrap_or(path.as_os_str().is_empty().not() as usize);
|
||||
let urn = urn.unwrap_or(path.file_name().is_some() as usize);
|
||||
Some((uri, urn))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
@ -132,27 +171,27 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_decode_port() -> Result<()> {
|
||||
fn assert(s: &str, uri: usize, urn: usize, len: usize) -> Result<()> {
|
||||
fn assert(s: &str, len: usize, uri: Option<usize>, urn: Option<usize>) -> Result<()> {
|
||||
let mut n = usize::MAX;
|
||||
let port = Scheme::decode_port(s.as_bytes(), &mut n)?;
|
||||
assert_eq!((port.0, port.1, usize::MAX - n), (uri, urn, len));
|
||||
let port = Scheme::decode_ports(s.as_bytes(), &mut n)?;
|
||||
assert_eq!((usize::MAX - n, port.0, port.1), (len, uri, urn));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Zeros
|
||||
assert("", 0, 0, 0)?;
|
||||
assert(":", 0, 0, 1)?;
|
||||
assert("::", 0, 0, 2)?;
|
||||
assert("", 0, None, None)?;
|
||||
assert(":", 1, None, None)?;
|
||||
assert("::", 2, None, None)?;
|
||||
|
||||
// URI
|
||||
assert(":2", 2, 0, 2)?;
|
||||
assert(":2:", 2, 0, 3)?;
|
||||
assert(":22:", 22, 0, 4)?;
|
||||
assert(":2", 2, Some(2), None)?;
|
||||
assert(":2:", 3, Some(2), None)?;
|
||||
assert(":22:", 4, Some(22), None)?;
|
||||
|
||||
// URN
|
||||
assert("::1", 0, 1, 3)?;
|
||||
assert(":2:1", 2, 1, 4)?;
|
||||
assert(":22:11", 22, 11, 6)?;
|
||||
assert("::1", 3, None, Some(1))?;
|
||||
assert(":2:1", 4, Some(2), Some(1))?;
|
||||
assert(":22:11", 6, Some(22), Some(11))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
27
yazi-shared/src/url/uri.rs
Normal file
27
yazi-shared/src/url/uri.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
use std::{ops::Deref, path::{Component, Path}};
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct Uri(Path);
|
||||
|
||||
impl Uri {
|
||||
#[inline]
|
||||
pub fn new<T: AsRef<Path> + ?Sized>(p: &T) -> &Self {
|
||||
unsafe { &*(p.as_ref() as *const Path as *const Self) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn count(&self) -> usize { self.0.components().count() }
|
||||
|
||||
#[inline]
|
||||
pub fn nth(&self, n: usize) -> Option<Component<'_>> { self.0.components().nth(n) }
|
||||
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool { self.0.as_os_str().is_empty() }
|
||||
}
|
||||
|
||||
impl Deref for Uri {
|
||||
type Target = Path;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.0 }
|
||||
}
|
||||
|
|
@ -197,6 +197,11 @@ impl Url {
|
|||
Some(self.loc.as_path()).filter(|_| !self.scheme.is_virtual())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn into_path(self) -> Option<PathBuf> {
|
||||
Some(self.loc.into_path()).filter(|_| !self.scheme.is_virtual())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_name(&mut self, name: impl AsRef<OsStr>) { self.loc.set_name(name); }
|
||||
|
||||
|
|
@ -214,7 +219,7 @@ impl Url {
|
|||
|
||||
pub fn parse(bytes: &[u8]) -> Result<(Scheme, PathBuf, Option<(usize, usize)>)> {
|
||||
let mut skip = 0;
|
||||
let (scheme, tilde, port) = Scheme::parse(bytes, &mut skip)?;
|
||||
let (scheme, tilde, uri, urn) = Scheme::parse(bytes, &mut skip)?;
|
||||
|
||||
let rest = if tilde {
|
||||
Cow::from(percent_decode(&bytes[skip..])).into_os_str()?
|
||||
|
|
@ -227,7 +232,9 @@ impl Url {
|
|||
Cow::Owned(s) => PathBuf::from(s),
|
||||
};
|
||||
|
||||
Ok((scheme, path, port))
|
||||
let ports = scheme.normalize_ports(uri, urn, &path)?;
|
||||
|
||||
Ok((scheme, path, ports))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -273,7 +280,7 @@ impl Url {
|
|||
|
||||
// FIXME: remove
|
||||
#[inline]
|
||||
pub fn into_path(self) -> PathBuf { self.loc.into_path() }
|
||||
pub fn into_path2(self) -> PathBuf { self.loc.into_path() }
|
||||
}
|
||||
|
||||
impl Debug for Url {
|
||||
|
|
@ -323,8 +330,8 @@ mod tests {
|
|||
("archive://:2:1//a/b.zip/c/d", "e/f", "archive://:4:1//a/b.zip/c/d/e/f"),
|
||||
("archive://:2:2//a/b.zip/c/d", "e/f", "archive://:4:1//a/b.zip/c/d/e/f"),
|
||||
// SFTP
|
||||
("sftp://remote//a", "b/c", "sftp://remote:1:1//a/b/c"),
|
||||
("sftp://remote:1:1//a/b/c", "d/e", "sftp://remote:1:1//a/b/c/d/e"),
|
||||
("sftp://remote//a", "b/c", "sftp://remote//a/b/c"),
|
||||
("sftp://remote:1:1//a/b/c", "d/e", "sftp://remote//a/b/c/d/e"),
|
||||
// Relative
|
||||
("search://kw", "b/c", "search://kw:2:2/b/c"),
|
||||
("search://kw/", "b/c", "search://kw:2:2/b/c"),
|
||||
|
|
@ -356,8 +363,8 @@ mod tests {
|
|||
("archive://:1:1//a/b.zip/c", Some("archive:////a/b.zip")),
|
||||
("archive:////a/b.zip", Some("regular:///a")),
|
||||
// SFTP
|
||||
("sftp://remote:1:1//a/b", Some("sftp://remote:1:1//a")),
|
||||
("sftp://remote:1:1//a", Some("sftp://remote:1//")),
|
||||
("sftp://remote:1:1//a/b", Some("sftp://remote//a")),
|
||||
("sftp://remote:1:1//a", Some("sftp://remote//")),
|
||||
("sftp://remote:1//", None),
|
||||
("sftp://remote//", None),
|
||||
// Relative
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::{borrow::{Borrow, Cow}, ffi::OsStr, ops::Deref, path::{Component, Path, PathBuf}};
|
||||
use std::{borrow::{Borrow, Cow}, ffi::OsStr, ops::Deref, path::{Path, PathBuf}};
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
|
|
@ -18,9 +18,6 @@ impl Urn {
|
|||
#[inline]
|
||||
pub fn count(&self) -> usize { self.0.components().count() }
|
||||
|
||||
#[inline]
|
||||
pub fn nth(&self, n: usize) -> Option<Component<'_>> { self.0.components().nth(n) }
|
||||
|
||||
#[inline]
|
||||
pub fn encoded_bytes(&self) -> &[u8] { self.0.as_os_str().as_encoded_bytes() }
|
||||
|
||||
|
|
@ -30,9 +27,6 @@ impl Urn {
|
|||
use std::os::unix::ffi::OsStrExt;
|
||||
self.name().is_some_and(|s| s.as_bytes().starts_with(b"."))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool { self.0.as_os_str().is_empty() }
|
||||
}
|
||||
|
||||
impl Deref for Urn {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue