mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
feat(bulk_rename): work in progress
This commit is contained in:
parent
e7672b1f06
commit
0a4cb798bf
6 changed files with 194 additions and 7 deletions
37
Cargo.lock
generated
37
Cargo.lock
generated
|
|
@ -1315,6 +1315,12 @@ dependencies = [
|
|||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.66"
|
||||
|
|
@ -1351,6 +1357,36 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ratatui"
|
||||
version = "0.22.0"
|
||||
|
|
@ -1529,6 +1565,7 @@ dependencies = [
|
|||
"crossterm 0.27.0",
|
||||
"libc",
|
||||
"parking_lot",
|
||||
"rand",
|
||||
"ratatui",
|
||||
"tokio",
|
||||
]
|
||||
|
|
|
|||
46
core/src/external/editor.rs
vendored
Normal file
46
core/src/external/editor.rs
vendored
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
use std::{env, ffi::{OsStr, OsString}, path::Path, str::FromStr};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use once_cell::sync::OnceCell;
|
||||
use tokio::process::Command;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Editor {
|
||||
name: String,
|
||||
options: Vec<String>,
|
||||
}
|
||||
|
||||
static EDITOR: OnceCell<Editor> = OnceCell::new();
|
||||
|
||||
fn try_init() -> Result<&'static Editor> {
|
||||
EDITOR.get_or_try_init(|| {
|
||||
env::var_os("EDITOR")
|
||||
.context("environment variable `EDITOR` is undefined")?
|
||||
.to_string_lossy()
|
||||
.parse()
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn edit(file: impl AsRef<Path>) -> Result<()> {
|
||||
let editor = try_init()?;
|
||||
// TODO: 编辑并返回是否成功
|
||||
// let output = Command::new(&editor.name)
|
||||
// .args(&editor.options)
|
||||
// .arg(file.as_ref())
|
||||
// .kill_on_drop(true)
|
||||
// .output()
|
||||
// .await?;
|
||||
todo!()
|
||||
}
|
||||
|
||||
impl FromStr for Editor {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||
let mut args = s.split(' ');
|
||||
Ok(Self {
|
||||
name: args.next().context("environment variable `EDITOR` is empty")?.to_owned(),
|
||||
options: args.map(str::to_owned).collect(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -115,12 +115,11 @@ impl Folder {
|
|||
pub fn select(&mut self, idx: Option<usize>, state: Option<bool>) -> bool {
|
||||
let len = self.files.len();
|
||||
let mut apply = |idx: usize, state: Option<bool>| -> bool {
|
||||
if state.is_none() {
|
||||
let Some(state) = state else {
|
||||
self.files[idx].is_selected = !self.files[idx].is_selected;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
let state = state.unwrap();
|
||||
if state != self.files[idx].is_selected {
|
||||
self.files[idx].is_selected = state;
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
use std::{collections::{BTreeMap, BTreeSet, HashMap, HashSet}, env, mem, path::{Path, PathBuf}};
|
||||
use std::{collections::{BTreeMap, BTreeSet, HashMap, HashSet}, env, mem, path::{Path, PathBuf}, process::exit};
|
||||
|
||||
use anyhow::Error;
|
||||
use config::{open::Opener, OPEN};
|
||||
use shared::MIME_DIR;
|
||||
use tokio::fs;
|
||||
use indexmap::IndexSet;
|
||||
use shared::{temp_path, MIME_DIR};
|
||||
use tokio::{fs::{self, OpenOptions}, io::{AsyncReadExt, AsyncWriteExt, BufReader, BufWriter}};
|
||||
use tracing::{debug, error};
|
||||
|
||||
use super::{PreviewData, Tab, Tabs, Watcher};
|
||||
use crate::{emit, external, files::{File, FilesOp}, input::InputOpt, manager::Folder, select::SelectOpt, tasks::Tasks};
|
||||
|
|
@ -222,7 +224,56 @@ impl Manager {
|
|||
false
|
||||
}
|
||||
|
||||
pub fn bulk_rename(&self) -> bool { false }
|
||||
pub fn bulk_rename(&self) -> bool {
|
||||
let selected: Vec<_> = self.selected().iter().map(|&f| f.path.clone()).collect();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let files: Vec<_> =
|
||||
selected.iter().map(|p| p.file_name().unwrap().to_string_lossy()).collect();
|
||||
let rename_file_path = temp_path(Some("txt"));
|
||||
|
||||
{
|
||||
let mut rename_file =
|
||||
match OpenOptions::new().write(true).create_new(true).open(&rename_file_path).await {
|
||||
Ok(f) => BufWriter::new(f),
|
||||
Err(e) => {
|
||||
error!("failed to open rename buffer: {e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(e) = rename_file.write_all(files.join("\n").as_bytes()).await {
|
||||
error!("failed to write content to rename buffer: {e}");
|
||||
return;
|
||||
}
|
||||
let _ = rename_file.flush().await;
|
||||
}
|
||||
|
||||
emit!(Open(vec![(rename_file_path.as_os_str().to_owned(), "text/plain".to_owned())], None));
|
||||
|
||||
let mut buf = String::new();
|
||||
{
|
||||
let mut rename_file = match tokio::fs::File::open(&rename_file_path).await {
|
||||
Ok(f) => BufReader::new(f),
|
||||
Err(e) => {
|
||||
error!("failed to read rename buffer: {e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let _ = rename_file.read_to_string(&mut buf).await;
|
||||
}
|
||||
let new_names = match parse_new_names(&buf, selected.len()) {
|
||||
Ok(names) => names,
|
||||
Err(e) => {
|
||||
println!("yazi: {e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
println!("new names: {new_names:?}");
|
||||
});
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn shell(&self, exec: &str, block: bool, confirm: bool) -> bool {
|
||||
let mut exec = exec.to_owned();
|
||||
|
|
@ -386,3 +437,19 @@ impl Manager {
|
|||
self.active().mode.is_visual() || self.current().has_selected()
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_new_names(text: &str, count: usize) -> anyhow::Result<IndexSet<&str>> {
|
||||
// NOTE: call `size_hint` on `str::Split` always returns 0
|
||||
let new_names: Vec<_> = text.split('\n').collect();
|
||||
if new_names.len() != count {
|
||||
anyhow::bail!("the number of new names doesn't match the number of old names");
|
||||
}
|
||||
|
||||
let mut names = IndexSet::with_capacity(count);
|
||||
for name in new_names {
|
||||
if !names.insert(name) {
|
||||
anyhow::bail!("there are more than one new entries named {name:?}");
|
||||
}
|
||||
}
|
||||
Ok(names)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ mod defer;
|
|||
mod fns;
|
||||
mod fs;
|
||||
mod mime;
|
||||
mod temp_path;
|
||||
mod term;
|
||||
mod throttle;
|
||||
mod tty;
|
||||
|
|
@ -14,6 +15,7 @@ pub use defer::*;
|
|||
pub use fns::*;
|
||||
pub use fs::*;
|
||||
pub use mime::*;
|
||||
pub use temp_path::*;
|
||||
pub use term::*;
|
||||
pub use throttle::*;
|
||||
pub use tty::*;
|
||||
|
|
|
|||
36
shared/src/temp_path.rs
Normal file
36
shared/src/temp_path.rs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use rand::{distributions::Alphanumeric, Rng};
|
||||
|
||||
const FILE_PREFIX: &str = "yazi-";
|
||||
const ID_LEN: usize = 10;
|
||||
|
||||
/// generate a path under the system temporary directory
|
||||
///
|
||||
/// `ext`: an ASCII string
|
||||
pub fn temp_path(ext: Option<&str>) -> PathBuf {
|
||||
let (ext, suffix_len) = ext.map(|ext| (ext, ext.len())).unwrap_or_default();
|
||||
|
||||
let mut name = String::with_capacity(FILE_PREFIX.len() + ID_LEN + suffix_len);
|
||||
name.push_str(FILE_PREFIX);
|
||||
rand::thread_rng().sample_iter(&Alphanumeric).take(ID_LEN).for_each(|c| name.push(c as char));
|
||||
|
||||
if !ext.is_empty() {
|
||||
name.push('.');
|
||||
name.push_str(ext);
|
||||
}
|
||||
|
||||
let tmp = std::env::temp_dir();
|
||||
tmp.join(&name)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::temp_path;
|
||||
|
||||
#[test]
|
||||
fn test_temp_path() {
|
||||
let p = temp_path(Some("txt"));
|
||||
println!("{p:?}");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue