diff options
-rw-r--r-- | src/copy_move.rs | 69 | ||||
-rw-r--r-- | src/decompress.rs | 3 | ||||
-rw-r--r-- | src/event_exec.rs | 13 | ||||
-rw-r--r-- | src/fileinfo.rs | 6 | ||||
-rw-r--r-- | src/keybindings.rs | 10 | ||||
-rw-r--r-- | src/log.rs | 4 | ||||
-rw-r--r-- | src/mount_help.rs | 11 | ||||
-rw-r--r-- | src/preview.rs | 8 | ||||
-rw-r--r-- | src/refresher.rs | 6 | ||||
-rw-r--r-- | src/status.rs | 8 | ||||
-rw-r--r-- | src/term_manager.rs | 45 | ||||
-rw-r--r-- | src/trash.rs | 15 | ||||
-rw-r--r-- | src/utils.rs | 14 |
13 files changed, 115 insertions, 97 deletions
diff --git a/src/copy_move.rs b/src/copy_move.rs index a84202cc..1f4dc823 100644 --- a/src/copy_move.rs +++ b/src/copy_move.rs @@ -7,10 +7,11 @@ use anyhow::{Context, Result}; use fs_extra; use indicatif::{InMemoryTerm, ProgressBar, ProgressDrawTarget, ProgressState, ProgressStyle}; use log::info; -use tuikit::prelude::{Attr, Color, Effect, Event, Key, Term}; +use tuikit::prelude::{Attr, Color, Effect, Term}; use crate::constant_strings_paths::NOTIFY_EXECUTABLE; use crate::fileinfo::human_size; +use crate::keybindings::REFRESH_EVENT; use crate::log_line; use crate::opener::execute_in_child; use crate::utils::{is_program_in_path, random_name}; @@ -137,12 +138,15 @@ impl CopyMove { /// /// This quite complex behavior is the only way I could find to keep the progress bar while allowing to /// create copies of files in the same dir. -pub fn copy_move( +pub fn copy_move<P>( copy_or_move: CopyMove, sources: Vec<PathBuf>, - dest: &str, + dest: P, term: Arc<Term>, -) -> Result<()> { +) -> Result<()> +where + P: AsRef<std::path::Path>, +{ let c_term = Arc::clone(&term); let (in_mem, pb, options) = copy_or_move.setup_progress_bar(term.term_size()?)?; let handle_progress = move |process_info: fs_extra::TransitProcess| { @@ -164,7 +168,7 @@ pub fn copy_move( } }; - let _ = c_term.send_event(Event::Key(Key::AltPageUp)); + let _ = c_term.send_event(REFRESH_EVENT); if let Err(e) = conflict_handler.solve_conflicts() { info!("Conflict Handler error: {e}"); @@ -180,28 +184,31 @@ struct ConflictHandler { /// The destination of the files. /// If there's no conflicting filenames, it's their final destination /// otherwise it's a temporary folder we'll create. - temp_dest: String, + temp_dest: PathBuf, /// True iff there's at least one file name conflict: /// an already existing file in the destination with the same name /// as a file from source. has_conflict: bool, /// Defined to the final destination if there's a conflict. /// None otherwise. - final_dest: Option<String>, + final_dest: Option<PathBuf>, } impl ConflictHandler { /// Creates a new `ConflictHandler` instance. /// We check for conflict and create the temporary folder if needed. - fn new(dest: &str, sources: &[PathBuf]) -> Result<Self> { - let has_conflict = ConflictHandler::check_filename_conflict(sources, dest)?; - let temp_dest: String; - let final_dest: Option<String>; + fn new<P>(dest: P, sources: &[PathBuf]) -> Result<Self> + where + P: AsRef<std::path::Path>, + { + let has_conflict = ConflictHandler::check_filename_conflict(sources, &dest)?; + let temp_dest: PathBuf; + let final_dest: Option<PathBuf>; if has_conflict { - temp_dest = Self::create_temporary_destination(dest)?; - final_dest = Some(dest.to_owned()); + temp_dest = Self::create_temporary_destination(&dest)?; + final_dest = Some(dest.as_ref().to_path_buf()); } else { - temp_dest = dest.to_owned(); + temp_dest = dest.as_ref().to_path_buf(); final_dest = None; }; @@ -214,12 +221,15 @@ impl ConflictHandler { /// Creates a randomly named folder in the destination. /// The name is `fm-random` where `random` is a random string of length 7. - fn create_temporary_destination(dest: &str) -> Result<String> { - let mut temp_dest = std::path::PathBuf::from(dest); + fn create_temporary_destination<P>(dest: P) -> Result<PathBuf> + where + P: AsRef<std::path::Path>, + { + let mut temp_dest = dest.as_ref().to_path_buf(); let rand_str = random_name(); temp_dest.push(rand_str); std::fs::create_dir(&temp_dest)?; - Ok(temp_dest.display().to_string()) + Ok(temp_dest) } /// Move every file from `temp_dest` to `final_dest` and delete `temp_dest`. @@ -251,11 +261,10 @@ impl ConflictHandler { .context("Couldn't cast the filename")? .to_owned(); - let mut final_dest = std::path::PathBuf::from( - self.final_dest - .clone() - .context("Final dest shouldn't be None")?, - ); + let mut final_dest = self + .final_dest + .clone() + .context("Final dest shouldn't be None")?; final_dest.push(&file_name); while final_dest.exists() { final_dest.pop(); @@ -267,16 +276,14 @@ impl ConflictHandler { } /// True iff `dest` contains any file with the same file name as one of `sources`. - fn check_filename_conflict(sources: &[PathBuf], dest: &str) -> Result<bool> { + fn check_filename_conflict<P>(sources: &[PathBuf], dest: P) -> Result<bool> + where + P: AsRef<std::path::Path>, + { for file in sources { - let filename = file - .file_name() - .context("Couldn't read filename")? - .to_str() - .context("Couldn't cast filename into str")? - .to_owned(); - let mut new_path = std::path::PathBuf::from(dest); - new_path.push(&filename); + let filename = file.file_name().context("Couldn't read filename")?; + let mut new_path = dest.as_ref().to_path_buf(); + new_path.push(filename); if new_path.exists() { return Ok(true); } diff --git a/src/decompress.rs b/src/decompress.rs index a2141592..0af25f5a 100644 --- a/src/decompress.rs +++ b/src/decompress.rs @@ -5,6 +5,7 @@ use std::path::Path; use tar::Archive; use crate::constant_strings_paths::TAR; +use crate::utils::path_to_string; /// Decompress a zipped compressed file into its parent directory. pub fn decompress_zip(source: &Path) -> Result<()> { @@ -64,7 +65,7 @@ where { if let Ok(output) = std::process::Command::new(TAR) .arg("tvf") - .arg(source.as_ref().display().to_string()) + .arg(path_to_string(&source)) .output() { let output = String::from_utf8(output.stdout).unwrap_or_default(); diff --git a/src/event_exec.rs b/src/event_exec.rs index 55d01341..4f7d3c37 100644 --- a/src/event_exec.rs +++ b/src/event_exec.rs @@ -31,6 +31,7 @@ use crate::selectable_content::SelectableContent; use crate::shell_parser::ShellCommandParser; use crate::status::Status; use crate::tab::Tab; +use crate::utils::path_to_string; use crate::utils::{ args_is_empty, is_program_in_path, is_sudo_command, open_in_current_neovim, string_to_path, }; @@ -390,14 +391,11 @@ impl EventAction { let Ok(fileinfo) = status.selected_non_mut().selected() else { return Ok(()); }; - let Some(path_str) = fileinfo.path.to_str() else { - return Ok(()); - }; - open_in_current_neovim(path_str, &nvim_server); + open_in_current_neovim(&fileinfo.path, &nvim_server); } else { let flagged = status.flagged.content.clone(); for file_path in flagged.iter() { - open_in_current_neovim(&file_path.display().to_string(), &nvim_server) + open_in_current_neovim(file_path, &nvim_server) } } @@ -1546,10 +1544,7 @@ impl LeaveMode { }; let (username, hostname, remote_path) = (strings[0], strings[1], strings[2]); - let current_path: &str = &tab - .directory_of_selected_previous_mode()? - .display() - .to_string(); + let current_path: &str = &path_to_string(&tab.directory_of_selected_previous_mode()?); let first_arg = &format!("{username}@{hostname}:{remote_path}"); let command_output = execute_and_capture_output_with_path( SSHFS_EXECUTABLE, diff --git a/src/fileinfo.rs b/src/fileinfo.rs index a5f28202..c3fa6a61 100644 --- a/src/fileinfo.rs +++ b/src/fileinfo.rs @@ -18,7 +18,7 @@ use crate::impl_selectable_content; use crate::sort::SortKind; use crate::tree::Node; use crate::users::Users; -use crate::utils::filename_from_path; +use crate::utils::{filename_from_path, path_to_string}; type Valid = bool; @@ -381,7 +381,7 @@ impl PathContent { /// Convert a path to a &str. /// It may fails if the path contains non valid utf-8 chars. pub fn path_to_str(&self) -> String { - self.path.display().to_string() + path_to_string(&self.path) } /// Sort the file with current key. @@ -433,7 +433,7 @@ impl PathContent { /// Path of the currently selected file. pub fn selected_path_string(&self) -> Option<String> { - Some(self.selected()?.path.display().to_string()) + Some(path_to_string(&self.selected()?.path)) } /// True if the path starts with a subpath. diff --git a/src/keybindings.rs b/src/keybindings.rs index d2a4c6b5..f0edd66f 100644 --- a/src/keybindings.rs +++ b/src/keybindings.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use std::str::FromStr; use std::string::ToString; -use tuikit::prelude::{from_keyname, Key}; +use tuikit::prelude::{from_keyname, Event, Key}; use crate::action_map::ActionMap; use crate::constant_strings_paths::CONFIG_PATH; @@ -23,6 +23,12 @@ impl Default for Bindings { } } +/// Reserved key used to send refresh event +/// This key can't be bound to anything. +pub const REFRESH_KEY: Key = Key::AltPageUp; +/// Refresh event, using a reserved key. +pub const REFRESH_EVENT: Event = Event::Key(REFRESH_KEY); + impl Bindings { fn new() -> Self { let binds = HashMap::from([ @@ -178,7 +184,7 @@ impl Bindings { fn keymap_is_reserved(&self, keymap: &Key) -> bool { match *keymap { // used to send refresh requests. - Key::AltPageUp => true, + REFRESH_KEY => true, _ => false, } } @@ -58,7 +58,7 @@ pub fn read_log() -> Result<Vec<String>> { } lazy_static! { - static ref LAST_LOG_LINE: RwLock<String> = RwLock::new("".to_string()); + static ref LAST_LOG_LINE: RwLock<String> = RwLock::new("".to_owned()); } /// Read the last value of the "log line". @@ -68,7 +68,7 @@ pub fn read_last_log_line() -> String { let Ok(last_log_line) = LAST_LOG_LINE.read() else { return "".to_owned(); }; - last_log_line.to_string() + last_log_line.to_owned() } /// Write a new log line to the global variable `LAST_LOG_LINE`. diff --git a/src/mount_help.rs b/src/mount_help.rs index 9155d9a4..e06379e4 100644 --- a/src/mount_help.rs +++ b/src/mount_help.rs @@ -25,5 +25,16 @@ pub trait MountHelper { /// String representation of the device fn as_string(&self) -> Result<String>; + /// Name of the device fn device_name(&self) -> Result<String>; + + /// Default attr. + /// Foreground is blue when device is mounted, white otherwise. + fn attr(&self) -> tuikit::attr::Attr { + if self.is_mounted() { + tuikit::attr::Attr::from(tuikit::attr::Color::BLUE) + } else { + tuikit::attr::Attr::default() + } + } } diff --git a/src/preview.rs b/src/preview.rs index e82a541f..aa14cb42 100644 --- a/src/preview.rs +++ b/src/preview.rs @@ -27,7 +27,7 @@ use crate::opener::execute_and_capture_output_without_check; use crate::sort::SortKind; use crate::tree::{ColoredString, Tree}; use crate::users::Users; -use crate::utils::{clear_tmp_file, filename_from_path, is_program_in_path}; +use crate::utils::{clear_tmp_file, filename_from_path, is_program_in_path, path_to_string}; /// Different kind of extension for grouped by previewers. /// Any extension we can preview should be matched here. @@ -383,7 +383,7 @@ impl BlockDevice { .args([ "-lfo", "FSTYPE,PATH,LABEL,UUID,FSVER,MOUNTPOINT,MODEL,SIZE,FSAVAIL,FSUSE%", - &fileinfo.path.display().to_string(), + &path_to_string(&fileinfo.path), ]) .output() { @@ -416,7 +416,7 @@ impl FifoCharDevice { fn new(fileinfo: &FileInfo) -> Self { let content: Vec<String>; if let Ok(output) = std::process::Command::new(LSOF) - .arg(&fileinfo.path.display().to_string()) + .arg(path_to_string(&fileinfo.path)) .output() { let s = String::from_utf8(output.stdout).unwrap_or_default(); @@ -845,7 +845,7 @@ impl Ueberzug { } fn office_thumbnail(calc_path: &Path) -> Result<Self> { - let calc_str = calc_path.display().to_string(); + let calc_str = path_to_string(&calc_path); let args = vec!["--convert-to", "pdf", "--outdir", "/tmp", &calc_str]; let output = std::process::Command::new(LIBREOFFICE) .args(args) diff --git a/src/refresher.rs b/src/refresher.rs index ce26ffb1..790a76f2 100644 --- a/src/refresher.rs +++ b/src/refresher.rs @@ -4,7 +4,8 @@ use std::thread; use std::time::Duration; use anyhow::Result; -use tuikit::prelude::{Event, Key}; + +use crate::keybindings::REFRESH_EVENT; /// Allows refresh if the current path has been modified externally. pub struct Refresher { @@ -21,7 +22,6 @@ impl Refresher { /// Event sent to Fm event poller which is interpreted /// as a request for refresh. /// This key can't be bound to anything (who would use that ?). - const REFRESH_EVENT: Event = Event::Key(Key::AltPageUp); /// Spawn a thread which sends events to the terminal. /// Those events are interpreted as refresh requests. @@ -48,7 +48,7 @@ impl Refresher { thread::sleep(Duration::from_millis(100)); if counter >= Self::TEN_SECONDS_IN_DECISECONDS { counter = 0; - if term.send_event(Self::REFRESH_EVENT).is_err() { + if term.send_event(REFRESH_EVENT).is_err() { break; } } diff --git a/src/status.rs b/src/status.rs index e700d2e8..f2d49e93 100644 --- a/src/status.rs +++ b/src/status.rs @@ -405,13 +405,11 @@ impl Status { pub fn cut_or_copy_flagged_files(&mut self, cut_or_copy: CopyMove) -> Result<()> { let sources = self.flagged.content.clone(); - let dest = self + let dest = &self .selected_non_mut() - .directory_of_selected_previous_mode()? - .display() - .to_string(); + .directory_of_selected_previous_mode()?; - copy_move(cut_or_copy, sources, &dest, Arc::clone(&self.term))?; + copy_move(cut_or_copy, sources, dest, Arc::clone(&self.term))?; self.clear_flags_and_reset_view() } diff --git a/src/term_manager.rs b/src/term_manager.rs index 633b2d67..69186496 100644 --- a/src/term_manager.rs +++ b/src/term_manager.rs @@ -25,6 +25,7 @@ use crate::status::Status; use crate::tab::Tab; use crate::trash::Trash; use crate::tree::calculate_top_bottom; +use crate::utils::path_to_string; /// Iter over the content, returning a triplet of `(index, line, attr)`. macro_rules! enumerated_colored_iter { @@ -673,8 +674,8 @@ impl<'a> Draw for WinSecondary<'a> { impl<'a> WinSecondary<'a> { const ATTR_YELLOW: Attr = color_to_attr(Color::YELLOW); - const EDIT_BOX_OFFSET: usize = 10; - const SORT_CURSOR_OFFSET: usize = 38; + const EDIT_BOX_OFFSET: usize = 11; + const SORT_CURSOR_OFFSET: usize = 39; const PASSWORD_CURSOR_OFFSET: usize = 9; fn new(status: &'a Status, index: usize) -> Self { @@ -687,8 +688,9 @@ impl<'a> WinSecondary<'a> { /// Display the possible completion items. The currently selected one is /// reversed. fn draw_completion(&self, canvas: &mut dyn Canvas) -> Result<()> { - for (row, candidate) in self.tab.completion.proposals.iter().enumerate() { - let attr = self.tab.completion.attr(row, &Attr::default()); + let content = &self.tab.completion.proposals; + for (row, candidate, attr) in enumerated_colored_iter!(content) { + let attr = self.tab.completion.attr(row, attr); Self::draw_content_line(canvas, row, candidate, attr)?; } Ok(()) @@ -783,8 +785,7 @@ impl<'a> WinSecondary<'a> { } fn draw_bulk(&self, canvas: &mut dyn Canvas) -> Result<()> { - let selectable = &self.status.bulk; - if let Some(selectable) = selectable { + if let Some(selectable) = &self.status.bulk { canvas.print(0, 0, "Action...")?; let content = selectable.content(); for (row, text, attr) in enumerated_colored_iter!(content) { @@ -798,7 +799,7 @@ impl<'a> WinSecondary<'a> { fn draw_trash(&self, canvas: &mut dyn Canvas) -> Result<()> { let trash = &self.status.trash; if trash.content().is_empty() { - self.draw_already_empty_trash(canvas) + self.draw_trash_is_empty(canvas) } else { self.draw_trash_content(canvas, trash) }; @@ -817,15 +818,16 @@ impl<'a> WinSecondary<'a> { fn draw_compress(&self, canvas: &mut dyn Canvas) -> Result<()> { let selectable = &self.status.compression; canvas.print_with_attr( - 1, - 0, - "Archive and compress the flagged files. Pick a compression algorithm.", + 2, + 2, + "Archive and compress the flagged files.", Self::ATTR_YELLOW, )?; + canvas.print_with_attr(3, 2, "Pick a compression algorithm.", Self::ATTR_YELLOW)?; let content = selectable.content(); for (row, compression_method, attr) in enumerated_colored_iter!(content) { let attr = selectable.attr(row, attr); - Self::draw_content_line(canvas, row, &compression_method.to_string(), attr)?; + Self::draw_content_line(canvas, row + 3, &compression_method.to_string(), attr)?; } Ok(()) } @@ -900,11 +902,7 @@ impl<'a> WinSecondary<'a> { T: MountHelper, { let row = calc_line_row(index, &self.tab.window) + 2; - let attr = if device.is_mounted() { - selectable.attr(index, &Attr::from(Color::BLUE)) - } else { - selectable.attr(index, &Attr::default()) - }; + let attr = selectable.attr(index, &device.attr()); canvas.print_with_attr(row, 3, &device.device_name()?, attr)?; Ok(()) } @@ -916,15 +914,11 @@ impl<'a> WinSecondary<'a> { canvas: &mut dyn Canvas, ) -> Result<()> { info!("confirmed action: {:?}", confirmed_mode); - let dest = self - .tab - .directory_of_selected_previous_mode()? - .display() - .to_string(); + let dest = path_to_string(&self.tab.directory_of_selected_previous_mode()?); Self::draw_content_line( canvas, - 2, + 0, &confirmed_mode.confirmation_string(&dest), ATTR_YELLOW_BOLD, )?; @@ -940,7 +934,7 @@ impl<'a> WinSecondary<'a> { for (row, path, attr) in enumerated_colored_iter!(content) { Self::draw_content_line( canvas, - row, + row + 2, path.to_str().context("Unreadable filename")?, *attr, )?; @@ -951,14 +945,14 @@ impl<'a> WinSecondary<'a> { fn draw_confirm_empty_trash(&self, canvas: &mut dyn Canvas) -> Result<()> { log::info!("draw_confirm_empty_trash"); if self.status.trash.is_empty() { - self.draw_already_empty_trash(canvas) + self.draw_trash_is_empty(canvas) } else { self.draw_confirm_non_empty_trash(canvas)? } Ok(()) } - fn draw_already_empty_trash(&self, canvas: &mut dyn Canvas) { + fn draw_trash_is_empty(&self, canvas: &mut dyn Canvas) { let _ = Self::draw_content_line(canvas, 0, "Trash is empty", ATTR_YELLOW_BOLD); } @@ -970,6 +964,7 @@ impl<'a> WinSecondary<'a> { } Ok(()) } + fn draw_content_line( canvas: &mut dyn Canvas, row: usize, diff --git a/src/trash.rs b/src/trash.rs index bb333f39..5b05a4e4 100644 --- a/src/trash.rs +++ b/src/trash.rs @@ -46,11 +46,11 @@ impl TrashInfo { fn to_string(&self) -> Result<String> { Ok(format!( "[Trash Info] -Path={} -DeletionDate={} +Path={origin} +DeletionDate={date} ", - url_escape::encode_fragment(path_to_string(&self.origin)?), - self.deletion_date + origin = url_escape::encode_fragment(&self.origin.to_string_lossy()), + date = self.deletion_date )) } @@ -170,7 +170,7 @@ impl std::fmt::Display for TrashInfo { write!( f, "{} - trashed on {}", - path_to_string(&self.origin).unwrap_or_default(), + &self.origin.display(), self.deletion_date ) } @@ -418,11 +418,6 @@ impl Trash { impl_selectable_content!(TrashInfo, Trash); -fn path_to_string(path: &Path) -> Result<&str> { - path.to_str() - .context("path_to_string: couldn't parse origin into string") -} - fn parsed_date_from_path_info(ds: &str) -> Result<()> { NaiveDateTime::parse_from_str(ds, TRASHINFO_DATETIME_FORMAT)?; Ok(()) diff --git a/src/utils.rs b/src/utils.rs index cffe90fc..3bb23065 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -153,8 +153,11 @@ pub fn is_sudo_command(executable: &str) -> bool { } /// Open the path in neovim. -pub fn open_in_current_neovim(path_str: &str, nvim_server: &str) { - let command = &format!("<esc>:e {path_str}<cr><esc>:set number<cr><esc>:close<cr>"); +pub fn open_in_current_neovim(path: &Path, nvim_server: &str) { + let command = &format!( + "<esc>:e {path_str}<cr><esc>:set number<cr><esc>:close<cr>", + path_str = path.display() + ); let _ = nvim(nvim_server, command); } @@ -184,3 +187,10 @@ pub fn clear_tmp_file() { pub fn is_dir_empty(path: &std::path::Path) -> Result<bool> { Ok(path.read_dir()?.next().is_none()) } + +pub fn path_to_string<P>(path: &P) -> String +where + P: AsRef<std::path::Path>, +{ + path.as_ref().to_string_lossy().into_owned() +} |