mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 23:01:05 +00:00
feat: remote filesystem support for interactive cd (#3398)
This commit is contained in:
parent
c7739c5941
commit
c6e03e91d2
20 changed files with 148 additions and 123 deletions
|
|
@ -68,8 +68,8 @@ impl Actor for Trigger {
|
||||||
impl Trigger {
|
impl Trigger {
|
||||||
fn split_url(s: &str) -> Option<(UrlBuf, PathBufDyn)> {
|
fn split_url(s: &str) -> Option<(UrlBuf, PathBufDyn)> {
|
||||||
let (scheme, path) = SchemeCow::parse(s.as_bytes()).ok()?;
|
let (scheme, path) = SchemeCow::parse(s.as_bytes()).ok()?;
|
||||||
|
let scheme = scheme.zeroed();
|
||||||
|
|
||||||
tracing::debug!(?scheme, ?path);
|
|
||||||
if scheme.is_local() && path.as_strand() == "~" {
|
if scheme.is_local() && path.as_strand() == "~" {
|
||||||
return None; // We don't autocomplete a `~`, but `~/`
|
return None; // We don't autocomplete a `~`, but `~/`
|
||||||
}
|
}
|
||||||
|
|
@ -86,7 +86,11 @@ impl Trigger {
|
||||||
(UrlCow::try_from((scheme, root)).ok()?.into_owned(), c.into())
|
(UrlCow::try_from((scheme, root)).ok()?.into_owned(), c.into())
|
||||||
}
|
}
|
||||||
Some((p, c)) => (expand_url(UrlCow::try_from((scheme, p)).ok()?), c.into()),
|
Some((p, c)) => (expand_url(UrlCow::try_from((scheme, p)).ok()?), c.into()),
|
||||||
None => (CWD.load().as_ref().clone(), path.into()),
|
None if CWD.load().scheme().covariant(&scheme) => (CWD.load().as_ref().clone(), path.into()),
|
||||||
|
None => {
|
||||||
|
let empty = PathDyn::with_str(scheme.kind(), "");
|
||||||
|
(UrlCow::try_from((scheme, empty)).ok()?.into_owned(), path.into())
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -108,6 +112,7 @@ mod tests {
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_split() {
|
fn test_split() {
|
||||||
|
yazi_shared::init_tests();
|
||||||
yazi_fs::init();
|
yazi_fs::init();
|
||||||
compare("", "", "");
|
compare("", "", "");
|
||||||
compare(" ", "", " ");
|
compare(" ", "", " ");
|
||||||
|
|
@ -124,6 +129,12 @@ mod tests {
|
||||||
compare("//foo/", "/foo/", "");
|
compare("//foo/", "/foo/", "");
|
||||||
compare("/foo/bar", "/foo/", "bar");
|
compare("/foo/bar", "/foo/", "bar");
|
||||||
compare("///foo/bar", "/foo/", "bar");
|
compare("///foo/bar", "/foo/", "bar");
|
||||||
|
|
||||||
|
CWD.set(&"sftp://test/".parse::<UrlBuf>().unwrap(), || {});
|
||||||
|
compare("sftp://test/a", "sftp://test/", "a");
|
||||||
|
compare("sftp://test//a", "sftp://test:0//", "a");
|
||||||
|
compare("sftp://test2/a", "sftp://test2/", "a");
|
||||||
|
compare("sftp://test2//a", "sftp://test2:0//", "a");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
|
|
@ -131,9 +142,11 @@ mod tests {
|
||||||
fn test_split() {
|
fn test_split() {
|
||||||
yazi_fs::init();
|
yazi_fs::init();
|
||||||
compare("foo", "", "foo");
|
compare("foo", "", "foo");
|
||||||
|
|
||||||
compare(r"foo\", r"foo\", "");
|
compare(r"foo\", r"foo\", "");
|
||||||
compare(r"foo\bar", r"foo\", "bar");
|
compare(r"foo\bar", r"foo\", "bar");
|
||||||
compare(r"foo\bar\", r"foo\bar\", "");
|
compare(r"foo\bar\", r"foo\bar\", "");
|
||||||
|
|
||||||
compare(r"C:\", r"C:\", "");
|
compare(r"C:\", r"C:\", "");
|
||||||
compare(r"C:\foo", r"C:\", "foo");
|
compare(r"C:\foo", r"C:\", "foo");
|
||||||
compare(r"C:\foo\", r"C:\foo\", "");
|
compare(r"C:\foo\", r"C:\foo\", "");
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,8 @@ use yazi_fs::{File, FilesOp, path::expand_url};
|
||||||
use yazi_macro::{act, err, render, succ};
|
use yazi_macro::{act, err, render, succ};
|
||||||
use yazi_parser::mgr::CdOpt;
|
use yazi_parser::mgr::CdOpt;
|
||||||
use yazi_proxy::{CmpProxy, InputProxy, MgrProxy};
|
use yazi_proxy::{CmpProxy, InputProxy, MgrProxy};
|
||||||
use yazi_shared::{Debounce, data::Data, errors::InputError, url::{UrlBuf, UrlLike}};
|
use yazi_shared::{Debounce, data::Data, errors::InputError, url::{AsUrl, UrlBuf, UrlLike}};
|
||||||
use yazi_vfs::VfsFile;
|
use yazi_vfs::{VfsFile, provider};
|
||||||
|
|
||||||
use crate::{Actor, Ctx};
|
use crate::{Actor, Ctx};
|
||||||
|
|
||||||
|
|
@ -24,7 +24,7 @@ impl Actor for Cd {
|
||||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||||
act!(mgr:escape_visual, cx)?;
|
act!(mgr:escape_visual, cx)?;
|
||||||
if opt.interactive {
|
if opt.interactive {
|
||||||
return Self::cd_interactive();
|
return Self::cd_interactive(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
let tab = cx.tab_mut();
|
let tab = cx.tab_mut();
|
||||||
|
|
@ -59,8 +59,8 @@ impl Actor for Cd {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cd {
|
impl Cd {
|
||||||
fn cd_interactive() -> Result<Data> {
|
fn cd_interactive(cx: &Ctx) -> Result<Data> {
|
||||||
let input = InputProxy::show(InputCfg::cd());
|
let input = InputProxy::show(InputCfg::cd(cx.cwd().as_url()));
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let rx = Debounce::new(UnboundedReceiverStream::new(input), Duration::from_millis(50));
|
let rx = Debounce::new(UnboundedReceiverStream::new(input), Duration::from_millis(50));
|
||||||
|
|
@ -70,6 +70,7 @@ impl Cd {
|
||||||
match result {
|
match result {
|
||||||
Ok(s) => {
|
Ok(s) => {
|
||||||
let Ok(url) = UrlBuf::try_from(s).map(expand_url) else { return };
|
let Ok(url) = UrlBuf::try_from(s).map(expand_url) else { return };
|
||||||
|
let Ok(url) = provider::absolute(&url).await else { return };
|
||||||
|
|
||||||
let Ok(file) = File::new(&url).await else { return };
|
let Ok(file) = File::new(&url).await else { return };
|
||||||
if file.is_dir() {
|
if file.is_dir() {
|
||||||
|
|
@ -79,10 +80,10 @@ impl Cd {
|
||||||
if let Some(p) = url.parent() {
|
if let Some(p) = url.parent() {
|
||||||
FilesOp::Upserting(p.into(), [(url.urn().into(), file)].into()).emit();
|
FilesOp::Upserting(p.into(), [(url.urn().into(), file)].into()).emit();
|
||||||
}
|
}
|
||||||
MgrProxy::reveal(&url);
|
MgrProxy::reveal(url);
|
||||||
}
|
}
|
||||||
Err(InputError::Completed(before, ticket)) => {
|
Err(InputError::Completed(before, ticket)) => {
|
||||||
CmpProxy::trigger(&before, ticket);
|
CmpProxy::trigger(before, ticket);
|
||||||
}
|
}
|
||||||
_ => break,
|
_ => break,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
use anyhow::{Result, bail};
|
use anyhow::Result;
|
||||||
use yazi_fs::FilesOp;
|
use yazi_macro::succ;
|
||||||
use yazi_macro::{act, succ};
|
use yazi_parser::{VoidOpt, mgr::DisplaceDoOpt};
|
||||||
use yazi_parser::{VoidOpt, mgr::{CdSource, DisplaceDoOpt}};
|
|
||||||
use yazi_proxy::MgrProxy;
|
use yazi_proxy::MgrProxy;
|
||||||
use yazi_shared::{data::Data, url::UrlLike};
|
use yazi_shared::{data::Data, url::UrlLike};
|
||||||
use yazi_vfs::provider;
|
use yazi_vfs::provider;
|
||||||
|
|
@ -23,42 +22,9 @@ impl Actor for Displace {
|
||||||
let tab = cx.tab().id;
|
let tab = cx.tab().id;
|
||||||
let from = cx.cwd().to_owned();
|
let from = cx.cwd().to_owned();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
MgrProxy::displace_do(tab, DisplaceDoOpt {
|
MgrProxy::displace_do(tab, DisplaceDoOpt { to: provider::canonicalize(&from).await, from });
|
||||||
to: provider::absolute(&from).await.map(|u| u.into_owned()),
|
|
||||||
from,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
succ!();
|
succ!();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Do
|
|
||||||
pub struct DisplaceDo;
|
|
||||||
|
|
||||||
impl Actor for DisplaceDo {
|
|
||||||
type Options = DisplaceDoOpt;
|
|
||||||
|
|
||||||
const NAME: &str = "displace_do";
|
|
||||||
|
|
||||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
|
||||||
if cx.cwd() != opt.from {
|
|
||||||
succ!()
|
|
||||||
}
|
|
||||||
|
|
||||||
let to = match opt.to {
|
|
||||||
Ok(url) => url,
|
|
||||||
Err(e) => return act!(mgr:update_files, cx, FilesOp::IOErr(opt.from, e.into())),
|
|
||||||
};
|
|
||||||
|
|
||||||
if !to.is_absolute() {
|
|
||||||
bail!("Target URL must be absolute");
|
|
||||||
} else if let Some(hovered) = cx.hovered()
|
|
||||||
&& let Ok(url) = to.try_join(hovered.urn())
|
|
||||||
{
|
|
||||||
act!(mgr:reveal, cx, (url, CdSource::Displace))
|
|
||||||
} else {
|
|
||||||
act!(mgr:cd, cx, (to, CdSource::Displace))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
36
yazi-actor/src/mgr/displace_do.rs
Normal file
36
yazi-actor/src/mgr/displace_do.rs
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
use anyhow::{Result, bail};
|
||||||
|
use yazi_fs::FilesOp;
|
||||||
|
use yazi_macro::{act, succ};
|
||||||
|
use yazi_parser::mgr::{CdSource, DisplaceDoOpt};
|
||||||
|
use yazi_shared::{data::Data, url::UrlLike};
|
||||||
|
|
||||||
|
use crate::{Actor, Ctx};
|
||||||
|
|
||||||
|
pub struct DisplaceDo;
|
||||||
|
|
||||||
|
impl Actor for DisplaceDo {
|
||||||
|
type Options = DisplaceDoOpt;
|
||||||
|
|
||||||
|
const NAME: &str = "displace_do";
|
||||||
|
|
||||||
|
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||||
|
if cx.cwd() != opt.from {
|
||||||
|
succ!()
|
||||||
|
}
|
||||||
|
|
||||||
|
let to = match opt.to {
|
||||||
|
Ok(url) => url,
|
||||||
|
Err(e) => return act!(mgr:update_files, cx, FilesOp::IOErr(opt.from, e.into())),
|
||||||
|
};
|
||||||
|
|
||||||
|
if !to.is_absolute() {
|
||||||
|
bail!("Target URL must be absolute");
|
||||||
|
} else if let Some(hovered) = cx.hovered()
|
||||||
|
&& let Ok(url) = to.try_join(hovered.urn())
|
||||||
|
{
|
||||||
|
act!(mgr:reveal, cx, (url, CdSource::Displace))
|
||||||
|
} else {
|
||||||
|
act!(mgr:cd, cx, (to, CdSource::Displace))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,7 @@ yazi_macro::mod_flat!(
|
||||||
copy
|
copy
|
||||||
create
|
create
|
||||||
displace
|
displace
|
||||||
|
displace_do
|
||||||
download
|
download
|
||||||
enter
|
enter
|
||||||
escape
|
escape
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ use yazi_fs::{FilesOp, cha::Cha};
|
||||||
use yazi_macro::{act, succ};
|
use yazi_macro::{act, succ};
|
||||||
use yazi_parser::{VoidOpt, mgr::{CdSource, SearchOpt, SearchOptVia}};
|
use yazi_parser::{VoidOpt, mgr::{CdSource, SearchOpt, SearchOptVia}};
|
||||||
use yazi_plugin::external;
|
use yazi_plugin::external;
|
||||||
use yazi_proxy::{InputProxy, MgrProxy};
|
use yazi_proxy::{AppProxy, InputProxy, MgrProxy};
|
||||||
use yazi_shared::{data::Data, url::UrlLike};
|
use yazi_shared::{data::Data, url::UrlLike};
|
||||||
|
|
||||||
use crate::{Actor, Ctx};
|
use crate::{Actor, Ctx};
|
||||||
|
|
@ -52,8 +52,10 @@ impl Actor for SearchDo {
|
||||||
handle.abort();
|
handle.abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
let cwd = tab.cwd().to_search(&opt.subject)?;
|
|
||||||
let hidden = tab.pref.show_hidden;
|
let hidden = tab.pref.show_hidden;
|
||||||
|
let Ok(cwd) = tab.cwd().to_search(&opt.subject) else {
|
||||||
|
succ!(AppProxy::notify_warn("Search", "Only local filesystem searches are supported"));
|
||||||
|
};
|
||||||
|
|
||||||
tab.search = Some(tokio::spawn(async move {
|
tab.search = Some(tokio::spawn(async move {
|
||||||
let rx = match opt.via {
|
let rx = match opt.via {
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ impl Actor for Stash {
|
||||||
const NAME: &str = "stash";
|
const NAME: &str = "stash";
|
||||||
|
|
||||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||||
if opt.target.is_internal() {
|
if opt.target.is_absolute() && opt.target.is_internal() {
|
||||||
cx.tab_mut().backstack.push(opt.target.as_url());
|
cx.tab_mut().backstack.push(opt.target.as_url());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use ratatui::{text::{Line, Text}, widgets::{Paragraph, Wrap}};
|
use ratatui::{text::{Line, Text}, widgets::{Paragraph, Wrap}};
|
||||||
use yazi_shared::{strand::ToStrand, url::UrlBuf};
|
use yazi_shared::{scheme::Encode as EncodeScheme, strand::ToStrand, url::{Url, UrlBuf}};
|
||||||
|
|
||||||
use super::{Offset, Position};
|
use super::{Offset, Position};
|
||||||
use crate::YAZI;
|
use crate::YAZI;
|
||||||
|
|
@ -31,9 +31,10 @@ pub struct ConfirmCfg {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InputCfg {
|
impl InputCfg {
|
||||||
pub fn cd() -> Self {
|
pub fn cd(cwd: Url) -> Self {
|
||||||
Self {
|
Self {
|
||||||
title: YAZI.input.cd_title.clone(),
|
title: YAZI.input.cd_title.clone(),
|
||||||
|
value: if cwd.kind().is_local() { String::new() } else { EncodeScheme(cwd).to_string() },
|
||||||
position: Position::new(YAZI.input.cd_origin, YAZI.input.cd_offset),
|
position: Position::new(YAZI.input.cd_origin, YAZI.input.cd_offset),
|
||||||
completion: true,
|
completion: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|
|
||||||
13
yazi-plugin/src/external/fd.rs
vendored
13
yazi-plugin/src/external/fd.rs
vendored
|
|
@ -2,8 +2,8 @@ use std::process::Stdio;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use tokio::{io::{AsyncBufReadExt, BufReader}, process::{Child, Command}, sync::mpsc::{self, UnboundedReceiver}};
|
use tokio::{io::{AsyncBufReadExt, BufReader}, process::{Child, Command}, sync::mpsc::{self, UnboundedReceiver}};
|
||||||
use yazi_fs::File;
|
use yazi_fs::{File, FsUrl};
|
||||||
use yazi_shared::url::{UrlBuf, UrlLike};
|
use yazi_shared::url::{AsUrl, UrlBuf, UrlLike};
|
||||||
use yazi_vfs::VfsFile;
|
use yazi_vfs::VfsFile;
|
||||||
|
|
||||||
pub struct FdOpt {
|
pub struct FdOpt {
|
||||||
|
|
@ -34,16 +34,9 @@ pub fn fd(opt: FdOpt) -> Result<UnboundedReceiver<File>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spawn(program: &str, opt: &FdOpt) -> std::io::Result<Child> {
|
fn spawn(program: &str, opt: &FdOpt) -> std::io::Result<Child> {
|
||||||
let Some(path) = opt.cwd.as_local() else {
|
|
||||||
return Err(std::io::Error::new(
|
|
||||||
std::io::ErrorKind::InvalidInput,
|
|
||||||
"fd can only search local filesystem",
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
Command::new(program)
|
Command::new(program)
|
||||||
.arg("--base-directory")
|
.arg("--base-directory")
|
||||||
.arg(path)
|
.arg(&*opt.cwd.as_url().unified_path())
|
||||||
.arg("--regex")
|
.arg("--regex")
|
||||||
.arg(if opt.hidden { "--hidden" } else { "--no-hidden" })
|
.arg(if opt.hidden { "--hidden" } else { "--no-hidden" })
|
||||||
.args(&opt.args)
|
.args(&opt.args)
|
||||||
|
|
|
||||||
12
yazi-plugin/src/external/rg.rs
vendored
12
yazi-plugin/src/external/rg.rs
vendored
|
|
@ -1,9 +1,9 @@
|
||||||
use std::process::Stdio;
|
use std::process::Stdio;
|
||||||
|
|
||||||
use anyhow::{Result, bail};
|
use anyhow::Result;
|
||||||
use tokio::{io::{AsyncBufReadExt, BufReader}, process::Command, sync::mpsc::{self, UnboundedReceiver}};
|
use tokio::{io::{AsyncBufReadExt, BufReader}, process::Command, sync::mpsc::{self, UnboundedReceiver}};
|
||||||
use yazi_fs::File;
|
use yazi_fs::{File, FsUrl};
|
||||||
use yazi_shared::url::{UrlBuf, UrlLike};
|
use yazi_shared::url::{AsUrl, UrlBuf, UrlLike};
|
||||||
use yazi_vfs::VfsFile;
|
use yazi_vfs::VfsFile;
|
||||||
|
|
||||||
pub struct RgOpt {
|
pub struct RgOpt {
|
||||||
|
|
@ -14,16 +14,12 @@ pub struct RgOpt {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rg(opt: RgOpt) -> Result<UnboundedReceiver<File>> {
|
pub fn rg(opt: RgOpt) -> Result<UnboundedReceiver<File>> {
|
||||||
let Some(path) = opt.cwd.as_local() else {
|
|
||||||
bail!("rg can only search local filesystem");
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut child = Command::new("rg")
|
let mut child = Command::new("rg")
|
||||||
.args(["--color=never", "--files-with-matches", "--smart-case"])
|
.args(["--color=never", "--files-with-matches", "--smart-case"])
|
||||||
.arg(if opt.hidden { "--hidden" } else { "--no-hidden" })
|
.arg(if opt.hidden { "--hidden" } else { "--no-hidden" })
|
||||||
.args(opt.args)
|
.args(opt.args)
|
||||||
.arg(opt.subject)
|
.arg(opt.subject)
|
||||||
.arg(path)
|
.arg(&*opt.cwd.as_url().unified_path())
|
||||||
.kill_on_drop(true)
|
.kill_on_drop(true)
|
||||||
.stdout(Stdio::piped())
|
.stdout(Stdio::piped())
|
||||||
.stderr(Stdio::null())
|
.stderr(Stdio::null())
|
||||||
|
|
|
||||||
12
yazi-plugin/src/external/rga.rs
vendored
12
yazi-plugin/src/external/rga.rs
vendored
|
|
@ -1,9 +1,9 @@
|
||||||
use std::process::Stdio;
|
use std::process::Stdio;
|
||||||
|
|
||||||
use anyhow::{Result, bail};
|
use anyhow::Result;
|
||||||
use tokio::{io::{AsyncBufReadExt, BufReader}, process::Command, sync::mpsc::{self, UnboundedReceiver}};
|
use tokio::{io::{AsyncBufReadExt, BufReader}, process::Command, sync::mpsc::{self, UnboundedReceiver}};
|
||||||
use yazi_fs::File;
|
use yazi_fs::{File, FsUrl};
|
||||||
use yazi_shared::url::{UrlBuf, UrlLike};
|
use yazi_shared::url::{AsUrl, UrlBuf, UrlLike};
|
||||||
use yazi_vfs::VfsFile;
|
use yazi_vfs::VfsFile;
|
||||||
|
|
||||||
pub struct RgaOpt {
|
pub struct RgaOpt {
|
||||||
|
|
@ -14,16 +14,12 @@ pub struct RgaOpt {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rga(opt: RgaOpt) -> Result<UnboundedReceiver<File>> {
|
pub fn rga(opt: RgaOpt) -> Result<UnboundedReceiver<File>> {
|
||||||
let Some(path) = opt.cwd.as_local() else {
|
|
||||||
bail!("rga can only search local filesystem");
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut child = Command::new("rga")
|
let mut child = Command::new("rga")
|
||||||
.args(["--color=never", "--files-with-matches", "--smart-case"])
|
.args(["--color=never", "--files-with-matches", "--smart-case"])
|
||||||
.arg(if opt.hidden { "--hidden" } else { "--no-hidden" })
|
.arg(if opt.hidden { "--hidden" } else { "--no-hidden" })
|
||||||
.args(opt.args)
|
.args(opt.args)
|
||||||
.arg(opt.subject)
|
.arg(opt.subject)
|
||||||
.arg(path)
|
.arg(&*opt.cwd.as_url().unified_path())
|
||||||
.kill_on_drop(true)
|
.kill_on_drop(true)
|
||||||
.stdout(Stdio::piped())
|
.stdout(Stdio::piped())
|
||||||
.stderr(Stdio::null())
|
.stderr(Stdio::null())
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ impl CmpProxy {
|
||||||
emit!(Call(relay!(cmp:show).with_any("opt", opt)));
|
emit!(Call(relay!(cmp:show).with_any("opt", opt)));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn trigger(word: &str, ticket: Id) {
|
pub fn trigger(word: impl Into<String>, ticket: Id) {
|
||||||
emit!(Call(relay!(cmp:trigger, [word]).with("ticket", ticket)));
|
emit!(Call(relay!(cmp:trigger, [word.into()]).with("ticket", ticket)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,8 @@ impl MgrProxy {
|
||||||
emit!(Call(relay!(mgr:arrow, [step.into()])));
|
emit!(Call(relay!(mgr:arrow, [step.into()])));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cd(target: &UrlBuf) {
|
pub fn cd(target: impl Into<UrlBuf>) {
|
||||||
emit!(Call(relay!(mgr:cd, [target]).with("raw", true)));
|
emit!(Call(relay!(mgr:cd, [target.into()]).with("raw", true)));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn displace_do(tab: Id, opt: DisplaceDoOpt) {
|
pub fn displace_do(tab: Id, opt: DisplaceDoOpt) {
|
||||||
|
|
@ -41,8 +41,8 @@ impl MgrProxy {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reveal(target: &UrlBuf) {
|
pub fn reveal(target: impl Into<UrlBuf>) {
|
||||||
emit!(Call(relay!(mgr:reveal, [target]).with("raw", true).with("no-dummy", true)));
|
emit!(Call(relay!(mgr:reveal, [target.into()]).with("raw", true).with("no-dummy", true)));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn search_do(opt: SearchOpt) {
|
pub fn search_do(opt: SearchOpt) {
|
||||||
|
|
|
||||||
|
|
@ -168,6 +168,14 @@ impl<'a> SchemeCow<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> SchemeCow<'a> {
|
impl<'a> SchemeCow<'a> {
|
||||||
|
#[inline]
|
||||||
|
pub fn into_domain(self) -> Option<SymbolCow<'a, str>> {
|
||||||
|
Some(match self {
|
||||||
|
SchemeCow::Borrowed(s) => s.domain()?.into(),
|
||||||
|
SchemeCow::Owned(s) => s.into_domain()?.into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn into_owned(self) -> Scheme {
|
pub fn into_owned(self) -> Scheme {
|
||||||
match self {
|
match self {
|
||||||
|
|
@ -176,13 +184,15 @@ impl<'a> SchemeCow<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
pub fn with_ports(self, uri: usize, urn: usize) -> Self {
|
||||||
pub fn into_domain(self) -> Option<SymbolCow<'a, str>> {
|
match self {
|
||||||
Some(match self {
|
Self::Borrowed(s) => s.with_ports(uri, urn).into(),
|
||||||
SchemeCow::Borrowed(s) => s.domain()?.into(),
|
Self::Owned(s) => s.with_ports(uri, urn).into(),
|
||||||
SchemeCow::Owned(s) => s.into_domain()?.into(),
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn zeroed(self) -> Self { self.with_ports(0, 0) }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
||||||
|
|
@ -43,13 +43,9 @@ impl From<SchemeRef<'_>> for Scheme {
|
||||||
|
|
||||||
impl<'a> SchemeRef<'a> {
|
impl<'a> SchemeRef<'a> {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub const fn kind(self) -> SchemeKind {
|
pub fn covariant(self, other: impl AsScheme) -> bool {
|
||||||
match self {
|
let other = other.as_scheme();
|
||||||
Self::Regular { .. } => SchemeKind::Regular,
|
if self.is_virtual() || other.is_virtual() { self == other } else { true }
|
||||||
Self::Search { .. } => SchemeKind::Search,
|
|
||||||
Self::Archive { .. } => SchemeKind::Archive,
|
|
||||||
Self::Sftp { .. } => SchemeKind::Sftp,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
@ -62,6 +58,16 @@ impl<'a> SchemeRef<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub const fn kind(self) -> SchemeKind {
|
||||||
|
match self {
|
||||||
|
Self::Regular { .. } => SchemeKind::Regular,
|
||||||
|
Self::Search { .. } => SchemeKind::Search,
|
||||||
|
Self::Archive { .. } => SchemeKind::Archive,
|
||||||
|
Self::Sftp { .. } => SchemeKind::Sftp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub const fn ports(self) -> (usize, usize) {
|
pub const fn ports(self) -> (usize, usize) {
|
||||||
match self {
|
match self {
|
||||||
|
|
@ -72,22 +78,6 @@ impl<'a> SchemeRef<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub const fn zeroed(self) -> Self {
|
|
||||||
match self {
|
|
||||||
Self::Regular { .. } => Self::Regular { uri: 0, urn: 0 },
|
|
||||||
Self::Search { domain, .. } => Self::Search { domain, uri: 0, urn: 0 },
|
|
||||||
Self::Archive { domain, .. } => Self::Archive { domain, uri: 0, urn: 0 },
|
|
||||||
Self::Sftp { domain, .. } => Self::Sftp { domain, uri: 0, urn: 0 },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn covariant(self, other: impl AsScheme) -> bool {
|
|
||||||
let other = other.as_scheme();
|
|
||||||
if self.is_virtual() || other.is_virtual() { self == other } else { true }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_owned(self) -> Scheme {
|
pub fn to_owned(self) -> Scheme {
|
||||||
match self {
|
match self {
|
||||||
Self::Regular { uri, urn } => Scheme::Regular { uri, urn },
|
Self::Regular { uri, urn } => Scheme::Regular { uri, urn },
|
||||||
|
|
@ -96,4 +86,16 @@ impl<'a> SchemeRef<'a> {
|
||||||
Self::Sftp { domain, uri, urn } => Scheme::Sftp { domain: domain.intern(), uri, urn },
|
Self::Sftp { domain, uri, urn } => Scheme::Sftp { domain: domain.intern(), uri, urn },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const fn with_ports(self, uri: usize, urn: usize) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::Regular { .. } => Self::Regular { uri, urn },
|
||||||
|
Self::Search { domain, .. } => Self::Search { domain, uri, urn },
|
||||||
|
Self::Archive { domain, .. } => Self::Archive { domain, uri, urn },
|
||||||
|
Self::Sftp { domain, .. } => Self::Sftp { domain, uri, urn },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub const fn zeroed(self) -> Self { self.with_ports(0, 0) }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,4 +38,7 @@ impl Scheme {
|
||||||
Self::Sftp { domain, .. } => Self::Sftp { domain, uri, urn },
|
Self::Sftp { domain, .. } => Self::Sftp { domain, uri, urn },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn zeroed(self) -> Self { self.with_ports(0, 0) }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,15 +44,15 @@ pub(super) fn copy_with_progress_impl(
|
||||||
Some(f)
|
Some(f)
|
||||||
};
|
};
|
||||||
|
|
||||||
let chunks = (cha.len + 10485760 - 1) / 10485760;
|
let chunks = (cha.len + 5242880 - 1) / 5242880;
|
||||||
let mut result = futures::stream::iter(0..chunks)
|
let mut result = futures::stream::iter(0..chunks)
|
||||||
.map(|i| {
|
.map(|i| {
|
||||||
let acc_ = acc_.clone();
|
let acc_ = acc_.clone();
|
||||||
let (from, to) = (from.clone(), to.clone());
|
let (from, to) = (from.clone(), to.clone());
|
||||||
let (src, dist) = (src.take(), dist.take());
|
let (src, dist) = (src.take(), dist.take());
|
||||||
async move {
|
async move {
|
||||||
let offset = i * 10485760;
|
let offset = i * 5242880;
|
||||||
let take = cha.len.saturating_sub(offset).min(10485760);
|
let take = cha.len.saturating_sub(offset).min(5242880);
|
||||||
|
|
||||||
let mut src = BufReader::with_capacity(524288, match src {
|
let mut src = BufReader::with_capacity(524288, match src {
|
||||||
Some(f) => f,
|
Some(f) => f,
|
||||||
|
|
@ -93,7 +93,7 @@ pub(super) fn copy_with_progress_impl(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.buffer_unordered(3)
|
.buffer_unordered(4)
|
||||||
.try_fold(None, |first, file| async { Ok(first.or(file)) })
|
.try_fold(None, |first, file| async { Ok(first.or(file)) })
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use std::{io, sync::Arc};
|
||||||
|
|
||||||
use tokio::{io::{AsyncWriteExt, BufReader, BufWriter}, sync::mpsc::Receiver};
|
use tokio::{io::{AsyncWriteExt, BufReader, BufWriter}, sync::mpsc::Receiver};
|
||||||
use yazi_config::vfs::{ProviderSftp, Vfs};
|
use yazi_config::vfs::{ProviderSftp, Vfs};
|
||||||
use yazi_fs::provider::{DirReader, FileHolder, Provider};
|
use yazi_fs::{CWD, provider::{DirReader, FileHolder, Provider}};
|
||||||
use yazi_sftp::fs::{Attrs, Flags};
|
use yazi_sftp::fs::{Attrs, Flags};
|
||||||
use yazi_shared::{loc::LocBuf, path::{AsPath, PathBufDyn}, pool::InternStr, scheme::SchemeKind, url::{Url, UrlBuf, UrlCow, UrlLike}};
|
use yazi_shared::{loc::LocBuf, path::{AsPath, PathBufDyn}, pool::InternStr, scheme::SchemeKind, url::{Url, UrlBuf, UrlCow, UrlLike}};
|
||||||
|
|
||||||
|
|
@ -26,7 +26,14 @@ impl<'a> Provider for Sftp<'a> {
|
||||||
type UrlCow = UrlCow<'a>;
|
type UrlCow = UrlCow<'a>;
|
||||||
|
|
||||||
async fn absolute(&self) -> io::Result<Self::UrlCow> {
|
async fn absolute(&self) -> io::Result<Self::UrlCow> {
|
||||||
Ok(if self.url.is_absolute() { self.url.into() } else { self.canonicalize().await?.into() })
|
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()
|
||||||
|
} else {
|
||||||
|
self.canonicalize().await?.into()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn canonicalize(&self) -> io::Result<UrlBuf> {
|
async fn canonicalize(&self) -> io::Result<UrlBuf> {
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ impl Linked {
|
||||||
Ok(to) if to != *from => _ = linked.write().entry_ref(from).insert(to),
|
Ok(to) if to != *from => _ = linked.write().entry_ref(from).insert(to),
|
||||||
Ok(_) => _ = linked.write().remove(from),
|
Ok(_) => _ = linked.write().remove(from),
|
||||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => _ = linked.write().remove(from),
|
Err(e) if e.kind() == std::io::ErrorKind::NotFound => _ = linked.write().remove(from),
|
||||||
Err(_) => {}
|
Err(e) => tracing::error!("Failed to canonicalize watched path {from:?}: {e:?}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -56,9 +56,7 @@ impl Watcher {
|
||||||
|
|
||||||
if !to_unwatch.is_empty() || !to_watch.is_empty() {
|
if !to_unwatch.is_empty() || !to_watch.is_empty() {
|
||||||
backend = Self::sync(backend, to_unwatch, to_watch).await;
|
backend = Self::sync(backend, to_unwatch, to_watch).await;
|
||||||
if !rx.has_changed().unwrap_or(false) {
|
backend = backend.sync().await;
|
||||||
backend = backend.sync().await;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if rx.changed().await.is_err() {
|
if rx.changed().await.is_err() {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue