summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/copy_move.rs69
-rw-r--r--src/decompress.rs3
-rw-r--r--src/event_exec.rs13
-rw-r--r--src/fileinfo.rs6
-rw-r--r--src/keybindings.rs10
-rw-r--r--src/log.rs4
-rw-r--r--src/mount_help.rs11
-rw-r--r--src/preview.rs8
-rw-r--r--src/refresher.rs6
-rw-r--r--src/status.rs8
-rw-r--r--src/term_manager.rs45
-rw-r--r--src/trash.rs15
-rw-r--r--src/utils.rs14
13 files changed, 115 insertions, 97 deletions
diff --git a/src/copy_move.rs b/src/copy_move.rs
index a84202c..1f4dc82 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 a214159..0af25f5 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 55d0134..4f7d3c3 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 a5f2820..c3fa6a6 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 d2a4c6b..f0edd66 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,
}
}
diff --git a/src/log.rs b/src/log.rs
index c85f742..6797442 100644
--- a/src/log.rs
+++ b/src/log.rs
@@ -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 9155d9a..e06379e 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 e82a541..aa14cb4 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 ce26ffb..790a76f 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 e700d2e..f2d49e9 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 633b2d6..6918649 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 bb333f3..5b05a4e 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 cffe90f..3bb2306 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()
+}