From 316179f8a1a68807b9fede160199cfc49c63444d Mon Sep 17 00:00:00 2001 From: Kyohei Uto Date: Sat, 11 Jan 2025 08:02:22 +0900 Subject: [PATCH] Add multiple renaming --- src/op.rs | 23 ++++++++++++--------- src/run.rs | 35 +++++++++++++++++++++++++------- src/state.rs | 56 +++++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 95 insertions(+), 19 deletions(-) diff --git a/src/op.rs b/src/op.rs index 0590a28..11f4882 100644 --- a/src/op.rs +++ b/src/op.rs @@ -13,7 +13,7 @@ pub struct Operation { pub enum OpKind { Delete(DeletedFiles), Put(PutFiles), - Rename(RenamedFile), + Rename(Vec<(PathBuf, PathBuf)>), } #[derive(Debug, Clone)] @@ -30,12 +30,6 @@ pub struct PutFiles { pub dir: PathBuf, } -#[derive(Debug, Clone)] -pub struct RenamedFile { - pub original_name: PathBuf, - pub new_name: PathBuf, -} - impl Operation { /// Discard undone operations when new one is pushed. pub fn branch(&mut self) { @@ -63,7 +57,12 @@ fn log(op: &OpKind) { info!("DELETE: {:?}", item_to_pathvec(&op.original)); } OpKind::Rename(op) => { - info!("RENAME: {:?} -> {:?}", op.original_name, op.new_name); + info!( + "RENAME: {:?}", + op.iter() + .map(|v| format!("{:?} -> {:?}", v.0, v.1)) + .collect::>() + ); } } } @@ -85,7 +84,13 @@ pub fn relog(op: &OpKind, undo: bool) { } OpKind::Rename(op) => { result.push_str("RENAME"); - info!("{} {:?} -> {:?}", result, op.original_name, op.new_name); + info!( + "{} {:?}", + result, + op.iter() + .map(|v| format!("{:?} -> {:?}", v.0, v.1)) + .collect::>() + ); } } } diff --git a/src/run.rs b/src/run.rs index ce1195e..8417787 100644 --- a/src/run.rs +++ b/src/run.rs @@ -1362,8 +1362,31 @@ fn _run(mut state: State, session_path: PathBuf) -> Result<(), FxError> { //rename KeyCode::Char('c') => { - //In visual mode, this is disabled. + //In visual mode, you can rename multiple items in default editor. if state.v_start.is_some() { + let items: Vec = state + .list + .iter() + .filter(|item| item.selected) + .map(ItemBuffer::new) + .collect(); + execute!(screen, EnterAlternateScreen)?; + let mut err: Option = None; + if let Err(e) = state.rename_multiple_items(&items) { + err = Some(e); + } + execute!(screen, EnterAlternateScreen)?; + hide_cursor(); + state.reset_selection(); + state.reload(state.layout.y)?; + if let Some(e) = err { + print_warning(e, state.layout.y); + } else { + print_info( + format!("Renamed {} items.", items.len()), + state.layout.y, + ); + } continue; } if len == 0 { @@ -1511,12 +1534,10 @@ fn _run(mut state: State, session_path: PathBuf) -> Result<(), FxError> { } state.operations.branch(); - state.operations.push(OpKind::Rename( - RenamedFile { - original_name: item.file_path.clone(), - new_name: to, - }, - )); + state.operations.push(OpKind::Rename(vec![( + item.file_path.clone(), + to, + )])); hide_cursor(); state.reload(state.layout.y)?; diff --git a/src/state.rs b/src/state.rs index e2eb006..70219e2 100644 --- a/src/state.rs +++ b/src/state.rs @@ -190,7 +190,8 @@ impl Registers { } } -/// To avoid cost copying ItemInfo, use ItemBuffer when tinkering with register. +/// To avoid cost copying ItemInfo, use ItemBuffer +/// when tinkering with register or multiple renaming. #[derive(Debug, Clone)] pub struct ItemBuffer { pub file_type: FileType, @@ -921,7 +922,9 @@ impl State { pub fn undo(&mut self, op: &OpKind) -> Result<(), FxError> { match op { OpKind::Rename(op) => { - std::fs::rename(&op.new_name, &op.original_name)?; + for (original, new) in op { + std::fs::rename(new, original)?; + } self.operations.pos += 1; self.update_list()?; self.clear_and_show_headline(); @@ -959,7 +962,9 @@ impl State { pub fn redo(&mut self, op: &OpKind) -> Result<(), FxError> { match op { OpKind::Rename(op) => { - std::fs::rename(&op.original_name, &op.new_name)?; + for (original, new) in op { + std::fs::rename(original, new)?; + } self.operations.pos -= 1; self.update_list()?; self.clear_and_show_headline(); @@ -1287,6 +1292,51 @@ impl State { self.list = result; } + /// Rename selected items at once. + pub fn rename_multiple_items(&mut self, items: &[ItemBuffer]) -> Result<(), FxError> { + let names: Vec<&str> = items.iter().map(|item| item.file_name.as_str()).collect(); + let mut file = tempfile::NamedTempFile::new()?; + writeln!(file, "{}", names.join("\n"))?; + + let mut default = Command::new(&self.default); + let path = file.into_temp_path(); + if let Err(e) = default + .arg(&path) + .status() + .map_err(|_| FxError::DefaultEditor) + { + Err(e) + } else { + let new_names = fs::read_to_string(&path)?; + let new_names: Vec<&str> = new_names + .split('\n') + .filter(|name| !name.is_empty()) + .collect(); + if new_names.len() != items.len() { + Err(FxError::Io( + format!( + "Rename failed: Expected {} names, but received {} names", + items.len(), + new_names.len() + ) + .to_string(), + )) + } else { + let mut result: Vec<(PathBuf, PathBuf)> = vec![]; + for (i, new_name) in new_names.iter().enumerate() { + let mut to = self.current_dir.clone(); + to.push(new_name); + std::fs::rename(&items[i].file_path, &to)?; + result.push((items[i].file_path.clone(), to)) + } + self.operations.branch(); + self.operations.push(OpKind::Rename(result)); + + Ok(()) + } + } + } + /// Reset all item's selected state and exit the select mode. pub fn reset_selection(&mut self) { for item in self.list.iter_mut() {