mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 23:01:05 +00:00
feat: VFS support for the enter and leave commands (#3035)
This commit is contained in:
parent
d5f225bc4a
commit
0b45ed868f
13 changed files with 119 additions and 88 deletions
36
Cargo.lock
generated
36
Cargo.lock
generated
|
|
@ -352,9 +352,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.30"
|
||||
version = "1.2.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7"
|
||||
checksum = "c3a42d84bb6b69d3a8b3eaacf0d88f179e1929695e1ad012b6cf64d9caaa5fd2"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"libc",
|
||||
|
|
@ -1335,9 +1335,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.8"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "360e552c93fa0e8152ab463bc4c4837fce76a225df11dfaeea66c313de5e61f7"
|
||||
checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"libc",
|
||||
|
|
@ -1557,9 +1557,9 @@ checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
|
|||
|
||||
[[package]]
|
||||
name = "notify"
|
||||
version = "8.1.0"
|
||||
version = "8.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3163f59cd3fa0e9ef8c32f242966a7b9994fd7378366099593e0e73077cd8c97"
|
||||
checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"fsevent-sys",
|
||||
|
|
@ -1984,9 +1984,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
|||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.38.0"
|
||||
version = "0.38.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8927b0664f5c5a98265138b7e3f90aa19a6b21353182469ace36d4ac527b7b1b"
|
||||
checksum = "9845d9dccf565065824e69f9f235fafba1587031eda353c1f1561cd6a6be78f4"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
|
@ -2164,9 +2164,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.5.0"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b"
|
||||
checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
|
||||
dependencies = [
|
||||
"getrandom 0.2.16",
|
||||
"libredox",
|
||||
|
|
@ -2344,9 +2344,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.141"
|
||||
version = "1.0.142"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3"
|
||||
checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
|
|
@ -2410,9 +2410,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.5"
|
||||
version = "1.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410"
|
||||
checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
|
@ -2684,9 +2684,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.47.0"
|
||||
version = "1.47.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43864ed400b6043a4757a25c7a64a8efde741aed79a056a2fb348a406701bb35"
|
||||
checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
|
|
@ -2726,9 +2726,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.15"
|
||||
version = "0.7.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df"
|
||||
checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
|
|
|
|||
|
|
@ -43,11 +43,11 @@ ratatui = { version = "0.29.0", features = [ "unstable-rendered-line
|
|||
regex = "1.11.1"
|
||||
scopeguard = "1.2.0"
|
||||
serde = { version = "1.0.219", features = [ "derive" ] }
|
||||
serde_json = "1.0.141"
|
||||
serde_json = "1.0.142"
|
||||
syntect = { version = "5.2.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] }
|
||||
tokio = { version = "1.47.0", features = [ "full" ] }
|
||||
tokio = { version = "1.47.1", features = [ "full" ] }
|
||||
tokio-stream = "0.1.17"
|
||||
tokio-util = "0.7.15"
|
||||
tokio-util = "0.7.16"
|
||||
toml = { version = "0.9.4" }
|
||||
tracing = { version = "0.1.41", features = [ "max_level_debug", "release_max_level_debug" ] }
|
||||
twox-hash = { version = "2.1.1", default-features = false, features = [ "std", "random", "xxhash3_128" ] }
|
||||
|
|
|
|||
|
|
@ -93,8 +93,10 @@ mod tests {
|
|||
use super::*;
|
||||
|
||||
fn compare(s: &str, parent: &str, child: &str) {
|
||||
let (p, c) = Trigger::split_url(s).unwrap();
|
||||
let p = p.strip_prefix(yazi_fs::CWD.load().as_ref()).unwrap_or(p);
|
||||
let (mut p, c) = Trigger::split_url(s).unwrap();
|
||||
if let Some(u) = p.strip_prefix(yazi_fs::CWD.load().as_ref()) {
|
||||
p = Url::from(&**u);
|
||||
}
|
||||
assert_eq!((p, c.as_urn()), (parent.parse().unwrap(), Urn::new(child)));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ impl Actor for Enter {
|
|||
fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {
|
||||
let Some(h) = cx.hovered().filter(|h| h.is_dir()) else { succ!() };
|
||||
|
||||
act!(mgr:cd, cx, (h.url.to_regular(), CdSource::Enter))
|
||||
let url = if h.url.is_search() { h.url.to_regular() } else { h.url.clone() };
|
||||
|
||||
act!(mgr:cd, cx, (url, CdSource::Enter))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ impl Actor for HoverDo {
|
|||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
// Hover on the file
|
||||
if let Some(u) = opt.url.strip_prefix(cx.cwd()) {
|
||||
render!(cx.current_mut().hover(u.urn()));
|
||||
render!(cx.current_mut().hover(u));
|
||||
}
|
||||
|
||||
// Turn on tracing
|
||||
|
|
|
|||
|
|
@ -19,6 +19,11 @@ impl Actor for Leave {
|
|||
.filter(|u| u != cx.cwd())
|
||||
.or_else(|| cx.cwd().parent_url());
|
||||
|
||||
if let Some(u) = url { act!(mgr:cd, cx, (u.into_regular(), CdSource::Leave)) } else { succ!() }
|
||||
let Some(mut url) = url else { succ!() };
|
||||
if url.is_search() {
|
||||
url = url.into_regular();
|
||||
}
|
||||
|
||||
act!(mgr:cd, cx, (url, CdSource::Leave))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -146,12 +146,12 @@ impl UserData for Url {
|
|||
})
|
||||
});
|
||||
methods.add_method("strip_prefix", |_, me, base: Value| {
|
||||
let url = match base {
|
||||
let urn = match base {
|
||||
Value::String(s) => me.strip_prefix(Self::try_from(s.as_bytes().as_ref())?),
|
||||
Value::UserData(ud) => me.strip_prefix(&ud.borrow::<Self>()?.inner),
|
||||
_ => Err("must be a string or a Url".into_lua_err())?,
|
||||
};
|
||||
Ok(url.map(Self::new))
|
||||
Ok(urn.map(Deref::deref).map(Self::new)) // TODO: return `Urn` instead of `Url`
|
||||
});
|
||||
|
||||
methods.add_function_mut("into_search", |_, (ud, domain): (AnyUserData, mlua::String)| {
|
||||
|
|
|
|||
|
|
@ -8,12 +8,12 @@ use yazi_shared::url::{Scheme, Url};
|
|||
#[derive(Debug, Deserialize)]
|
||||
#[serde(try_from = "String")]
|
||||
pub struct Pattern {
|
||||
inner: globset::GlobMatcher,
|
||||
scheme: PatternScheme,
|
||||
is_dir: bool,
|
||||
is_star: bool,
|
||||
inner: globset::GlobMatcher,
|
||||
scheme: PatternScheme,
|
||||
pub is_dir: bool,
|
||||
is_star: bool,
|
||||
#[cfg(windows)]
|
||||
sep_lit: bool,
|
||||
sep_lit: bool,
|
||||
}
|
||||
|
||||
impl Pattern {
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ anyhow = { workspace = true }
|
|||
crossterm = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
indexmap = { workspace = true }
|
||||
notify = { version = "8.1.0", default-features = false, features = [ "macos_fsevent" ] }
|
||||
notify = { version = "8.2.0", default-features = false, features = [ "macos_fsevent" ] }
|
||||
parking_lot = { workspace = true }
|
||||
ratatui = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use yazi_adapter::Dimension;
|
|||
use yazi_config::{LAYOUT, popup::{Origin, Position}};
|
||||
use yazi_fs::{File, FolderStage};
|
||||
use yazi_macro::render;
|
||||
use yazi_shared::{Id, Ids, url::Url};
|
||||
use yazi_shared::{Id, Ids, url::{Url, Urn}};
|
||||
|
||||
use super::{Backstack, Finder, Folder, History, Mode, Preference, Preview};
|
||||
use crate::{spot::Spot, tab::Selected};
|
||||
|
|
@ -125,7 +125,9 @@ impl Tab {
|
|||
|
||||
if let Some(parent) = &mut self.parent {
|
||||
apply(parent);
|
||||
parent.hover(self.current.url.urn()); // The parent should always track the CWD
|
||||
|
||||
// The parent should always track the CWD
|
||||
parent.hover(self.current.url.strip_prefix(&parent.url).unwrap_or(Urn::new("")));
|
||||
}
|
||||
|
||||
self
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use std::borrow::Cow;
|
||||
use std::{borrow::Cow, str::FromStr};
|
||||
|
||||
use globset::GlobBuilder;
|
||||
use mlua::{ExternalError, ExternalResult, Function, IntoLua, IntoLuaMulti, Lua, Table, Value};
|
||||
use mlua::{ExternalError, Function, IntoLua, IntoLuaMulti, Lua, Table, Value};
|
||||
use yazi_binding::{Cha, Composer, ComposerGet, ComposerSet, Error, File, Url, UrlRef};
|
||||
use yazi_config::Pattern;
|
||||
use yazi_fs::{mounts::PARTITIONS, provider, remove_dir_clean};
|
||||
|
||||
use crate::bindings::SizeCalculator;
|
||||
|
|
@ -105,19 +105,8 @@ fn remove(lua: &Lua) -> mlua::Result<Function> {
|
|||
|
||||
fn read_dir(lua: &Lua) -> mlua::Result<Function> {
|
||||
lua.create_async_function(|lua, (dir, options): (UrlRef, Table)| async move {
|
||||
// FIXME: VFS
|
||||
let glob = if let Ok(s) = options.raw_get::<mlua::String>("glob") {
|
||||
return Err("`glob` is disabled temporarily".into_lua_err());
|
||||
Some(
|
||||
GlobBuilder::new(&s.to_str()?)
|
||||
.case_insensitive(true)
|
||||
.literal_separator(true)
|
||||
.backslash_escape(false)
|
||||
.empty_alternates(true)
|
||||
.build()
|
||||
.into_lua_err()?
|
||||
.compile_matcher(),
|
||||
)
|
||||
let pat = if let Ok(s) = options.raw_get::<mlua::String>("glob") {
|
||||
Some(Pattern::from_str(&s.to_str()?)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
|
@ -132,14 +121,10 @@ fn read_dir(lua: &Lua) -> mlua::Result<Function> {
|
|||
|
||||
let mut files = vec![];
|
||||
while let Ok(Some(next)) = it.next_entry().await {
|
||||
if files.len() >= limit {
|
||||
break;
|
||||
}
|
||||
|
||||
let url = next.url();
|
||||
// if glob.as_ref().is_some_and(|g| !g.is_match(&path)) {
|
||||
// continue;
|
||||
// }
|
||||
if pat.as_ref().is_some_and(|p| !p.match_url(&url, p.is_dir)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let file = if !resolve {
|
||||
yazi_fs::File::from_dummy(url, next.file_type().await.ok())
|
||||
|
|
@ -148,15 +133,14 @@ fn read_dir(lua: &Lua) -> mlua::Result<Function> {
|
|||
} else {
|
||||
yazi_fs::File::from_dummy(url, next.file_type().await.ok())
|
||||
};
|
||||
|
||||
files.push(File::new(file));
|
||||
if files.len() == limit {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let tbl = lua.create_table_with_capacity(files.len(), 0)?;
|
||||
for f in files {
|
||||
tbl.raw_push(f)?;
|
||||
}
|
||||
|
||||
tbl.into_lua_multi(&lua)
|
||||
lua.create_sequence_from(files)?.into_lua_multi(&lua)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -94,21 +94,26 @@ impl Url {
|
|||
|
||||
#[inline]
|
||||
pub fn base(&self) -> Url {
|
||||
use Scheme as S;
|
||||
|
||||
let loc: Loc = self.loc.base().into();
|
||||
match &self.scheme {
|
||||
Scheme::Regular => Self { loc, scheme: Scheme::Regular },
|
||||
Scheme::Search(_) => self.with(loc),
|
||||
Scheme::Archive(_) => self.with(loc),
|
||||
Scheme::Sftp(_) => self.with(loc),
|
||||
match self.scheme {
|
||||
S::Regular => Self { loc, scheme: S::Regular },
|
||||
S::Search(_) => self.with(loc),
|
||||
S::Archive(_) => self.with(loc),
|
||||
S::Sftp(_) => self.with(loc),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn join(&self, path: impl AsRef<Path>) -> Self {
|
||||
use Scheme as S;
|
||||
|
||||
let loc: Loc = self.loc.join(path).into();
|
||||
match self.scheme {
|
||||
Scheme::Regular => Self { loc: self.loc.join(path).into(), scheme: Scheme::Regular },
|
||||
Scheme::Search(_) => self.with(self.loc.join(path)),
|
||||
Scheme::Archive(_) => self.with(self.loc.join(path)),
|
||||
Scheme::Sftp(_) => self.with(self.loc.join(path)),
|
||||
S::Regular => Self { loc, scheme: S::Regular },
|
||||
S::Search(_) => self.with(loc),
|
||||
S::Archive(_) => self.with(loc),
|
||||
S::Sftp(_) => Self { loc, scheme: self.scheme.clone() },
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -127,27 +132,55 @@ impl Url {
|
|||
pub fn os_str(&self) -> Cow<'_, OsStr> { self.components().os_str() }
|
||||
|
||||
pub fn parent_url(&self) -> Option<Url> {
|
||||
use Scheme as S;
|
||||
|
||||
let parent = self.loc.parent()?;
|
||||
let urn = self.loc.urn();
|
||||
|
||||
Some(match &self.scheme {
|
||||
Scheme::Regular => Self { loc: parent.into(), scheme: Scheme::Regular },
|
||||
Scheme::Search(_) if urn == Urn::new("") => {
|
||||
Self { loc: parent.into(), scheme: Scheme::Regular }
|
||||
}
|
||||
Scheme::Search(_) => self.with(parent),
|
||||
Scheme::Archive(_) => self.with(parent),
|
||||
Scheme::Sftp(_) => self.with(parent),
|
||||
Some(match self.scheme {
|
||||
S::Regular => Self { loc: parent.into(), scheme: S::Regular },
|
||||
|
||||
S::Search(_) if urn.is_empty() => Self { loc: parent.into(), scheme: S::Regular },
|
||||
S::Search(_) => self.with(parent),
|
||||
|
||||
S::Archive(_) if urn.is_empty() => Self { loc: parent.into(), scheme: S::Regular },
|
||||
S::Archive(_) => self.with(parent),
|
||||
|
||||
S::Sftp(_) => Self { loc: parent.into(), scheme: self.scheme.clone() },
|
||||
})
|
||||
}
|
||||
|
||||
pub fn strip_prefix(&self, base: impl AsRef<Url>) -> Option<Self> {
|
||||
let base = base.as_ref();
|
||||
if !self.scheme.covariant(&base.scheme) {
|
||||
return None;
|
||||
}
|
||||
pub fn strip_prefix(&self, base: impl AsRef<Url>) -> Option<&Urn> {
|
||||
use Scheme as S;
|
||||
|
||||
Some(self.with(self.loc.strip_prefix(&base.loc).ok()?))
|
||||
let base = base.as_ref();
|
||||
let prefix = self.loc.strip_prefix(&base.loc).ok()?;
|
||||
|
||||
Some(Urn::new(match (&self.scheme, &base.scheme) {
|
||||
// Same scheme
|
||||
(S::Regular, S::Regular) => Some(prefix),
|
||||
(S::Search(_), S::Search(_)) => Some(prefix),
|
||||
(S::Archive(a), S::Archive(b)) => Some(prefix).filter(|_| a == b),
|
||||
(S::Sftp(a), S::Sftp(b)) => Some(prefix).filter(|_| a == b),
|
||||
|
||||
// Both are local files
|
||||
(S::Regular, S::Search(_)) => Some(prefix),
|
||||
(S::Search(_), S::Regular) => Some(prefix),
|
||||
|
||||
// Only the entry of archives is a local file
|
||||
(S::Regular, S::Archive(_)) => Some(prefix).filter(|_| base.urn().is_empty()),
|
||||
(S::Search(_), S::Archive(_)) => Some(prefix).filter(|_| base.urn().is_empty()),
|
||||
(S::Archive(_), S::Regular) => Some(prefix).filter(|_| self.urn().is_empty()),
|
||||
(S::Archive(_), S::Search(_)) => Some(prefix).filter(|_| self.urn().is_empty()),
|
||||
|
||||
// Independent virtual file space
|
||||
(S::Regular, S::Sftp(_)) => None,
|
||||
(S::Search(_), S::Sftp(_)) => None,
|
||||
(S::Archive(_), S::Sftp(_)) => None,
|
||||
(S::Sftp(_), S::Regular) => None,
|
||||
(S::Sftp(_), S::Search(_)) => None,
|
||||
(S::Sftp(_), S::Archive(_)) => None,
|
||||
}?))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
|
|||
|
|
@ -24,6 +24,9 @@ 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
Reference in a new issue