Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions yazi-actor/src/cmp/trigger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ 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();

tracing::debug!(?scheme, ?path);
if scheme.is_local() && path.as_strand() == "~" {
return None; // We don't autocomplete a `~`, but `~/`
}
Expand All @@ -86,7 +86,11 @@ impl Trigger {
(UrlCow::try_from((scheme, root)).ok()?.into_owned(), 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())
}
})
}
}
Expand All @@ -108,6 +112,7 @@ mod tests {
#[cfg(unix)]
#[test]
fn test_split() {
yazi_shared::init_tests();
yazi_fs::init();
compare("", "", "");
compare(" ", "", " ");
Expand All @@ -124,16 +129,24 @@ mod tests {
compare("//foo/", "/foo/", "");
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)]
#[test]
fn test_split() {
yazi_fs::init();
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing initialization call yazi_shared::init_tests() in the Windows test. The Unix test at line 115 includes this call, but the Windows test is missing it. This inconsistency could lead to test failures on Windows if the initialization is required.

Copilot uses AI. Check for mistakes.
compare("foo", "", "foo");

compare(r"foo\", r"foo\", "");
compare(r"foo\bar", r"foo\", "bar");
compare(r"foo\bar\", r"foo\bar\", "");

compare(r"C:\", r"C:\", "");
compare(r"C:\foo", r"C:\", "foo");
compare(r"C:\foo\", r"C:\foo\", "");
Expand Down
15 changes: 8 additions & 7 deletions yazi-actor/src/mgr/cd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ use yazi_fs::{File, FilesOp, path::expand_url};
use yazi_macro::{act, err, render, succ};
use yazi_parser::mgr::CdOpt;
use yazi_proxy::{CmpProxy, InputProxy, MgrProxy};
use yazi_shared::{Debounce, data::Data, errors::InputError, url::{UrlBuf, UrlLike}};
use yazi_vfs::VfsFile;
use yazi_shared::{Debounce, data::Data, errors::InputError, url::{AsUrl, UrlBuf, UrlLike}};
use yazi_vfs::{VfsFile, provider};

use crate::{Actor, Ctx};

Expand All @@ -24,7 +24,7 @@ impl Actor for Cd {
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
act!(mgr:escape_visual, cx)?;
if opt.interactive {
return Self::cd_interactive();
return Self::cd_interactive(cx);
}

let tab = cx.tab_mut();
Expand Down Expand Up @@ -59,8 +59,8 @@ impl Actor for Cd {
}

impl Cd {
fn cd_interactive() -> Result<Data> {
let input = InputProxy::show(InputCfg::cd());
fn cd_interactive(cx: &Ctx) -> Result<Data> {
let input = InputProxy::show(InputCfg::cd(cx.cwd().as_url()));

tokio::spawn(async move {
let rx = Debounce::new(UnboundedReceiverStream::new(input), Duration::from_millis(50));
Expand All @@ -70,6 +70,7 @@ impl Cd {
match result {
Ok(s) => {
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 };
if file.is_dir() {
Expand All @@ -79,10 +80,10 @@ impl Cd {
if let Some(p) = url.parent() {
FilesOp::Upserting(p.into(), [(url.urn().into(), file)].into()).emit();
}
MgrProxy::reveal(&url);
MgrProxy::reveal(url);
}
Err(InputError::Completed(before, ticket)) => {
CmpProxy::trigger(&before, ticket);
CmpProxy::trigger(before, ticket);
}
_ => break,
}
Expand Down
42 changes: 4 additions & 38 deletions yazi-actor/src/mgr/displace.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use anyhow::{Result, bail};
use yazi_fs::FilesOp;
use yazi_macro::{act, succ};
use yazi_parser::{VoidOpt, mgr::{CdSource, DisplaceDoOpt}};
use anyhow::Result;
use yazi_macro::succ;
use yazi_parser::{VoidOpt, mgr::DisplaceDoOpt};
use yazi_proxy::MgrProxy;
use yazi_shared::{data::Data, url::UrlLike};
use yazi_vfs::provider;
Expand All @@ -23,42 +22,9 @@ impl Actor for Displace {
let tab = cx.tab().id;
let from = cx.cwd().to_owned();
tokio::spawn(async move {
MgrProxy::displace_do(tab, DisplaceDoOpt {
to: provider::absolute(&from).await.map(|u| u.into_owned()),
from,
});
MgrProxy::displace_do(tab, DisplaceDoOpt { to: provider::canonicalize(&from).await, from });
});

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 changes: 36 additions & 0 deletions yazi-actor/src/mgr/displace_do.rs
Original file line number Diff line number Diff line change
@@ -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))
}
}
}
1 change: 1 addition & 0 deletions yazi-actor/src/mgr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ yazi_macro::mod_flat!(
copy
create
displace
displace_do
download
enter
escape
Expand Down
6 changes: 4 additions & 2 deletions yazi-actor/src/mgr/search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use yazi_fs::{FilesOp, cha::Cha};
use yazi_macro::{act, succ};
use yazi_parser::{VoidOpt, mgr::{CdSource, SearchOpt, SearchOptVia}};
use yazi_plugin::external;
use yazi_proxy::{InputProxy, MgrProxy};
use yazi_proxy::{AppProxy, InputProxy, MgrProxy};
use yazi_shared::{data::Data, url::UrlLike};

use crate::{Actor, Ctx};
Expand Down Expand Up @@ -52,8 +52,10 @@ impl Actor for SearchDo {
handle.abort();
}

let cwd = tab.cwd().to_search(&opt.subject)?;
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 {
let rx = match opt.via {
Expand Down
2 changes: 1 addition & 1 deletion yazi-actor/src/mgr/stash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ impl Actor for Stash {
const NAME: &str = "stash";

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());
}

Expand Down
5 changes: 3 additions & 2 deletions yazi-config/src/popup/options.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
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 crate::YAZI;
Expand Down Expand Up @@ -31,9 +31,10 @@ pub struct ConfirmCfg {
}

impl InputCfg {
pub fn cd() -> Self {
pub fn cd(cwd: Url) -> Self {
Self {
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),
completion: true,
..Default::default()
Expand Down
13 changes: 3 additions & 10 deletions yazi-plugin/src/external/fd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use std::process::Stdio;

use anyhow::Result;
use tokio::{io::{AsyncBufReadExt, BufReader}, process::{Child, Command}, sync::mpsc::{self, UnboundedReceiver}};
use yazi_fs::File;
use yazi_shared::url::{UrlBuf, UrlLike};
use yazi_fs::{File, FsUrl};
use yazi_shared::url::{AsUrl, UrlBuf, UrlLike};
use yazi_vfs::VfsFile;

pub struct FdOpt {
Expand Down Expand Up @@ -34,16 +34,9 @@ pub fn fd(opt: FdOpt) -> Result<UnboundedReceiver<File>> {
}

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)
.arg("--base-directory")
.arg(path)
.arg(&*opt.cwd.as_url().unified_path())
.arg("--regex")
.arg(if opt.hidden { "--hidden" } else { "--no-hidden" })
.args(&opt.args)
Expand Down
12 changes: 4 additions & 8 deletions yazi-plugin/src/external/rg.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use std::process::Stdio;

use anyhow::{Result, bail};
use anyhow::Result;
use tokio::{io::{AsyncBufReadExt, BufReader}, process::Command, sync::mpsc::{self, UnboundedReceiver}};
use yazi_fs::File;
use yazi_shared::url::{UrlBuf, UrlLike};
use yazi_fs::{File, FsUrl};
use yazi_shared::url::{AsUrl, UrlBuf, UrlLike};
use yazi_vfs::VfsFile;

pub struct RgOpt {
Expand All @@ -14,16 +14,12 @@ pub struct RgOpt {
}

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")
.args(["--color=never", "--files-with-matches", "--smart-case"])
.arg(if opt.hidden { "--hidden" } else { "--no-hidden" })
.args(opt.args)
.arg(opt.subject)
.arg(path)
.arg(&*opt.cwd.as_url().unified_path())
.kill_on_drop(true)
.stdout(Stdio::piped())
.stderr(Stdio::null())
Expand Down
12 changes: 4 additions & 8 deletions yazi-plugin/src/external/rga.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use std::process::Stdio;

use anyhow::{Result, bail};
use anyhow::Result;
use tokio::{io::{AsyncBufReadExt, BufReader}, process::Command, sync::mpsc::{self, UnboundedReceiver}};
use yazi_fs::File;
use yazi_shared::url::{UrlBuf, UrlLike};
use yazi_fs::{File, FsUrl};
use yazi_shared::url::{AsUrl, UrlBuf, UrlLike};
use yazi_vfs::VfsFile;

pub struct RgaOpt {
Expand All @@ -14,16 +14,12 @@ pub struct RgaOpt {
}

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")
.args(["--color=never", "--files-with-matches", "--smart-case"])
.arg(if opt.hidden { "--hidden" } else { "--no-hidden" })
.args(opt.args)
.arg(opt.subject)
.arg(path)
.arg(&*opt.cwd.as_url().unified_path())
.kill_on_drop(true)
.stdout(Stdio::piped())
.stderr(Stdio::null())
Expand Down
4 changes: 2 additions & 2 deletions yazi-proxy/src/cmp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ impl CmpProxy {
emit!(Call(relay!(cmp:show).with_any("opt", opt)));
}

pub fn trigger(word: &str, ticket: Id) {
emit!(Call(relay!(cmp:trigger, [word]).with("ticket", ticket)));
pub fn trigger(word: impl Into<String>, ticket: Id) {
emit!(Call(relay!(cmp:trigger, [word.into()]).with("ticket", ticket)));
}
}
8 changes: 4 additions & 4 deletions yazi-proxy/src/mgr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ impl MgrProxy {
emit!(Call(relay!(mgr:arrow, [step.into()])));
}

pub fn cd(target: &UrlBuf) {
emit!(Call(relay!(mgr:cd, [target]).with("raw", true)));
pub fn cd(target: impl Into<UrlBuf>) {
emit!(Call(relay!(mgr:cd, [target.into()]).with("raw", true)));
}

pub fn displace_do(tab: Id, opt: DisplaceDoOpt) {
Expand Down Expand Up @@ -41,8 +41,8 @@ impl MgrProxy {
));
}

pub fn reveal(target: &UrlBuf) {
emit!(Call(relay!(mgr:reveal, [target]).with("raw", true).with("no-dummy", true)));
pub fn reveal(target: impl Into<UrlBuf>) {
emit!(Call(relay!(mgr:reveal, [target.into()]).with("raw", true).with("no-dummy", true)));
}

pub fn search_do(opt: SearchOpt) {
Expand Down
Loading
Loading