From b8db557ffec35917e94df200315ca8737e05af5e Mon Sep 17 00:00:00 2001 From: veeso Date: Sun, 9 May 2021 21:08:31 +0200 Subject: [PATCH 1/7] Added file selection to file_list component --- src/ui/components/file_list.rs | 314 +++++++++++++++++++++++++++++---- 1 file changed, 282 insertions(+), 32 deletions(-) diff --git a/src/ui/components/file_list.rs b/src/ui/components/file_list.rs index 105c084..7c98774 100644 --- a/src/ui/components/file_list.rs +++ b/src/ui/components/file_list.rs @@ -27,7 +27,7 @@ */ // ext use tuirealm::components::utils::get_block; -use tuirealm::event::{Event, KeyCode}; +use tuirealm::event::{Event, KeyCode, KeyModifiers}; use tuirealm::props::{BordersProps, Props, PropsBuilder, TextParts, TextSpan}; use tuirealm::tui::{ layout::{Corner, Rect}, @@ -133,33 +133,34 @@ impl FileListPropsBuilder { /// OwnStates contains states for this component #[derive(Clone)] struct OwnStates { - list_index: usize, // Index of selected element in list - list_len: usize, // Length of file list - focus: bool, // Has focus? + list_index: usize, // Index of selected element in list + selected: Vec, // Selected files + focus: bool, // Has focus? } impl Default for OwnStates { fn default() -> Self { OwnStates { list_index: 0, - list_len: 0, + selected: Vec::new(), focus: false, } } } impl OwnStates { - /// ### set_list_len + /// ### init_list_states /// - /// Set list length - pub fn set_list_len(&mut self, len: usize) { - self.list_len = len; + /// Initialize list states + pub fn init_list_states(&mut self, len: usize) { + self.selected = Vec::with_capacity(len); + self.fix_list_index(); } - /// ### get_list_index + /// ### list_index /// /// Return current value for list index - pub fn get_list_index(&self) -> usize { + pub fn list_index(&self) -> usize { self.list_index } @@ -168,7 +169,7 @@ impl OwnStates { /// Incremenet list index pub fn incr_list_index(&mut self) { // Check if index is at last element - if self.list_index + 1 < self.list_len { + if self.list_index + 1 < self.list_len() { self.list_index += 1; } } @@ -183,16 +184,83 @@ impl OwnStates { } } + /// ### list_len + /// + /// Returns the length of the file list, which is actually the capacity of the selection vector + pub fn list_len(&self) -> usize { + self.selected.capacity() + } + + /// ### is_selected + /// + /// Returns whether the file with index `entry` is selected + pub fn is_selected(&self, entry: usize) -> bool { + self.selected.contains(&entry) + } + + /// ### is_selection_empty + /// + /// Returns whether the selection is currently empty + pub fn is_selection_empty(&self) -> bool { + self.selected.is_empty() + } + + /// ### get_selection + /// + /// Returns current file selection + pub fn get_selection(&self) -> Vec { + self.selected.clone() + } + /// ### fix_list_index /// /// Keep index if possible, otherwise set to lenght - 1 - pub fn fix_list_index(&mut self) { - if self.list_index >= self.list_len && self.list_len > 0 { - self.list_index = self.list_len - 1; - } else if self.list_len == 0 { + fn fix_list_index(&mut self) { + if self.list_index >= self.list_len() && self.list_len() > 0 { + self.list_index = self.list_len() - 1; + } else if self.list_len() == 0 { self.list_index = 0; } } + + // -- select manipulation + + /// ### toggle_file + /// + /// Select or deselect file with provided entry index + pub fn toggle_file(&mut self, entry: usize) { + match self.is_selected(entry) { + true => self.deselect(entry), + false => self.select(entry), + } + } + + /// ### select_all + /// + /// Select all files + pub fn select_all(&mut self) { + for i in 0..self.list_len() { + self.select(i); + } + } + + /// ### select + /// + /// Select provided index if not selected yet + fn select(&mut self, entry: usize) { + if !self.is_selected(entry) { + self.selected.push(entry); + } + } + + /// ### deselect + /// + /// Remove element file with associated index + fn deselect(&mut self, entry: usize) { + if self.is_selected(entry) { + self.selected.retain(|&x| x != entry); + } + } } // -- Component @@ -213,11 +281,8 @@ impl FileList { pub fn new(props: Props) -> Self { // Initialize states let mut states: OwnStates = OwnStates::default(); - // Set list length - states.set_list_len(match &props.texts.spans { - Some(tokens) => tokens.len(), - None => 0, - }); + // Init list states + states.init_list_states(props.texts.spans.as_ref().map(|x| x.len()).unwrap_or(0)); FileList { props, states } } } @@ -231,7 +296,14 @@ impl Component for FileList { None => vec![], Some(lines) => lines .iter() - .map(|line| ListItem::new(Span::from(line.content.to_string()))) + .enumerate() + .map(|(num, line)| { + let to_display: String = match self.states.is_selected(num) { + true => format!("*{}", line.content), + false => line.content.to_string(), + }; + ListItem::new(Span::from(to_display)) + }) .collect(), }; let (fg, bg): (Color, Color) = match self.states.focus { @@ -263,13 +335,15 @@ impl Component for FileList { fn update(&mut self, props: Props) -> Msg { self.props = props; - // re-Set list length - self.states.set_list_len(match &self.props.texts.spans { - Some(tokens) => tokens.len(), - None => 0, - }); - // Fix list index - self.states.fix_list_index(); + // re-Set list states + self.states.init_list_states( + self.props + .texts + .spans + .as_ref() + .map(|x| x.len()) + .unwrap_or(0), + ); Msg::None } @@ -305,6 +379,20 @@ impl Component for FileList { } Msg::None } + KeyCode::Char('a') => match key.modifiers.intersects(KeyModifiers::CONTROL) { + // CTRL+C + true => { + // Select all + self.states.select_all(); + Msg::None + } + false => Msg::None, + }, + KeyCode::Char('m') => { + // Toggle current file in selection + self.states.toggle_file(self.states.list_index()); + Msg::None + } KeyCode::Enter => { // Report event Msg::OnSubmit(self.get_state()) @@ -320,8 +408,22 @@ impl Component for FileList { } } + /// ### get_state + /// + /// Get state returns for this component two different payloads based on the states: + /// - if the file selection is empty, returns the highlighted item as `One` of `Usize` + /// - if at least one item is selected, return the selected as a `Vec` of `Usize` fn get_state(&self) -> Payload { - Payload::One(Value::Usize(self.states.get_list_index())) + match self.states.is_selection_empty() { + true => Payload::One(Value::Usize(self.states.list_index())), + false => Payload::Vec( + self.states + .get_selection() + .into_iter() + .map(Value::Usize) + .collect(), + ), + } } // -- events @@ -349,6 +451,72 @@ mod tests { use pretty_assertions::assert_eq; use tuirealm::event::KeyEvent; + #[test] + fn test_ui_components_file_list_states() { + let mut states: OwnStates = OwnStates::default(); + assert_eq!(states.list_len(), 0); + assert_eq!(states.selected.len(), 0); + assert_eq!(states.focus, false); + // Init states + states.init_list_states(4); + assert_eq!(states.list_len(), 4); + assert_eq!(states.selected.len(), 0); + assert!(states.is_selection_empty()); + // Select all files + states.select_all(); + assert_eq!(states.list_len(), 4); + assert_eq!(states.selected.len(), 4); + assert_eq!(states.is_selection_empty(), false); + assert_eq!(states.get_selection(), vec![0, 1, 2, 3]); + // Verify reset + states.init_list_states(5); + assert_eq!(states.list_len(), 5); + assert_eq!(states.selected.len(), 0); + // Toggle file + states.toggle_file(2); + assert_eq!(states.list_len(), 5); + assert_eq!(states.selected.len(), 1); + assert_eq!(states.selected[0], 2); + states.toggle_file(4); + assert_eq!(states.list_len(), 5); + assert_eq!(states.selected.len(), 2); + assert_eq!(states.selected[1], 4); + states.toggle_file(2); + assert_eq!(states.list_len(), 5); + assert_eq!(states.selected.len(), 1); + assert_eq!(states.selected[0], 4); + // Select twice (nothing should change) + states.select(4); + assert_eq!(states.list_len(), 5); + assert_eq!(states.selected.len(), 1); + assert_eq!(states.selected[0], 4); + // Deselect not-selectd item + states.deselect(2); + assert_eq!(states.list_len(), 5); + assert_eq!(states.selected.len(), 1); + assert_eq!(states.selected[0], 4); + // Index + states.init_list_states(2); + states.incr_list_index(); + assert_eq!(states.list_index(), 1); + states.incr_list_index(); + assert_eq!(states.list_index(), 1); + states.decr_list_index(); + assert_eq!(states.list_index(), 0); + states.decr_list_index(); + assert_eq!(states.list_index(), 0); + // Try fixing index + states.init_list_states(5); + states.list_index = 4; + states.init_list_states(3); + assert_eq!(states.list_index(), 2); + states.init_list_states(6); + assert_eq!(states.list_index(), 2); + // Focus + states.focus = true; + assert_eq!(states.focus, true); + } + #[test] fn test_ui_components_file_list() { // Make component @@ -375,7 +543,9 @@ mod tests { assert_eq!(component.props.texts.spans.as_ref().unwrap().len(), 2); // Verify states assert_eq!(component.states.list_index, 0); - assert_eq!(component.states.list_len, 2); + assert_eq!(component.states.selected.len(), 0); + assert_eq!(component.states.list_len(), 2); + assert_eq!(component.states.selected.capacity(), 2); assert_eq!(component.states.focus, false); // Focus component.active(); @@ -408,7 +578,7 @@ mod tests { ); // Verify states assert_eq!(component.states.list_index, 1); // Kept - assert_eq!(component.states.list_len, 3); + assert_eq!(component.states.list_len(), 3); // get value assert_eq!(component.get_state(), Payload::One(Value::Usize(1))); // Render @@ -452,4 +622,84 @@ mod tests { Msg::OnKey(KeyEvent::from(KeyCode::Backspace)) ); } + + #[test] + fn test_ui_components_file_list_selection() { + // Make component + let mut component: FileList = FileList::new( + FileListPropsBuilder::default() + .with_files( + Some(String::from("files")), + vec![ + String::from("file1"), + String::from("file2"), + String::from("file3"), + ], + ) + .build(), + ); + // Get state + assert_eq!(component.get_state(), Payload::One(Value::Usize(0))); + // Select one + assert_eq!( + component.on(Event::Key(KeyEvent::from(KeyCode::Char('m')))), + Msg::None + ); + // Now should be a vec + assert_eq!(component.get_state(), Payload::Vec(vec![Value::Usize(0)])); + // De-select + assert_eq!( + component.on(Event::Key(KeyEvent::from(KeyCode::Char('m')))), + Msg::None + ); + assert_eq!(component.get_state(), Payload::One(Value::Usize(0))); + // Go down + assert_eq!( + component.on(Event::Key(KeyEvent::from(KeyCode::Down))), + Msg::None + ); + // Select + assert_eq!( + component.on(Event::Key(KeyEvent::from(KeyCode::Char('m')))), + Msg::None + ); + assert_eq!(component.get_state(), Payload::Vec(vec![Value::Usize(1)])); + // Go down and select + assert_eq!( + component.on(Event::Key(KeyEvent::from(KeyCode::Down))), + Msg::None + ); + assert_eq!( + component.on(Event::Key(KeyEvent::from(KeyCode::Char('m')))), + Msg::None + ); + assert_eq!( + component.get_state(), + Payload::Vec(vec![Value::Usize(1), Value::Usize(2)]) + ); + // Select all + assert_eq!( + component.on(Event::Key(KeyEvent { + code: KeyCode::Char('a'), + modifiers: KeyModifiers::CONTROL, + })), + Msg::None + ); + // All selected + assert_eq!( + component.get_state(), + Payload::Vec(vec![Value::Usize(1), Value::Usize(2), Value::Usize(0)]) + ); + // Update files + component.update( + FileListPropsBuilder::from(component.get_props()) + .with_files( + Some(String::from("filelist")), + vec![String::from("file1"), String::from("file2")], + ) + .build(), + ); + // Selection should now be empty + assert_eq!(component.get_state(), Payload::One(Value::Usize(1))); + } } From d96558c3df4649620caf7bb71d20bda28441ba9a Mon Sep 17 00:00:00 2001 From: veeso Date: Wed, 12 May 2021 20:55:17 +0200 Subject: [PATCH 2/7] wip --- .../activities/filetransfer/actions/copy.rs | 103 +++++++++++------- .../activities/filetransfer/actions/delete.rs | 14 ++- src/ui/activities/filetransfer/actions/mod.rs | 30 ++--- .../activities/filetransfer/actions/rename.rs | 2 +- .../activities/filetransfer/actions/save.rs | 4 +- src/ui/components/file_list.rs | 2 +- 6 files changed, 90 insertions(+), 65 deletions(-) diff --git a/src/ui/activities/filetransfer/actions/copy.rs b/src/ui/activities/filetransfer/actions/copy.rs index 3b3d7c4..c614b14 100644 --- a/src/ui/activities/filetransfer/actions/copy.rs +++ b/src/ui/activities/filetransfer/actions/copy.rs @@ -28,39 +28,49 @@ // locals use super::{FileTransferActivity, FsEntry, LogLevel}; use std::path::PathBuf; +use tuirealm::{Payload, Value}; impl FileTransferActivity { /// ### action_local_copy /// /// Copy file on local pub(crate) fn action_local_copy(&mut self, input: String) { - if let Some(idx) = self.get_local_file_idx() { - let dest_path: PathBuf = PathBuf::from(input); - let entry: FsEntry = self.local().get(idx).unwrap().clone(); - match self.host.copy(&entry, dest_path.as_path()) { - Ok(_) => { - self.log( - LogLevel::Info, + match self.get_local_file_state() { + Some(Payload::One(Value::Usize(idx))) => { + let dest_path: PathBuf = PathBuf::from(input); + let entry: FsEntry = self.local().get(idx).unwrap().clone(); + match self.host.copy(&entry, dest_path.as_path()) { + Ok(_) => { + self.log( + LogLevel::Info, + format!( + "Copied \"{}\" to \"{}\"", + entry.get_abs_path().display(), + dest_path.display() + ), + ); + // Reload entries + let wrkdir: PathBuf = self.local().wrkdir.clone(); + self.local_scan(wrkdir.as_path()); + } + Err(err) => self.log_and_alert( + LogLevel::Error, format!( - "Copied \"{}\" to \"{}\"", + "Could not copy \"{}\" to \"{}\": {}", entry.get_abs_path().display(), - dest_path.display() + dest_path.display(), + err ), - ); - // Reload entries - let wrkdir: PathBuf = self.local().wrkdir.clone(); - self.local_scan(wrkdir.as_path()); - } - Err(err) => self.log_and_alert( - LogLevel::Error, - format!( - "Could not copy \"{}\" to \"{}\": {}", - entry.get_abs_path().display(), - dest_path.display(), - err ), - ), + } } + Some(Payload::Vec(_)) => { + self.log_and_alert( + LogLevel::Warn, + format!("Copy is not supported when using seleection"), + ); + } + _ => {} } } @@ -68,31 +78,40 @@ impl FileTransferActivity { /// /// Copy file on remote pub(crate) fn action_remote_copy(&mut self, input: String) { - if let Some(idx) = self.get_remote_file_idx() { - let dest_path: PathBuf = PathBuf::from(input); - let entry: FsEntry = self.remote().get(idx).unwrap().clone(); - match self.client.as_mut().copy(&entry, dest_path.as_path()) { - Ok(_) => { - self.log( - LogLevel::Info, + match self.get_local_file_state() { + Some(Payload::One(Value::Usize(idx))) => { + let dest_path: PathBuf = PathBuf::from(input); + let entry: FsEntry = self.remote().get(idx).unwrap().clone(); + match self.client.as_mut().copy(&entry, dest_path.as_path()) { + Ok(_) => { + self.log( + LogLevel::Info, + format!( + "Copied \"{}\" to \"{}\"", + entry.get_abs_path().display(), + dest_path.display() + ), + ); + self.reload_remote_dir(); + } + Err(err) => self.log_and_alert( + LogLevel::Error, format!( - "Copied \"{}\" to \"{}\"", + "Could not copy \"{}\" to \"{}\": {}", entry.get_abs_path().display(), - dest_path.display() + dest_path.display(), + err ), - ); - self.reload_remote_dir(); - } - Err(err) => self.log_and_alert( - LogLevel::Error, - format!( - "Could not copy \"{}\" to \"{}\": {}", - entry.get_abs_path().display(), - dest_path.display(), - err ), - ), + } } + Some(Payload::Vec(_)) => { + self.log_and_alert( + LogLevel::Warn, + format!("Copy is not supported when using seleection"), + ); + } + _ => {} } } } diff --git a/src/ui/activities/filetransfer/actions/delete.rs b/src/ui/activities/filetransfer/actions/delete.rs index 4628c19..d231d18 100644 --- a/src/ui/activities/filetransfer/actions/delete.rs +++ b/src/ui/activities/filetransfer/actions/delete.rs @@ -28,9 +28,21 @@ // locals use super::{FileTransferActivity, FsEntry, LogLevel}; use std::path::PathBuf; +use tuirealm::{Payload, Value}; impl FileTransferActivity { pub(crate) fn action_local_delete(&mut self) { + // Get selection + let selection: Vec = match self.get_local_file_state() { + Some(Payload::One(Value::Usize(idx))) => vec![idx], + Some(Payload::Vec(list)) => list.into_iter().map(|x| { + match x { + Value::Usize(x) => x, + _ => panic!("File selection contains non-usize value"), + } + }), + + } let entry: Option = self.get_local_file_entry().cloned(); if let Some(entry) = entry { let full_path: PathBuf = entry.get_abs_path(); @@ -57,7 +69,7 @@ impl FileTransferActivity { } pub(crate) fn action_remote_delete(&mut self) { - if let Some(idx) = self.get_remote_file_idx() { + if let Some(idx) = self.get_remote_file_state() { // Check if file entry exists let entry = self.remote().get(idx).cloned(); if let Some(entry) = entry { diff --git a/src/ui/activities/filetransfer/actions/mod.rs b/src/ui/activities/filetransfer/actions/mod.rs index 1dda2ce..23397ab 100644 --- a/src/ui/activities/filetransfer/actions/mod.rs +++ b/src/ui/activities/filetransfer/actions/mod.rs @@ -45,9 +45,9 @@ impl FileTransferActivity { /// /// Get local file entry pub(crate) fn get_local_file_entry(&self) -> Option<&FsEntry> { - match self.get_local_file_idx() { - None => None, - Some(idx) => self.local().get(idx), + match self.get_local_file_state() { + Some(Payload::One(Value::Usize(idx))) => self.local().get(idx), + _ => None, } } @@ -55,31 +55,25 @@ impl FileTransferActivity { /// /// Get remote file entry pub(crate) fn get_remote_file_entry(&self) -> Option<&FsEntry> { - match self.get_remote_file_idx() { - None => None, - Some(idx) => self.remote().get(idx), + match self.get_remote_file_state() { + Some(Payload::One(Value::Usize(idx))) => self.remote().get(idx), + _ => None, } } // -- private - /// ### get_local_file_idx + /// ### get_local_file_state /// /// Get index of selected file in the local tab - fn get_local_file_idx(&self) -> Option { - match self.view.get_state(super::COMPONENT_EXPLORER_LOCAL) { - Some(Payload::One(Value::Usize(idx))) => Some(idx), - _ => None, - } + fn get_local_file_state(&self) -> Option { + self.view.get_state(super::COMPONENT_EXPLORER_LOCAL) } - /// ### get_remote_file_idx + /// ### get_remote_file_state /// /// Get index of selected file in the remote file - fn get_remote_file_idx(&self) -> Option { - match self.view.get_state(super::COMPONENT_EXPLORER_REMOTE) { - Some(Payload::One(Value::Usize(idx))) => Some(idx), - _ => None, - } + fn get_remote_file_state(&self) -> Option { + self.view.get_state(super::COMPONENT_EXPLORER_REMOTE) } } diff --git a/src/ui/activities/filetransfer/actions/rename.rs b/src/ui/activities/filetransfer/actions/rename.rs index 47a483a..5cae46d 100644 --- a/src/ui/activities/filetransfer/actions/rename.rs +++ b/src/ui/activities/filetransfer/actions/rename.rs @@ -68,7 +68,7 @@ impl FileTransferActivity { } pub(crate) fn action_remote_rename(&mut self, input: String) { - if let Some(idx) = self.get_remote_file_idx() { + if let Some(idx) = self.get_remote_file_state() { let entry = self.remote().get(idx).cloned(); if let Some(entry) = entry { let dst_path: PathBuf = PathBuf::from(input); diff --git a/src/ui/activities/filetransfer/actions/save.rs b/src/ui/activities/filetransfer/actions/save.rs index 22912a9..4a34079 100644 --- a/src/ui/activities/filetransfer/actions/save.rs +++ b/src/ui/activities/filetransfer/actions/save.rs @@ -31,7 +31,7 @@ use std::path::PathBuf; impl FileTransferActivity { pub(crate) fn action_local_saveas(&mut self, input: String) { - if let Some(idx) = self.get_local_file_idx() { + if let Some(idx) = self.get_local_file_state() { // Get pwd let wrkdir: PathBuf = self.remote().wrkdir.clone(); if self.local().get(idx).is_some() { @@ -43,7 +43,7 @@ impl FileTransferActivity { } pub(crate) fn action_remote_saveas(&mut self, input: String) { - if let Some(idx) = self.get_remote_file_idx() { + if let Some(idx) = self.get_remote_file_state() { // Get pwd let wrkdir: PathBuf = self.local().wrkdir.clone(); if self.remote().get(idx).is_some() { diff --git a/src/ui/components/file_list.rs b/src/ui/components/file_list.rs index 7c98774..6849ddd 100644 --- a/src/ui/components/file_list.rs +++ b/src/ui/components/file_list.rs @@ -380,7 +380,7 @@ impl Component for FileList { Msg::None } KeyCode::Char('a') => match key.modifiers.intersects(KeyModifiers::CONTROL) { - // CTRL+C + // CTRL+A true => { // Select all self.states.select_all(); From 2b3acee97c7229a21c8aed376aca6ab35263577e Mon Sep 17 00:00:00 2001 From: veeso Date: Sat, 15 May 2021 17:03:44 +0200 Subject: [PATCH 3/7] Handle file selections in activity --- .../activities/filetransfer/actions/copy.rs | 149 ++++++++++-------- .../activities/filetransfer/actions/delete.rs | 128 ++++++++------- .../activities/filetransfer/actions/edit.rs | 51 +++--- .../activities/filetransfer/actions/exec.rs | 5 +- .../activities/filetransfer/actions/find.rs | 112 ++++++------- .../activities/filetransfer/actions/mkdir.rs | 4 +- src/ui/activities/filetransfer/actions/mod.rs | 118 +++++++++++--- .../filetransfer/actions/newfile.rs | 6 +- .../activities/filetransfer/actions/rename.rs | 146 ++++++++++------- .../activities/filetransfer/actions/save.rs | 68 ++++++-- src/ui/activities/filetransfer/session.rs | 8 +- src/ui/activities/filetransfer/update.rs | 70 +++----- 12 files changed, 496 insertions(+), 369 deletions(-) diff --git a/src/ui/activities/filetransfer/actions/copy.rs b/src/ui/activities/filetransfer/actions/copy.rs index c614b14..bf28e5a 100644 --- a/src/ui/activities/filetransfer/actions/copy.rs +++ b/src/ui/activities/filetransfer/actions/copy.rs @@ -26,51 +26,34 @@ * SOFTWARE. */ // locals -use super::{FileTransferActivity, FsEntry, LogLevel}; -use std::path::PathBuf; -use tuirealm::{Payload, Value}; +use super::{FileTransferActivity, FsEntry, LogLevel, SelectedEntry}; +use std::path::{Path, PathBuf}; impl FileTransferActivity { /// ### action_local_copy /// /// Copy file on local pub(crate) fn action_local_copy(&mut self, input: String) { - match self.get_local_file_state() { - Some(Payload::One(Value::Usize(idx))) => { + match self.get_local_selected_entries() { + SelectedEntry::One(entry) => { let dest_path: PathBuf = PathBuf::from(input); - let entry: FsEntry = self.local().get(idx).unwrap().clone(); - match self.host.copy(&entry, dest_path.as_path()) { - Ok(_) => { - self.log( - LogLevel::Info, - format!( - "Copied \"{}\" to \"{}\"", - entry.get_abs_path().display(), - dest_path.display() - ), - ); - // Reload entries - let wrkdir: PathBuf = self.local().wrkdir.clone(); - self.local_scan(wrkdir.as_path()); - } - Err(err) => self.log_and_alert( - LogLevel::Error, - format!( - "Could not copy \"{}\" to \"{}\": {}", - entry.get_abs_path().display(), - dest_path.display(), - err - ), - ), + self.local_copy_file(&entry, dest_path.as_path()); + // Reload entries + self.reload_local_dir(); + } + SelectedEntry::Multi(entries) => { + // Try to copy each file to Input/{FILE_NAME} + let base_path: PathBuf = PathBuf::from(input); + // Iter files + for entry in entries.iter() { + let mut dest_path: PathBuf = base_path.clone(); + dest_path.push(entry.get_name()); + self.local_copy_file(entry, dest_path.as_path()); } + // Reload entries + self.reload_local_dir(); } - Some(Payload::Vec(_)) => { - self.log_and_alert( - LogLevel::Warn, - format!("Copy is not supported when using seleection"), - ); - } - _ => {} + SelectedEntry::None => {} } } @@ -78,40 +61,74 @@ impl FileTransferActivity { /// /// Copy file on remote pub(crate) fn action_remote_copy(&mut self, input: String) { - match self.get_local_file_state() { - Some(Payload::One(Value::Usize(idx))) => { + match self.get_remote_selected_entries() { + SelectedEntry::One(entry) => { let dest_path: PathBuf = PathBuf::from(input); - let entry: FsEntry = self.remote().get(idx).unwrap().clone(); - match self.client.as_mut().copy(&entry, dest_path.as_path()) { - Ok(_) => { - self.log( - LogLevel::Info, - format!( - "Copied \"{}\" to \"{}\"", - entry.get_abs_path().display(), - dest_path.display() - ), - ); - self.reload_remote_dir(); - } - Err(err) => self.log_and_alert( - LogLevel::Error, - format!( - "Could not copy \"{}\" to \"{}\": {}", - entry.get_abs_path().display(), - dest_path.display(), - err - ), - ), - } + self.remote_copy_file(&entry, dest_path.as_path()); + // Reload entries + self.reload_remote_dir(); } - Some(Payload::Vec(_)) => { - self.log_and_alert( - LogLevel::Warn, - format!("Copy is not supported when using seleection"), + SelectedEntry::Multi(entries) => { + // Try to copy each file to Input/{FILE_NAME} + let base_path: PathBuf = PathBuf::from(input); + // Iter files + for entry in entries.iter() { + let mut dest_path: PathBuf = base_path.clone(); + dest_path.push(entry.get_name()); + self.remote_copy_file(entry, dest_path.as_path()); + } + // Reload entries + self.reload_remote_dir(); + } + SelectedEntry::None => {} + } + } + + fn local_copy_file(&mut self, entry: &FsEntry, dest: &Path) { + match self.host.copy(entry, dest) { + Ok(_) => { + self.log( + LogLevel::Info, + format!( + "Copied \"{}\" to \"{}\"", + entry.get_abs_path().display(), + dest.display() + ), ); } - _ => {} + Err(err) => self.log_and_alert( + LogLevel::Error, + format!( + "Could not copy \"{}\" to \"{}\": {}", + entry.get_abs_path().display(), + dest.display(), + err + ), + ), + } + } + + fn remote_copy_file(&mut self, entry: &FsEntry, dest: &Path) { + match self.client.as_mut().copy(entry, dest) { + Ok(_) => { + self.log( + LogLevel::Info, + format!( + "Copied \"{}\" to \"{}\"", + entry.get_abs_path().display(), + dest.display() + ), + ); + } + Err(err) => self.log_and_alert( + LogLevel::Error, + format!( + "Could not copy \"{}\" to \"{}\": {}", + entry.get_abs_path().display(), + dest.display(), + err + ), + ), } } } diff --git a/src/ui/activities/filetransfer/actions/delete.rs b/src/ui/activities/filetransfer/actions/delete.rs index d231d18..b497d06 100644 --- a/src/ui/activities/filetransfer/actions/delete.rs +++ b/src/ui/activities/filetransfer/actions/delete.rs @@ -26,70 +26,90 @@ * SOFTWARE. */ // locals -use super::{FileTransferActivity, FsEntry, LogLevel}; -use std::path::PathBuf; -use tuirealm::{Payload, Value}; +use super::{FileTransferActivity, FsEntry, LogLevel, SelectedEntry}; impl FileTransferActivity { pub(crate) fn action_local_delete(&mut self) { - // Get selection - let selection: Vec = match self.get_local_file_state() { - Some(Payload::One(Value::Usize(idx))) => vec![idx], - Some(Payload::Vec(list)) => list.into_iter().map(|x| { - match x { - Value::Usize(x) => x, - _ => panic!("File selection contains non-usize value"), - } - }), - - } - let entry: Option = self.get_local_file_entry().cloned(); - if let Some(entry) = entry { - let full_path: PathBuf = entry.get_abs_path(); - // Delete file or directory and report status as popup - match self.host.remove(&entry) { - Ok(_) => { - // Reload files - let p: PathBuf = self.local().wrkdir.clone(); - self.local_scan(p.as_path()); - // Log - self.log( - LogLevel::Info, - format!("Removed file \"{}\"", full_path.display()), - ); - } - Err(err) => { - self.log_and_alert( - LogLevel::Error, - format!("Could not delete file \"{}\": {}", full_path.display(), err), - ); - } + match self.get_local_selected_entries() { + SelectedEntry::One(entry) => { + // Delete file + self.local_remove_file(&entry); + // Reload + self.reload_local_dir(); } + SelectedEntry::Multi(entries) => { + // Iter files + for entry in entries.iter() { + // Delete file + self.local_remove_file(entry); + } + // Reload entries + self.reload_local_dir(); + } + SelectedEntry::None => {} } } pub(crate) fn action_remote_delete(&mut self) { - if let Some(idx) = self.get_remote_file_state() { - // Check if file entry exists - let entry = self.remote().get(idx).cloned(); - if let Some(entry) = entry { - let full_path: PathBuf = entry.get_abs_path(); + match self.get_remote_selected_entries() { + SelectedEntry::One(entry) => { // Delete file - match self.client.remove(&entry) { - Ok(_) => { - self.reload_remote_dir(); - self.log( - LogLevel::Info, - format!("Removed file \"{}\"", full_path.display()), - ); - } - Err(err) => { - self.log_and_alert( - LogLevel::Error, - format!("Could not delete file \"{}\": {}", full_path.display(), err), - ); - } + self.remote_remove_file(&entry); + // Reload + self.reload_remote_dir(); + } + SelectedEntry::Multi(entries) => { + // Iter files + for entry in entries.iter() { + // Delete file + self.remote_remove_file(entry); } + // Reload entries + self.reload_remote_dir(); + } + SelectedEntry::None => {} + } + } + + pub(crate) fn local_remove_file(&mut self, entry: &FsEntry) { + match self.host.remove(&entry) { + Ok(_) => { + // Log + self.log( + LogLevel::Info, + format!("Removed file \"{}\"", entry.get_abs_path().display()), + ); + } + Err(err) => { + self.log_and_alert( + LogLevel::Error, + format!( + "Could not delete file \"{}\": {}", + entry.get_abs_path().display(), + err + ), + ); + } + } + } + + pub(crate) fn remote_remove_file(&mut self, entry: &FsEntry) { + match self.client.remove(&entry) { + Ok(_) => { + self.log( + LogLevel::Info, + format!("Removed file \"{}\"", entry.get_abs_path().display()), + ); + } + Err(err) => { + self.log_and_alert( + LogLevel::Error, + format!( + "Could not delete file \"{}\": {}", + entry.get_abs_path().display(), + err + ), + ); } } } diff --git a/src/ui/activities/filetransfer/actions/edit.rs b/src/ui/activities/filetransfer/actions/edit.rs index 3cb1543..6607376 100644 --- a/src/ui/activities/filetransfer/actions/edit.rs +++ b/src/ui/activities/filetransfer/actions/edit.rs @@ -26,51 +26,54 @@ * SOFTWARE. */ // locals -use super::{FileTransferActivity, FsEntry, LogLevel}; -use std::path::PathBuf; +use super::{FileTransferActivity, FsEntry, LogLevel, SelectedEntry}; impl FileTransferActivity { pub(crate) fn action_edit_local_file(&mut self) { - if self.get_local_file_entry().is_some() { - let fsentry: FsEntry = self.get_local_file_entry().unwrap().clone(); + let entries: Vec = match self.get_local_selected_entries() { + SelectedEntry::One(entry) => vec![entry], + SelectedEntry::Multi(entries) => entries, + SelectedEntry::None => vec![], + }; + // Edit all entries + for entry in entries.iter() { // Check if file - if fsentry.is_file() { + if entry.is_file() { self.log( LogLevel::Info, - format!("Opening file \"{}\"...", fsentry.get_abs_path().display()), + format!("Opening file \"{}\"...", entry.get_abs_path().display()), ); // Edit file - match self.edit_local_file(fsentry.get_abs_path().as_path()) { - Ok(_) => { - // Reload directory - let pwd: PathBuf = self.local().wrkdir.clone(); - self.local_scan(pwd.as_path()); - } - Err(err) => self.log_and_alert(LogLevel::Error, err), + if let Err(err) = self.edit_local_file(entry.get_abs_path().as_path()) { + self.log_and_alert(LogLevel::Error, err); } } } + // Reload entries + self.reload_local_dir(); } pub(crate) fn action_edit_remote_file(&mut self) { - if self.get_remote_file_entry().is_some() { - let fsentry: FsEntry = self.get_remote_file_entry().unwrap().clone(); + let entries: Vec = match self.get_remote_selected_entries() { + SelectedEntry::One(entry) => vec![entry], + SelectedEntry::Multi(entries) => entries, + SelectedEntry::None => vec![], + }; + // Edit all entries + for entry in entries.iter() { // Check if file - if let FsEntry::File(file) = fsentry.clone() { + if let FsEntry::File(file) = entry { self.log( LogLevel::Info, - format!("Opening file \"{}\"...", fsentry.get_abs_path().display()), + format!("Opening file \"{}\"...", entry.get_abs_path().display()), ); // Edit file - match self.edit_remote_file(&file) { - Ok(_) => { - // Reload directory - let pwd: PathBuf = self.remote().wrkdir.clone(); - self.remote_scan(pwd.as_path()); - } - Err(err) => self.log_and_alert(LogLevel::Error, err), + if let Err(err) = self.edit_remote_file(&file) { + self.log_and_alert(LogLevel::Error, err); } } } + // Reload entries + self.reload_remote_dir(); } } diff --git a/src/ui/activities/filetransfer/actions/exec.rs b/src/ui/activities/filetransfer/actions/exec.rs index 73ab3cb..ba897c2 100644 --- a/src/ui/activities/filetransfer/actions/exec.rs +++ b/src/ui/activities/filetransfer/actions/exec.rs @@ -27,7 +27,6 @@ */ // locals use super::{FileTransferActivity, LogLevel}; -use std::path::PathBuf; impl FileTransferActivity { pub(crate) fn action_local_exec(&mut self, input: String) { @@ -35,8 +34,8 @@ impl FileTransferActivity { Ok(output) => { // Reload files self.log(LogLevel::Info, format!("\"{}\": {}", input, output)); - let wrkdir: PathBuf = self.local().wrkdir.clone(); - self.local_scan(wrkdir.as_path()); + // Reload entries + self.reload_local_dir(); } Err(err) => { // Report err diff --git a/src/ui/activities/filetransfer/actions/find.rs b/src/ui/activities/filetransfer/actions/find.rs index 6268bfb..291c4f6 100644 --- a/src/ui/activities/filetransfer/actions/find.rs +++ b/src/ui/activities/filetransfer/actions/find.rs @@ -27,7 +27,7 @@ */ // locals use super::super::browser::FileExplorerTab; -use super::{FileTransferActivity, FsEntry, LogLevel}; +use super::{FileTransferActivity, FsEntry, SelectedEntry}; use std::path::PathBuf; @@ -46,12 +46,12 @@ impl FileTransferActivity { } } - pub(crate) fn action_find_changedir(&mut self, idx: usize) { + pub(crate) fn action_find_changedir(&mut self) { // Match entry - if let Some(entry) = self.found().as_ref().unwrap().get(idx) { + if let SelectedEntry::One(entry) = self.get_found_selected_entries() { // Get path: if a directory, use directory path; if it is a File, get parent path let path: PathBuf = match entry { - FsEntry::Directory(dir) => dir.abs_path.clone(), + FsEntry::Directory(dir) => dir.abs_path, FsEntry::File(file) => match file.abs_path.parent() { None => PathBuf::from("."), Some(p) => p.to_path_buf(), @@ -69,78 +69,60 @@ impl FileTransferActivity { } } - pub(crate) fn action_find_transfer(&mut self, idx: usize, name: Option) { - let entry: Option = self.found().as_ref().unwrap().get(idx).cloned(); - if let Some(entry) = entry { - // Download file - match self.browser.tab() { + pub(crate) fn action_find_transfer(&mut self, save_as: Option) { + let wrkdir: PathBuf = match self.browser.tab() { + FileExplorerTab::FindLocal | FileExplorerTab::Local => self.remote().wrkdir.clone(), + FileExplorerTab::FindRemote | FileExplorerTab::Remote => self.local().wrkdir.clone(), + }; + match self.get_found_selected_entries() { + SelectedEntry::One(entry) => match self.browser.tab() { FileExplorerTab::FindLocal | FileExplorerTab::Local => { - let wrkdir: PathBuf = self.remote().wrkdir.clone(); - self.filetransfer_send(&entry.get_realfile(), wrkdir.as_path(), name); + self.filetransfer_send(&entry.get_realfile(), wrkdir.as_path(), save_as); } FileExplorerTab::FindRemote | FileExplorerTab::Remote => { - let wrkdir: PathBuf = self.local().wrkdir.clone(); - self.filetransfer_recv(&entry.get_realfile(), wrkdir.as_path(), name); + self.filetransfer_recv(&entry.get_realfile(), wrkdir.as_path(), save_as); + } + }, + SelectedEntry::Multi(entries) => { + // In case of selection: save multiple files in wrkdir/input + let mut dest_path: PathBuf = wrkdir; + if let Some(save_as) = save_as { + dest_path.push(save_as); + } + // Iter files + for entry in entries.iter() { + self.filetransfer_recv(&entry.get_realfile(), dest_path.as_path(), None); } } + SelectedEntry::None => {} } } - pub(crate) fn action_find_delete(&mut self, idx: usize) { - let entry: Option = self.found().as_ref().unwrap().get(idx).cloned(); - if let Some(entry) = entry { - // Download file - match self.browser.tab() { - FileExplorerTab::FindLocal | FileExplorerTab::Local => { - let full_path: PathBuf = entry.get_abs_path(); - // Delete file or directory and report status as popup - match self.host.remove(&entry) { - Ok(_) => { - // Reload files - let p: PathBuf = self.local().wrkdir.clone(); - self.local_scan(p.as_path()); - // Log - self.log( - LogLevel::Info, - format!("Removed file \"{}\"", full_path.display()), - ); - } - Err(err) => { - self.log_and_alert( - LogLevel::Error, - format!( - "Could not delete file \"{}\": {}", - full_path.display(), - err - ), - ); - } - } - } - FileExplorerTab::FindRemote | FileExplorerTab::Remote => { - let full_path: PathBuf = entry.get_abs_path(); + pub(crate) fn action_find_delete(&mut self) { + match self.get_found_selected_entries() { + SelectedEntry::One(entry) => { + // Delete file + self.remove_found_file(&entry); + } + SelectedEntry::Multi(entries) => { + // Iter files + for entry in entries.iter() { // Delete file - match self.client.remove(&entry) { - Ok(_) => { - self.reload_remote_dir(); - self.log( - LogLevel::Info, - format!("Removed file \"{}\"", full_path.display()), - ); - } - Err(err) => { - self.log_and_alert( - LogLevel::Error, - format!( - "Could not delete file \"{}\": {}", - full_path.display(), - err - ), - ); - } - } + self.remove_found_file(entry); } } + SelectedEntry::None => {} + } + } + + fn remove_found_file(&mut self, entry: &FsEntry) { + match self.browser.tab() { + FileExplorerTab::FindLocal | FileExplorerTab::Local => { + self.local_remove_file(entry); + } + FileExplorerTab::FindRemote | FileExplorerTab::Remote => { + self.remote_remove_file(entry); + } } } } diff --git a/src/ui/activities/filetransfer/actions/mkdir.rs b/src/ui/activities/filetransfer/actions/mkdir.rs index 3f78823..3664920 100644 --- a/src/ui/activities/filetransfer/actions/mkdir.rs +++ b/src/ui/activities/filetransfer/actions/mkdir.rs @@ -35,8 +35,8 @@ impl FileTransferActivity { Ok(_) => { // Reload files self.log(LogLevel::Info, format!("Created directory \"{}\"", input)); - let wrkdir: PathBuf = self.local().wrkdir.clone(); - self.local_scan(wrkdir.as_path()); + // Reload entries + self.reload_local_dir(); } Err(err) => { // Report err diff --git a/src/ui/activities/filetransfer/actions/mod.rs b/src/ui/activities/filetransfer/actions/mod.rs index 23397ab..ef4a39d 100644 --- a/src/ui/activities/filetransfer/actions/mod.rs +++ b/src/ui/activities/filetransfer/actions/mod.rs @@ -40,40 +40,114 @@ pub(crate) mod newfile; pub(crate) mod rename; pub(crate) mod save; +pub(crate) enum SelectedEntry { + One(FsEntry), + Multi(Vec), + None, +} + +enum SelectedEntryIndex { + One(usize), + Multi(Vec), + None, +} + +impl From> for SelectedEntry { + fn from(opt: Option<&FsEntry>) -> Self { + match opt { + Some(e) => SelectedEntry::One(e.clone()), + None => SelectedEntry::None, + } + } +} + +impl From> for SelectedEntry { + fn from(files: Vec<&FsEntry>) -> Self { + SelectedEntry::Multi(files.into_iter().cloned().collect()) + } +} + impl FileTransferActivity { - /// ### get_local_file_entry + /// ### get_local_selected_entries /// /// Get local file entry - pub(crate) fn get_local_file_entry(&self) -> Option<&FsEntry> { - match self.get_local_file_state() { - Some(Payload::One(Value::Usize(idx))) => self.local().get(idx), - _ => None, + pub(crate) fn get_local_selected_entries(&self) -> SelectedEntry { + match self.get_selected_index(super::COMPONENT_EXPLORER_LOCAL) { + SelectedEntryIndex::One(idx) => SelectedEntry::from(self.local().get(idx)), + SelectedEntryIndex::Multi(files) => { + let files: Vec<&FsEntry> = files + .iter() + .map(|x| self.local().get(*x)) // Usize to Option + .filter(|x| x.is_some()) // Get only some values + .map(|x| x.unwrap()) // Option to FsEntry + .collect(); + SelectedEntry::from(files) + } + SelectedEntryIndex::None => SelectedEntry::None, } } - /// ### get_remote_file_entry + /// ### get_remote_selected_entries /// /// Get remote file entry - pub(crate) fn get_remote_file_entry(&self) -> Option<&FsEntry> { - match self.get_remote_file_state() { - Some(Payload::One(Value::Usize(idx))) => self.remote().get(idx), - _ => None, + pub(crate) fn get_remote_selected_entries(&self) -> SelectedEntry { + match self.get_selected_index(super::COMPONENT_EXPLORER_REMOTE) { + SelectedEntryIndex::One(idx) => SelectedEntry::from(self.remote().get(idx)), + SelectedEntryIndex::Multi(files) => { + let files: Vec<&FsEntry> = files + .iter() + .map(|x| self.remote().get(*x)) // Usize to Option + .filter(|x| x.is_some()) // Get only some values + .map(|x| x.unwrap()) // Option to FsEntry + .collect(); + SelectedEntry::from(files) + } + SelectedEntryIndex::None => SelectedEntry::None, + } + } + + /// ### get_remote_selected_entries + /// + /// Get remote file entry + pub(crate) fn get_found_selected_entries(&self) -> SelectedEntry { + match self.get_selected_index(super::COMPONENT_EXPLORER_FIND) { + SelectedEntryIndex::One(idx) => { + SelectedEntry::from(self.found().as_ref().unwrap().get(idx)) + } + SelectedEntryIndex::Multi(files) => { + let files: Vec<&FsEntry> = files + .iter() + .map(|x| self.found().as_ref().unwrap().get(*x)) // Usize to Option + .filter(|x| x.is_some()) // Get only some values + .map(|x| x.unwrap()) // Option to FsEntry + .collect(); + SelectedEntry::from(files) + } + SelectedEntryIndex::None => SelectedEntry::None, } } // -- private - /// ### get_local_file_state - /// - /// Get index of selected file in the local tab - fn get_local_file_state(&self) -> Option { - self.view.get_state(super::COMPONENT_EXPLORER_LOCAL) - } - - /// ### get_remote_file_state - /// - /// Get index of selected file in the remote file - fn get_remote_file_state(&self) -> Option { - self.view.get_state(super::COMPONENT_EXPLORER_REMOTE) + fn get_selected_index(&self, component: &str) -> SelectedEntryIndex { + eprintln!( + "INDEX FOR {}: {:?}", + component, + self.view.get_state(component) + ); + match self.view.get_state(component) { + Some(Payload::One(Value::Usize(idx))) => SelectedEntryIndex::One(idx), + Some(Payload::Vec(files)) => { + let list: Vec = files + .iter() + .map(|x| match x { + Value::Usize(v) => *v, + _ => 0, + }) + .collect(); + SelectedEntryIndex::Multi(list) + } + _ => SelectedEntryIndex::None, + } } } diff --git a/src/ui/activities/filetransfer/actions/newfile.rs b/src/ui/activities/filetransfer/actions/newfile.rs index f09baa8..ac65f79 100644 --- a/src/ui/activities/filetransfer/actions/newfile.rs +++ b/src/ui/activities/filetransfer/actions/newfile.rs @@ -59,8 +59,7 @@ impl FileTransferActivity { ); } // Reload files - let path: PathBuf = self.local().wrkdir.clone(); - self.local_scan(path.as_path()); + self.reload_local_dir(); } pub(crate) fn action_remote_newfile(&mut self, input: String) { @@ -119,8 +118,7 @@ impl FileTransferActivity { ); } // Reload files - let path: PathBuf = self.remote().wrkdir.clone(); - self.remote_scan(path.as_path()); + self.reload_remote_dir(); } } } diff --git a/src/ui/activities/filetransfer/actions/rename.rs b/src/ui/activities/filetransfer/actions/rename.rs index 5cae46d..9e18b39 100644 --- a/src/ui/activities/filetransfer/actions/rename.rs +++ b/src/ui/activities/filetransfer/actions/rename.rs @@ -26,77 +26,103 @@ * SOFTWARE. */ // locals -use super::{FileTransferActivity, FsEntry, LogLevel}; -use std::path::PathBuf; +use super::{FileTransferActivity, FsEntry, LogLevel, SelectedEntry}; +use std::path::{Path, PathBuf}; impl FileTransferActivity { pub(crate) fn action_local_rename(&mut self, input: String) { - let entry: Option = self.get_local_file_entry().cloned(); - if let Some(entry) = entry { - let mut dst_path: PathBuf = PathBuf::from(input); - // Check if path is relative - if dst_path.as_path().is_relative() { - let mut wrkdir: PathBuf = self.local().wrkdir.clone(); - wrkdir.push(dst_path); - dst_path = wrkdir; + match self.get_local_selected_entries() { + SelectedEntry::One(entry) => { + let dest_path: PathBuf = PathBuf::from(input); + self.local_rename_file(&entry, dest_path.as_path()); + // Reload entries + self.reload_local_dir(); } - let full_path: PathBuf = entry.get_abs_path(); - // Rename file or directory and report status as popup - match self.host.rename(&entry, dst_path.as_path()) { - Ok(_) => { - // Reload files - let path: PathBuf = self.local().wrkdir.clone(); - self.local_scan(path.as_path()); - // Log - self.log( - LogLevel::Info, - format!( - "Renamed file \"{}\" to \"{}\"", - full_path.display(), - dst_path.display() - ), - ); - } - Err(err) => { - self.log_and_alert( - LogLevel::Error, - format!("Could not rename file \"{}\": {}", full_path.display(), err), - ); + SelectedEntry::Multi(entries) => { + // Try to copy each file to Input/{FILE_NAME} + let base_path: PathBuf = PathBuf::from(input); + // Iter files + for entry in entries.iter() { + let mut dest_path: PathBuf = base_path.clone(); + dest_path.push(entry.get_name()); + self.local_rename_file(entry, dest_path.as_path()); } + // Reload entries + self.reload_local_dir(); } + SelectedEntry::None => {} } } pub(crate) fn action_remote_rename(&mut self, input: String) { - if let Some(idx) = self.get_remote_file_state() { - let entry = self.remote().get(idx).cloned(); - if let Some(entry) = entry { - let dst_path: PathBuf = PathBuf::from(input); - let full_path: PathBuf = entry.get_abs_path(); - // Rename file or directory and report status as popup - match self.client.as_mut().rename(&entry, dst_path.as_path()) { - Ok(_) => { - // Reload files - let path: PathBuf = self.remote().wrkdir.clone(); - self.remote_scan(path.as_path()); - // Log - self.log( - LogLevel::Info, - format!( - "Renamed file \"{}\" to \"{}\"", - full_path.display(), - dst_path.display() - ), - ); - } - Err(err) => { - self.log_and_alert( - LogLevel::Error, - format!("Could not rename file \"{}\": {}", full_path.display(), err), - ); - } - } + match self.get_remote_selected_entries() { + SelectedEntry::One(entry) => { + let dest_path: PathBuf = PathBuf::from(input); + self.remote_rename_file(&entry, dest_path.as_path()); + // Reload entries + self.reload_remote_dir(); } + SelectedEntry::Multi(entries) => { + // Try to copy each file to Input/{FILE_NAME} + let base_path: PathBuf = PathBuf::from(input); + // Iter files + for entry in entries.iter() { + let mut dest_path: PathBuf = base_path.clone(); + dest_path.push(entry.get_name()); + self.remote_rename_file(entry, dest_path.as_path()); + } + // Reload entries + self.reload_remote_dir(); + } + SelectedEntry::None => {} + } + } + + fn local_rename_file(&mut self, entry: &FsEntry, dest: &Path) { + match self.host.rename(entry, dest) { + Ok(_) => { + self.log( + LogLevel::Info, + format!( + "Moved \"{}\" to \"{}\"", + entry.get_abs_path().display(), + dest.display() + ), + ); + } + Err(err) => self.log_and_alert( + LogLevel::Error, + format!( + "Could not move \"{}\" to \"{}\": {}", + entry.get_abs_path().display(), + dest.display(), + err + ), + ), + } + } + + fn remote_rename_file(&mut self, entry: &FsEntry, dest: &Path) { + match self.client.as_mut().rename(entry, dest) { + Ok(_) => { + self.log( + LogLevel::Info, + format!( + "Moved \"{}\" to \"{}\"", + entry.get_abs_path().display(), + dest.display() + ), + ); + } + Err(err) => self.log_and_alert( + LogLevel::Error, + format!( + "Could not move \"{}\" to \"{}\": {}", + entry.get_abs_path().display(), + dest.display(), + err + ), + ), } } } diff --git a/src/ui/activities/filetransfer/actions/save.rs b/src/ui/activities/filetransfer/actions/save.rs index 4a34079..3956e17 100644 --- a/src/ui/activities/filetransfer/actions/save.rs +++ b/src/ui/activities/filetransfer/actions/save.rs @@ -26,31 +26,65 @@ * SOFTWARE. */ // locals -use super::{FileTransferActivity, FsEntry}; +use super::{FileTransferActivity, SelectedEntry}; use std::path::PathBuf; impl FileTransferActivity { pub(crate) fn action_local_saveas(&mut self, input: String) { - if let Some(idx) = self.get_local_file_state() { - // Get pwd - let wrkdir: PathBuf = self.remote().wrkdir.clone(); - if self.local().get(idx).is_some() { - let file: FsEntry = self.local().get(idx).unwrap().clone(); - // Call upload; pass realfile, keep link name - self.filetransfer_send(&file.get_realfile(), wrkdir.as_path(), Some(input)); - } - } + self.action_local_send_file(Some(input)); } pub(crate) fn action_remote_saveas(&mut self, input: String) { - if let Some(idx) = self.get_remote_file_state() { - // Get pwd - let wrkdir: PathBuf = self.local().wrkdir.clone(); - if self.remote().get(idx).is_some() { - let file: FsEntry = self.remote().get(idx).unwrap().clone(); - // Call upload; pass realfile, keep link name - self.filetransfer_recv(&file.get_realfile(), wrkdir.as_path(), Some(input)); + self.action_remote_recv_file(Some(input)); + } + + pub(crate) fn action_local_send(&mut self) { + self.action_local_send_file(None); + } + + pub(crate) fn action_remote_recv(&mut self) { + self.action_remote_recv_file(None); + } + + fn action_local_send_file(&mut self, save_as: Option) { + let wrkdir: PathBuf = self.remote().wrkdir.clone(); + match self.get_local_selected_entries() { + SelectedEntry::One(entry) => { + self.filetransfer_send(&entry.get_realfile(), wrkdir.as_path(), save_as); } + SelectedEntry::Multi(entries) => { + // In case of selection: save multiple files in wrkdir/input + let mut dest_path: PathBuf = wrkdir; + if let Some(save_as) = save_as { + dest_path.push(save_as); + } + // Iter files + for entry in entries.iter() { + self.filetransfer_send(&entry.get_realfile(), dest_path.as_path(), None); + } + } + SelectedEntry::None => {} + } + } + + fn action_remote_recv_file(&mut self, save_as: Option) { + let wrkdir: PathBuf = self.local().wrkdir.clone(); + match self.get_remote_selected_entries() { + SelectedEntry::One(entry) => { + self.filetransfer_recv(&entry.get_realfile(), wrkdir.as_path(), save_as); + } + SelectedEntry::Multi(entries) => { + // In case of selection: save multiple files in wrkdir/input + let mut dest_path: PathBuf = wrkdir; + if let Some(save_as) = save_as { + dest_path.push(save_as); + } + // Iter files + for entry in entries.iter() { + self.filetransfer_recv(&entry.get_realfile(), dest_path.as_path(), None); + } + } + SelectedEntry::None => {} } } } diff --git a/src/ui/activities/filetransfer/session.rs b/src/ui/activities/filetransfer/session.rs index d1c66d1..481a572 100644 --- a/src/ui/activities/filetransfer/session.rs +++ b/src/ui/activities/filetransfer/session.rs @@ -145,6 +145,11 @@ impl FileTransferActivity { } } + pub(super) fn reload_local_dir(&mut self) { + let wrkdir: PathBuf = self.local().wrkdir.clone(); + self.local_scan(wrkdir.as_path()); + } + /// ### filetransfer_send /// /// Send fs entry to remote. @@ -257,8 +262,7 @@ impl FileTransferActivity { } } // Scan dir on remote - let path: PathBuf = self.remote().wrkdir.clone(); - self.remote_scan(path.as_path()); + self.reload_remote_dir(); // If aborted; show popup if self.transfer.aborted { // Log abort diff --git a/src/ui/activities/filetransfer/update.rs b/src/ui/activities/filetransfer/update.rs index d7bd6d0..26e4316 100644 --- a/src/ui/activities/filetransfer/update.rs +++ b/src/ui/activities/filetransfer/update.rs @@ -29,10 +29,10 @@ extern crate bytesize; // locals use super::{ - browser::FileExplorerTab, FileTransferActivity, LogLevel, COMPONENT_EXPLORER_FIND, - COMPONENT_EXPLORER_LOCAL, COMPONENT_EXPLORER_REMOTE, COMPONENT_INPUT_COPY, - COMPONENT_INPUT_EXEC, COMPONENT_INPUT_FIND, COMPONENT_INPUT_GOTO, COMPONENT_INPUT_MKDIR, - COMPONENT_INPUT_NEWFILE, COMPONENT_INPUT_RENAME, COMPONENT_INPUT_SAVEAS, + actions::SelectedEntry, browser::FileExplorerTab, FileTransferActivity, LogLevel, + COMPONENT_EXPLORER_FIND, COMPONENT_EXPLORER_LOCAL, COMPONENT_EXPLORER_REMOTE, + COMPONENT_INPUT_COPY, COMPONENT_INPUT_EXEC, COMPONENT_INPUT_FIND, COMPONENT_INPUT_GOTO, + COMPONENT_INPUT_MKDIR, COMPONENT_INPUT_NEWFILE, COMPONENT_INPUT_RENAME, COMPONENT_INPUT_SAVEAS, COMPONENT_LIST_FILEINFO, COMPONENT_LOG_BOX, COMPONENT_PROGRESS_BAR, COMPONENT_RADIO_DELETE, COMPONENT_RADIO_DISCONNECT, COMPONENT_RADIO_QUIT, COMPONENT_RADIO_SORTING, COMPONENT_TEXT_ERROR, COMPONENT_TEXT_FATAL, COMPONENT_TEXT_HELP, @@ -101,18 +101,8 @@ impl FileTransferActivity { } } (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_SPACE) => { - // Get pwd - let wrkdir: PathBuf = self.remote().wrkdir.clone(); - // Get file and clone (due to mutable / immutable stuff...) - if self.get_local_file_entry().is_some() { - let file: FsEntry = self.get_local_file_entry().unwrap().clone(); - let name: String = file.get_name().to_string(); - // Call upload; pass realfile, keep link name - self.filetransfer_send(&file.get_realfile(), wrkdir.as_path(), Some(name)); - self.update_remote_filelist() - } else { - None - } + self.action_local_send(); + self.update_remote_filelist() } (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_A) => { // Toggle hidden files @@ -121,8 +111,7 @@ impl FileTransferActivity { self.update_local_filelist() } (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_I) => { - let file: Option = self.get_local_file_entry().cloned(); - if let Some(file) = file { + if let SelectedEntry::One(file) = self.get_local_selected_entries() { self.mount_file_info(&file); } None @@ -175,17 +164,8 @@ impl FileTransferActivity { } } (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_SPACE) => { - // Get file and clone (due to mutable / immutable stuff...) - if self.get_remote_file_entry().is_some() { - let file: FsEntry = self.get_remote_file_entry().unwrap().clone(); - let name: String = file.get_name().to_string(); - // Call upload; pass realfile, keep link name - let wrkdir: PathBuf = self.local().wrkdir.clone(); - self.filetransfer_recv(&file.get_realfile(), wrkdir.as_path(), Some(name)); - self.update_local_filelist() - } else { - None - } + self.action_remote_recv(); + self.update_local_filelist() } (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_BACKSPACE) => { // Go to previous directory @@ -204,8 +184,7 @@ impl FileTransferActivity { self.update_remote_filelist() } (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_I) => { - let file: Option = self.get_remote_file_entry().cloned(); - if let Some(file) = file { + if let SelectedEntry::One(file) = self.get_remote_selected_entries() { self.mount_file_info(&file); } None @@ -324,9 +303,9 @@ impl FileTransferActivity { self.finalize_find(); None } - (COMPONENT_EXPLORER_FIND, Msg::OnSubmit(Payload::One(Value::Usize(idx)))) => { + (COMPONENT_EXPLORER_FIND, Msg::OnSubmit(_)) => { // Find changedir - self.action_find_changedir(*idx); + self.action_find_changedir(); // Umount find self.umount_find(); // Finalize find @@ -340,17 +319,12 @@ impl FileTransferActivity { } (COMPONENT_EXPLORER_FIND, &MSG_KEY_SPACE) => { // Get entry - match self.view.get_state(COMPONENT_EXPLORER_FIND) { - Some(Payload::One(Value::Usize(idx))) => { - self.action_find_transfer(idx, None); - // Reload files - match self.browser.tab() { - // NOTE: swapped by purpose - FileExplorerTab::FindLocal => self.update_remote_filelist(), - FileExplorerTab::FindRemote => self.update_local_filelist(), - _ => None, - } - } + self.action_find_transfer(None); + // Reload files + match self.browser.tab() { + // NOTE: swapped by purpose + FileExplorerTab::FindLocal => self.update_remote_filelist(), + FileExplorerTab::FindRemote => self.update_local_filelist(), _ => None, } } @@ -540,11 +514,7 @@ impl FileTransferActivity { FileExplorerTab::Remote => self.action_remote_saveas(input.to_string()), FileExplorerTab::FindLocal | FileExplorerTab::FindRemote => { // Get entry - if let Some(Payload::One(Value::Usize(idx))) = - self.view.get_state(COMPONENT_EXPLORER_FIND) - { - self.action_find_transfer(idx, Some(input.to_string())); - } + self.action_find_transfer(Some(input.to_string())); } } self.umount_saveas(); @@ -579,7 +549,7 @@ impl FileTransferActivity { if let Some(Payload::One(Value::Usize(idx))) = self.view.get_state(COMPONENT_EXPLORER_FIND) { - self.action_find_delete(idx); + self.action_find_delete(); // Reload entries self.found_mut().unwrap().del_entry(idx); self.update_find_list(); From dd00e1f55afa0f01ef8f70ebf23412bcd04c40a6 Mon Sep 17 00:00:00 2001 From: veeso Date: Sat, 15 May 2021 17:20:06 +0200 Subject: [PATCH 4/7] Selection help; Fixed find actions for selections --- .../activities/filetransfer/actions/find.rs | 17 ++++++++++++- src/ui/activities/filetransfer/actions/mod.rs | 5 ---- src/ui/activities/filetransfer/update.rs | 25 +++++++++++++------ src/ui/activities/filetransfer/view.rs | 16 ++++++++++++ 4 files changed, 50 insertions(+), 13 deletions(-) diff --git a/src/ui/activities/filetransfer/actions/find.rs b/src/ui/activities/filetransfer/actions/find.rs index 291c4f6..297a3da 100644 --- a/src/ui/activities/filetransfer/actions/find.rs +++ b/src/ui/activities/filetransfer/actions/find.rs @@ -91,7 +91,22 @@ impl FileTransferActivity { } // Iter files for entry in entries.iter() { - self.filetransfer_recv(&entry.get_realfile(), dest_path.as_path(), None); + match self.browser.tab() { + FileExplorerTab::FindLocal | FileExplorerTab::Local => { + self.filetransfer_send( + &entry.get_realfile(), + dest_path.as_path(), + None, + ); + } + FileExplorerTab::FindRemote | FileExplorerTab::Remote => { + self.filetransfer_recv( + &entry.get_realfile(), + dest_path.as_path(), + None, + ); + } + } } } SelectedEntry::None => {} diff --git a/src/ui/activities/filetransfer/actions/mod.rs b/src/ui/activities/filetransfer/actions/mod.rs index ef4a39d..3dc9139 100644 --- a/src/ui/activities/filetransfer/actions/mod.rs +++ b/src/ui/activities/filetransfer/actions/mod.rs @@ -130,11 +130,6 @@ impl FileTransferActivity { // -- private fn get_selected_index(&self, component: &str) -> SelectedEntryIndex { - eprintln!( - "INDEX FOR {}: {:?}", - component, - self.view.get_state(component) - ); match self.view.get_state(component) { Some(Payload::One(Value::Usize(idx))) => SelectedEntryIndex::One(idx), Some(Payload::Vec(files)) => { diff --git a/src/ui/activities/filetransfer/update.rs b/src/ui/activities/filetransfer/update.rs index 26e4316..4c3f156 100644 --- a/src/ui/activities/filetransfer/update.rs +++ b/src/ui/activities/filetransfer/update.rs @@ -546,14 +546,25 @@ impl FileTransferActivity { FileExplorerTab::Remote => self.action_remote_delete(), FileExplorerTab::FindLocal | FileExplorerTab::FindRemote => { // Get entry - if let Some(Payload::One(Value::Usize(idx))) = - self.view.get_state(COMPONENT_EXPLORER_FIND) - { - self.action_find_delete(); - // Reload entries - self.found_mut().unwrap().del_entry(idx); - self.update_find_list(); + self.action_find_delete(); + // Delete entries + match self.view.get_state(COMPONENT_EXPLORER_FIND) { + Some(Payload::One(Value::Usize(idx))) => { + // Reload entries + self.found_mut().unwrap().del_entry(idx); + } + Some(Payload::Vec(values)) => { + values + .iter() + .map(|x| match x { + Value::Usize(v) => *v, + _ => 0, + }) + .for_each(|x| self.found_mut().unwrap().del_entry(x)); + } + _ => {} } + self.update_find_list(); } } self.umount_radio_delete(); diff --git a/src/ui/activities/filetransfer/view.rs b/src/ui/activities/filetransfer/view.rs index bee6c03..48884ca 100644 --- a/src/ui/activities/filetransfer/view.rs +++ b/src/ui/activities/filetransfer/view.rs @@ -975,6 +975,14 @@ impl FileTransferActivity { ) .add_col(TextSpan::from(" Reload directory content")) .add_row() + .add_col( + TextSpanBuilder::new("") + .bold() + .with_foreground(Color::Cyan) + .build(), + ) + .add_col(TextSpan::from(" Select file")) + .add_row() .add_col( TextSpanBuilder::new("") .bold() @@ -1047,6 +1055,14 @@ impl FileTransferActivity { ) .add_col(TextSpan::from(" Delete selected file")) .add_row() + .add_col( + TextSpanBuilder::new("") + .bold() + .with_foreground(Color::Cyan) + .build(), + ) + .add_col(TextSpan::from(" Select all files")) + .add_row() .add_col( TextSpanBuilder::new("") .bold() From c43ab28fb03ec385a8be810cb1f216840cdbb9fc Mon Sep 17 00:00:00 2001 From: veeso Date: Sat, 15 May 2021 17:43:33 +0200 Subject: [PATCH 5/7] Work on multiple files docs --- CHANGELOG.md | 6 ++++++ README.md | 2 -- docs/man.md | 41 ++++++++++++++++++++++++++++++++++++++--- 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f81e2e..ad1b8e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,8 +25,14 @@ Released on FIXME: ?? - Added the possibility to enabled the synchronized brower navigation - when you enter a directory, the same directory will be entered on the other tab - Enable sync browser with `` + - Read more on manual: [Synchronized browsing](docs/man.md#Synchronized-browsing-) - **Remote and Local hosts file formatter**: - Added the possibility to set different formatters for local and remote hosts +- **Work on multiple files**: + - Added the possibility to work on **multiple files simultaneously** + - Select a file with ``, the file when selected will have a `*` prepended to its name + - Select all files in the current directory with `` + - Read more on manual: [Work on multiple files](docs/man.md#Work-on-multiple-files-) - Enhancements - Added a status bar in the file explorer showing whether the sync browser is enabled and which file sorting mode is selected - Removed the goold old figlet title diff --git a/README.md b/README.md index 301ab7f..7d65229 100644 --- a/README.md +++ b/README.md @@ -184,8 +184,6 @@ The developer documentation can be found on Rust Docs at ` to move the remote explorer panel and `` to move back to the local explorer panel. Whenever you are in the find results panel, you need to press `` to exit panel and go back to the previous panel. + +### Keybindings ⌨ | Key | Command | Reminder | |---------------|-------------------------------------------------------|-------------| @@ -100,7 +114,8 @@ Password can be basically provided through 3 ways when address argument is provi | `` | Go to supplied path | Go to | | `` | Show help | Help | | `` | Show info about selected file or directory | Info | -| `` | Reload current directory's content | List | +| `` | Reload current directory's content / Clear selection | List | +| `` | Select a file | Mark | | `` | Create new file with provided name | New | | `` | Edit file; see [Text editor](#text-editor-) | Open | | `` | Quit termscp | Quit | @@ -110,8 +125,28 @@ Password can be basically provided through 3 ways when address argument is provi | `` | Execute a command | eXecute | | `` | Toggle synchronized browsing | sYnc | | `` | Delete file | | +| `` | Select all files | | | `` | Abort file transfer process | | +### Work on multiple files 🥷 + +You can opt to work on multiple files, selecting them pressing ``, in order to select the current file, or pressing ``, which will select all the files in the working directory. +Once a file is marked for selection, it will be displayed with a `*` on the left. +When working on selection, only selected file will be processed for actions, while the current highlighted item will be ignored. +It is possible to work on multiple files also when in the find result panel. +All the actions are available when working with multiple files, but be aware that some actions work in a slightly different way. Let's dive in: + +- *Copy*: whenever you copy a file, you'll be prompted to insert the destination name. When working with multiple file, this name refers to the destination directory where all these files will be copied. +- *Rename*: same as copy, but will move files there. +- *Save as*: same as copy, but will write them there. + +### Synchronized browsing ⏲️ + +When enabled, synchronized browsing, will allow you to synchronize the navigation between the two panels. +This means that whenever you'll change the working directory on one panel, the same action will be reproduced on the other panel. If you want to enable synchronized browsing just press ``; press twice to disable. While enabled, the synchronized browising state will be reported on the status bar on `ON`. + +*Warning*: at the moment, whenever you try to access an unexisting directory, you won't be prompted to create it. This might change in a future update. + --- ## Bookmarks ⭐ From 2c64cd5cce396b349a575b8b1c9a81fec3b2d936 Mon Sep 17 00:00:00 2001 From: veeso Date: Sat, 15 May 2021 17:46:29 +0200 Subject: [PATCH 6/7] Changed Multi variant to Many --- src/ui/activities/filetransfer/actions/copy.rs | 4 ++-- src/ui/activities/filetransfer/actions/delete.rs | 4 ++-- src/ui/activities/filetransfer/actions/edit.rs | 4 ++-- src/ui/activities/filetransfer/actions/find.rs | 4 ++-- src/ui/activities/filetransfer/actions/mod.rs | 14 +++++++------- src/ui/activities/filetransfer/actions/rename.rs | 4 ++-- src/ui/activities/filetransfer/actions/save.rs | 4 ++-- 7 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/ui/activities/filetransfer/actions/copy.rs b/src/ui/activities/filetransfer/actions/copy.rs index bf28e5a..550c643 100644 --- a/src/ui/activities/filetransfer/actions/copy.rs +++ b/src/ui/activities/filetransfer/actions/copy.rs @@ -41,7 +41,7 @@ impl FileTransferActivity { // Reload entries self.reload_local_dir(); } - SelectedEntry::Multi(entries) => { + SelectedEntry::Many(entries) => { // Try to copy each file to Input/{FILE_NAME} let base_path: PathBuf = PathBuf::from(input); // Iter files @@ -68,7 +68,7 @@ impl FileTransferActivity { // Reload entries self.reload_remote_dir(); } - SelectedEntry::Multi(entries) => { + SelectedEntry::Many(entries) => { // Try to copy each file to Input/{FILE_NAME} let base_path: PathBuf = PathBuf::from(input); // Iter files diff --git a/src/ui/activities/filetransfer/actions/delete.rs b/src/ui/activities/filetransfer/actions/delete.rs index b497d06..3d95a19 100644 --- a/src/ui/activities/filetransfer/actions/delete.rs +++ b/src/ui/activities/filetransfer/actions/delete.rs @@ -37,7 +37,7 @@ impl FileTransferActivity { // Reload self.reload_local_dir(); } - SelectedEntry::Multi(entries) => { + SelectedEntry::Many(entries) => { // Iter files for entry in entries.iter() { // Delete file @@ -58,7 +58,7 @@ impl FileTransferActivity { // Reload self.reload_remote_dir(); } - SelectedEntry::Multi(entries) => { + SelectedEntry::Many(entries) => { // Iter files for entry in entries.iter() { // Delete file diff --git a/src/ui/activities/filetransfer/actions/edit.rs b/src/ui/activities/filetransfer/actions/edit.rs index 6607376..ec88186 100644 --- a/src/ui/activities/filetransfer/actions/edit.rs +++ b/src/ui/activities/filetransfer/actions/edit.rs @@ -32,7 +32,7 @@ impl FileTransferActivity { pub(crate) fn action_edit_local_file(&mut self) { let entries: Vec = match self.get_local_selected_entries() { SelectedEntry::One(entry) => vec![entry], - SelectedEntry::Multi(entries) => entries, + SelectedEntry::Many(entries) => entries, SelectedEntry::None => vec![], }; // Edit all entries @@ -56,7 +56,7 @@ impl FileTransferActivity { pub(crate) fn action_edit_remote_file(&mut self) { let entries: Vec = match self.get_remote_selected_entries() { SelectedEntry::One(entry) => vec![entry], - SelectedEntry::Multi(entries) => entries, + SelectedEntry::Many(entries) => entries, SelectedEntry::None => vec![], }; // Edit all entries diff --git a/src/ui/activities/filetransfer/actions/find.rs b/src/ui/activities/filetransfer/actions/find.rs index 297a3da..7e0a97d 100644 --- a/src/ui/activities/filetransfer/actions/find.rs +++ b/src/ui/activities/filetransfer/actions/find.rs @@ -83,7 +83,7 @@ impl FileTransferActivity { self.filetransfer_recv(&entry.get_realfile(), wrkdir.as_path(), save_as); } }, - SelectedEntry::Multi(entries) => { + SelectedEntry::Many(entries) => { // In case of selection: save multiple files in wrkdir/input let mut dest_path: PathBuf = wrkdir; if let Some(save_as) = save_as { @@ -119,7 +119,7 @@ impl FileTransferActivity { // Delete file self.remove_found_file(&entry); } - SelectedEntry::Multi(entries) => { + SelectedEntry::Many(entries) => { // Iter files for entry in entries.iter() { // Delete file diff --git a/src/ui/activities/filetransfer/actions/mod.rs b/src/ui/activities/filetransfer/actions/mod.rs index 3dc9139..2e13003 100644 --- a/src/ui/activities/filetransfer/actions/mod.rs +++ b/src/ui/activities/filetransfer/actions/mod.rs @@ -42,13 +42,13 @@ pub(crate) mod save; pub(crate) enum SelectedEntry { One(FsEntry), - Multi(Vec), + Many(Vec), None, } enum SelectedEntryIndex { One(usize), - Multi(Vec), + Many(Vec), None, } @@ -63,7 +63,7 @@ impl From> for SelectedEntry { impl From> for SelectedEntry { fn from(files: Vec<&FsEntry>) -> Self { - SelectedEntry::Multi(files.into_iter().cloned().collect()) + SelectedEntry::Many(files.into_iter().cloned().collect()) } } @@ -74,7 +74,7 @@ impl FileTransferActivity { pub(crate) fn get_local_selected_entries(&self) -> SelectedEntry { match self.get_selected_index(super::COMPONENT_EXPLORER_LOCAL) { SelectedEntryIndex::One(idx) => SelectedEntry::from(self.local().get(idx)), - SelectedEntryIndex::Multi(files) => { + SelectedEntryIndex::Many(files) => { let files: Vec<&FsEntry> = files .iter() .map(|x| self.local().get(*x)) // Usize to Option @@ -93,7 +93,7 @@ impl FileTransferActivity { pub(crate) fn get_remote_selected_entries(&self) -> SelectedEntry { match self.get_selected_index(super::COMPONENT_EXPLORER_REMOTE) { SelectedEntryIndex::One(idx) => SelectedEntry::from(self.remote().get(idx)), - SelectedEntryIndex::Multi(files) => { + SelectedEntryIndex::Many(files) => { let files: Vec<&FsEntry> = files .iter() .map(|x| self.remote().get(*x)) // Usize to Option @@ -114,7 +114,7 @@ impl FileTransferActivity { SelectedEntryIndex::One(idx) => { SelectedEntry::from(self.found().as_ref().unwrap().get(idx)) } - SelectedEntryIndex::Multi(files) => { + SelectedEntryIndex::Many(files) => { let files: Vec<&FsEntry> = files .iter() .map(|x| self.found().as_ref().unwrap().get(*x)) // Usize to Option @@ -140,7 +140,7 @@ impl FileTransferActivity { _ => 0, }) .collect(); - SelectedEntryIndex::Multi(list) + SelectedEntryIndex::Many(list) } _ => SelectedEntryIndex::None, } diff --git a/src/ui/activities/filetransfer/actions/rename.rs b/src/ui/activities/filetransfer/actions/rename.rs index 9e18b39..80f3fd5 100644 --- a/src/ui/activities/filetransfer/actions/rename.rs +++ b/src/ui/activities/filetransfer/actions/rename.rs @@ -38,7 +38,7 @@ impl FileTransferActivity { // Reload entries self.reload_local_dir(); } - SelectedEntry::Multi(entries) => { + SelectedEntry::Many(entries) => { // Try to copy each file to Input/{FILE_NAME} let base_path: PathBuf = PathBuf::from(input); // Iter files @@ -62,7 +62,7 @@ impl FileTransferActivity { // Reload entries self.reload_remote_dir(); } - SelectedEntry::Multi(entries) => { + SelectedEntry::Many(entries) => { // Try to copy each file to Input/{FILE_NAME} let base_path: PathBuf = PathBuf::from(input); // Iter files diff --git a/src/ui/activities/filetransfer/actions/save.rs b/src/ui/activities/filetransfer/actions/save.rs index 3956e17..508d99c 100644 --- a/src/ui/activities/filetransfer/actions/save.rs +++ b/src/ui/activities/filetransfer/actions/save.rs @@ -52,7 +52,7 @@ impl FileTransferActivity { SelectedEntry::One(entry) => { self.filetransfer_send(&entry.get_realfile(), wrkdir.as_path(), save_as); } - SelectedEntry::Multi(entries) => { + SelectedEntry::Many(entries) => { // In case of selection: save multiple files in wrkdir/input let mut dest_path: PathBuf = wrkdir; if let Some(save_as) = save_as { @@ -73,7 +73,7 @@ impl FileTransferActivity { SelectedEntry::One(entry) => { self.filetransfer_recv(&entry.get_realfile(), wrkdir.as_path(), save_as); } - SelectedEntry::Multi(entries) => { + SelectedEntry::Many(entries) => { // In case of selection: save multiple files in wrkdir/input let mut dest_path: PathBuf = wrkdir; if let Some(save_as) = save_as { From ff158add1f256a1261410269b6aae441b6de4cdd Mon Sep 17 00:00:00 2001 From: veeso Date: Sat, 15 May 2021 17:52:58 +0200 Subject: [PATCH 7/7] Restored '' key behaviour for file list --- src/ui/components/file_list.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/ui/components/file_list.rs b/src/ui/components/file_list.rs index 6849ddd..ec21799 100644 --- a/src/ui/components/file_list.rs +++ b/src/ui/components/file_list.rs @@ -386,7 +386,7 @@ impl Component for FileList { self.states.select_all(); Msg::None } - false => Msg::None, + false => Msg::OnKey(key), }, KeyCode::Char('m') => { // Toggle current file in selection @@ -621,6 +621,11 @@ mod tests { component.on(Event::Key(KeyEvent::from(KeyCode::Backspace))), Msg::OnKey(KeyEvent::from(KeyCode::Backspace)) ); + // Verify 'A' still works + assert_eq!( + component.on(Event::Key(KeyEvent::from(KeyCode::Char('a')))), + Msg::OnKey(KeyEvent::from(KeyCode::Char('a'))) + ); } #[test]