mirror of
https://github.com/sxyazi/yazi.git
synced 2026-05-13 08:16:40 +00:00
feat: bulk create (#3793)
Some checks failed
Cachix / Publish Flake (push) Has been cancelled
Cachix / Publish Flake-1 (push) Has been cancelled
Check / clippy (push) Has been cancelled
Check / rustfmt (push) Has been cancelled
Check / stylua (push) Has been cancelled
Draft / build-unix (gcc-aarch64-linux-gnu, ubuntu-latest, aarch64-unknown-linux-gnu) (push) Has been cancelled
Draft / build-unix (gcc-i686-linux-gnu, ubuntu-latest, i686-unknown-linux-gnu) (push) Has been cancelled
Draft / build-unix (gcc-riscv64-linux-gnu, ubuntu-latest, riscv64gc-unknown-linux-gnu) (push) Has been cancelled
Draft / build-unix (gcc-sparc64-linux-gnu, ubuntu-latest, sparc64-unknown-linux-gnu) (push) Has been cancelled
Draft / build-unix (macos-latest, aarch64-apple-darwin) (push) Has been cancelled
Draft / build-unix (macos-latest, x86_64-apple-darwin) (push) Has been cancelled
Draft / build-unix (ubuntu-latest, x86_64-unknown-linux-gnu) (push) Has been cancelled
Draft / build-windows (windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
Draft / build-windows (windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
Draft / build-musl (aarch64-unknown-linux-musl) (push) Has been cancelled
Draft / build-musl (x86_64-unknown-linux-musl) (push) Has been cancelled
Draft / build-snap (amd64, ubuntu-latest) (push) Has been cancelled
Draft / build-snap (arm64, ubuntu-24.04-arm) (push) Has been cancelled
Test / test (macos-latest) (push) Has been cancelled
Test / test (ubuntu-latest) (push) Has been cancelled
Test / test (windows-latest) (push) Has been cancelled
Draft / snap (push) Has been cancelled
Draft / draft (push) Has been cancelled
Draft / nightly (push) Has been cancelled
Some checks failed
Cachix / Publish Flake (push) Has been cancelled
Cachix / Publish Flake-1 (push) Has been cancelled
Check / clippy (push) Has been cancelled
Check / rustfmt (push) Has been cancelled
Check / stylua (push) Has been cancelled
Draft / build-unix (gcc-aarch64-linux-gnu, ubuntu-latest, aarch64-unknown-linux-gnu) (push) Has been cancelled
Draft / build-unix (gcc-i686-linux-gnu, ubuntu-latest, i686-unknown-linux-gnu) (push) Has been cancelled
Draft / build-unix (gcc-riscv64-linux-gnu, ubuntu-latest, riscv64gc-unknown-linux-gnu) (push) Has been cancelled
Draft / build-unix (gcc-sparc64-linux-gnu, ubuntu-latest, sparc64-unknown-linux-gnu) (push) Has been cancelled
Draft / build-unix (macos-latest, aarch64-apple-darwin) (push) Has been cancelled
Draft / build-unix (macos-latest, x86_64-apple-darwin) (push) Has been cancelled
Draft / build-unix (ubuntu-latest, x86_64-unknown-linux-gnu) (push) Has been cancelled
Draft / build-windows (windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
Draft / build-windows (windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
Draft / build-musl (aarch64-unknown-linux-musl) (push) Has been cancelled
Draft / build-musl (x86_64-unknown-linux-musl) (push) Has been cancelled
Draft / build-snap (amd64, ubuntu-latest) (push) Has been cancelled
Draft / build-snap (arm64, ubuntu-24.04-arm) (push) Has been cancelled
Test / test (macos-latest) (push) Has been cancelled
Test / test (ubuntu-latest) (push) Has been cancelled
Test / test (windows-latest) (push) Has been cancelled
Draft / snap (push) Has been cancelled
Draft / draft (push) Has been cancelled
Draft / nightly (push) Has been cancelled
Co-authored-by: sxyazi <sxyazi@gmail.com>
This commit is contained in:
parent
247f925e53
commit
fde563380b
22 changed files with 243 additions and 42 deletions
|
|
@ -12,6 +12,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Bulk create ([#3793])
|
||||||
|
|
||||||
## [v26.5.6]
|
## [v26.5.6]
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
@ -1704,6 +1708,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
||||||
[#3780]: https://github.com/sxyazi/yazi/pull/3780
|
[#3780]: https://github.com/sxyazi/yazi/pull/3780
|
||||||
[#3781]: https://github.com/sxyazi/yazi/pull/3781
|
[#3781]: https://github.com/sxyazi/yazi/pull/3781
|
||||||
[#3792]: https://github.com/sxyazi/yazi/pull/3792
|
[#3792]: https://github.com/sxyazi/yazi/pull/3792
|
||||||
|
[#3793]: https://github.com/sxyazi/yazi/pull/3793
|
||||||
[#3804]: https://github.com/sxyazi/yazi/pull/3804
|
[#3804]: https://github.com/sxyazi/yazi/pull/3804
|
||||||
[#3813]: https://github.com/sxyazi/yazi/pull/3813
|
[#3813]: https://github.com/sxyazi/yazi/pull/3813
|
||||||
[#3846]: https://github.com/sxyazi/yazi/pull/3846
|
[#3846]: https://github.com/sxyazi/yazi/pull/3846
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ Yazi (means "duck") is a terminal file manager written in Rust, based on non-blo
|
||||||
- 🧰 Integration with ripgrep, fd, fzf, zoxide
|
- 🧰 Integration with ripgrep, fd, fzf, zoxide
|
||||||
- 💫 Vim-like input/pick/confirm/which/notify component, auto-completion for cd paths
|
- 💫 Vim-like input/pick/confirm/which/notify component, auto-completion for cd paths
|
||||||
- 🏷️ Multi-Tab Support, Cross-directory selection, Scrollable Preview (for videos, PDFs, archives, code, directories, etc.)
|
- 🏷️ Multi-Tab Support, Cross-directory selection, Scrollable Preview (for videos, PDFs, archives, code, directories, etc.)
|
||||||
- 🔄 Bulk Renaming, Archive Extraction, Visual Mode, File Chooser, [Git Integration](https://github.com/yazi-rs/plugins/tree/main/git.yazi), [Mount Manager](https://github.com/yazi-rs/plugins/tree/main/mount.yazi)
|
- 🔄 Bulk Rename/Create, Archive Extraction, Visual Mode, File Chooser, [Git Integration](https://github.com/yazi-rs/plugins/tree/main/git.yazi), [Mount Manager](https://github.com/yazi-rs/plugins/tree/main/mount.yazi)
|
||||||
- 🎨 Theme System, Mouse Support, Trash Bin, Custom Layouts, CSI u, OSC 52
|
- 🎨 Theme System, Mouse Support, Trash Bin, Custom Layouts, CSI u, OSC 52
|
||||||
- ... and more!
|
- ... and more!
|
||||||
|
|
||||||
|
|
|
||||||
176
yazi-actor/src/mgr/bulk_create.rs
Normal file
176
yazi-actor/src/mgr/bulk_create.rs
Normal file
|
|
@ -0,0 +1,176 @@
|
||||||
|
use std::{fmt::{self, Display}, io::{self, Read, Write}, path::{MAIN_SEPARATOR, Path}, sync::Arc};
|
||||||
|
|
||||||
|
use anyhow::{Result, anyhow};
|
||||||
|
use scopeguard::defer;
|
||||||
|
use yazi_binding::Permit;
|
||||||
|
use yazi_config::{YAZI, opener::OpenerRule};
|
||||||
|
use yazi_fs::{File, FilesOp, Splatter, provider::{Provider, local::Local}};
|
||||||
|
use yazi_macro::succ;
|
||||||
|
use yazi_parser::VoidForm;
|
||||||
|
use yazi_proxy::TasksProxy;
|
||||||
|
use yazi_scheduler::{AppProxy, NotifyProxy};
|
||||||
|
use yazi_shared::{data::Data, strand::Strand, terminal_clear, url::{AsUrl, UrlBuf, UrlCow, UrlLike}};
|
||||||
|
use yazi_shim::path::CROSS_SEPARATOR;
|
||||||
|
use yazi_term::YIELD_TO_SUBPROCESS;
|
||||||
|
use yazi_tty::TTY;
|
||||||
|
use yazi_vfs::{VfsFile, provider};
|
||||||
|
use yazi_watcher::WATCHER;
|
||||||
|
|
||||||
|
use crate::{Actor, Ctx};
|
||||||
|
pub struct BulkCreate;
|
||||||
|
impl Actor for BulkCreate {
|
||||||
|
type Form = VoidForm;
|
||||||
|
|
||||||
|
const NAME: &str = "bulk_create";
|
||||||
|
|
||||||
|
fn act(cx: &mut Ctx, _: Self::Form) -> Result<Data> {
|
||||||
|
let Some(opener) = Self::opener() else {
|
||||||
|
succ!(NotifyProxy::push_warn("Bulk create", "No text opener found"));
|
||||||
|
};
|
||||||
|
|
||||||
|
let cwd = cx.cwd().clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let tmp = YAZI.preview.tmpfile("bulk-create");
|
||||||
|
provider::create_new(&tmp).await?;
|
||||||
|
|
||||||
|
defer! {
|
||||||
|
let tmp = tmp.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
Local::regular(&tmp).remove_file().await
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
TasksProxy::process_exec(
|
||||||
|
cwd.clone(),
|
||||||
|
Splatter::new(&[UrlCow::default(), tmp.as_url().into()]).splat(&opener.run),
|
||||||
|
vec![UrlCow::default(), UrlBuf::from(&tmp).into()],
|
||||||
|
opener.block,
|
||||||
|
opener.orphan,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let _permit = Permit::new(YIELD_TO_SUBPROCESS.acquire().await.unwrap(), AppProxy::resume());
|
||||||
|
AppProxy::stop().await;
|
||||||
|
|
||||||
|
let content = Local::regular(&tmp).read_to_string().await?;
|
||||||
|
Self::r#do(cwd, content.lines().filter_map(Entry::parse).collect()).await
|
||||||
|
});
|
||||||
|
succ!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BulkCreate {
|
||||||
|
async fn r#do(cwd: UrlBuf, todo: Vec<Entry<'_>>) -> Result<()> {
|
||||||
|
terminal_clear(TTY.writer())?;
|
||||||
|
if todo.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
} else if !Self::ask_continue(&todo, None)? {
|
||||||
|
return Ok(()); // TODO: support `bulk_exit`?
|
||||||
|
}
|
||||||
|
|
||||||
|
let _permit = WATCHER.acquire().await.unwrap();
|
||||||
|
let (mut failed, mut succeeded) = (vec![], Vec::with_capacity(todo.len()));
|
||||||
|
for entry in todo {
|
||||||
|
let Ok(dist) = cwd.try_join(entry.path) else {
|
||||||
|
failed.push((entry, anyhow!("Invalid path")));
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let result: io::Result<()> = if entry.is_dir {
|
||||||
|
provider::create_dir_all(&dist).await
|
||||||
|
} else if let Some(parent) = dist.parent() {
|
||||||
|
provider::create_dir_all(parent).await.ok();
|
||||||
|
provider::create_new(&dist).await.map(|_| ())
|
||||||
|
} else {
|
||||||
|
Err(io::Error::other("No parent directory"))
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(e) = result {
|
||||||
|
failed.push((entry, e.into()));
|
||||||
|
} else if let Ok(f) = File::new(dist).await {
|
||||||
|
succeeded.push(f);
|
||||||
|
} else {
|
||||||
|
failed.push((entry, anyhow!("Failed to retrieve file info")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !succeeded.is_empty() {
|
||||||
|
// err!(Pubsub::pub_after_bulk_create(it)); // FIXME
|
||||||
|
FilesOp::create(succeeded);
|
||||||
|
}
|
||||||
|
drop(_permit);
|
||||||
|
|
||||||
|
if !failed.is_empty() {
|
||||||
|
Self::output_failed(failed).await?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn opener() -> Option<Arc<OpenerRule>> {
|
||||||
|
YAZI
|
||||||
|
.open
|
||||||
|
.match_dummy(Path::new("bulk-create.txt"), "text/plain")
|
||||||
|
.and_then(|r| YAZI.opener.block(&r))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ask_continue(todo: &[Entry], decision: Option<bool>) -> Result<bool> {
|
||||||
|
if let Some(decision) = decision {
|
||||||
|
return Ok(decision);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut w = TTY.lockout();
|
||||||
|
for entry in todo {
|
||||||
|
writeln!(w, "{entry}")?;
|
||||||
|
}
|
||||||
|
write!(w, "Continue to create? (y/N): ")?;
|
||||||
|
w.flush()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut buf = [0; 10];
|
||||||
|
_ = TTY.reader().read(&mut buf)?;
|
||||||
|
Ok(buf[0] == b'y' || buf[0] == b'Y')
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn output_failed(failed: Vec<(Entry<'_>, anyhow::Error)>) -> Result<()> {
|
||||||
|
let mut stdout = TTY.lockout();
|
||||||
|
terminal_clear(&mut *stdout)?;
|
||||||
|
|
||||||
|
writeln!(stdout, "Failed to create:")?;
|
||||||
|
for (entry, err) in failed {
|
||||||
|
writeln!(stdout, "{entry}: {err}")?;
|
||||||
|
}
|
||||||
|
writeln!(stdout, "\nPress ENTER to exit")?;
|
||||||
|
|
||||||
|
stdout.flush()?;
|
||||||
|
TTY.reader().read_exact(&mut [0])?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Entry
|
||||||
|
struct Entry<'a> {
|
||||||
|
path: Strand<'a>,
|
||||||
|
is_dir: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Entry<'a> {
|
||||||
|
fn parse(s: &'a str) -> Option<Self> {
|
||||||
|
let (path, is_dir) = match s.strip_suffix(CROSS_SEPARATOR) {
|
||||||
|
Some(p) => (p, true),
|
||||||
|
None => (s, false),
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(Self { path: path.into(), is_dir }).filter(|_| !path.is_empty())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Entry<'_> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
if self.is_dir {
|
||||||
|
write!(f, "{}{MAIN_SEPARATOR}", self.path.display())
|
||||||
|
} else {
|
||||||
|
self.path.display().fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -45,7 +45,7 @@ impl Actor for BulkRename {
|
||||||
let cwd = cx.cwd().clone();
|
let cwd = cx.cwd().clone();
|
||||||
let batcher = cx.core.mgr.batcher.clone();
|
let batcher = cx.core.mgr.batcher.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let tmp = YAZI.preview.tmpfile("bulk");
|
let tmp = YAZI.preview.tmpfile("bulk-rename");
|
||||||
|
|
||||||
Gate::default()
|
Gate::default()
|
||||||
.write(true)
|
.write(true)
|
||||||
|
|
@ -143,7 +143,7 @@ impl BulkRename {
|
||||||
|
|
||||||
if !succeeded.is_empty() {
|
if !succeeded.is_empty() {
|
||||||
let it = succeeded.iter().map(|(o, n)| (o.as_url(), n.url.as_url()));
|
let it = succeeded.iter().map(|(o, n)| (o.as_url(), n.url.as_url()));
|
||||||
err!(Pubsub::pub_after_bulk(it));
|
err!(Pubsub::pub_after_bulk_rename(it));
|
||||||
FilesOp::rename(succeeded);
|
FilesOp::rename(succeeded);
|
||||||
}
|
}
|
||||||
drop(permit);
|
drop(permit);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
yazi_macro::mod_flat!(
|
yazi_macro::mod_flat!(
|
||||||
arrow
|
arrow
|
||||||
back
|
back
|
||||||
|
bulk_create
|
||||||
bulk_exit
|
bulk_exit
|
||||||
bulk_rename
|
bulk_rename
|
||||||
cd
|
cd
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# A TOML linter such as https://github.com/tombi-toml/tombi can use this schema to validate your config.
|
# A TOML linter such as Tombi can use this schema to validate your config.
|
||||||
# If you encounter any problems, please make an issue at https://github.com/yazi-rs/schemas.
|
# If you encounter any problems, please file an issue at https://github.com/yazi-rs/schemas.
|
||||||
|
|
||||||
#:schema https://yazi-rs.github.io/schemas/keymap.json
|
#:schema https://yazi-rs.github.io/schemas/keymap.json
|
||||||
|
|
||||||
|
|
@ -76,6 +76,7 @@ keymap = [
|
||||||
{ on = "d", run = "remove", desc = "Trash selected files" },
|
{ on = "d", run = "remove", desc = "Trash selected files" },
|
||||||
{ on = "D", run = "remove --permanently", desc = "Permanently delete selected files" },
|
{ on = "D", run = "remove --permanently", desc = "Permanently delete selected files" },
|
||||||
{ on = "a", run = "create", desc = "Create a file (ends with / for directories)" },
|
{ on = "a", run = "create", desc = "Create a file (ends with / for directories)" },
|
||||||
|
{ on = "A", run = "bulk_create", desc = "Bulk create files" },
|
||||||
{ on = "r", run = "rename --cursor=before_ext", desc = "Rename selected file(s)" },
|
{ on = "r", run = "rename --cursor=before_ext", desc = "Rename selected file(s)" },
|
||||||
{ on = ";", run = "shell --interactive", desc = "Run a shell command" },
|
{ on = ";", run = "shell --interactive", desc = "Run a shell command" },
|
||||||
{ on = ":", run = "shell --block --interactive", desc = "Run a shell command (block until finishes)" },
|
{ on = ":", run = "shell --block --interactive", desc = "Run a shell command (block until finishes)" },
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# A TOML linter such as https://github.com/tombi-toml/tombi can use this schema to validate your config.
|
# A TOML linter such as Tombi can use this schema to validate your config.
|
||||||
# If you encounter any issues, please make an issue at https://github.com/yazi-rs/schemas.
|
# If you encounter any problems, please file an issue at https://github.com/yazi-rs/schemas.
|
||||||
|
|
||||||
#:schema https://yazi-rs.github.io/schemas/yazi.json
|
#:schema https://yazi-rs.github.io/schemas/yazi.json
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,11 @@ use yazi_shared::url::{Url, UrlCow};
|
||||||
use super::Ember;
|
use super::Ember;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
pub struct EmberBulk<'a> {
|
pub struct EmberBulkRename<'a> {
|
||||||
pub changes: HashMap<UrlCow<'a>, UrlCow<'a>>,
|
pub changes: HashMap<UrlCow<'a>, UrlCow<'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> EmberBulk<'a> {
|
impl<'a> EmberBulkRename<'a> {
|
||||||
pub fn borrowed<I>(changes: I) -> Ember<'a>
|
pub fn borrowed<I>(changes: I) -> Ember<'a>
|
||||||
where
|
where
|
||||||
I: Iterator<Item = (Url<'a>, Url<'a>)>,
|
I: Iterator<Item = (Url<'a>, Url<'a>)>,
|
||||||
|
|
@ -19,7 +19,7 @@ impl<'a> EmberBulk<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EmberBulk<'static> {
|
impl EmberBulkRename<'static> {
|
||||||
pub fn owned<'a, I>(changes: I) -> Ember<'static>
|
pub fn owned<'a, I>(changes: I) -> Ember<'static>
|
||||||
where
|
where
|
||||||
I: Iterator<Item = (Url<'a>, Url<'a>)>,
|
I: Iterator<Item = (Url<'a>, Url<'a>)>,
|
||||||
|
|
@ -31,11 +31,11 @@ impl EmberBulk<'static> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<EmberBulk<'a>> for Ember<'a> {
|
impl<'a> From<EmberBulkRename<'a>> for Ember<'a> {
|
||||||
fn from(value: EmberBulk<'a>) -> Self { Self::Bulk(value) }
|
fn from(value: EmberBulkRename<'a>) -> Self { Self::BulkRename(value) }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoLua for EmberBulk<'_> {
|
impl IntoLua for EmberBulkRename<'_> {
|
||||||
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
|
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
|
||||||
lua
|
lua
|
||||||
.create_table_from(
|
.create_table_from(
|
||||||
|
|
@ -2,7 +2,7 @@ use anyhow::{Result, bail};
|
||||||
use mlua::{ExternalResult, IntoLua, Lua, Value};
|
use mlua::{ExternalResult, IntoLua, Lua, Value};
|
||||||
use yazi_shared::Id;
|
use yazi_shared::Id;
|
||||||
|
|
||||||
use super::{EmberBulk, EmberBye, EmberCd, EmberCustom, EmberDelete, EmberDownload, EmberDuplicate, EmberHey, EmberHi, EmberHover, EmberLoad, EmberMount, EmberMove, EmberRename, EmberTab, EmberTrash, EmberYank};
|
use super::{EmberBulkRename, EmberBye, EmberCd, EmberCustom, EmberDelete, EmberDownload, EmberDuplicate, EmberHey, EmberHi, EmberHover, EmberLoad, EmberMount, EmberMove, EmberRename, EmberTab, EmberTrash, EmberYank};
|
||||||
use crate::Payload;
|
use crate::Payload;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
@ -15,7 +15,7 @@ pub enum Ember<'a> {
|
||||||
Load(EmberLoad<'a>),
|
Load(EmberLoad<'a>),
|
||||||
Hover(EmberHover<'a>),
|
Hover(EmberHover<'a>),
|
||||||
Rename(EmberRename<'a>),
|
Rename(EmberRename<'a>),
|
||||||
Bulk(EmberBulk<'a>),
|
BulkRename(EmberBulkRename<'a>),
|
||||||
Yank(EmberYank<'a>),
|
Yank(EmberYank<'a>),
|
||||||
Duplicate(EmberDuplicate<'a>),
|
Duplicate(EmberDuplicate<'a>),
|
||||||
Move(EmberMove<'a>),
|
Move(EmberMove<'a>),
|
||||||
|
|
@ -37,7 +37,7 @@ impl Ember<'static> {
|
||||||
"load" => Self::Load(serde_json::from_str(body)?),
|
"load" => Self::Load(serde_json::from_str(body)?),
|
||||||
"hover" => Self::Hover(serde_json::from_str(body)?),
|
"hover" => Self::Hover(serde_json::from_str(body)?),
|
||||||
"rename" => Self::Rename(serde_json::from_str(body)?),
|
"rename" => Self::Rename(serde_json::from_str(body)?),
|
||||||
"bulk" => Self::Bulk(serde_json::from_str(body)?),
|
"bulk-rename" => Self::BulkRename(serde_json::from_str(body)?),
|
||||||
"@yank" => Self::Yank(serde_json::from_str(body)?),
|
"@yank" => Self::Yank(serde_json::from_str(body)?),
|
||||||
"duplicate" => Self::Duplicate(serde_json::from_str(body)?),
|
"duplicate" => Self::Duplicate(serde_json::from_str(body)?),
|
||||||
"move" => Self::Move(serde_json::from_str(body)?),
|
"move" => Self::Move(serde_json::from_str(body)?),
|
||||||
|
|
@ -65,7 +65,7 @@ impl Ember<'static> {
|
||||||
| "load"
|
| "load"
|
||||||
| "hover"
|
| "hover"
|
||||||
| "rename"
|
| "rename"
|
||||||
| "bulk"
|
| "bulk-rename"
|
||||||
| "@yank"
|
| "@yank"
|
||||||
| "duplicate"
|
| "duplicate"
|
||||||
| "move"
|
| "move"
|
||||||
|
|
@ -104,7 +104,7 @@ impl<'a> Ember<'a> {
|
||||||
Self::Load(_) => "load",
|
Self::Load(_) => "load",
|
||||||
Self::Hover(_) => "hover",
|
Self::Hover(_) => "hover",
|
||||||
Self::Rename(_) => "rename",
|
Self::Rename(_) => "rename",
|
||||||
Self::Bulk(_) => "bulk",
|
Self::BulkRename(_) => "bulk-rename",
|
||||||
Self::Yank(_) => "@yank",
|
Self::Yank(_) => "@yank",
|
||||||
Self::Duplicate(_) => "duplicate",
|
Self::Duplicate(_) => "duplicate",
|
||||||
Self::Move(_) => "move",
|
Self::Move(_) => "move",
|
||||||
|
|
@ -132,7 +132,7 @@ impl<'a> IntoLua for Ember<'a> {
|
||||||
Self::Hover(b) => b.into_lua(lua),
|
Self::Hover(b) => b.into_lua(lua),
|
||||||
Self::Tab(b) => b.into_lua(lua),
|
Self::Tab(b) => b.into_lua(lua),
|
||||||
Self::Rename(b) => b.into_lua(lua),
|
Self::Rename(b) => b.into_lua(lua),
|
||||||
Self::Bulk(b) => b.into_lua(lua),
|
Self::BulkRename(b) => b.into_lua(lua),
|
||||||
Self::Yank(b) => b.into_lua(lua),
|
Self::Yank(b) => b.into_lua(lua),
|
||||||
Self::Duplicate(b) => b.into_lua(lua),
|
Self::Duplicate(b) => b.into_lua(lua),
|
||||||
Self::Move(b) => b.into_lua(lua),
|
Self::Move(b) => b.into_lua(lua),
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
yazi_macro::mod_flat!(
|
yazi_macro::mod_flat!(
|
||||||
bulk bye cd custom delete download duplicate ember hey hi hover load mount r#move rename tab trash yank
|
bulk_rename bye cd custom delete download duplicate ember hey hi hover load mount r#move rename tab trash yank
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,7 @@ impl Display for Payload<'_> {
|
||||||
Ember::Hover(b) => serde_json::to_string(b),
|
Ember::Hover(b) => serde_json::to_string(b),
|
||||||
Ember::Tab(b) => serde_json::to_string(b),
|
Ember::Tab(b) => serde_json::to_string(b),
|
||||||
Ember::Rename(b) => serde_json::to_string(b),
|
Ember::Rename(b) => serde_json::to_string(b),
|
||||||
Ember::Bulk(b) => serde_json::to_string(b),
|
Ember::BulkRename(b) => serde_json::to_string(b),
|
||||||
Ember::Yank(b) => serde_json::to_string(b),
|
Ember::Yank(b) => serde_json::to_string(b),
|
||||||
Ember::Duplicate(b) => serde_json::to_string(b),
|
Ember::Duplicate(b) => serde_json::to_string(b),
|
||||||
Ember::Move(b) => serde_json::to_string(b),
|
Ember::Move(b) => serde_json::to_string(b),
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ use yazi_fs::FolderStage;
|
||||||
use yazi_shared::{Id, url::{Url, UrlBuf, UrlBufCov}};
|
use yazi_shared::{Id, url::{Url, UrlBuf, UrlBufCov}};
|
||||||
use yazi_shim::cell::RoCell;
|
use yazi_shim::cell::RoCell;
|
||||||
|
|
||||||
use crate::{Client, ID, PEERS, ember::{Ember, EmberBulk, EmberDuplicateItem, EmberHi, EmberMoveItem}};
|
use crate::{Client, ID, PEERS, ember::{Ember, EmberBulkRename, EmberDuplicateItem, EmberHi, EmberMoveItem}};
|
||||||
|
|
||||||
pub static LOCAL: RoCell<RwLock<HashMap<String, HashMap<String, Function>>>> = RoCell::new();
|
pub static LOCAL: RoCell<RwLock<HashMap<String, HashMap<String, Function>>>> = RoCell::new();
|
||||||
|
|
||||||
|
|
@ -128,18 +128,18 @@ impl Pubsub {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pub_after_bulk<'a, I>(changes: I) -> Result<()>
|
pub fn pub_after_bulk_rename<'a, I>(changes: I) -> Result<()>
|
||||||
where
|
where
|
||||||
I: Iterator<Item = (Url<'a>, Url<'a>)> + Clone,
|
I: Iterator<Item = (Url<'a>, Url<'a>)> + Clone,
|
||||||
{
|
{
|
||||||
if BOOT.local_events.contains("bulk") {
|
if BOOT.local_events.contains("bulk-rename") {
|
||||||
EmberBulk::borrowed(changes.clone()).with_receiver(*ID).flush()?;
|
EmberBulkRename::borrowed(changes.clone()).with_receiver(*ID).flush()?;
|
||||||
}
|
}
|
||||||
if PEERS.read().values().any(|p| p.able("bulk")) {
|
if PEERS.read().values().any(|p| p.able("bulk-rename")) {
|
||||||
Client::push(EmberBulk::borrowed(changes.clone()))?;
|
Client::push(EmberBulkRename::borrowed(changes.clone()))?;
|
||||||
}
|
}
|
||||||
if LOCAL.read().contains_key("bulk") {
|
if LOCAL.read().contains_key("bulk-rename") {
|
||||||
Self::r#pub(EmberBulk::owned(changes))?;
|
Self::r#pub(EmberBulkRename::owned(changes))?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -132,6 +132,7 @@ impl<'a> Executor<'a> {
|
||||||
on!(search_do);
|
on!(search_do);
|
||||||
on!(bulk_exit);
|
on!(bulk_exit);
|
||||||
on!(bulk_rename);
|
on!(bulk_rename);
|
||||||
|
on!(bulk_create);
|
||||||
|
|
||||||
// Filter
|
// Filter
|
||||||
on!(filter);
|
on!(filter);
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,18 @@ impl FilesOp {
|
||||||
ticket
|
ticket
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create(files: Vec<File>) {
|
||||||
|
let mut parents: HashMap<UrlBuf, Vec<_>> = Default::default();
|
||||||
|
for file in files {
|
||||||
|
if let Some(p) = file.url.parent() {
|
||||||
|
parents.get_or_insert_default(p).push(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (p, files) in parents {
|
||||||
|
Self::Creating(p, files).emit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn rename(map: HashMap<UrlBuf, File>) {
|
pub fn rename(map: HashMap<UrlBuf, File>) {
|
||||||
let mut parents: HashMap<UrlBuf, (HashSet<_>, HashMap<_, _>)> = Default::default();
|
let mut parents: HashMap<UrlBuf, (HashSet<_>, HashMap<_, _>)> = Default::default();
|
||||||
for (o, n) in map {
|
for (o, n) in map {
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ pub enum Spark<'a> {
|
||||||
// Mgr
|
// Mgr
|
||||||
Arrow(crate::ArrowForm),
|
Arrow(crate::ArrowForm),
|
||||||
Back(crate::VoidForm),
|
Back(crate::VoidForm),
|
||||||
|
BulkCreate(crate::VoidForm),
|
||||||
BulkExit(crate::mgr::BulkExitForm),
|
BulkExit(crate::mgr::BulkExitForm),
|
||||||
BulkRename(crate::VoidForm),
|
BulkRename(crate::VoidForm),
|
||||||
Cd(crate::mgr::CdForm),
|
Cd(crate::mgr::CdForm),
|
||||||
|
|
@ -215,6 +216,7 @@ impl<'a> IntoLua for Spark<'a> {
|
||||||
// Mgr
|
// Mgr
|
||||||
Self::Arrow(b) => b.into_lua(lua),
|
Self::Arrow(b) => b.into_lua(lua),
|
||||||
Self::Back(b) => b.into_lua(lua),
|
Self::Back(b) => b.into_lua(lua),
|
||||||
|
Self::BulkCreate(b) => b.into_lua(lua),
|
||||||
Self::BulkExit(b) => b.into_lua(lua),
|
Self::BulkExit(b) => b.into_lua(lua),
|
||||||
Self::BulkRename(b) => b.into_lua(lua),
|
Self::BulkRename(b) => b.into_lua(lua),
|
||||||
Self::Cd(b) => b.into_lua(lua),
|
Self::Cd(b) => b.into_lua(lua),
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
yazi_macro::mod_pub!(arc_swap cell crossterm mlua ratatui serde strum toml vec);
|
yazi_macro::mod_pub!(arc_swap cell crossterm mlua path ratatui serde strum toml vec);
|
||||||
|
|
||||||
yazi_macro::mod_flat!(twox);
|
yazi_macro::mod_flat!(twox);
|
||||||
|
|
|
||||||
1
yazi-shim/src/path/mod.rs
Normal file
1
yazi-shim/src/path/mod.rs
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
yazi_macro::mod_flat!(separator);
|
||||||
5
yazi-shim/src/path/separator.rs
Normal file
5
yazi-shim/src/path/separator.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
#[cfg(windows)]
|
||||||
|
pub const CROSS_SEPARATOR: [char; 2] = ['/', '\\'];
|
||||||
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
pub const CROSS_SEPARATOR: char = std::path::MAIN_SEPARATOR;
|
||||||
|
|
@ -3,16 +3,17 @@ use std::path::MAIN_SEPARATOR_STR;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use yazi_macro::{act, render, succ};
|
use yazi_macro::{act, render, succ};
|
||||||
use yazi_shared::data::Data;
|
use yazi_shared::data::Data;
|
||||||
|
use yazi_shim::path::CROSS_SEPARATOR;
|
||||||
|
|
||||||
use crate::input::{Input, SEPARATOR, parser::CompleteOpt};
|
use crate::input::{Input, parser::CompleteOpt};
|
||||||
|
|
||||||
impl Input {
|
impl Input {
|
||||||
pub fn complete(&mut self, opt: CompleteOpt) -> Result<Data> {
|
pub fn complete(&mut self, opt: CompleteOpt) -> Result<Data> {
|
||||||
let (before, after) = self.partition();
|
let (before, after) = self.partition();
|
||||||
let new = if let Some((prefix, _)) = before.rsplit_once(SEPARATOR) {
|
let new = if let Some((prefix, _)) = before.rsplit_once(CROSS_SEPARATOR) {
|
||||||
format!("{prefix}/{}{after}", opt.completable()).replace(SEPARATOR, MAIN_SEPARATOR_STR)
|
format!("{prefix}/{}{after}", opt.completable()).replace(CROSS_SEPARATOR, MAIN_SEPARATOR_STR)
|
||||||
} else {
|
} else {
|
||||||
format!("{}{after}", opt.completable()).replace(SEPARATOR, MAIN_SEPARATOR_STR)
|
format!("{}{after}", opt.completable()).replace(CROSS_SEPARATOR, MAIN_SEPARATOR_STR)
|
||||||
};
|
};
|
||||||
|
|
||||||
let snap = self.snap_mut();
|
let snap = self.snap_mut();
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,10 @@ use tokio::sync::mpsc;
|
||||||
use yazi_config::YAZI;
|
use yazi_config::YAZI;
|
||||||
use yazi_macro::act;
|
use yazi_macro::act;
|
||||||
use yazi_shared::Ids;
|
use yazi_shared::Ids;
|
||||||
|
use yazi_shim::path::CROSS_SEPARATOR;
|
||||||
|
|
||||||
use super::{InputSnap, InputSnaps, mode::InputMode, op::InputOp};
|
use super::{InputSnap, InputSnaps, mode::InputMode, op::InputOp};
|
||||||
use crate::{CLIPBOARD, input::{InputEvent, InputOpt, SEPARATOR}};
|
use crate::{CLIPBOARD, input::{InputEvent, InputOpt}};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Input {
|
pub struct Input {
|
||||||
|
|
@ -152,7 +153,7 @@ impl Input {
|
||||||
let snap = self.snap();
|
let snap = self.snap();
|
||||||
let idx = snap.idx(snap.cursor).unwrap();
|
let idx = snap.idx(snap.cursor).unwrap();
|
||||||
|
|
||||||
if let Some(sep) = snap.value[idx..].find(SEPARATOR).map(|i| idx + i) {
|
if let Some(sep) = snap.value[idx..].find(CROSS_SEPARATOR).map(|i| idx + i) {
|
||||||
(&snap.value[..sep], &snap.value[sep + 1..])
|
(&snap.value[..sep], &snap.value[sep + 1..])
|
||||||
} else {
|
} else {
|
||||||
(&snap.value, "")
|
(&snap.value, "")
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
yazi_macro::mod_pub!(actor parser);
|
yazi_macro::mod_pub!(actor parser);
|
||||||
|
|
||||||
yazi_macro::mod_flat!(event input mode op option separator snap snaps widget);
|
yazi_macro::mod_flat!(event input mode op option snap snaps widget);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
#[cfg(windows)]
|
|
||||||
pub(super) const SEPARATOR: [char; 2] = ['/', '\\'];
|
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
pub(super) const SEPARATOR: char = std::path::MAIN_SEPARATOR;
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue