diff options
-rw-r--r-- | development.md | 8 | ||||
-rw-r--r-- | src/event_exec.rs | 74 | ||||
-rw-r--r-- | src/flagged.rs | 51 | ||||
-rw-r--r-- | src/lib.rs | 1 | ||||
-rw-r--r-- | src/status.rs | 31 | ||||
-rw-r--r-- | src/term_manager.rs | 6 |
6 files changed, 100 insertions, 71 deletions
diff --git a/development.md b/development.md index 3d92d34..d4df011 100644 --- a/development.md +++ b/development.md @@ -240,10 +240,12 @@ - [ ] trait for struct or thing with index - - [ ] trait<T> { collection: Vec<T>, index: usize}: next, prev etc. - - [ ] harmonize code for multiple instances - - [ ] macro to impl auto + - [x] trait<T> { collection: Vec<T>, index: usize}: next, prev etc. + - [x] harmonize code for multiple instances + - [x] macro to impl auto + - [x] struct for flagged. Use a vector instead of hashset... :( - [ ] derive + - [ ] regroup shortcut, history, jump, visited - [ ] Version 0.2.0 : tests diff --git a/src/event_exec.rs b/src/event_exec.rs index be9d2d3..e9bfea2 100644 --- a/src/event_exec.rs +++ b/src/event_exec.rs @@ -2,7 +2,6 @@ use std::borrow::Borrow; use std::cmp::min; use std::fs; use std::path; -use std::path::PathBuf; use crate::bulkrename::Bulkrename; use crate::completion::CompletionKind; @@ -63,7 +62,7 @@ impl EventExec { .content .iter() .for_each(|file| { - status.flagged.insert(file.path.clone()); + status.flagged.push(file.path.clone()); }); status.reset_tabs_view() } @@ -75,39 +74,28 @@ impl EventExec { .path_content .content .iter() - .for_each(|file| { - if status.flagged.contains(&file.path.clone()) { - status.flagged.remove(&file.path.clone()); - } else { - status.flagged.insert(file.path.clone()); - } - }); + .for_each(|file| status.flagged.toggle(&file.path)); status.reset_tabs_view() } /// Toggle a single flag and move down one row. pub fn event_toggle_flag(status: &mut Status) -> FmResult<()> { - let file = status.tabs[status.index] - .path_content - .selected() - .ok_or_else(|| FmError::custom("event toggle flag", "No selected file"))?; - status.toggle_flag_on_path(file.path.clone()); - Self::event_down_one_row(status.selected()); + if let Some(file) = status.selected().path_content.selected() { + let path = file.path.clone(); + status.toggle_flag_on_path(&path); + Self::event_down_one_row(status.selected()); + } Ok(()) } /// Move to the next file in the jump list. pub fn event_jumplist_next(status: &mut Status) { - if status.jump_index < status.flagged.len() { - status.jump_index += 1; - } + status.flagged.next() } /// Move to the previous file in the jump list. pub fn event_jumplist_prev(status: &mut Status) { - if status.jump_index > 0 { - status.jump_index -= 1; - } + status.flagged.prev() } /// Change to CHMOD mode allowing to edit permissions of a file. @@ -117,7 +105,7 @@ impl EventExec { } status.selected().mode = Mode::InputSimple(InputKind::Chmod); if status.flagged.is_empty() { - status.flagged.insert( + status.flagged.push( status.tabs[status.index] .path_content .selected() @@ -133,7 +121,8 @@ impl EventExec { /// Does nothing if no file is flagged. pub fn event_jump(status: &mut Status) -> FmResult<()> { if !status.flagged.is_empty() { - status.jump_index = 0; + status.flagged.index = 0; + info!("entering jump mode"); status.selected().mode = Mode::Jump } Ok(()) @@ -171,7 +160,7 @@ impl EventExec { /// Creates a symlink of every flagged file to the current directory. pub fn event_symlink(status: &mut Status) -> FmResult<()> { - for oldpath in status.flagged.iter() { + for oldpath in status.flagged.content.iter() { let filename = oldpath .as_path() .file_name() @@ -208,7 +197,7 @@ impl EventExec { /// Recursively delete all flagged files. pub fn exec_delete_files(status: &mut Status) -> FmResult<()> { - for pathbuf in status.flagged.iter() { + for pathbuf in status.flagged.content.iter() { if pathbuf.is_dir() { std::fs::remove_dir_all(pathbuf)?; } else { @@ -230,7 +219,7 @@ impl EventExec { let permissions: u32 = u32::from_str_radix(&status.selected().input.string(), 8).unwrap_or(0_u32); if permissions <= Status::MAX_PERMISSIONS { - for path in status.flagged.iter() { + for path in status.flagged.content.iter() { Status::set_permissions(path.clone(), permissions)? } status.flagged.clear() @@ -254,27 +243,24 @@ impl EventExec { /// If the user selected a directory, we jump inside it. /// Otherwise, we jump to the parent and select the file. pub fn exec_jump(status: &mut Status) -> FmResult<()> { - let jump_target = Self::find_jump_path(status); - let target_dir = match jump_target.parent() { - Some(parent) => parent, - None => &jump_target, - }; - let tab = status.selected(); - tab.input.clear(); - tab.history.push(&target_dir.to_path_buf()); - tab.path_content.change_directory(target_dir)?; - let index = tab.find_jump_index(&jump_target).unwrap_or_default(); - tab.path_content.select_index(index); - tab.set_window(); - tab.scroll_to(index); + if let Some(jump_target) = status.flagged.selected() { + let jump_target = jump_target.to_owned(); + let target_dir = match jump_target.parent() { + Some(parent) => parent, + None => &jump_target, + }; + let tab = status.selected(); + tab.input.clear(); + tab.history.push(&target_dir.to_path_buf()); + tab.path_content.change_directory(target_dir)?; + let index = tab.find_jump_index(&jump_target).unwrap_or_default(); + tab.path_content.select_index(index); + tab.set_window(); + tab.scroll_to(index); + } Ok(()) } - fn find_jump_path(status: &Status) -> PathBuf { - let jump_list: Vec<&PathBuf> = status.flagged.iter().collect(); - jump_list[status.jump_index].clone() - } - /// Execute a command requiring a confirmation (Delete, Move or Copy). pub fn exec_confirmed_action( status: &mut Status, diff --git a/src/flagged.rs b/src/flagged.rs new file mode 100644 index 0000000..d8e55aa --- /dev/null +++ b/src/flagged.rs @@ -0,0 +1,51 @@ +use std::path::{Path, PathBuf}; + +use crate::impl_selectable_content; + +#[derive(Clone, Debug, Default)] +pub struct Flagged { + pub content: Vec<PathBuf>, + pub index: usize, +} + +impl Flagged { + pub fn push(&mut self, path: PathBuf) { + if self.content.contains(&path) { + return; + } + self.content.push(path); + self.content.sort() + } + + pub fn remove(&mut self, path: PathBuf) { + if let Some(index) = self.content.iter().position(|x| *x == path) { + self.content.remove(index); + } + } + + pub fn toggle(&mut self, path: &Path) { + if let Some(index) = self.content.iter().position(|x| *x == path) { + self.content.remove(index); + } else { + self.push(path.to_path_buf()); + }; + } + + pub fn clear(&mut self) { + self.content.clear() + } + + pub fn contains(&self, path: &PathBuf) -> bool { + self.content.contains(path) + } + + pub fn filtered(&self, current_path: &Path) -> Vec<&Path> { + self.content + .iter() + .filter(|p| p.starts_with(current_path)) + .map(|p| p.as_path()) + .collect() + } +} + +impl_selectable_content!(PathBuf, Flagged); @@ -12,6 +12,7 @@ pub mod event_dispatch; pub mod event_exec; pub mod fileinfo; pub mod filter; +pub mod flagged; pub mod fm_error; pub mod git; pub mod help; diff --git a/src/status.rs b/src/status.rs index 4f9ad35..4b6fe43 100644 --- a/src/status.rs +++ b/src/status.rs @@ -1,4 +1,3 @@ -use std::collections::HashSet; use std::fs; use std::os::unix::fs::PermissionsExt; use std::path::{self, Path, PathBuf}; @@ -14,6 +13,7 @@ use crate::color_cache::ColorCache; use crate::config::Config; use crate::constant_strings_paths::OPENER_PATH; use crate::copy_move::{copy_move, CopyMove}; +use crate::flagged::Flagged; use crate::fm_error::{FmError, FmResult}; use crate::marks::Marks; use crate::opener::{load_opener, Opener}; @@ -35,10 +35,8 @@ pub struct Status { pub tabs: [Tab; 2], /// Index of the current selected tab pub index: usize, - /// Set of flagged files - pub flagged: HashSet<PathBuf>, - /// Index in the jump list - pub jump_index: usize, + /// The flagged files + pub flagged: Flagged, /// Marks allows you to jump to a save mark pub marks: Marks, /// Colors for extension @@ -81,8 +79,7 @@ impl Status { Ok(Self { tabs: [tab.clone(), tab], index: 0, - flagged: HashSet::new(), - jump_index: 0, + flagged: Flagged::default(), marks: Marks::read_from_config_file(), colors: ColorCache::default(), skimer: Skimer::new(term.clone()), @@ -127,12 +124,8 @@ impl Status { } /// Toggle the flagged attribute of a path. - pub fn toggle_flag_on_path(&mut self, path: PathBuf) { - if self.flagged.contains(&path) { - self.flagged.remove(&path); - } else { - self.flagged.insert(path); - }; + pub fn toggle_flag_on_path(&mut self, path: &Path) { + self.flagged.toggle(path) } /// Replace the tab content with what was returned by skim. @@ -172,22 +165,18 @@ impl Status { /// directory. /// It's necessary since the user may have flagged files OUTSIDE of current /// directory before calling Bulkrename. - /// It may creates confusion since the same filename can be used in + /// It may be confusing since the same filename can be used in /// different places. pub fn filtered_flagged_files(&self) -> Vec<&Path> { - let path_content = self.selected_non_mut().path_content.clone(); self.flagged - .iter() - .filter(|p| path_content.contains(p)) - .map(|p| p.as_path()) - .collect() + .filtered(&self.selected_non_mut().path_content.path) } /// Execute a move or a copy of the flagged files to current directory. /// A progress bar is displayed (invisible for small files) and a notification /// is sent every time, even for 0 bytes files... pub fn cut_or_copy_flagged_files(&mut self, cut_or_copy: CopyMove) -> FmResult<()> { - let sources: Vec<PathBuf> = self.flagged.iter().map(|path| path.to_owned()).collect(); + let sources = self.flagged.content.clone(); let dest = self .selected_non_mut() .path_str() @@ -223,7 +212,7 @@ impl Status { let re = Regex::new(&self.selected_non_mut().input.string())?; for file in self.tabs[self.index].path_content.content.iter() { if re.is_match(&file.path.to_string_lossy()) { - self.flagged.insert(file.path.clone()); + self.flagged.push(file.path.clone()); } } Ok(()) diff --git a/src/term_manager.rs b/src/term_manager.rs index eea7c76..be7cc37 100644 --- a/src/term_manager.rs +++ b/src/term_manager.rs @@ -265,9 +265,9 @@ impl<'a> WinTab<'a> { /// Display the possible jump destination from flagged files. fn jump_list(&self, tabs: &Status, canvas: &mut dyn Canvas) -> FmResult<()> { canvas.print(0, 0, "Jump to...")?; - for (row, path) in tabs.flagged.iter().enumerate() { + for (row, path) in tabs.flagged.content.iter().enumerate() { let mut attr = Attr::default(); - if row == tabs.jump_index { + if row == tabs.flagged.index { attr.effect |= Effect::REVERSE; } let _ = canvas.print_with_attr( @@ -343,7 +343,7 @@ impl<'a> WinTab<'a> { confirmed_mode: ConfirmedAction, canvas: &mut dyn Canvas, ) -> FmResult<()> { - for (row, path) in status.flagged.iter().enumerate() { + for (row, path) in status.flagged.content.iter().enumerate() { canvas.print_with_attr( row + ContentWindow::WINDOW_MARGIN_TOP + 2, 4, |