Skip to content

Commit 782ecb1

Browse files
authored
feat!: live editing of remote files (#3264)
1 parent 46569a1 commit 782ecb1

File tree

64 files changed

+621
-339
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+621
-339
lines changed

Cargo.lock

Lines changed: 169 additions & 73 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,31 +24,31 @@ ansi-to-tui = "7.0.0"
2424
anyhow = "1.0.100"
2525
base64 = "0.22.1"
2626
bitflags = { version = "2.9.4", features = [ "serde" ] }
27-
clap = { version = "4.5.48", features = [ "derive" ] }
27+
clap = { version = "4.5.49", features = [ "derive" ] }
2828
core-foundation-sys = "0.8.7"
2929
crossterm = { version = "0.29.0", features = [ "event-stream" ] }
3030
dirs = "6.0.0"
3131
foldhash = "0.2.0"
3232
futures = "0.3.31"
33-
globset = "0.4.16"
33+
globset = "0.4.17"
3434
hashbrown = { version = "0.16.0", features = [ "serde" ] }
35-
indexmap = { version = "2.11.4", features = [ "serde" ] }
35+
indexmap = { version = "2.12.0", features = [ "serde" ] }
3636
libc = "0.2.177"
37-
lru = "0.16.1"
37+
lru = "0.16.2"
3838
mlua = { version = "0.11.4", features = [ "anyhow", "async", "error-send", "lua54", "macros", "serde" ] }
3939
objc = "0.2.7"
4040
ordered-float = { version = "5.1.0", features = [ "serde" ] }
4141
parking_lot = "0.12.5"
4242
paste = "1.0.15"
4343
percent-encoding = "2.3.2"
4444
ratatui = { version = "0.29.0", features = [ "unstable-rendered-line-info", "unstable-widget-ref" ] }
45-
regex = "1.12.1"
46-
russh = { version = "0.54.5", default-features = false, features = [ "ring", "rsa" ] }
45+
regex = "1.12.2"
46+
russh = { version = "0.54.6", default-features = false, features = [ "ring", "rsa" ] }
4747
scopeguard = "1.2.0"
4848
serde = { version = "1.0.228", features = [ "derive" ] }
4949
serde_json = "1.0.145"
5050
syntect = { version = "5.3.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] }
51-
tokio = { version = "1.47.1", features = [ "full" ] }
51+
tokio = { version = "1.48.0", features = [ "full" ] }
5252
tokio-stream = "0.1.17"
5353
tokio-util = "0.7.16"
5454
toml = { version = "0.9.8" }

yazi-actor/src/mgr/bulk_rename.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ impl Actor for BulkRename {
5252

5353
defer! { tokio::spawn(Local.remove_file(tmp.clone())); }
5454
TasksProxy::process_exec(
55-
cwd,
55+
cwd.into(),
5656
Splatter::new(&[UrlCow::default(), tmp.as_url().into()]).splat(&opener.run),
5757
vec![UrlCow::default(), UrlBuf::from(&tmp).into()],
5858
opener.block,

yazi-actor/src/mgr/download.rs

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ use std::{mem, time::{Duration, Instant}};
22

33
use anyhow::Result;
44
use futures::{StreamExt, stream::FuturesUnordered};
5+
use hashbrown::HashSet;
56
use tokio::sync::oneshot;
6-
use yazi_fs::File;
7-
use yazi_macro::{act, succ};
8-
use yazi_parser::mgr::DownloadOpt;
9-
use yazi_shared::data::Data;
7+
use yazi_fs::{File, FsUrl, provider::{Provider, local::Local}};
8+
use yazi_macro::succ;
9+
use yazi_parser::mgr::{DownloadOpt, OpenOpt};
10+
use yazi_proxy::MgrProxy;
11+
use yazi_shared::{data::Data, url::UrlCow};
1012
use yazi_vfs::VfsFile;
1113

1214
use crate::{Actor, Ctx};
@@ -19,26 +21,30 @@ impl Actor for Download {
1921
const NAME: &str = "download";
2022

2123
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
22-
act!(mgr:escape_visual, cx)?;
23-
24-
let mut wg1 = FuturesUnordered::new();
25-
for url in opt.urls {
26-
let (tx, rx) = oneshot::channel();
27-
cx.tasks.scheduler.file_download(url.to_owned(), Some(tx));
28-
wg1.push(async move { (rx.await == Ok(true), url) });
29-
}
30-
24+
let cwd = cx.cwd().clone();
3125
let scheduler = cx.tasks.scheduler.clone();
26+
3227
tokio::spawn(async move {
28+
Self::prepare(&opt.urls).await;
29+
30+
let mut wg1 = FuturesUnordered::new();
31+
for url in opt.urls {
32+
let (tx, rx) = oneshot::channel();
33+
scheduler.file_download(url.to_owned(), Some(tx));
34+
wg1.push(async move { (rx.await == Ok(false), url) });
35+
}
36+
3337
let mut wg2 = vec![];
38+
let mut urls = Vec::with_capacity(wg1.len());
3439
let mut files = Vec::with_capacity(wg1.len());
3540
let mut instant = Instant::now();
3641
while let Some((success, url)) = wg1.next().await {
3742
if !success {
3843
continue;
3944
}
4045

41-
let Ok(f) = File::new(url).await else { continue };
46+
let Ok(f) = File::new(&url).await else { continue };
47+
urls.push(url);
4248
files.push(f);
4349

4450
if instant.elapsed() >= Duration::from_secs(1) {
@@ -50,9 +56,29 @@ impl Actor for Download {
5056
if !files.is_empty() {
5157
wg2.push(scheduler.fetch_mimetype(files));
5258
}
53-
futures::future::join_all(wg2).await;
59+
if futures::future::join_all(wg2).await.into_iter().any(|b| !b) {
60+
return;
61+
}
62+
if opt.open && !urls.is_empty() {
63+
MgrProxy::open(OpenOpt {
64+
cwd: Some(cwd.into()),
65+
targets: urls,
66+
interactive: false,
67+
hovered: false,
68+
});
69+
}
5470
});
5571

5672
succ!();
5773
}
5874
}
75+
76+
impl Download {
77+
async fn prepare(urls: &[UrlCow<'_>]) {
78+
let roots: HashSet<_> = urls.iter().filter_map(|u| u.cache_root()).collect();
79+
for mut root in roots {
80+
root.push("%lock");
81+
Local.create_dir_all(root).await.ok();
82+
}
83+
}
84+
}

yazi-actor/src/mgr/open.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ impl Actor for Open {
4848
.map(|(i, _)| i)
4949
.collect();
5050

51-
let cwd = cx.cwd().clone();
51+
let cwd = opt.cwd.unwrap_or_else(|| cx.cwd().clone().into());
5252
if todo.is_empty() {
5353
return act!(mgr:open_do, cx, OpenDoOpt { cwd, targets: opt.targets, interactive: opt.interactive });
5454
}

yazi-actor/src/mgr/open_do.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use yazi_fs::Splatter;
55
use yazi_macro::succ;
66
use yazi_parser::{mgr::OpenDoOpt, tasks::ProcessOpenOpt};
77
use yazi_proxy::{PickProxy, TasksProxy};
8-
use yazi_shared::{data::Data, url::{UrlBuf, UrlCow}};
8+
use yazi_shared::{data::Data, url::UrlCow};
99

1010
use crate::{Actor, Ctx};
1111

@@ -60,7 +60,7 @@ impl Actor for OpenDo {
6060

6161
impl OpenDo {
6262
// TODO: remove
63-
fn match_and_open(cx: &Ctx, cwd: UrlBuf, targets: Vec<(UrlCow<'static>, &str)>) {
63+
fn match_and_open(cx: &Ctx, cwd: UrlCow<'static>, targets: Vec<(UrlCow<'static>, &str)>) {
6464
let mut openers = HashMap::new();
6565
for (url, mime) in targets {
6666
if let Some(opener) = YAZI.opener.first(YAZI.open.all(&url, mime)) {

yazi-actor/src/mgr/shell.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,12 @@ impl Actor for Shell {
3939
}
4040

4141
TasksProxy::open_shell_compat(ProcessOpenOpt {
42-
cwd,
43-
cmd: Splatter::new(&selected).splat(opt.run.as_ref()),
44-
args: selected,
45-
block: opt.block,
42+
cwd: cwd.into(),
43+
cmd: Splatter::new(&selected).splat(opt.run.as_ref()),
44+
args: selected,
45+
block: opt.block,
4646
orphan: opt.orphan,
47-
done: None,
47+
done: None,
4848
spread: true,
4949
});
5050
});

yazi-adapter/src/adapter.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::{env, fmt::Display, path::Path};
1+
use std::{env, fmt::Display, path::PathBuf};
22

33
use anyhow::Result;
44
use ratatui::layout::Rect;
@@ -35,11 +35,15 @@ impl Display for Adapter {
3535
}
3636

3737
impl Adapter {
38-
pub async fn image_show(self, path: &Path, max: Rect) -> Result<Rect> {
38+
pub async fn image_show<P>(self, path: P, max: Rect) -> Result<Rect>
39+
where
40+
P: Into<PathBuf>,
41+
{
3942
if max.is_empty() {
4043
return Ok(Rect::default());
4144
}
4245

46+
let path = path.into();
4347
match self {
4448
Self::Kgp => drivers::Kgp::image_show(path, max).await,
4549
Self::KgpOld => drivers::KgpOld::image_show(path, max).await,

yazi-adapter/src/drivers/chafa.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::{io::Write, path::Path, process::Stdio};
1+
use std::{io::Write, path::PathBuf, process::Stdio};
22

33
use ansi_to_tui::IntoText;
44
use anyhow::{Result, bail};
@@ -11,7 +11,7 @@ use crate::{Adapter, Emulator};
1111
pub(crate) struct Chafa;
1212

1313
impl Chafa {
14-
pub(crate) async fn image_show(path: &Path, max: Rect) -> Result<Rect> {
14+
pub(crate) async fn image_show(path: PathBuf, max: Rect) -> Result<Rect> {
1515
let child = Command::new("chafa")
1616
.args([
1717
"-f",

yazi-adapter/src/drivers/iip.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::{fmt::Write, io::Write as ioWrite, path::Path};
1+
use std::{fmt::Write, io::Write as ioWrite, path::PathBuf};
22

33
use anyhow::Result;
44
use base64::{Engine, engine::{Config, general_purpose::STANDARD}};
@@ -12,7 +12,7 @@ use crate::{CLOSE, Emulator, Image, START, adapter::Adapter};
1212
pub(crate) struct Iip;
1313

1414
impl Iip {
15-
pub(crate) async fn image_show(path: &Path, max: Rect) -> Result<Rect> {
15+
pub(crate) async fn image_show(path: PathBuf, max: Rect) -> Result<Rect> {
1616
let img = Image::downscale(path, max).await?;
1717
let area = Image::pixel_area((img.width(), img.height()), max);
1818
let b = Self::encode(img).await?;

0 commit comments

Comments
 (0)