mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 23:01:05 +00:00
fix: unify path expansion and absolutization behavior (#3438)
This commit is contained in:
parent
80b3b8465d
commit
e9eb8b9155
51 changed files with 453 additions and 230 deletions
16
.github/workflows/draft.yml
vendored
16
.github/workflows/draft.yml
vendored
|
|
@ -59,7 +59,7 @@ jobs:
|
|||
run: ./scripts/build.sh ${{ matrix.target }}
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: ${{ matrix.target }}
|
||||
path: |
|
||||
|
|
@ -105,7 +105,7 @@ jobs:
|
|||
Compress-Archive -Path ${env:TARGET_NAME} -DestinationPath "${env:TARGET_NAME}.zip"
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: ${{ matrix.target }}
|
||||
path: yazi-${{ matrix.target }}.zip
|
||||
|
|
@ -133,7 +133,7 @@ jobs:
|
|||
run: ./scripts/build.sh ${{ matrix.target }}
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: ${{ matrix.target }}
|
||||
path: |
|
||||
|
|
@ -155,7 +155,7 @@ jobs:
|
|||
fetch-depth: 0
|
||||
|
||||
- name: Setup LXD
|
||||
uses: canonical/setup-lxd@v0.1.3
|
||||
uses: canonical/setup-lxd@v1
|
||||
|
||||
- name: Setup snapcraft
|
||||
run: sudo snap install --classic snapcraft
|
||||
|
|
@ -167,7 +167,7 @@ jobs:
|
|||
run: mv yazi_*.snap yazi-${{ matrix.arch }}.snap
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: snap-${{ matrix.arch }}
|
||||
path: yazi-${{ matrix.arch }}.snap
|
||||
|
|
@ -176,7 +176,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
needs: [build-snap]
|
||||
steps:
|
||||
- uses: actions/download-artifact@v6
|
||||
- uses: actions/download-artifact@v7
|
||||
with:
|
||||
pattern: snap-*
|
||||
merge-multiple: true
|
||||
|
|
@ -205,7 +205,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
needs: [build-unix, build-windows, build-musl, build-snap]
|
||||
steps:
|
||||
- uses: actions/download-artifact@v6
|
||||
- uses: actions/download-artifact@v7
|
||||
with:
|
||||
merge-multiple: true
|
||||
|
||||
|
|
@ -234,7 +234,7 @@ jobs:
|
|||
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/download-artifact@v6
|
||||
- uses: actions/download-artifact@v7
|
||||
with:
|
||||
merge-multiple: true
|
||||
|
||||
|
|
|
|||
2
.github/workflows/lock.yml
vendored
2
.github/workflows/lock.yml
vendored
|
|
@ -16,7 +16,7 @@ jobs:
|
|||
pull-requests: write
|
||||
discussions: write
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v5
|
||||
- uses: dessant/lock-threads@v6
|
||||
with:
|
||||
issue-inactive-days: "30"
|
||||
issue-comment: >
|
||||
|
|
|
|||
2
.github/workflows/publish.yml
vendored
2
.github/workflows/publish.yml
vendored
|
|
@ -11,7 +11,7 @@ jobs:
|
|||
winget:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/download-artifact@v6
|
||||
- uses: actions/download-artifact@v7
|
||||
with:
|
||||
merge-multiple: true
|
||||
|
||||
|
|
|
|||
29
Cargo.lock
generated
29
Cargo.lock
generated
|
|
@ -63,9 +63,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "aligned"
|
||||
version = "0.4.2"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "377e4c0ba83e4431b10df45c1d4666f178ea9c552cac93e60c3a88bf32785923"
|
||||
checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685"
|
||||
dependencies = [
|
||||
"as-slice",
|
||||
]
|
||||
|
|
@ -431,9 +431,9 @@ checksum = "f4ad8f11f288f48ca24471bbd51ac257aaeaaa07adae295591266b792902ae64"
|
|||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.19.0"
|
||||
version = "3.19.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
|
||||
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
|
||||
|
||||
[[package]]
|
||||
name = "by_address"
|
||||
|
|
@ -2002,9 +2002,9 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
|
|||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.10"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb"
|
||||
checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"libc",
|
||||
|
|
@ -2081,9 +2081,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "luajit-src"
|
||||
version = "210.6.4+e17ee83"
|
||||
version = "210.6.5+7152e15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35a0ceb2a395ffa403a863adcf365e82cc8d8338ac7f5f949b9df5ca3de251e1"
|
||||
checksum = "29e64ac463f01a02ee793423f9b351369cf244c5ee8bb9e2729a75b2eb404181"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"which",
|
||||
|
|
@ -2199,9 +2199,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "moxcms"
|
||||
version = "0.7.10"
|
||||
version = "0.7.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80986bbbcf925ebd3be54c26613d861255284584501595cf418320c078945608"
|
||||
checksum = "ac9557c559cd6fc9867e122e20d2cbefc9ca29d80d027a8e39310920ed2f0a97"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"pxfm",
|
||||
|
|
@ -4105,9 +4105,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "uzers"
|
||||
version = "0.12.1"
|
||||
version = "0.12.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4df81ff504e7d82ad53e95ed1ad5b72103c11253f39238bcc0235b90768a97dd"
|
||||
checksum = "0b8275fb1afee25b4111d2dc8b5c505dbbc4afd0b990cb96deb2d88bff8be18d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
|
|
@ -5008,6 +5008,7 @@ dependencies = [
|
|||
"yazi-fs",
|
||||
"yazi-macro",
|
||||
"yazi-shared",
|
||||
"yazi-vfs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -5272,9 +5273,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "zune-jpeg"
|
||||
version = "0.5.5"
|
||||
version = "0.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc6fb7703e32e9a07fb3f757360338b3a567a5054f21b5f52a666752e333d58e"
|
||||
checksum = "51d915729b0e7d5fe35c2f294c5dc10b30207cc637920e5b59077bfa3da63f28"
|
||||
dependencies = [
|
||||
"zune-core",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ tracing = { version = "0.1.43", features = [ "max_level_debug", "rel
|
|||
twox-hash = { version = "2.1.2", default-features = false, features = [ "std", "random", "xxhash3_128" ] }
|
||||
typed-path = "0.12.0"
|
||||
unicode-width = { version = "0.2.0", default-features = false }
|
||||
uzers = "0.12.1"
|
||||
uzers = "0.12.2"
|
||||
|
||||
[workspace.lints.clippy]
|
||||
format_push_string = "warn"
|
||||
|
|
|
|||
37
scripts/validate-form/package-lock.json
generated
37
scripts/validate-form/package-lock.json
generated
|
|
@ -8,27 +8,37 @@
|
|||
"name": "validate-form",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.10.1",
|
||||
"@actions/core": "^2.0.1",
|
||||
"@actions/github": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/core": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.11.1.tgz",
|
||||
"integrity": "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==",
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-2.0.1.tgz",
|
||||
"integrity": "sha512-oBfqT3GwkvLlo1fjvhQLQxuwZCGTarTE5OuZ2Wg10hvhBj7LRIlF611WT4aZS6fDhO5ZKlY7lCAZTlpmyaHaeg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/exec": "^1.1.1",
|
||||
"@actions/http-client": "^2.0.1"
|
||||
"@actions/exec": "^2.0.0",
|
||||
"@actions/http-client": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/core/node_modules/@actions/http-client": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-3.0.0.tgz",
|
||||
"integrity": "sha512-1s3tXAfVMSz9a4ZEBkXXRQD4QhY3+GAsWSbaYpeknPOKEeyRiU3lH+bHiLMZdo2x/fIeQ/hscL1wCkDLVM2DZQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tunnel": "^0.0.6",
|
||||
"undici": "^5.28.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/exec": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz",
|
||||
"integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==",
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-2.0.0.tgz",
|
||||
"integrity": "sha512-k8ngrX2voJ/RIN6r9xB82NVqKpnMRtxDoiO+g3olkIUpQNqjArXrCQceduQZCQj3P3xm32pChRLqRrtXTlqhIw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/io": "^1.0.1"
|
||||
"@actions/io": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/github": {
|
||||
|
|
@ -57,9 +67,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@actions/io": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz",
|
||||
"integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==",
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/io/-/io-2.0.0.tgz",
|
||||
"integrity": "sha512-Jv33IN09XLO+0HS79aaODsvIRyduiF7NY/F6LYeK5oeUmrsz7aFdRphQjFoESF4jS7lMauDOttKALcpapVDIAg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@fastify/busboy": {
|
||||
|
|
@ -85,6 +95,7 @@
|
|||
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.1.tgz",
|
||||
"integrity": "sha512-dKYCMuPO1bmrpuogcjQ8z7ICCH3FP6WmxpwC03yjzGfZhj9fTJg6+bS1+UAplekbN2C+M61UNllGOOoAfGCrdQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@octokit/auth-token": "^4.0.0",
|
||||
"@octokit/graphql": "^7.1.0",
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.10.1",
|
||||
"@actions/core": "^2.0.1",
|
||||
"@actions/github": "^6.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
use std::mem;
|
||||
|
||||
use anyhow::Result;
|
||||
use yazi_fs::{CWD, path::clean_url, provider::{DirReader, FileHolder}};
|
||||
use yazi_fs::{path::clean_url, provider::{DirReader, FileHolder}};
|
||||
use yazi_macro::{act, render, succ};
|
||||
use yazi_parser::cmp::{CmpItem, ShowOpt, TriggerOpt};
|
||||
use yazi_proxy::CmpProxy;
|
||||
use yazi_shared::{AnyAsciiChar, data::Data, natsort, path::{AsPath, PathBufDyn, PathCow, PathDyn, PathLike}, scheme::{SchemeCow, SchemeLike}, strand::{AsStrand, StrandLike}, url::{UrlBuf, UrlCow, UrlLike}};
|
||||
use yazi_shared::{AnyAsciiChar, data::Data, natsort, path::{AsPath, PathBufDyn, PathLike}, scheme::{SchemeCow, SchemeLike}, strand::{AsStrand, StrandLike}, url::{UrlBuf, UrlCow, UrlLike}};
|
||||
use yazi_vfs::provider;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
|
@ -67,37 +67,35 @@ impl Actor for Trigger {
|
|||
|
||||
impl Trigger {
|
||||
fn split_url(s: &str) -> Option<(UrlBuf, PathBufDyn)> {
|
||||
let (scheme, path) = SchemeCow::parse(s.as_bytes()).ok()?;
|
||||
let scheme = scheme.zeroed();
|
||||
|
||||
if scheme.is_local() && path.as_strand() == "~" {
|
||||
return None; // We don't autocomplete a `~`, but `~/`
|
||||
}
|
||||
|
||||
let cwd = CWD.load();
|
||||
let abs = if !path.is_absolute() && cwd.scheme().covariant(&scheme) {
|
||||
cwd.loc().try_join(&path).ok()?.into()
|
||||
} else {
|
||||
PathCow::from(&path)
|
||||
};
|
||||
|
||||
let sep = if cfg!(windows) {
|
||||
AnyAsciiChar::new(b"/\\").unwrap()
|
||||
} else {
|
||||
AnyAsciiChar::new(b"/").unwrap()
|
||||
};
|
||||
|
||||
let child = path.rsplit_pred(sep).map_or(path.as_path(), |(_, c)| c);
|
||||
let parent =
|
||||
PathDyn::with(scheme.kind(), abs.encoded_bytes().strip_suffix(child.encoded_bytes())?)
|
||||
.ok()?;
|
||||
let (scheme, path) = SchemeCow::parse(s.as_bytes()).ok()?;
|
||||
let scheme = scheme.zeroed();
|
||||
|
||||
Some((clean_url(UrlCow::try_from((scheme, parent)).ok()?), child.into()))
|
||||
// We don't complete a `~`, but `~/`
|
||||
if scheme.is_local() && path.as_strand() == "~" {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Child
|
||||
let child = path.rsplit_pred(sep).map_or(path.as_path(), |(_, c)| c).to_owned();
|
||||
|
||||
// Parent
|
||||
let url = UrlCow::try_from((scheme.clone().zeroed(), path)).ok()?;
|
||||
let abs = if let Some(u) = provider::try_absolute(&url) { u } else { url };
|
||||
let parent = abs.loc().try_strip_suffix(&child).ok()?;
|
||||
|
||||
Some((clean_url(UrlCow::try_from((scheme, parent)).ok()?), child))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use yazi_fs::CWD;
|
||||
use yazi_shared::url::UrlLike;
|
||||
|
||||
use super::*;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::ops::Deref;
|
||||
use std::{ops::Deref, ptr};
|
||||
|
||||
use mlua::{AnyUserData, IntoLua, UserData, UserDataFields, UserDataMethods, Value};
|
||||
use yazi_binding::{Style, cached_field};
|
||||
|
|
@ -73,9 +73,7 @@ impl UserData for File {
|
|||
|
||||
fields.add_field_method_get("idx", |_, me| Ok(me.idx + 1));
|
||||
fields.add_field_method_get("is_hovered", |_, me| Ok(me.idx == me.folder.cursor));
|
||||
fields.add_field_method_get("in_current", |_, me| {
|
||||
Ok(&*me.folder as *const _ == &me.tab.current as *const _)
|
||||
});
|
||||
fields.add_field_method_get("in_current", |_, me| Ok(ptr::eq(&*me.folder, &me.tab.current)));
|
||||
fields.add_field_method_get("in_preview", |_, me| {
|
||||
Ok(me.idx == me.folder.cursor && me.tab.hovered().is_some_and(|f| f.url == me.folder.url))
|
||||
});
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ impl Actor for TabCreate {
|
|||
}
|
||||
|
||||
let mut tab = Tab::default();
|
||||
let (cd, url) = if let Some(wd) = opt.wd {
|
||||
let (cd, url) = if let Some(wd) = opt.url {
|
||||
(true, wd.into_owned())
|
||||
} else if let Some(h) = cx.hovered() {
|
||||
tab.pref = cx.tab().pref.clone();
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use std::path::PathBuf;
|
|||
|
||||
use futures::executor::block_on;
|
||||
use hashbrown::HashSet;
|
||||
use yazi_fs::{CWD, Xdg, path::expand_url};
|
||||
use yazi_fs::{CWD, Xdg, path::clean_url};
|
||||
use yazi_shared::{strand::StrandBuf, url::{UrlBuf, UrlLike}};
|
||||
use yazi_vfs::provider;
|
||||
|
||||
|
|
@ -27,7 +27,7 @@ impl Boot {
|
|||
}
|
||||
|
||||
async fn go(entry: &UrlBuf) -> (UrlBuf, StrandBuf) {
|
||||
let mut entry = expand_url(entry);
|
||||
let mut entry = clean_url(entry);
|
||||
|
||||
if let Ok(u) = provider::absolute(&entry).await
|
||||
&& u.is_owned()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
yazi_macro::mod_pub!(keymap mgr open opener plugin popup preview tasks theme which vfs);
|
||||
|
||||
yazi_macro::mod_flat!(icon layout pattern platform preset priority style yazi);
|
||||
yazi_macro::mod_flat!(icon layout pattern platform preset priority style utils yazi);
|
||||
|
||||
use std::io::{Read, Write};
|
||||
|
||||
|
|
|
|||
|
|
@ -3,10 +3,11 @@ 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_url};
|
||||
use yazi_fs::Xdg;
|
||||
use yazi_shared::{SStr, timestamp_us};
|
||||
|
||||
use super::PreviewWrap;
|
||||
use crate::normalize_path;
|
||||
|
||||
#[rustfmt::skip]
|
||||
const TABS: &[&str] = &["", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "];
|
||||
|
|
@ -50,10 +51,10 @@ impl Preview {
|
|||
|
||||
self.cache_dir = if self.cache_dir.as_os_str().is_empty() {
|
||||
Xdg::cache_dir().to_owned()
|
||||
} else if let Some(p) = expand_url(self.cache_dir).into_local() {
|
||||
} else if let Some(p) = normalize_path(self.cache_dir) {
|
||||
p
|
||||
} else {
|
||||
bail!("[preview].cache_dir must be a path within local filesystem.");
|
||||
bail!("[preview].cache_dir must be either empty or an absolute path.");
|
||||
};
|
||||
|
||||
std::fs::create_dir_all(&self.cache_dir)
|
||||
|
|
|
|||
|
|
@ -25,8 +25,8 @@ impl From<ratatui::style::Style> for Style {
|
|||
let all = value.add_modifier - sub;
|
||||
|
||||
Self {
|
||||
fg: value.fg.map(Into::into),
|
||||
bg: value.bg.map(Into::into),
|
||||
fg: value.fg,
|
||||
bg: value.bg,
|
||||
bold: if all.contains(M::BOLD) { Some(true) } else if sub.contains(M::BOLD) { Some(false) } else { None },
|
||||
dim: if all.contains(M::DIM) { Some(true) } else if sub.contains(M::DIM) { Some(false) } else { None },
|
||||
italic: if all.contains(M::ITALIC) { Some(true) } else if sub.contains(M::ITALIC) { Some(false) } else { None },
|
||||
|
|
@ -75,8 +75,8 @@ impl From<Style> for ratatui::style::Style {
|
|||
}
|
||||
|
||||
Self {
|
||||
fg: value.fg.map(Into::into),
|
||||
bg: value.bg.map(Into::into),
|
||||
fg: value.fg,
|
||||
bg: value.bg,
|
||||
underline_color: None,
|
||||
add_modifier: add,
|
||||
sub_modifier: sub,
|
||||
|
|
|
|||
|
|
@ -3,11 +3,10 @@ use std::path::PathBuf;
|
|||
use anyhow::{Context, Result, anyhow, bail};
|
||||
use serde::Deserialize;
|
||||
use yazi_codegen::{DeserializeOver1, DeserializeOver2};
|
||||
use yazi_fs::{Xdg, ok_or_not_found, path::expand_url};
|
||||
use yazi_shared::url::UrlBuf;
|
||||
use yazi_fs::{Xdg, ok_or_not_found};
|
||||
|
||||
use super::{Filetype, Flavor, Icon};
|
||||
use crate::Style;
|
||||
use crate::{Style, normalize_path};
|
||||
|
||||
#[derive(Deserialize, DeserializeOver1)]
|
||||
pub struct Theme {
|
||||
|
|
@ -240,8 +239,8 @@ impl Theme {
|
|||
self.mgr.syntect_theme = self
|
||||
.flavor
|
||||
.syntect_path(light)
|
||||
.or_else(|| expand_url(UrlBuf::from(&self.mgr.syntect_theme)).into_local())
|
||||
.ok_or(anyhow!("[mgr].syntect_theme must be a path within local filesystem"))?;
|
||||
.or_else(|| normalize_path(self.mgr.syntect_theme))
|
||||
.ok_or(anyhow!("[mgr].syntect_theme must be either empty or an absolute path."))?;
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
|
|
|||
12
yazi-config/src/utils.rs
Normal file
12
yazi-config/src/utils.rs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use yazi_fs::path::{clean_url, expand_url};
|
||||
use yazi_shared::url::UrlBuf;
|
||||
|
||||
pub(crate) fn normalize_path(path: PathBuf) -> Option<PathBuf> {
|
||||
clean_url(yazi_fs::provider::local::try_absolute(expand_url(UrlBuf::from(path)))?)
|
||||
.into_loc()
|
||||
.into_os()
|
||||
.ok()
|
||||
.filter(|p| p.as_os_str().is_empty() || p.is_absolute())
|
||||
}
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
use std::{io, path::PathBuf};
|
||||
use std::{io, mem, path::PathBuf};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use yazi_fs::path::expand_url;
|
||||
|
||||
use crate::normalize_path;
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[serde(tag = "type", rename_all = "kebab-case")]
|
||||
|
|
@ -44,9 +45,8 @@ pub struct ServiceSftp {
|
|||
impl ServiceSftp {
|
||||
fn reshape(&mut self) -> io::Result<()> {
|
||||
if !self.key_file.as_os_str().is_empty() {
|
||||
self.key_file = expand_url(&self.key_file)
|
||||
.into_local()
|
||||
.ok_or_else(|| io::Error::other("key_file must be a path within local filesystem"))?;
|
||||
self.key_file = normalize_path(mem::take(&mut self.key_file))
|
||||
.ok_or_else(|| io::Error::other("key_file must be either empty or an absolute path"))?;
|
||||
}
|
||||
|
||||
self.identity_agent = if self.identity_agent.as_os_str().is_empty() {
|
||||
|
|
@ -55,9 +55,9 @@ impl ServiceSftp {
|
|||
.filter(|p| p.is_absolute())
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
expand_url(&self.identity_agent)
|
||||
.into_local()
|
||||
.ok_or_else(|| io::Error::other("identity_agent must be a path within local filesystem"))?
|
||||
normalize_path(mem::take(&mut self.identity_agent)).ok_or_else(|| {
|
||||
io::Error::other("identity_agent must be either empty or an absolute path")
|
||||
})?
|
||||
};
|
||||
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ impl Vfs {
|
|||
Preset::vfs()?.deserialize_over(toml::Deserializer::parse(&Vfs::read()?)?)?.reshape()
|
||||
})
|
||||
.await?
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))
|
||||
.map_err(io::Error::other)
|
||||
}
|
||||
|
||||
LOADED.get_or_try_init(init).await
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ impl Deref for File {
|
|||
fn deref(&self) -> &Self::Target { &self.cha }
|
||||
}
|
||||
|
||||
impl From<&File> for File {
|
||||
fn from(value: &File) -> Self { value.clone() }
|
||||
impl From<&Self> for File {
|
||||
fn from(value: &Self) -> Self { value.clone() }
|
||||
}
|
||||
|
||||
impl File {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ pub fn clean_url<'a>(url: impl Into<UrlCow<'a>>) -> UrlBuf {
|
|||
cow.trail().components().count() - 1,
|
||||
);
|
||||
|
||||
let scheme = cow.scheme().to_owned().with_ports(uri, urn);
|
||||
let scheme = cow.into_scheme().into_owned().with_ports(uri, urn);
|
||||
(scheme, path).try_into().expect("UrlBuf from cleaned path")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,20 +1,19 @@
|
|||
use std::{borrow::Cow, path::PathBuf};
|
||||
use std::borrow::Cow;
|
||||
|
||||
use yazi_shared::{loc::LocBuf, path::{PathBufDyn, PathCow, PathDyn, PathKind, PathLike}, pool::InternStr, url::{AsUrl, Url, UrlBuf, UrlCow, UrlLike}, wtf8::FromWtf8Vec};
|
||||
|
||||
use crate::{CWD, path::clean_url};
|
||||
use yazi_shared::{loc::LocBuf, path::{PathBufDyn, PathCow, PathKind, PathLike}, pool::InternStr, url::{AsUrl, Url, UrlBuf, UrlCow, UrlLike}, wtf8::FromWtf8Vec};
|
||||
|
||||
#[inline]
|
||||
pub fn expand_url<'a>(url: impl Into<UrlCow<'a>>) -> UrlBuf {
|
||||
clean_url(expand_url_impl(url.into().as_url()))
|
||||
}
|
||||
pub fn expand_url<'a>(url: impl Into<UrlCow<'a>>) -> UrlCow<'a> { expand_url_impl(url.into()) }
|
||||
|
||||
fn expand_url_impl<'a>(url: Url<'a>) -> UrlCow<'a> {
|
||||
fn expand_url_impl(url: UrlCow) -> UrlCow {
|
||||
let (o_base, o_rest, o_urn) = url.triple();
|
||||
|
||||
let n_base = expand_variables(o_base);
|
||||
let n_rest = expand_variables(o_rest);
|
||||
let n_urn = expand_variables(o_urn);
|
||||
let n_base = expand_variables(o_base.into());
|
||||
let n_rest = expand_variables(o_rest.into());
|
||||
let n_urn = expand_variables(o_urn.into());
|
||||
if n_base.is_borrowed() && n_rest.is_borrowed() && n_urn.is_borrowed() {
|
||||
return url;
|
||||
}
|
||||
|
||||
let rest_diff = n_rest.components().count() as isize - o_rest.components().count() as isize;
|
||||
let urn_diff = n_urn.components().count() as isize - o_urn.components().count() as isize;
|
||||
|
|
@ -28,7 +27,7 @@ fn expand_url_impl<'a>(url: Url<'a>) -> UrlCow<'a> {
|
|||
let uri = (uri_count + rest_diff + urn_diff) as usize;
|
||||
let urn = (urn_count + urn_diff) as usize;
|
||||
|
||||
let expanded = match url {
|
||||
match url.as_url() {
|
||||
Url::Regular(_) => UrlBuf::Regular(
|
||||
LocBuf::<std::path::PathBuf>::with(path.into_os().unwrap(), uri, urn).unwrap(),
|
||||
),
|
||||
|
|
@ -44,12 +43,11 @@ fn expand_url_impl<'a>(url: Url<'a>) -> UrlCow<'a> {
|
|||
loc: LocBuf::<typed_path::UnixPathBuf>::with(path.into_unix().unwrap(), uri, urn).unwrap(),
|
||||
domain: domain.intern(),
|
||||
},
|
||||
};
|
||||
|
||||
absolute_url(expanded)
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
fn expand_variables<'a>(p: PathDyn<'a>) -> PathCow<'a> {
|
||||
fn expand_variables(p: PathCow) -> PathCow {
|
||||
// ${HOME} or $HOME
|
||||
#[cfg(unix)]
|
||||
let re = regex::bytes::Regex::new(r"\$(?:\{([^}]+)\}|([a-zA-Z\d_]+))").unwrap();
|
||||
|
|
@ -76,53 +74,6 @@ fn expand_variables<'a>(p: PathDyn<'a>) -> PathCow<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn absolute_url<'a>(url: impl Into<UrlCow<'a>>) -> UrlCow<'a> { absolute_url_impl(url.into()) }
|
||||
|
||||
fn absolute_url_impl<'a>(url: UrlCow<'a>) -> UrlCow<'a> {
|
||||
if url.kind().is_virtual() {
|
||||
return url;
|
||||
}
|
||||
|
||||
let path = url.loc().as_os().expect("must be a local path");
|
||||
let b = path.as_os_str().as_encoded_bytes();
|
||||
|
||||
let loc = if cfg!(windows) && b.len() == 2 && b[1] == b':' && b[0].is_ascii_alphabetic() {
|
||||
LocBuf::<PathBuf>::with(
|
||||
format!(r"{}:\", b[0].to_ascii_uppercase() as char).into(),
|
||||
if url.has_base() { 0 } else { 2 },
|
||||
if url.has_trail() { 0 } else { 2 },
|
||||
)
|
||||
.expect("Loc from drive letter")
|
||||
} else if let Ok(rest) = path.strip_prefix("~/")
|
||||
&& let Some(home) = dirs::home_dir()
|
||||
&& home.is_absolute()
|
||||
{
|
||||
let add = home.components().count() - 1; // Home root ("~") has offset by the absolute root ("/")
|
||||
LocBuf::<PathBuf>::with(
|
||||
home.join(rest),
|
||||
url.uri().components().count() + if url.has_base() { 0 } else { add },
|
||||
url.urn().components().count() + if url.has_trail() { 0 } else { add },
|
||||
)
|
||||
.expect("Loc from home directory")
|
||||
} else if !url.is_absolute() {
|
||||
let cwd = CWD.path();
|
||||
LocBuf::<PathBuf>::with(
|
||||
cwd.join(path),
|
||||
url.uri().components().count(),
|
||||
url.urn().components().count(),
|
||||
)
|
||||
.expect("Loc from relative path")
|
||||
} else {
|
||||
return url;
|
||||
};
|
||||
|
||||
match url.as_url() {
|
||||
Url::Regular(_) => UrlBuf::Regular(loc).into(),
|
||||
Url::Search { domain, .. } => UrlBuf::Search { loc, domain: domain.intern() }.into(),
|
||||
Url::Archive { .. } | Url::Sftp { .. } => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use anyhow::Result;
|
||||
|
|
@ -179,7 +130,7 @@ mod tests {
|
|||
|
||||
for (input, expected) in cases {
|
||||
let u: UrlBuf = input.parse()?;
|
||||
assert_eq!(format!("{:?}", expand_url(u)), expected);
|
||||
assert_eq!(format!("{:?}", expand_url(u).as_url()), expected);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
|||
56
yazi-fs/src/provider/local/absolute.rs
Normal file
56
yazi-fs/src/provider/local/absolute.rs
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use yazi_shared::{loc::LocBuf, pool::InternStr, url::{AsUrl, Url, UrlBuf, UrlCow, UrlLike}};
|
||||
|
||||
use crate::CWD;
|
||||
|
||||
pub fn try_absolute<'a, U>(url: U) -> Option<UrlCow<'a>>
|
||||
where
|
||||
U: Into<UrlCow<'a>>,
|
||||
{
|
||||
try_absolute_impl(url.into())
|
||||
}
|
||||
|
||||
fn try_absolute_impl<'a>(url: UrlCow<'a>) -> Option<UrlCow<'a>> {
|
||||
if url.kind().is_virtual() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let path = url.loc().as_os().expect("must be a local path");
|
||||
let b = path.as_os_str().as_encoded_bytes();
|
||||
|
||||
let loc = if cfg!(windows) && b.len() == 2 && b[1] == b':' && b[0].is_ascii_alphabetic() {
|
||||
LocBuf::<PathBuf>::with(
|
||||
format!(r"{}:\", b[0].to_ascii_uppercase() as char).into(),
|
||||
if url.has_base() { 0 } else { 2 },
|
||||
if url.has_trail() { 0 } else { 2 },
|
||||
)
|
||||
.expect("Loc from drive letter")
|
||||
} else if let Ok(rest) = path.strip_prefix("~/")
|
||||
&& let Some(home) = dirs::home_dir()
|
||||
&& home.is_absolute()
|
||||
{
|
||||
let add = home.components().count() - 1; // Home root ("~") has offset by the absolute root ("/")
|
||||
LocBuf::<PathBuf>::with(
|
||||
home.join(rest),
|
||||
url.uri().components().count() + if url.has_base() { 0 } else { add },
|
||||
url.urn().components().count() + if url.has_trail() { 0 } else { add },
|
||||
)
|
||||
.expect("Loc from home directory")
|
||||
} else if !url.is_absolute() {
|
||||
LocBuf::<PathBuf>::with(
|
||||
CWD.path().join(path),
|
||||
url.uri().components().count(),
|
||||
url.urn().components().count(),
|
||||
)
|
||||
.expect("Loc from relative path")
|
||||
} else {
|
||||
return Some(url);
|
||||
};
|
||||
|
||||
Some(match url.as_url() {
|
||||
Url::Regular(_) => UrlBuf::Regular(loc).into(),
|
||||
Url::Search { domain, .. } => UrlBuf::Search { loc, domain: domain.intern() }.into(),
|
||||
Url::Archive { .. } | Url::Sftp { .. } => None?,
|
||||
})
|
||||
}
|
||||
|
|
@ -53,7 +53,7 @@ pub(super) fn copy_with_progress_impl(
|
|||
let (done_tx, mut done_rx) = oneshot::channel();
|
||||
|
||||
tokio::spawn({
|
||||
let to = to.to_owned();
|
||||
let to = to.clone();
|
||||
async move {
|
||||
done_tx.send(copy_impl(from, to, attrs).await).ok();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use std::{io, path::Path, sync::Arc};
|
|||
use tokio::sync::mpsc;
|
||||
use yazi_shared::{path::{AsPath, PathBufDyn}, scheme::SchemeKind, strand::AsStrand, url::{Url, UrlBuf, UrlCow}};
|
||||
|
||||
use crate::{cha::Cha, path::absolute_url, provider::{Attrs, Capabilities, Provider}};
|
||||
use crate::{cha::Cha, provider::{Attrs, Capabilities, Provider}};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Local<'a> {
|
||||
|
|
@ -18,7 +18,10 @@ impl<'a> Provider for Local<'a> {
|
|||
type ReadDir = super::ReadDir;
|
||||
type UrlCow = UrlCow<'a>;
|
||||
|
||||
async fn absolute(&self) -> io::Result<Self::UrlCow> { Ok(absolute_url(self.url)) }
|
||||
async fn absolute(&self) -> io::Result<Self::UrlCow> {
|
||||
super::try_absolute(self.url)
|
||||
.ok_or_else(|| io::Error::other("Cannot get absolute path for local URL"))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
async fn canonicalize(&self) -> io::Result<UrlBuf> {
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
yazi_macro::mod_flat!(calculator casefold copier dir_entry gate identical local read_dir);
|
||||
yazi_macro::mod_flat!(absolute calculator casefold copier dir_entry gate identical local read_dir);
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ yazi-config = { path = "../yazi-config", version = "25.9.15" }
|
|||
yazi-fs = { path = "../yazi-fs", version = "25.9.15" }
|
||||
yazi-macro = { path = "../yazi-macro", version = "25.9.15" }
|
||||
yazi-shared = { path = "../yazi-shared", version = "25.9.15" }
|
||||
yazi-vfs = { path = "../yazi-vfs", version = "25.9.15" }
|
||||
|
||||
# External dependencies
|
||||
anyhow = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
yazi_macro::mod_flat!(deprecate mouse notify plugin stop update_progress);
|
||||
yazi_macro::mod_flat!(deprecate mouse notify plugin resume stop update_progress);
|
||||
|
|
|
|||
10
yazi-parser/src/app/resume.rs
Normal file
10
yazi-parser/src/app/resume.rs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
use tokio::sync::oneshot;
|
||||
use yazi_shared::event::CmdCow;
|
||||
|
||||
pub struct ResumeOpt {
|
||||
pub tx: Option<oneshot::Sender<()>>,
|
||||
}
|
||||
|
||||
impl From<CmdCow> for ResumeOpt {
|
||||
fn from(mut c: CmdCow) -> Self { Self { tx: c.take_any("tx") } }
|
||||
}
|
||||
|
|
@ -1,11 +1,12 @@
|
|||
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use yazi_fs::path::expand_url;
|
||||
use yazi_shared::{event::CmdCow, url::{Url, UrlBuf, UrlCow}};
|
||||
use yazi_fs::path::{clean_url, expand_url};
|
||||
use yazi_shared::{event::CmdCow, url::{Url, UrlBuf}};
|
||||
use yazi_vfs::provider;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CdOpt {
|
||||
pub target: UrlCow<'static>,
|
||||
pub target: UrlBuf,
|
||||
pub interactive: bool,
|
||||
pub source: CdSource,
|
||||
}
|
||||
|
|
@ -18,19 +19,23 @@ impl From<CmdCow> for CdOpt {
|
|||
target = expand_url(target).into();
|
||||
}
|
||||
|
||||
Self { target, interactive: c.bool("interactive"), source: CdSource::Cd }
|
||||
}
|
||||
}
|
||||
if let Some(u) = provider::try_absolute(&target)
|
||||
&& u.is_owned()
|
||||
{
|
||||
target = u.into_static();
|
||||
}
|
||||
|
||||
impl From<(UrlCow<'static>, CdSource)> for CdOpt {
|
||||
fn from((target, source): (UrlCow<'static>, CdSource)) -> Self {
|
||||
Self { target, interactive: false, source }
|
||||
Self {
|
||||
target: clean_url(target),
|
||||
interactive: c.bool("interactive"),
|
||||
source: CdSource::Cd,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(UrlBuf, CdSource)> for CdOpt {
|
||||
fn from((target, source): (UrlBuf, CdSource)) -> Self {
|
||||
Self::from((UrlCow::from(target), source))
|
||||
Self { target, interactive: false, source }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
|
||||
use yazi_fs::path::expand_url;
|
||||
use yazi_shared::{event::CmdCow, url::{UrlBuf, UrlCow}};
|
||||
use yazi_fs::path::{clean_url, expand_url};
|
||||
use yazi_shared::{event::CmdCow, url::UrlBuf};
|
||||
use yazi_vfs::provider;
|
||||
|
||||
use crate::mgr::CdSource;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RevealOpt {
|
||||
pub target: UrlCow<'static>,
|
||||
pub target: UrlBuf,
|
||||
pub source: CdSource,
|
||||
pub no_dummy: bool,
|
||||
}
|
||||
|
|
@ -19,30 +20,22 @@ impl From<CmdCow> for RevealOpt {
|
|||
target = expand_url(target).into();
|
||||
}
|
||||
|
||||
Self { target, source: CdSource::Reveal, no_dummy: c.bool("no-dummy") }
|
||||
}
|
||||
}
|
||||
if let Some(u) = provider::try_absolute(&target)
|
||||
&& u.is_owned()
|
||||
{
|
||||
target = u.into_static();
|
||||
}
|
||||
|
||||
impl From<UrlCow<'static>> for RevealOpt {
|
||||
fn from(target: UrlCow<'static>) -> Self {
|
||||
Self { target, source: CdSource::Reveal, no_dummy: false }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(UrlCow<'static>, CdSource)> for RevealOpt {
|
||||
fn from((target, source): (UrlCow<'static>, CdSource)) -> Self {
|
||||
Self { target, source, no_dummy: false }
|
||||
Self { target: clean_url(target), source: CdSource::Reveal, no_dummy: c.bool("no-dummy") }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UrlBuf> for RevealOpt {
|
||||
fn from(target: UrlBuf) -> Self { Self::from(UrlCow::from(target)) }
|
||||
fn from(target: UrlBuf) -> Self { Self { target, source: CdSource::Reveal, no_dummy: false } }
|
||||
}
|
||||
|
||||
impl From<(UrlBuf, CdSource)> for RevealOpt {
|
||||
fn from((target, source): (UrlBuf, CdSource)) -> Self {
|
||||
Self::from((UrlCow::from(target), source))
|
||||
}
|
||||
fn from((target, source): (UrlBuf, CdSource)) -> Self { Self { target, source, no_dummy: false } }
|
||||
}
|
||||
|
||||
impl FromLua for RevealOpt {
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@ use anyhow::bail;
|
|||
use mlua::{ExternalError, FromLua, IntoLua, Lua, LuaSerdeExt, Value};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use yazi_binding::Url;
|
||||
use yazi_shared::{event::CmdCow, url::UrlCow};
|
||||
use yazi_shared::{event::CmdCow, url::UrlBuf};
|
||||
|
||||
use crate::mgr::{CdOpt, CdSource};
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct StashOpt {
|
||||
pub target: UrlCow<'static>,
|
||||
pub target: UrlBuf,
|
||||
pub source: CdSource,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,25 +1,35 @@
|
|||
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
|
||||
use yazi_boot::BOOT;
|
||||
use yazi_fs::path::expand_url;
|
||||
use yazi_fs::path::{clean_url, expand_url};
|
||||
use yazi_shared::{event::CmdCow, url::UrlCow};
|
||||
use yazi_vfs::provider;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TabCreateOpt {
|
||||
pub wd: Option<UrlCow<'static>>,
|
||||
pub url: Option<UrlCow<'static>>,
|
||||
}
|
||||
|
||||
impl From<CmdCow> for TabCreateOpt {
|
||||
fn from(mut c: CmdCow) -> Self {
|
||||
if c.bool("current") {
|
||||
return Self { wd: None };
|
||||
return Self { url: None };
|
||||
}
|
||||
let Ok(mut wd) = c.take_first() else {
|
||||
return Self { wd: Some(UrlCow::from(&BOOT.cwds[0])) };
|
||||
|
||||
let Ok(mut url) = c.take_first() else {
|
||||
return Self { url: Some(UrlCow::from(&BOOT.cwds[0])) };
|
||||
};
|
||||
|
||||
if !c.bool("raw") {
|
||||
wd = expand_url(wd).into();
|
||||
url = expand_url(url).into();
|
||||
}
|
||||
Self { wd: Some(wd) }
|
||||
|
||||
if let Some(u) = provider::try_absolute(&url)
|
||||
&& u.is_owned()
|
||||
{
|
||||
url = u.into_static();
|
||||
}
|
||||
|
||||
Self { url: Some(clean_url(url).into()) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ pub(super) fn hide(lua: &Lua) -> mlua::Result<Value> {
|
|||
|
||||
pub(super) fn printable(lua: &Lua) -> mlua::Result<Value> {
|
||||
let f = lua.create_function(|lua, s: mlua::String| {
|
||||
Ok(match replace_to_printable(&*s.as_bytes(), false, 1, true) {
|
||||
Ok(match replace_to_printable(&s.as_bytes(), false, 1, true) {
|
||||
Cow::Borrowed(_) => s,
|
||||
Cow::Owned(new) => lua.create_string(&new)?,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -161,14 +161,23 @@ fn calc_size(lua: &Lua) -> mlua::Result<Function> {
|
|||
})
|
||||
}
|
||||
|
||||
#[allow(irrefutable_let_patterns)]
|
||||
fn expand_url(lua: &Lua) -> mlua::Result<Function> {
|
||||
lua.create_function(|_, value: Value| {
|
||||
lua.create_function(|lua, value: Value| {
|
||||
use yazi_fs::path::expand_url;
|
||||
Ok(Url::new(match value {
|
||||
Value::String(s) => expand_url(UrlCow::try_from(&*s.as_bytes())?),
|
||||
Value::UserData(ud) => expand_url(&*ud.borrow::<yazi_binding::Url>()?),
|
||||
match &value {
|
||||
Value::String(s) => Url::new(expand_url(UrlCow::try_from(&*s.as_bytes())?)).into_lua(lua),
|
||||
Value::UserData(ud) => {
|
||||
if let u = expand_url(&*ud.borrow::<yazi_binding::Url>()?)
|
||||
&& u.is_owned()
|
||||
{
|
||||
Url::new(u.into_owned()).into_lua(lua)
|
||||
} else {
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
_ => Err("must be a string or a Url".into_lua_err())?,
|
||||
}))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,10 +25,10 @@ impl AppProxy {
|
|||
emit!(Call(relay!(app:update_notify, [dur.as_secs_f64()])));
|
||||
}
|
||||
|
||||
pub fn notify_warn(title: &str, content: impl ToString) {
|
||||
pub fn notify_warn(title: impl Into<String>, content: impl Into<String>) {
|
||||
Self::notify(NotifyOpt {
|
||||
title: title.to_owned(),
|
||||
content: content.to_string(),
|
||||
title: title.into(),
|
||||
content: content.into(),
|
||||
level: NotifyLevel::Warn,
|
||||
timeout: Duration::from_secs(5),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ impl TasksProxy {
|
|||
block: bool,
|
||||
orphan: bool,
|
||||
) {
|
||||
let done = CompletionToken::new();
|
||||
let done = CompletionToken::default();
|
||||
emit!(Call(relay!(tasks:process_open).with_any("opt", ProcessOpenOpt {
|
||||
cwd,
|
||||
cmd,
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ impl Process {
|
|||
let (id, cmd) = (task.id, task.cmd.clone());
|
||||
let result = super::shell(task.into()).await;
|
||||
if let Err(e) = result {
|
||||
AppProxy::notify_warn(&cmd.to_string_lossy(), format!("Failed to start process: {e}"));
|
||||
AppProxy::notify_warn(cmd.to_string_lossy(), format!("Failed to start process: {e}"));
|
||||
return Ok(self.ops.out(id, ProcessOutBlock::Succ));
|
||||
}
|
||||
|
||||
|
|
@ -32,7 +32,7 @@ impl Process {
|
|||
Some(code) => format!("Process exited with status code: {code}"),
|
||||
None => "Process terminated by signal".to_string(),
|
||||
};
|
||||
AppProxy::notify_warn(&cmd.to_string_lossy(), &content);
|
||||
AppProxy::notify_warn(cmd.to_string_lossy(), content);
|
||||
}
|
||||
|
||||
Ok(self.ops.out(id, ProcessOutBlock::Succ))
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ impl Task {
|
|||
name,
|
||||
prog: T::default().into(),
|
||||
hook: None,
|
||||
done: CompletionToken::new(),
|
||||
done: Default::default(),
|
||||
|
||||
logs: Default::default(),
|
||||
logger: Default::default(),
|
||||
|
|
|
|||
|
|
@ -2,14 +2,12 @@ use std::sync::{Arc, atomic::{AtomicU8, Ordering}};
|
|||
|
||||
use tokio::sync::Notify;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct CompletionToken {
|
||||
inner: Arc<(AtomicU8, Notify)>,
|
||||
}
|
||||
|
||||
impl CompletionToken {
|
||||
pub fn new() -> Self { Self { inner: Arc::new((AtomicU8::new(0), Notify::new())) } }
|
||||
|
||||
pub fn complete(&self, success: bool) {
|
||||
let new = if success { 1 } else { 2 };
|
||||
self.inner.0.compare_exchange(0, new, Ordering::Relaxed, Ordering::Relaxed).ok();
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use std::borrow::Cow;
|
|||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::path::{AsPath, PathBufDyn, PathDyn, PathKind};
|
||||
use crate::path::{AsPath, PathBufDyn, PathDyn, PathDynError, PathKind};
|
||||
|
||||
// --- PathCow
|
||||
#[derive(Debug)]
|
||||
|
|
@ -19,6 +19,10 @@ impl From<PathBufDyn> for PathCow<'_> {
|
|||
fn from(value: PathBufDyn) -> Self { Self::Owned(value) }
|
||||
}
|
||||
|
||||
impl<'a> From<std::path::PathBuf> for PathCow<'a> {
|
||||
fn from(value: std::path::PathBuf) -> Self { Self::Owned(value.into()) }
|
||||
}
|
||||
|
||||
impl<'a> From<&'a PathCow<'_>> for PathCow<'a> {
|
||||
fn from(value: &'a PathCow<'_>) -> Self { Self::Borrowed(value.as_path()) }
|
||||
}
|
||||
|
|
@ -43,11 +47,20 @@ impl PartialEq<&str> for PathCow<'_> {
|
|||
impl<'a> PathCow<'a> {
|
||||
pub fn into_owned(self) -> PathBufDyn {
|
||||
match self {
|
||||
Self::Borrowed(s) => s.to_owned(),
|
||||
Self::Owned(s) => s,
|
||||
Self::Borrowed(p) => p.to_owned(),
|
||||
Self::Owned(p) => p,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_os(self) -> Result<std::path::PathBuf, PathDynError> {
|
||||
match self {
|
||||
PathCow::Borrowed(p) => p.to_os_owned(),
|
||||
PathCow::Owned(p) => p.into_os(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_borrowed(&self) -> bool { matches!(self, Self::Borrowed(_)) }
|
||||
|
||||
pub fn with<K, T>(kind: K, bytes: T) -> Result<Self>
|
||||
where
|
||||
K: Into<PathKind>,
|
||||
|
|
|
|||
|
|
@ -110,3 +110,22 @@ impl From<std::path::StripPrefixError> for StripPrefixError {
|
|||
impl From<typed_path::StripPrefixError> for StripPrefixError {
|
||||
fn from(_: typed_path::StripPrefixError) -> Self { Self::NotPrefix }
|
||||
}
|
||||
|
||||
// --- StripSuffixError
|
||||
#[derive(Debug, Error)]
|
||||
pub enum StripSuffixError {
|
||||
#[error("calling strip_suffix on URLs with different schemes")]
|
||||
Exotic,
|
||||
#[error("the base is not a suffix of the path")]
|
||||
NotSuffix,
|
||||
#[error("calling strip_suffix on paths with different encodings")]
|
||||
WrongEncoding,
|
||||
}
|
||||
|
||||
impl From<StrandError> for StripSuffixError {
|
||||
fn from(err: StrandError) -> Self {
|
||||
match err {
|
||||
StrandError::AsOs | StrandError::AsUtf8 => Self::WrongEncoding,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use std::borrow::Cow;
|
|||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::{Utf8BytePredictor, path::{AsPath, Components, Display, EndsWithError, JoinError, PathBufDyn, PathCow, PathDyn, PathDynError, PathKind, RsplitOnceError, StartsWithError, StripPrefixError}, strand::{AsStrand, Strand}};
|
||||
use crate::{Utf8BytePredictor, path::{AsPath, Components, Display, EndsWithError, JoinError, PathBufDyn, PathCow, PathDyn, PathDynError, PathKind, RsplitOnceError, StartsWithError, StripPrefixError, StripSuffixError}, strand::{AsStrand, Strand}};
|
||||
|
||||
pub trait PathLike: AsPath {
|
||||
fn as_os(&self) -> Result<&std::path::Path, PathDynError> { self.as_path().as_os() }
|
||||
|
|
@ -85,6 +85,13 @@ pub trait PathLike: AsPath {
|
|||
{
|
||||
self.as_path().try_strip_prefix(base)
|
||||
}
|
||||
|
||||
fn try_strip_suffix<T>(&self, suffix: T) -> Result<PathDyn<'_>, StripSuffixError>
|
||||
where
|
||||
T: AsStrand,
|
||||
{
|
||||
self.as_path().try_strip_suffix(suffix)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> From<&P> for PathBufDyn
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use anyhow::Result;
|
|||
use hashbrown::Equivalent;
|
||||
|
||||
use super::{RsplitOnceError, StartsWithError};
|
||||
use crate::{BytesExt, Utf8BytePredictor, path::{AsPath, Components, Display, EndsWithError, JoinError, PathBufDyn, PathDynError, PathKind, StripPrefixError}, strand::{AsStrand, Strand, StrandError}};
|
||||
use crate::{BytesExt, Utf8BytePredictor, path::{AsPath, Components, Display, EndsWithError, JoinError, PathBufDyn, PathDynError, PathKind, StripPrefixError, StripSuffixError}, strand::{AsStrand, Strand, StrandError}};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum PathDyn<'p> {
|
||||
|
|
@ -252,6 +252,26 @@ impl<'p> PathDyn<'p> {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn try_strip_suffix<T>(self, suffix: T) -> Result<Self, StripSuffixError>
|
||||
where
|
||||
T: AsStrand,
|
||||
{
|
||||
let s = suffix.as_strand();
|
||||
let mut me_comps = self.components();
|
||||
let mut suf_comps = match self.kind() {
|
||||
PathKind::Os => Components::Os(std::path::Path::new(s.as_os()?).components()),
|
||||
PathKind::Unix => Components::Unix(typed_path::UnixPath::new(s.encoded_bytes()).components()),
|
||||
};
|
||||
|
||||
while let Some(next) = suf_comps.next_back() {
|
||||
if me_comps.next_back() != Some(next) {
|
||||
return Err(StripSuffixError::NotSuffix);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(me_comps.path())
|
||||
}
|
||||
|
||||
pub fn with<K, S>(kind: K, strand: &'p S) -> Result<Self, StrandError>
|
||||
where
|
||||
K: Into<PathKind>,
|
||||
|
|
|
|||
|
|
@ -103,6 +103,14 @@ impl<'a> TryFrom<Cow<'a, str>> for UrlCow<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<(SchemeRef<'a>, PathDyn<'a>)> for UrlCow<'a> {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from((scheme, path): (SchemeRef<'a>, PathDyn<'a>)) -> Result<Self, Self::Error> {
|
||||
(SchemeCow::Borrowed(scheme), path).try_into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<(SchemeRef<'a>, PathCow<'a>)> for UrlCow<'a> {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
|
|
@ -245,6 +253,28 @@ impl<'a> UrlCow<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn into_static(self) -> UrlCow<'static> {
|
||||
match self {
|
||||
UrlCow::Regular(loc) => UrlCow::Regular(loc),
|
||||
UrlCow::Search { loc, domain } => UrlCow::Search { loc, domain: domain.into_owned().into() },
|
||||
UrlCow::Archive { loc, domain } => {
|
||||
UrlCow::Archive { loc, domain: domain.into_owned().into() }
|
||||
}
|
||||
UrlCow::Sftp { loc, domain } => UrlCow::Sftp { loc, domain: domain.into_owned().into() },
|
||||
|
||||
UrlCow::RegularRef(loc) => UrlCow::Regular(loc.into()),
|
||||
UrlCow::SearchRef { loc, domain } => {
|
||||
UrlCow::Search { loc: loc.into(), domain: domain.into_owned().into() }
|
||||
}
|
||||
UrlCow::ArchiveRef { loc, domain } => {
|
||||
UrlCow::Archive { loc: loc.into(), domain: domain.into_owned().into() }
|
||||
}
|
||||
UrlCow::SftpRef { loc, domain } => {
|
||||
UrlCow::Sftp { loc: loc.into(), domain: domain.into_owned().into() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_owned(&self) -> UrlBuf { self.as_url().into() }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,6 +54,8 @@ where
|
|||
|
||||
fn trail(&self) -> Url<'_> { self.as_url().trail() }
|
||||
|
||||
fn triple(&self) -> (PathDyn<'_>, PathDyn<'_>, PathDyn<'_>) { self.as_url().triple() }
|
||||
|
||||
fn try_ends_with(&self, child: impl AsUrl) -> Result<bool, EndsWithError> {
|
||||
self.as_url().try_ends_with(child)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use hashbrown::Equivalent;
|
|||
use serde::Serialize;
|
||||
|
||||
use super::Encode as EncodeUrl;
|
||||
use crate::{loc::{Loc, LocBuf}, path::{AsPath, AsPathRef, EndsWithError, JoinError, PathBufDyn, PathDyn, PathDynError, PathLike, StartsWithError, StripPrefixError}, pool::InternStr, scheme::{Encode as EncodeScheme, SchemeCow, SchemeKind, SchemeRef}, strand::{AsStrand, Strand}, url::{AsUrl, Components, UrlBuf, UrlCow}};
|
||||
use crate::{loc::{Loc, LocBuf}, path::{AsPath, AsPathRef, EndsWithError, JoinError, PathBufDyn, PathDyn, PathDynError, PathLike, StartsWithError, StripPrefixError, StripSuffixError}, pool::InternStr, scheme::{Encode as EncodeScheme, SchemeCow, SchemeKind, SchemeRef}, strand::{AsStrand, Strand}, url::{AsUrl, Components, UrlBuf, UrlCow}};
|
||||
|
||||
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
|
||||
pub enum Url<'a> {
|
||||
|
|
@ -384,6 +384,52 @@ impl<'a> Url<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn try_strip_suffix(self, other: impl AsUrl) -> Result<PathDyn<'a>, StripSuffixError> {
|
||||
use StripSuffixError::{Exotic, NotSuffix};
|
||||
use Url as U;
|
||||
|
||||
let other = other.as_url();
|
||||
let suffix = self.loc().try_strip_suffix(other.loc())?;
|
||||
|
||||
match (self, other) {
|
||||
// Same scheme
|
||||
(U::Regular(_), U::Regular(_)) => Ok(suffix),
|
||||
(U::Search { .. }, U::Search { .. }) => Ok(suffix),
|
||||
(U::Archive { domain: a, .. }, U::Archive { domain: b, .. }) => {
|
||||
Some(suffix).filter(|_| a == b).ok_or(Exotic)
|
||||
}
|
||||
(U::Sftp { domain: a, .. }, U::Sftp { domain: b, .. }) => {
|
||||
Some(suffix).filter(|_| a == b).ok_or(Exotic)
|
||||
}
|
||||
|
||||
// Both are local files
|
||||
(U::Regular(_), U::Search { .. }) => Ok(suffix),
|
||||
(U::Search { .. }, U::Regular(_)) => Ok(suffix),
|
||||
|
||||
// Only the entry of archives is a local file
|
||||
(U::Regular(_), U::Archive { .. }) => {
|
||||
Some(suffix).filter(|_| other.uri().is_empty()).ok_or(NotSuffix)
|
||||
}
|
||||
(U::Search { .. }, U::Archive { .. }) => {
|
||||
Some(suffix).filter(|_| other.uri().is_empty()).ok_or(NotSuffix)
|
||||
}
|
||||
(U::Archive { .. }, U::Regular(_)) => {
|
||||
Some(suffix).filter(|_| self.uri().is_empty()).ok_or(NotSuffix)
|
||||
}
|
||||
(U::Archive { .. }, U::Search { .. }) => {
|
||||
Some(suffix).filter(|_| self.uri().is_empty()).ok_or(NotSuffix)
|
||||
}
|
||||
|
||||
// Independent virtual file space
|
||||
(U::Regular(_), U::Sftp { .. }) => Err(Exotic),
|
||||
(U::Search { .. }, U::Sftp { .. }) => Err(Exotic),
|
||||
(U::Archive { .. }, U::Sftp { .. }) => Err(Exotic),
|
||||
(U::Sftp { .. }, U::Regular(_)) => Err(Exotic),
|
||||
(U::Sftp { .. }, U::Search { .. }) => Err(Exotic),
|
||||
(U::Sftp { .. }, U::Archive { .. }) => Err(Exotic),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn uri(self) -> PathDyn<'a> {
|
||||
match self {
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ pub(super) fn copy_with_progress_impl(
|
|||
}
|
||||
};
|
||||
|
||||
let chunks = (cha.len + PER_CHUNK - 1) / PER_CHUNK;
|
||||
let chunks = cha.len.div_ceil(PER_CHUNK);
|
||||
let it = futures::stream::iter(0..chunks)
|
||||
.map(|i| {
|
||||
let acc_ = acc_.clone();
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use std::io;
|
|||
|
||||
use tokio::sync::mpsc;
|
||||
use yazi_fs::{cha::Cha, provider::{Attrs, Capabilities, Provider, local::Local}};
|
||||
use yazi_shared::{path::PathBufDyn, strand::AsStrand, url::{AsUrl, UrlBuf, UrlCow}};
|
||||
use yazi_shared::{path::PathBufDyn, strand::AsStrand, url::{AsUrl, Url, UrlBuf, UrlCow}};
|
||||
|
||||
use super::{Providers, ReadDir, RwFile};
|
||||
|
||||
|
|
@ -247,6 +247,18 @@ where
|
|||
Providers::new(url.as_url()).await?.trash().await
|
||||
}
|
||||
|
||||
pub fn try_absolute<'a, U>(url: U) -> Option<UrlCow<'a>>
|
||||
where
|
||||
U: Into<UrlCow<'a>>,
|
||||
{
|
||||
let url = url.into();
|
||||
match url.as_url() {
|
||||
Url::Regular(_) | Url::Search { .. } => yazi_fs::provider::local::try_absolute(url),
|
||||
Url::Archive { .. } => None, // TODO
|
||||
Url::Sftp { .. } => crate::provider::sftp::try_absolute(url),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn write<U, C>(url: U, contents: C) -> io::Result<()>
|
||||
where
|
||||
U: AsUrl,
|
||||
|
|
|
|||
21
yazi-vfs/src/provider/sftp/absolute.rs
Normal file
21
yazi-vfs/src/provider/sftp/absolute.rs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
use yazi_fs::CWD;
|
||||
use yazi_shared::url::{UrlCow, UrlLike};
|
||||
|
||||
pub fn try_absolute<'a, U>(url: U) -> Option<UrlCow<'a>>
|
||||
where
|
||||
U: Into<UrlCow<'a>>,
|
||||
{
|
||||
try_absolute_impl(url.into())
|
||||
}
|
||||
|
||||
fn try_absolute_impl<'a>(url: UrlCow<'a>) -> Option<UrlCow<'a>> {
|
||||
if url.is_absolute() {
|
||||
Some(url)
|
||||
} else if let cwd = CWD.load()
|
||||
&& cwd.scheme().covariant(url.scheme())
|
||||
{
|
||||
Some(cwd.try_join(url.loc()).ok()?.into())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
yazi_macro::mod_flat!(conn gate metadata read_dir sftp);
|
||||
yazi_macro::mod_flat!(absolute conn gate metadata read_dir sftp);
|
||||
|
||||
static CONN: yazi_shared::RoCell<
|
||||
parking_lot::Mutex<
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use std::{io, sync::Arc};
|
|||
|
||||
use tokio::{io::{AsyncWriteExt, BufReader, BufWriter}, sync::mpsc::Receiver};
|
||||
use yazi_config::vfs::{ServiceSftp, Vfs};
|
||||
use yazi_fs::{CWD, provider::{Capabilities, DirReader, FileHolder, Provider}};
|
||||
use yazi_fs::provider::{Capabilities, DirReader, FileHolder, Provider};
|
||||
use yazi_sftp::fs::{Attrs, Flags};
|
||||
use yazi_shared::{loc::LocBuf, path::{AsPath, PathBufDyn}, pool::InternStr, scheme::SchemeKind, strand::AsStrand, url::{Url, UrlBuf, UrlCow, UrlLike}};
|
||||
|
||||
|
|
@ -26,11 +26,8 @@ impl<'a> Provider for Sftp<'a> {
|
|||
type UrlCow = UrlCow<'a>;
|
||||
|
||||
async fn absolute(&self) -> io::Result<Self::UrlCow> {
|
||||
let cwd = CWD.load();
|
||||
Ok(if self.url.is_absolute() {
|
||||
self.url.into()
|
||||
} else if cwd.scheme().covariant(self.url.scheme()) {
|
||||
cwd.try_join(self.path)?.into()
|
||||
Ok(if let Some(u) = super::try_absolute(self.url) {
|
||||
u
|
||||
} else {
|
||||
self.canonicalize().await?.into()
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue